diff --git a/code/__DEFINES/strippable.dm b/code/__DEFINES/strippable.dm
new file mode 100644
index 0000000000..f62c4d6c1b
--- /dev/null
+++ b/code/__DEFINES/strippable.dm
@@ -0,0 +1,30 @@
+// All of these must be matched in StripMenu.js.
+#define STRIPPABLE_ITEM_HEAD "head"
+#define STRIPPABLE_ITEM_BACK "back"
+#define STRIPPABLE_ITEM_MASK "wear_mask"
+#define STRIPPABLE_ITEM_EYES "glasses"
+#define STRIPPABLE_ITEM_L_EAR "wear_l_ear"
+#define STRIPPABLE_ITEM_R_EAR "wear_r_ear"
+#define STRIPPABLE_ITEM_JUMPSUIT "w_uniform"
+#define STRIPPABLE_ITEM_SUIT "wear_suit"
+#define STRIPPABLE_ITEM_GLOVES "gloves"
+#define STRIPPABLE_ITEM_FEET "shoes"
+#define STRIPPABLE_ITEM_SUIT_STORAGE "j_store"
+#define STRIPPABLE_ITEM_ID "id"
+#define STRIPPABLE_ITEM_BELT "belt"
+#define STRIPPABLE_ITEM_LPOCKET "l_store"
+#define STRIPPABLE_ITEM_RPOCKET "r_store"
+#define STRIPPABLE_ITEM_LHAND "l_hand"
+#define STRIPPABLE_ITEM_RHAND "r_hand"
+#define STRIPPABLE_ITEM_HANDCUFFS "handcuffs"
+#define STRIPPABLE_ITEM_LEGCUFFS "legcuffs"
+
+
+/// This slot is not obscured.
+#define STRIPPABLE_OBSCURING_NONE 0
+
+/// This slot is completely obscured, and cannot be accessed.
+#define STRIPPABLE_OBSCURING_COMPLETELY 1
+
+/// This slot can't be seen, but can be accessed.
+#define STRIPPABLE_OBSCURING_HIDDEN 2
diff --git a/code/_macros.dm b/code/_macros.dm
index 6c1f37b4bc..0e85827bc0 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -67,6 +67,11 @@
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
///Accesses an associative list, returns null if nothing is found
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
+///Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made.
+#define LAZYORASSOCLIST(lazy_list, key, value) \
+ LAZYINITLIST(lazy_list); \
+ LAZYINITLIST(lazy_list[key]); \
+ lazy_list[key] |= value;
// Insert an object A into a sorted list using cmp_proc (/code/_helpers/cmp.dm) for comparison.
#define ADD_SORTED(list, A, cmp_proc) if(!list.len) {list.Add(A)} else {list.Insert(FindElementIndex(A, list, cmp_proc), A)}
diff --git a/code/_onclick/click_hold.dm b/code/_onclick/click_hold.dm
index 996f7ed2bf..e972821615 100644
--- a/code/_onclick/click_hold.dm
+++ b/code/_onclick/click_hold.dm
@@ -97,9 +97,8 @@
/client/MouseDrop(datum/over_object, datum/src_location, over_location, src_control, over_control, params)
. = ..()
+ if(over_object)
+ SEND_SIGNAL(over_object, COMSIG_ATOM_DROP_ON, src_location, src)
if(src_location)
SEND_SIGNAL(src_location, COMSIG_ATOM_DROPPED_ON, over_object, src)
-
- if(over_object)
- SEND_SIGNAL(over_object, COMSIG_ATOM_DROP_ON, src_location, src)
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index fff5e9200d..4dcc0d6468 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -7,6 +7,7 @@
*/
/atom/MouseDrop(atom/over)
if(!usr || !over) return
+
if(!Adjacent(usr) || !over.Adjacent(usr)) return // should stop you from dragging through windows
spawn(0)
diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm
new file mode 100644
index 0000000000..e0daaee74a
--- /dev/null
+++ b/code/datums/elements/strippable.dm
@@ -0,0 +1,536 @@
+/// An element for atoms that, when dragged and dropped onto a mob, opens a strip panel.
+/datum/element/strippable
+ element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
+ id_arg_index = 2
+
+ /// An assoc list of keys to /datum/strippable_item
+ var/list/items
+
+ /// A proc path that returns TRUE/FALSE if we should show the strip panel for this entity.
+ /// If it does not exist, the strip menu will always show.
+ /// Will be called with (mob/user).
+ var/should_strip_proc_path
+
+ /// An existing strip menus
+ var/list/strip_menus
+
+/datum/element/strippable/Attach(datum/target, list/items, should_strip_proc_path)
+ . = ..()
+ if (!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, COMSIG_ATOM_DROP_ON, PROC_REF(mouse_drop_onto))
+
+ src.items = items
+ src.should_strip_proc_path = should_strip_proc_path
+
+/datum/element/strippable/Detach(datum/source, force)
+ . = ..()
+
+ UnregisterSignal(source, COMSIG_ATOM_DROP_ON)
+
+ if (!isnull(strip_menus))
+ QDEL_NULL(strip_menus[source])
+
+/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user)
+ SIGNAL_HANDLER
+ if (user == source)
+ return
+
+ if (over == source)
+ return
+
+ var/mob/overmob = over
+ if (!ishuman(overmob))
+ return
+
+ if (!overmob.Adjacent(source))
+ return
+
+ if (!overmob.client)
+ return
+
+ if (overmob.client != user)
+ return
+
+ if (!isnull(should_strip_proc_path) && !call(source, should_strip_proc_path)(overmob))
+ return
+
+ var/datum/strip_menu/strip_menu
+
+ if (isnull(strip_menu))
+ strip_menu = new(source, src)
+ LAZYSET(strip_menus, source, strip_menu)
+
+ INVOKE_ASYNC(strip_menu, PROC_REF(tgui_interact), overmob)
+
+/// A representation of an item that can be stripped down
+/datum/strippable_item
+ /// The STRIPPABLE_ITEM_* key
+ var/key
+
+ /// Should we warn about dangerous clothing?
+ var/warn_dangerous_clothing = TRUE
+
+/// Gets the item from the given source.
+/datum/strippable_item/proc/get_item(atom/source)
+
+/// Tries to equip the item onto the given source.
+/// Returns TRUE/FALSE depending on if it is allowed.
+/// This should be used for checking if an item CAN be equipped.
+/// It should not perform the equipping itself.
+/datum/strippable_item/proc/try_equip(atom/source, obj/item/equipping, mob/user)
+ if ((equipping.flags_item & ITEM_ABSTRACT))
+ return FALSE
+ if ((equipping.flags_item & NODROP))
+ to_chat(user, SPAN_WARNING("You can't put [equipping] on [source], it's stuck to your hand!"))
+ return FALSE
+ if (ishuman(source))
+ var/mob/living/carbon/human/sourcehuman = source
+ if(HAS_TRAIT(sourcehuman, TRAIT_UNSTRIPPABLE) && !sourcehuman.is_mob_incapacitated())
+ to_chat(src, SPAN_DANGER("[sourcehuman] is too strong to force [equipping] onto them!"))
+ return
+ return TRUE
+
+/// Start the equipping process. This is the proc you should yield in.
+/// Returns TRUE/FALSE depending on if it is allowed.
+/datum/strippable_item/proc/start_equip(atom/source, obj/item/equipping, mob/user)
+ source.visible_message(
+ SPAN_NOTICE("[user] tries to put [equipping] on [source]."),
+ SPAN_NOTICE("[user] tries to put [equipping] on you.")
+ )
+
+ if (ismob(source))
+ var/mob/sourcemob = source
+ sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] is having [equipping] put on them by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] [key_name(user)] is putting [equipping] on [key_name(sourcemob)]")
+
+ return TRUE
+
+/// The proc that places the item on the source. This should not yield.
+/datum/strippable_item/proc/finish_equip(atom/source, obj/item/equipping, mob/user)
+ SHOULD_NOT_SLEEP(TRUE)
+
+/// Tries to unequip the item from the given source.
+/// Returns TRUE/FALSE depending on if it is allowed.
+/// This should be used for checking if it CAN be unequipped.
+/// It should not perform the unequipping itself.
+/datum/strippable_item/proc/try_unequip(atom/source, mob/user)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ var/obj/item/item = get_item(source)
+ if (isnull(item))
+ return FALSE
+
+ if (user.action_busy && !skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED))
+ to_chat(user, SPAN_WARNING("You can't do this right now."))
+ return FALSE
+
+ if ((item.flags_inventory & CANTSTRIP) || ((item.flags_item & NODROP) && !(item.flags_item & FORCEDROP_CONDITIONAL)) || (item.flags_item & ITEM_ABSTRACT))
+ return FALSE
+
+ if (ishuman(source))
+ var/mob/living/carbon/human/sourcehuman = source
+ if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcehuman.stat == DEAD || sourcehuman.health < HEALTH_THRESHOLD_CRIT) && !sourcehuman.get_target_lock(user.faction_group))
+ to_chat(user, SPAN_WARNING("You can't strip items of a crit or dead member of another faction!"))
+ return FALSE
+
+ if(HAS_TRAIT(sourcehuman, TRAIT_UNSTRIPPABLE) && !sourcehuman.is_mob_incapacitated())
+ to_chat(src, SPAN_DANGER("[sourcehuman] has an unbreakable grip on their equipment!"))
+ return
+
+ return TRUE
+
+/// Start the unequipping process. This is the proc you should yield in.
+/// Returns TRUE/FALSE depending on if it is allowed.
+/datum/strippable_item/proc/start_unequip(atom/source, mob/user)
+ var/obj/item/item = get_item(source)
+ if (isnull(item))
+ return FALSE
+
+ source.visible_message(
+ SPAN_WARNING("[user] tries to remove [source]'s [item]."),
+ SPAN_DANGER("[user] tries to remove your [item].")
+ )
+
+ if (ismob(source))
+ var/mob/sourcemob = source
+ sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] is being stripped of [item] by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] [key_name(user)] is stripping [key_name(sourcemob)] of [item]")
+
+ item.add_fingerprint(user)
+
+ return TRUE
+
+/// The proc that unequips the item from the source. This should not yield.
+/datum/strippable_item/proc/finish_unequip(atom/source, mob/user)
+
+/// Returns a STRIPPABLE_OBSCURING_* define to report on whether or not this is obscured.
+/datum/strippable_item/proc/get_obscuring(atom/source)
+ SHOULD_NOT_SLEEP(TRUE)
+ return STRIPPABLE_OBSCURING_NONE
+
+/// Returns the ID of this item's strippable action.
+/// Return `null` if there is no alternate action.
+/// Any return value of this must be in StripMenu.
+/datum/strippable_item/proc/get_alternate_action(atom/source, mob/user)
+ return null
+
+/// Performs an alternative action on this strippable_item.
+/// `has_alternate_action` needs to be TRUE.
+/datum/strippable_item/proc/alternate_action(atom/source, mob/user)
+
+/// Returns whether or not this item should show.
+/datum/strippable_item/proc/should_show(atom/source, mob/user)
+ return TRUE
+
+/// A preset for equipping items onto mob slots
+/datum/strippable_item/mob_item_slot
+ /// The ITEM_SLOT_* to equip to.
+ var/item_slot
+
+/datum/strippable_item/proc/has_no_item_alt_action()
+ return FALSE
+
+/datum/strippable_item/mob_item_slot/get_item(atom/source)
+ if (!ismob(source))
+ return null
+
+ var/mob/mob_source = source
+ return mob_source.get_item_by_slot(key)
+
+/datum/strippable_item/mob_item_slot/try_equip(atom/source, obj/item/equipping, mob/user)
+ . = ..()
+ if (!.)
+ return
+
+ if (!ismob(source))
+ return FALSE
+ if (user.action_busy)
+ to_chat(user, SPAN_WARNING("You can't do this right now."))
+ return FALSE
+ if (!equipping.mob_can_equip(
+ source,
+ key
+ ))
+ to_chat(user, SPAN_WARNING("\The [equipping] doesn't fit in that place!"))
+ return FALSE
+ if(equipping.flags_item & WIELDED)
+ equipping.unwield(user)
+ return TRUE
+
+/datum/strippable_item/mob_item_slot/start_equip(atom/source, obj/item/equipping, mob/user)
+ . = ..()
+ if (!.)
+ return
+
+ if (!ismob(source))
+ return FALSE
+
+ var/time_to_strip = HUMAN_STRIP_DELAY
+ var/mob/sourcemob = source
+
+ if (ishuman(sourcemob) && ishuman(user))
+ var/mob/living/carbon/human/sourcehuman = sourcemob
+ var/mob/living/carbon/human/userhuman = user
+ time_to_strip = userhuman.get_strip_delay(userhuman, sourcehuman)
+
+ if (!do_after(user, time_to_strip, INTERRUPT_ALL, BUSY_ICON_FRIENDLY, source, INTERRUPT_MOVED, BUSY_ICON_FRIENDLY))
+ return FALSE
+
+ if (!equipping.mob_can_equip(
+ sourcemob,
+ key
+ ))
+ return FALSE
+
+ if (!user.temp_drop_inv_item(equipping))
+ return FALSE
+
+ return TRUE
+
+/datum/strippable_item/mob_item_slot/finish_equip(atom/source, obj/item/equipping, mob/user)
+ if (!ismob(source))
+ return FALSE
+
+ var/mob/sourcemob = source
+ sourcemob.equip_to_slot_if_possible(equipping, key)
+
+/datum/strippable_item/mob_item_slot/get_obscuring(atom/source)
+ return FALSE
+
+/datum/strippable_item/mob_item_slot/start_unequip(atom/source, mob/user)
+ . = ..()
+ if (!.)
+ return
+
+ return start_unequip_mob(get_item(source), source, user)
+
+/datum/strippable_item/mob_item_slot/finish_unequip(atom/source, mob/user)
+ var/obj/item/item = get_item(source)
+ if (isnull(item))
+ return FALSE
+
+ if (!ismob(source))
+ return FALSE
+
+ return finish_unequip_mob(item, source, user)
+
+/// A utility function for `/datum/strippable_item`s to start unequipping an item from a mob.
+/datum/strippable_item/mob_item_slot/proc/start_unequip_mob(obj/item/item, mob/living/carbon/human/source, mob/living/carbon/human/user)
+ var/time_to_strip = HUMAN_STRIP_DELAY
+
+ if (istype(source) && istype(user))
+ time_to_strip = user.get_strip_delay(user, source)
+
+ if (!do_after(user, time_to_strip, INTERRUPT_ALL, BUSY_ICON_HOSTILE, source, INTERRUPT_MOVED, BUSY_ICON_HOSTILE))
+ return FALSE
+
+ return TRUE
+
+/// A utility function for `/datum/strippable_item`s to finish unequipping an item from a mob.
+/datum/strippable_item/mob_item_slot/proc/finish_unequip_mob(obj/item/item, mob/source, mob/user)
+ if (!source.drop_inv_item_on_ground(item, force = (item.flags_item & FORCEDROP_CONDITIONAL))) //force if we can drop the item in this case
+ return FALSE
+
+ if (ismob(source))
+ var/mob/sourcemob = source
+ sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] has been stripped of [item] by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] [key_name(user)] has been stripped of [key_name(sourcemob)] of [item]")
+
+ // Updates speed in case stripped speed affecting item
+ source.recalculate_move_delay = TRUE
+
+/// A representation of the stripping UI
+/datum/strip_menu
+ /// The owner who has the element /datum/element/strippable
+ var/atom/movable/owner
+
+ /// The strippable element itself
+ var/datum/element/strippable/strippable
+
+ /// A lazy list of user mobs to a list of strip menu keys that they're interacting with
+ var/list/interactions
+
+/datum/strip_menu/New(atom/movable/owner, datum/element/strippable/strippable)
+ . = ..()
+ src.owner = owner
+ src.strippable = strippable
+
+/datum/strip_menu/Destroy()
+ owner = null
+ strippable = null
+
+ return ..()
+
+/datum/strip_menu/tgui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "StripMenu")
+ ui.open()
+
+
+/datum/strip_menu/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/inventory),
+ )
+
+/datum/strip_menu/ui_data(mob/user)
+ var/list/data = list()
+
+ var/list/items = list()
+
+ for (var/strippable_key in strippable.items)
+ var/datum/strippable_item/item_data = strippable.items[strippable_key]
+
+ if (!item_data.should_show(owner, user))
+ continue
+
+ var/list/result
+
+ if(strippable_key in LAZYACCESS(interactions, user))
+ LAZYSET(result, "interacting", TRUE)
+
+ var/obscuring = item_data.get_obscuring(owner)
+ if (obscuring != STRIPPABLE_OBSCURING_NONE)
+ LAZYSET(result, "obscured", obscuring)
+ items[strippable_key] = result
+ continue
+
+ var/obj/item/item = item_data.get_item(owner)
+ if (isnull(item))
+ if (item_data.has_no_item_alt_action())
+ LAZYINITLIST(result)
+ result["no_item_action"] = item_data.get_alternate_action(owner, user)
+ items[strippable_key] = result
+ continue
+
+ LAZYINITLIST(result)
+
+ result["icon"] = icon2base64(icon(item.icon, item.icon_state, frame = 1))
+ result["name"] = item.name
+ result["alternate"] = item_data.get_alternate_action(owner, user)
+
+ items[strippable_key] = result
+
+ data["items"] = items
+
+ // While most `\the`s are implicit, this one is not.
+ // In this case, `\The` would otherwise be used.
+ // This doesn't match with what it's used for, which is to say "Stripping the alien drone",
+ // as opposed to "Stripping The alien drone".
+ // Human names will still show without "the", as they are proper nouns.
+ data["name"] = "\the [owner]"
+
+ return data
+
+/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if (.)
+ return
+
+ . = TRUE
+
+ var/mob/user = ui.user
+
+ switch (action)
+ if ("equip")
+ var/key = params["key"]
+ var/datum/strippable_item/strippable_item = strippable.items[key]
+
+ if (isnull(strippable_item))
+ return
+
+ if (!strippable_item.should_show(owner, user))
+ return
+
+ if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
+ return
+
+ var/item = strippable_item.get_item(owner)
+ if (!isnull(item))
+ return
+
+ var/obj/item/held_item = user.get_held_item()
+ if (isnull(held_item))
+ return
+
+ if (!strippable_item.try_equip(owner, held_item, user))
+ return
+
+ LAZYORASSOCLIST(interactions, user, key)
+
+ // Yielding call
+ var/should_finish = strippable_item.start_equip(owner, held_item, user)
+
+ LAZYREMOVEASSOC(interactions, user, key)
+
+ if (!should_finish)
+ return
+
+ if (QDELETED(src) || QDELETED(owner))
+ return
+
+ // They equipped an item in the meantime
+ if (!isnull(strippable_item.get_item(owner)))
+ return
+
+ if (!user.Adjacent(owner))
+ return
+
+ strippable_item.finish_equip(owner, held_item, user)
+ if ("strip")
+ var/key = params["key"]
+ var/datum/strippable_item/strippable_item = strippable.items[key]
+
+ if (isnull(strippable_item))
+ return
+
+ if (!strippable_item.should_show(owner, user))
+ return
+
+ if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
+ return
+
+ var/item = strippable_item.get_item(owner)
+ if (isnull(item))
+ return
+
+ if (!strippable_item.try_unequip(owner, user))
+ return
+
+ LAZYORASSOCLIST(interactions, user, key)
+
+ var/should_unequip = strippable_item.start_unequip(owner, user)
+
+ LAZYREMOVEASSOC(interactions, user, key)
+
+ // Yielding call
+ if (!should_unequip)
+ return
+
+ if (QDELETED(src) || QDELETED(owner))
+ return
+
+ // They changed the item in the meantime
+ if (strippable_item.get_item(owner) != item)
+ return
+
+ if (!user.Adjacent(owner))
+ return
+
+ strippable_item.finish_unequip(owner, user)
+ if ("alt")
+ var/key = params["key"]
+ var/datum/strippable_item/strippable_item = strippable.items[key]
+
+ if (isnull(strippable_item))
+ return
+
+ if (!strippable_item.should_show(owner, user))
+ return
+
+ if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
+ return
+
+ var/item = strippable_item.get_item(owner)
+ if (isnull(item) && !strippable_item.has_no_item_alt_action())
+ return
+
+ if (isnull(strippable_item.get_alternate_action(owner, user)))
+ return
+
+ LAZYORASSOCLIST(interactions, user, key)
+
+ // Potentially yielding
+ strippable_item.alternate_action(owner, user)
+
+ LAZYREMOVEASSOC(interactions, user, key)
+
+/datum/strip_menu/ui_host(mob/user)
+ return owner
+
+/datum/strip_menu/ui_status(mob/user, datum/ui_state/state)
+ . = ..()
+
+ if (isliving(user))
+ var/mob/living/living_user = user
+
+ if (
+ . == UI_UPDATE \
+ && user.stat == CONSCIOUS \
+ && living_user.body_position == LYING_DOWN \
+ && user.Adjacent(owner)
+ )
+ return UI_INTERACTIVE
+
+/// Creates an assoc list of keys to /datum/strippable_item
+/proc/create_strippable_list(types)
+ var/list/strippable_items = list()
+
+ for (var/strippable_type in types)
+ var/datum/strippable_item/strippable_item = new strippable_type
+ strippable_items[strippable_item.key] = strippable_item
+
+ return strippable_items
diff --git a/code/datums/weather/weather_events/long.dm b/code/datums/weather/weather_events/long.dm
new file mode 100644
index 0000000000..9e569dd172
--- /dev/null
+++ b/code/datums/weather/weather_events/long.dm
@@ -0,0 +1,48 @@
+// Weather events for Big Red
+/datum/weather_event/dust/infinite
+ name = "Duststorm (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/sand/infinite
+ name = "Sandstorm (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/rock/infinite
+ name = "Rockstorm (Infinite)"
+ length = INFINITY
+
+// Weather events for Chances Claim
+/datum/weather_event/light_rain/lv522/infinite
+ name = "LV522 Light Rain (Infinite)"
+ length = INFINITY
+
+// Weather events for LV624
+/datum/weather_event/light_rain/infinite
+ name = "Light Rain (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/heavy_rain/infinite
+ name = "Heavy Rain (Infinite)"
+ length = INFINITY
+
+// Weather events for New Varadero
+/datum/weather_event/light_rain/varadero/infinite
+ name = "Tropical Storm (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/monsoon/infinite
+ name = "Monsoon Warning (Infinite)"
+ length = INFINITY
+
+// Weather events for Sorokyne
+/datum/weather_event/snow/infinite
+ name = "Snow (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/snowstorm/infinite
+ name = "Snowstorm (Infinite)"
+ length = INFINITY
+
+/datum/weather_event/blizzard/infinite
+ name = "Blizzard (Infinite)"
+ length = INFINITY
diff --git a/code/datums/weather/weather_events/lv522_chances_claim.dm b/code/datums/weather/weather_events/lv522_chances_claim.dm
index 6b7b296b8c..9d1ec84d71 100644
--- a/code/datums/weather/weather_events/lv522_chances_claim.dm
+++ b/code/datums/weather/weather_events/lv522_chances_claim.dm
@@ -1,4 +1,6 @@
/datum/weather_event/light_rain/lv522
+ name = "LV522 Light Rain"
+ display_name = "Light Rain"
length = 3 MINUTES
lightning_chance = 4
diff --git a/code/datums/weather/weather_events/new_varadero.dm b/code/datums/weather/weather_events/new_varadero.dm
index f2af23c3f1..36845a2940 100644
--- a/code/datums/weather/weather_events/new_varadero.dm
+++ b/code/datums/weather/weather_events/new_varadero.dm
@@ -1,22 +1,13 @@
-/datum/weather_event/light_rain
+/datum/weather_event/light_rain/varadero
name = "Tropical Storm"
display_name = "Tropical Storm"
length = 4 MINUTES
- fullscreen_type = /atom/movable/screen/fullscreen/weather/low
- turf_overlay_icon_state = "strata_storm"
turf_overlay_alpha = 40
- effect_message = null
- damage_per_tick = 0
-
has_process = TRUE
lightning_chance = 1
- ambience = 'sound/ambience/rainforest.ogg'
-
- fire_smothering_strength = 1
-
/datum/weather_event/monsoon
name = "Monsoon Warning"
display_name = "Monsoon Warning"
diff --git a/code/datums/weather/weather_map_holders/new_varadero.dm b/code/datums/weather/weather_map_holders/new_varadero.dm
index 8222001f47..1edb2c42c8 100644
--- a/code/datums/weather/weather_map_holders/new_varadero.dm
+++ b/code/datums/weather/weather_map_holders/new_varadero.dm
@@ -5,7 +5,7 @@
no_weather_turf_icon_state = "strata_clearsky"
potential_weather_events = list(
- /datum/weather_event/light_rain,
+ /datum/weather_event/light_rain/varadero,
/datum/weather_event/monsoon,
)
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
index 7ba67133b5..327af25c33 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
@@ -98,7 +98,6 @@
list("USCM Technician Backpack", round(scale * 15), /obj/item/storage/backpack/marine/tech, VENDOR_ITEM_REGULAR),
list("USCM Satchel", round(scale * 15), /obj/item/storage/backpack/marine/satchel, VENDOR_ITEM_REGULAR),
list("Technician Chestrig", round(scale * 15), /obj/item/storage/backpack/marine/satchel/tech, VENDOR_ITEM_REGULAR),
- list("Shotgun Scabbard", round(scale * 5), /obj/item/storage/large_holster/m37, VENDOR_ITEM_REGULAR),
list("RESTRICTED BACKPACKS", -1, null, null),
list("Radio Telephone Backpack", round(max(1,(scale * 0.5))), /obj/item/storage/backpack/marine/satchel/rto, VENDOR_ITEM_REGULAR),
@@ -117,7 +116,6 @@
list("First-Aid Pouch (Injectors)", round(scale * 15), /obj/item/storage/pouch/firstaid/full, VENDOR_ITEM_REGULAR),
list("Flare Pouch (Full)", round(scale * 15), /obj/item/storage/pouch/flare/full, VENDOR_ITEM_REGULAR),
list("Magazine Pouch", round(scale * 15), /obj/item/storage/pouch/magazine, VENDOR_ITEM_REGULAR),
- list("Shotgun Shell Pouch", round(scale * 15), /obj/item/storage/pouch/shotgun, VENDOR_ITEM_REGULAR),
list("Medium General Pouch", round(scale * 15), /obj/item/storage/pouch/general/medium, VENDOR_ITEM_REGULAR),
list("Pistol Magazine Pouch", round(scale * 15), /obj/item/storage/pouch/magazine/pistol, VENDOR_ITEM_REGULAR),
list("Pistol Pouch", round(scale * 15), /obj/item/storage/pouch/pistol, VENDOR_ITEM_REGULAR),
@@ -131,7 +129,9 @@
list("Sling Pouch", round(scale * 1.25), /obj/item/storage/pouch/sling, VENDOR_ITEM_REGULAR),
list("MASK", -1, null, null, null),
- list("Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas, VENDOR_ITEM_REGULAR),
+ list("M5 Standalone Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas/m5, VENDOR_ITEM_REGULAR),
+ list("M5 Integrated Gas Mask", round(scale * 10), /obj/item/prop/helmetgarb/helmet_gasmask, VENDOR_ITEM_REGULAR),
+ list("Tactical Wrap", round(scale * 10), /obj/item/clothing/mask/rebreather/scarf/tacticalmask, VENDOR_ITEM_REGULAR),
list("Heat Absorbent Coif", round(scale * 10), /obj/item/clothing/mask/rebreather/scarf, VENDOR_ITEM_REGULAR),
list("Rebreather", round(scale * 10), /obj/item/clothing/mask/rebreather, MARINE_CAN_BUY_MASK, VENDOR_ITEM_REGULAR),
@@ -215,18 +215,14 @@
list("Sling Pouch", round(scale * 1.25), /obj/item/storage/pouch/sling, VENDOR_ITEM_REGULAR),
list("MASK", -1, null, null, null),
- list("Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas, VENDOR_ITEM_REGULAR),
+ list("PMK-63 Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas/upp, VENDOR_ITEM_REGULAR),
+ list("PMK-63N Integrated Gas Mask", round(scale * 10), /obj/item/prop/helmetgarb/helmet_gasmask/upp, VENDOR_ITEM_REGULAR),
list("Heat Absorbent Coif", round(scale * 10), /obj/item/clothing/mask/rebreather/scarf/tan, VENDOR_ITEM_REGULAR),
list("Rebreather", round(scale * 10), /obj/item/clothing/mask/rebreather, MARINE_CAN_BUY_MASK, VENDOR_ITEM_REGULAR),
list("MISCELLANEOUS", -1, null, null, null),
list("Ballistic goggles", round(scale * 10), /obj/item/clothing/glasses/mgoggles, VENDOR_ITEM_REGULAR),
- list("M1A1 Ballistic goggles", round(scale * 10), /obj/item/clothing/glasses/mgoggles/v2, VENDOR_ITEM_REGULAR),
list("Prescription ballistic goggles", round(scale * 10), /obj/item/clothing/glasses/mgoggles/prescription, VENDOR_ITEM_REGULAR),
- list("Marine RPG glasses", round(scale * 10), /obj/item/clothing/glasses/regular, VENDOR_ITEM_REGULAR),
- list("M5 Integrated Gas Mask", round(scale * 10), /obj/item/prop/helmetgarb/helmet_gasmask, VENDOR_ITEM_REGULAR),
- list("M10 Helmet Netting", round(scale * 10), /obj/item/prop/helmetgarb/netting, VENDOR_ITEM_REGULAR),
- list("M10 Helmet Rain Cover", round(scale * 10), /obj/item/prop/helmetgarb/raincover, VENDOR_ITEM_REGULAR),
list("Firearm Lubricant", round(scale * 15), /obj/item/prop/helmetgarb/gunoil, VENDOR_ITEM_REGULAR),
list("UPP Airborne Reconnaissance Shoulder Patch", round(scale * 15), /obj/item/clothing/accessory/patch/upp/platoon, VENDOR_ITEM_REGULAR),
list("UPPA Shoulder Patch", round(scale * 15), /obj/item/clothing/accessory/patch/upp, VENDOR_ITEM_REGULAR),
@@ -259,7 +255,6 @@
list("BACKPACK", -1, null, null, null),
list("Lightweight IMP Backpack", round(scale * 15), /obj/item/storage/backpack/marine/standard, VENDOR_ITEM_REGULAR),
list("USCM Satchel", round(scale * 15), /obj/item/storage/backpack/marine/satchel, VENDOR_ITEM_REGULAR),
- list("Shotgun Scabbard", round(scale * 5), /obj/item/storage/large_holster/m37/standard, VENDOR_ITEM_REGULAR),
list("BELTS", -1, null, null),
list("M276 Pattern Ammo Load Rig", round(scale * 15), /obj/item/storage/belt/marine/standard, VENDOR_ITEM_REGULAR),
@@ -275,7 +270,6 @@
list("First-Aid Pouch (Injectors)", round(scale * 15), /obj/item/storage/pouch/firstaid/full, VENDOR_ITEM_REGULAR),
list("Flare Pouch (Full)", round(scale * 15), /obj/item/storage/pouch/flare/full, VENDOR_ITEM_REGULAR),
list("Magazine Pouch", round(scale * 15), /obj/item/storage/pouch/magazine, VENDOR_ITEM_REGULAR),
- list("Shotgun Shell Pouch", round(scale * 15), /obj/item/storage/pouch/shotgun, VENDOR_ITEM_REGULAR),
list("Medium General Pouch", round(scale * 15), /obj/item/storage/pouch/general/medium, VENDOR_ITEM_REGULAR),
list("Pistol Magazine Pouch", round(scale * 15), /obj/item/storage/pouch/magazine/pistol, VENDOR_ITEM_REGULAR),
list("Pistol Pouch", round(scale * 15), /obj/item/storage/pouch/pistol, VENDOR_ITEM_REGULAR),
@@ -289,7 +283,7 @@
list("Sling Pouch", round(scale * 1.25), /obj/item/storage/pouch/sling, VENDOR_ITEM_REGULAR),
list("MASK", -1, null, null, null),
- list("Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas, VENDOR_ITEM_REGULAR),
+ list("M5 Standalone Gas Mask", round(scale * 15), /obj/item/clothing/mask/gas/m5, VENDOR_ITEM_REGULAR),
list("Heat Absorbent Coif", round(scale * 10), /obj/item/clothing/mask/rebreather/scarf, VENDOR_ITEM_REGULAR),
list("Rebreather", round(scale * 10), /obj/item/clothing/mask/rebreather, MARINE_CAN_BUY_MASK, VENDOR_ITEM_REGULAR),
@@ -298,8 +292,6 @@
list("M1A1 Ballistic goggles", round(scale * 10), /obj/item/clothing/glasses/mgoggles/v2, VENDOR_ITEM_REGULAR),
list("Prescription ballistic goggles", round(scale * 10), /obj/item/clothing/glasses/mgoggles/prescription, VENDOR_ITEM_REGULAR),
list("Marine RPG glasses", round(scale * 10), /obj/item/clothing/glasses/regular, VENDOR_ITEM_REGULAR),
- list("M10 Helmet Netting", round(scale * 10), /obj/item/prop/helmetgarb/netting, VENDOR_ITEM_REGULAR),
- list("M10 Helmet Rain Cover", round(scale * 10), /obj/item/prop/helmetgarb/raincover, VENDOR_ITEM_REGULAR),
list("Firearm Lubricant", round(scale * 15), /obj/item/prop/helmetgarb/gunoil, VENDOR_ITEM_REGULAR),
list("USCM Flair", round(scale * 15), /obj/item/prop/helmetgarb/flair_uscm, VENDOR_ITEM_REGULAR),
list("FORECON Shoulder Patch", round(scale * 15), /obj/item/clothing/accessory/patch/forecon, VENDOR_ITEM_REGULAR),
diff --git a/code/game/objects/effects/landmarks/item_pool.dm b/code/game/objects/effects/landmarks/item_pool.dm
index e01a6bbc3f..9a39a5cf59 100644
--- a/code/game/objects/effects/landmarks/item_pool.dm
+++ b/code/game/objects/effects/landmarks/item_pool.dm
@@ -41,3 +41,7 @@
/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot
icon_state = "ipool_bubshot"
type_to_spawn = /obj/item/ammo_magazine/shotgun/buckshot
+
+/obj/effect/landmark/item_pool_spawner/survivor_ammo/buckshot/upp
+ icon_state = "ipool_bubshot"
+ type_to_spawn = /obj/item/ammo_magazine/handful/shotgun/heavy/buckshot
diff --git a/code/game/objects/items/misc.dm b/code/game/objects/items/misc.dm
index ea0168453c..d4c9c13b58 100644
--- a/code/game/objects/items/misc.dm
+++ b/code/game/objects/items/misc.dm
@@ -56,13 +56,13 @@
..()
if(!gripped)
user.visible_message(SPAN_NOTICE("[user] grips [src] tightly."), SPAN_NOTICE("You grip [src] tightly."))
- flags_item |= NODROP
+ flags_item |= NODROP|FORCEDROP_CONDITIONAL
ADD_TRAIT(user, TRAIT_HOLDS_CANE, TRAIT_SOURCE_ITEM)
user.AddComponent(/datum/component/footstep, 6, 35, 4, 1, "cane_step")
gripped = TRUE
else
user.visible_message(SPAN_NOTICE("[user] loosens \his grip on [src]."), SPAN_NOTICE("You loosen your grip on [src]."))
- flags_item &= ~NODROP
+ flags_item &= ~(NODROP|FORCEDROP_CONDITIONAL)
REMOVE_TRAIT(user, TRAIT_HOLDS_CANE, TRAIT_SOURCE_ITEM)
// Ideally, this would be something like a component added onto every mob that prioritizes certain sounds, such as stomping over canes.
var/component = user.GetComponent(/datum/component/footstep)
diff --git a/code/game/objects/items/props/helmetgarb.dm b/code/game/objects/items/props/helmetgarb.dm
index ce63aaa13a..2c83f5beee 100644
--- a/code/game/objects/items/props/helmetgarb.dm
+++ b/code/game/objects/items/props/helmetgarb.dm
@@ -493,7 +493,7 @@
/obj/item/prop/helmetgarb/helmet_gasmask
name = "\improper M5 integrated gasmask"
- desc = "The USCM had its funding pulled for these when it became apparent that not every deployed enlisted was wearing a helmet 24/7; much to the bafflement of UA High Command."
+ desc = "The standard service gas mask of the USCM as part of a modernization program meant to replace the need for MOPP gear. While the program failed, these rarely do."
icon_state = "helmet_gasmask"
/obj/item/prop/helmetgarb/helmet_gasmask/on_enter_storage(obj/item/storage/internal/helmet_internal_inventory)
@@ -520,6 +520,11 @@
helmet_item.flags_inventory &= ~(BLOCKGASEFFECT)
helmet_item.flags_inv_hide &= ~(HIDEFACE)
+/obj/item/prop/helmetgarb/helmet_gasmask/upp
+ name = "\improper PMK-63N integrated gasmask"
+ desc = "The frontline variant of the PMK-63 produced to be compatible with in service helmets."
+ icon_state = "helmet_uppgasmask"
+
/obj/item/prop/helmetgarb/trimmed_wire
name = "trimmed barbed wire"
desc = "It is a length of barbed wire that's had most of the sharp points filed down so that it is safe to handle."
diff --git a/code/game/objects/items/reagent_containers/food/condiment.dm b/code/game/objects/items/reagent_containers/food/condiment.dm
index a13489f0af..e676b310f1 100644
--- a/code/game/objects/items/reagent_containers/food/condiment.dm
+++ b/code/game/objects/items/reagent_containers/food/condiment.dm
@@ -86,7 +86,7 @@
to_chat(user, SPAN_NOTICE(" You transfer [trans] units of the condiment to [target]."))
/obj/item/reagent_container/food/condiment/on_reagent_change()
- if(icon_state == "saltshakersmall" || icon_state == "peppermillsmall" || icon_state == "hotsauce_cholula" || icon_state == "hotsauce_franks" || icon_state == "hotsauce_sriracha" || icon_state == "hotsauce_tabasco" || icon_state == "coldsauce_cole")
+ if(icon_state == "saltshakersmall" || icon_state == "peppermillsmall" || icon_state == "hotsauce_cholula" || icon_state == "hotsauce_franks" || icon_state == "hotsauce_sriracha" || icon_state == "hotsauce_tabasco" || icon_state == "coldsauce_cole" || icon_state == "eggpowder" || icon_state == "milkpowder" || icon_state == "bjpowder" || icon_state == "ojpowder" || icon_state == "ajpowder" || icon_state == "wjpowder" || icon_state == "gjpowder" || icon_state == "pjpowder")
return
if(reagents.reagent_list.len > 0)
switch(reagents.get_master_reagent_id())
@@ -236,3 +236,79 @@
/obj/item/reagent_container/food/condiment/coldsauce/Initialize()
. = ..()
reagents.add_reagent("frostoil", 60)
+
+/obj/item/reagent_container/food/condiment/juice
+ name = "Juice Packet"
+ desc = "A small packet of dehydrated fruit-juice powder. Mix with water for a 'tasty' beverage. This one appears to be missing the label indicating what flavor it is however."
+ w_class = SIZE_TINY
+ icon_state = "bjpowder"
+ amount_per_transfer_from_this = 1
+ volume = 6
+
+/obj/item/reagent_container/food/condiment/juice/orange
+ name = "Orange Juice Packet"
+ desc = "A small packet of dehydrated orange-juice powder. Mix with water for a 'tasty' beverage."
+ icon_state = "ojpowder"
+
+/obj/item/reagent_container/food/condiment/juice/orange/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_orange_juice", 6)
+
+/obj/item/reagent_container/food/condiment/juice/apple
+ name = "Apple Juice Packet"
+ desc = "A small packet of dehydrated apple-juice powder. Mix with water for a 'tasty' beverage."
+ icon_state = "ajpowder"
+
+/obj/item/reagent_container/food/condiment/juice/apple/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_apple_juice", 6)
+
+/obj/item/reagent_container/food/condiment/juice/watermelon
+ name = "Watermelon Juice Packet"
+ desc = "A small packet of dehydrated watermelon-juice powder. Mix with water for a 'tasty' beverage."
+ icon_state = "wjpowder"
+
+/obj/item/reagent_container/food/condiment/juice/watermelon/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_watermelon_juice", 6)
+
+/obj/item/reagent_container/food/condiment/juice/grape
+ name = "Grape Juice Packet"
+ desc = "A small packet of dehydrated grape-juice powder. Mix with water for a 'tasty' beverage."
+ icon_state = "gjpowder"
+
+/obj/item/reagent_container/food/condiment/juice/grape/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_grape_juice", 6)
+
+/obj/item/reagent_container/food/condiment/juice/pineapple
+ name = "Pineapple Juice Packet"
+ desc = "A small packet of dehydrated pineapple-juice powder. Mix with water for a 'tasty' beverage."
+ icon_state = "pjpowder"
+
+/obj/item/reagent_container/food/condiment/juice/pineapple/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_pineapple_juice", 6)
+
+/obj/item/reagent_container/food/condiment/juice/egg
+ name = "Powdered Egg Packet"
+ desc = "A small packet of dehydrated egg-whites and egg-yolk powder. Mix with water for a passable alternative to the real deal."
+ icon_state = "eggpowder"
+ volume = 12
+ w_class = SIZE_SMALL
+
+/obj/item/reagent_container/food/condiment/juice/egg/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_egg_powder", 12)
+
+/obj/item/reagent_container/food/condiment/juice/milk
+ name = "Dehydrated Milk Packet"
+ desc = "A small packet of dehydrated milk powder. Mix with water for a passable alternative to actual milk."
+ icon_state = "milkpowder"
+ amount_per_transfer_from_this = 1
+ volume = 12
+ w_class = SIZE_SMALL
+
+/obj/item/reagent_container/food/condiment/juice/milk/Initialize()
+ . = ..()
+ reagents.add_reagent("dehydrated_milk_powder", 12)
diff --git a/code/game/objects/items/reagent_containers/food/snacks.dm b/code/game/objects/items/reagent_containers/food/snacks.dm
index d27d6adb09..35f34d9d80 100644
--- a/code/game/objects/items/reagent_containers/food/snacks.dm
+++ b/code/game/objects/items/reagent_containers/food/snacks.dm
@@ -567,6 +567,16 @@
new newegg(loc)
qdel(src)
+/obj/item/reagent_container/food/snacks/egg/dried
+ name = "reconstituted egg"
+ desc = "An egg! Or it was in another life, at least. Now it's just a sad pile of goop that might work in recipes."
+ icon_state = "honeycomb"
+ filling_color = "#FDFFD1"
+
+/obj/item/reagent_container/food/snacks/egg/dried/Initialize()
+ . = ..()
+ reagents.add_reagent("egg", 1)
+
/obj/item/reagent_container/food/snacks/friedegg
name = "Fried egg"
desc = "A fried egg, with a touch of salt and pepper."
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index d544b5b281..bcfa7ba1fa 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -711,7 +711,7 @@
icon_state = "mealpack"
w_class = SIZE_SMALL
can_hold = list()
- storage_slots = 7
+ storage_slots = 8
max_w_class = 0
use_sound = "rip"
var/isopened = 0
@@ -728,8 +728,9 @@
//1 in 3 chance of getting a fortune cookie
var/cookie = rand(1,3)
var/matches_type = rand(1, 5)
+ var/juice_type = rand(1, 5)
if(cookie == 1)
- storage_slots = 8
+ storage_slots = 9
new /obj/item/reagent_container/food/snacks/packaged_meal(src, main)
new /obj/item/reagent_container/food/snacks/packaged_meal(src, second)
new /obj/item/reagent_container/food/snacks/packaged_meal(src, side)
@@ -749,6 +750,17 @@
new /obj/item/storage/fancy/cigar/matchbook/wy_gold(src)
if(5)
new /obj/item/storage/fancy/cigar/matchbook/brown(src)
+ switch(juice_type)
+ if(1)
+ new /obj/item/reagent_container/food/condiment/juice/orange(src)
+ if(2)
+ new /obj/item/reagent_container/food/condiment/juice/apple(src)
+ if(3)
+ new /obj/item/reagent_container/food/condiment/juice/watermelon(src)
+ if(4)
+ new /obj/item/reagent_container/food/condiment/juice/grape(src)
+ if(5)
+ new /obj/item/reagent_container/food/condiment/juice/pineapple(src)
/obj/item/storage/box/MRE/Initialize()
. = ..()
@@ -761,3 +773,14 @@
else if(!isopened)
isopened = 1
icon_state = "mealpackopened"
+
+/obj/item/storage/box/powderedmilk
+ name = "box of powdered milk packets"
+ desc = "It has a weird stain on it."
+
+/obj/item/storage/box/powderedmilk/fill_preset_inventory()
+ new /obj/item/reagent_container/food/condiment/juice/milk(src)
+ new /obj/item/reagent_container/food/condiment/juice/milk(src)
+ new /obj/item/reagent_container/food/condiment/juice/milk(src)
+ new /obj/item/reagent_container/food/condiment/juice/milk(src)
+ new /obj/item/reagent_container/food/condiment/juice/milk(src)
diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm
index be7571fa84..36e0ea702a 100644
--- a/code/game/objects/items/weapons/twohanded.dm
+++ b/code/game/objects/items/weapons/twohanded.dm
@@ -101,7 +101,7 @@
w_class = SIZE_HUGE
icon_state = "offhand"
name = "offhand"
- flags_item = DELONDROP|TWOHANDED|WIELDED
+ flags_item = DELONDROP|TWOHANDED|WIELDED|CANTSTRIP
/obj/item/weapon/twohanded/offhand/unwield(mob/user)
if(flags_item & WIELDED)
diff --git a/code/game/turfs/transit.dm b/code/game/turfs/transit.dm
index 11f3d50ce4..2e5396d0b1 100644
--- a/code/game/turfs/transit.dm
+++ b/code/game/turfs/transit.dm
@@ -261,6 +261,10 @@
shuttle_tag = DROPSHIP_TRIPOLI
dir = SOUTH
+/turf/open/space/transit/dropship/upp
+ shuttle_tag = DROPSHIP_UPP
+ dir = SOUTH
+
/turf/open/space/transit/south
dir = SOUTH
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index 8e19a19053..b887cff60c 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -341,3 +341,24 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (!item_filename)
return
. = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))
+
+/datum/asset/simple/inventory
+ assets = list(
+ "inventory-glasses.png" = 'icons/ui_Icons/inventory/glasses.png',
+ "inventory-head.png" = 'icons/ui_Icons/inventory/head.png',
+ "inventory-neck.png" = 'icons/ui_Icons/inventory/neck.png',
+ "inventory-mask.png" = 'icons/ui_Icons/inventory/mask.png',
+ "inventory-ears.png" = 'icons/ui_Icons/inventory/ears.png',
+ "inventory-uniform.png" = 'icons/ui_Icons/inventory/uniform.png',
+ "inventory-suit.png" = 'icons/ui_Icons/inventory/suit.png',
+ "inventory-gloves.png" = 'icons/ui_Icons/inventory/gloves.png',
+ "inventory-hand_l.png" = 'icons/ui_Icons/inventory/hand_l.png',
+ "inventory-hand_r.png" = 'icons/ui_Icons/inventory/hand_r.png',
+ "inventory-shoes.png" = 'icons/ui_Icons/inventory/shoes.png',
+ "inventory-suit_storage.png" = 'icons/ui_Icons/inventory/suit_storage.png',
+ "inventory-id.png" = 'icons/ui_Icons/inventory/id.png',
+ "inventory-belt.png" = 'icons/ui_Icons/inventory/belt.png',
+ "inventory-back.png" = 'icons/ui_Icons/inventory/back.png',
+ "inventory-pocket.png" = 'icons/ui_Icons/inventory/pocket.png',
+ "inventory-collar.png" = 'icons/ui_Icons/inventory/collar.png',
+ )
diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm
index 2c9b5ba243..44a9a88a6b 100644
--- a/code/modules/client/preferences_gear.dm
+++ b/code/modules/client/preferences_gear.dm
@@ -138,6 +138,7 @@ var/global/list/gear_datums_by_name = list()
/datum/gear/mask/gas
display_name = "Gas mask"
path = /obj/item/clothing/mask/gas
+ cost = 1
/datum/gear/mask/scarf_black
display_name = "Scarf, black"
@@ -1096,10 +1097,6 @@ var/global/list/gear_datums_by_name = list()
display_name = "Solar Devils shoulder patch"
path = /obj/item/clothing/accessory/patch/devils
-/datum/gear/misc/patch_uscm/falcon
- display_name = "Falling Falcons shoulder patch"
- path = /obj/item/clothing/accessory/patch/falcon
-
/datum/gear/misc/family_photo
display_name = "Family photo"
path = /obj/item/prop/helmetgarb/family_photo
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index a2366692f9..f3519dbf00 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -294,6 +294,7 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list(
/obj/item/prop/helmetgarb/helmet_nvg/cosmetic = HELMET_GARB_RELAY_ICON_STATE,
/obj/item/prop/helmetgarb/helmet_nvg/marsoc = HELMET_GARB_RELAY_ICON_STATE,
/obj/item/prop/helmetgarb/helmet_gasmask = "helmet_gasmask",
+ /obj/item/prop/helmetgarb/helmet_gasmask/upp = "helmet_uppgasmask",
/obj/item/prop/helmetgarb/flair_initech = "flair_initech",
/obj/item/prop/helmetgarb/flair_io = "flair_io",
/obj/item/prop/helmetgarb/flair_peace ="flair_peace_smiley",
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 1b1ff8d606..a0a39f136e 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -1,7 +1,7 @@
/obj/item/clothing/mask/gas
name = "gas mask"
- desc = "A face-covering mask that can be connected to an air supply. Filters harmful gases from the air."
+ desc = "A civilian grade, face-covering mask that can be connected to an air supply. Filters harmful gases from the air."
icon_state = "gas_alt"
flags_inventory = COVERMOUTH | COVEREYES | ALLOWINTERNALS | BLOCKGASEFFECT | ALLOWREBREATH | ALLOWCPR
flags_inv_hide = HIDEEARS|HIDEFACE|HIDELOWHAIR
@@ -31,6 +31,18 @@
icon_state = "kutjevo_respirator"
item_state = "kutjevo_respirator"
+/obj/item/clothing/mask/gas/m5
+ name = "\improper M5 standalone gasmask"
+ desc = "The standard service gas mask of the USCM as part of a modernization program meant to replace the need for MOPP gear. This one is the alternative variant meant for Marines not in combat kit."
+ icon_state = "m5_gasmask"
+ item_state = "m5_gasmask"
+
+/obj/item/clothing/mask/gas/upp
+ name = "\improper PMK-63 gasmask"
+ desc = "The Union service mask issued to backline troops not prepared for combat."
+ icon_state = "upp_gasmask"
+ item_state = "upp_gasmask"
+
/obj/item/clothing/mask/gas/pve_mopp
name = "\improper M2 MOPP mask"
desc = "The M2 MOPP mask includes a full covering cowl that securely attaches to the MOPP suit. It is capable of protecting of a variety of radiological and biological threats."
diff --git a/code/modules/cm_marines/equipment/guncases.dm b/code/modules/cm_marines/equipment/guncases.dm
index 7dbaf80a2e..be7db6068a 100644
--- a/code/modules/cm_marines/equipment/guncases.dm
+++ b/code/modules/cm_marines/equipment/guncases.dm
@@ -240,6 +240,8 @@
/obj/item/storage/box/guncase/shotgunpump/special/fill_preset_inventory()
new /obj/item/weapon/gun/shotgun/pump(src)
new /obj/item/ammo_magazine/shotgun/buckshot/special(src)
+ new /obj/item/storage/pouch/shotgun(src)
+ new /obj/item/storage/large_holster/m37(src)
/obj/item/storage/box/guncase/mk45_automag
name = "\improper MK-45 Automagnum case"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 4ea2f35aa6..2a419eddf1 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -399,23 +399,6 @@
bodytemperature = max(bodytemperature, BODYTEMP_HEAT_DAMAGE_LIMIT+10)
recalculate_move_delay = TRUE
-
-/mob/living/carbon/show_inv(mob/living/carbon/user as mob)
- user.set_interaction(src)
- var/dat = {"
-
[name]
-
-
Head(Mask): [(wear_mask ? wear_mask : "Nothing")]
-
Left Hand: [(l_hand ? l_hand : "Nothing")]
-
Right Hand: [(r_hand ? r_hand : "Nothing")]
-
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? " Set Internal" : "")]
-
[(handcuffed ? "Handcuffed" : "Not Handcuffed")]
-
[(internal ? "Remove Internal" : "")]
-
Refresh
-
Close
-
"}
- show_browser(user, dat, name, "mob[name]")
-
/**
* Called by [/mob/dead/observer/proc/do_observe] when a carbon mob is observed by a ghost with [/datum/preferences/var/auto_observe] enabled.
*
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 350bcec3e8..c394431c72 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -13,7 +13,7 @@
create_reagents(1000)
if(!real_name || !name)
change_real_name(src, "unknown")
-
+ AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human, should_strip))
. = ..()
prev_gender = gender // Debug for plural genders
@@ -270,49 +270,6 @@
return TRUE
return FALSE
-
-
-/mob/living/carbon/human/show_inv(mob/living/user)
- if(ismaintdrone(user))
- return
- var/obj/item/clothing/under/suit = null
- if(istype(w_uniform, /obj/item/clothing/under))
- suit = w_uniform
-
- user.set_interaction(src)
- var/dat = {"
-
[name]
-
-
(Exo)Suit: [(wear_suit ? wear_suit : "Nothing")]
-
Suit Storage: [(s_store ? s_store : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(s_store, /obj/item/tank) && !( internal )) ? " Set Internal" : "")]
-
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? " Set Internal" : "")]
-
Head(Mask): [(wear_mask ? wear_mask : "Nothing")]
-
Left Hand: [(l_hand ? l_hand : "Nothing")]
-
Right Hand: [(r_hand ? r_hand : "Nothing")]
-
Gloves: [(gloves ? gloves : "Nothing")]
-
Eyes: [(glasses ? glasses : "Nothing")]
-
Left Ear: [(wear_l_ear ? wear_l_ear : "Nothing")]
-
Right Ear: [(wear_r_ear ? wear_r_ear : "Nothing")]
-
Head: [(head ? head : "Nothing")]
-
Shoes: [(shoes ? shoes : "Nothing")]
-
Belt: [(belt ? belt : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(belt, /obj/item/tank) && !internal) ? " Set Internal" : "")]
-
Uniform: [(w_uniform ? w_uniform : "Nothing")] [(suit) ? ((suit.has_sensor == UNIFORM_HAS_SENSORS) ? " Sensors" : "") : null]
-
ID: [(wear_id ? wear_id : "Nothing")]
-
Left Pocket: [(l_store ? l_store : "Nothing")]
-
Right Pocket: [(r_store ? r_store : "Nothing")]
-
- [handcuffed ? "
Handcuffed" : ""]
- [legcuffed ? "
Legcuffed" : ""]
- [suit && LAZYLEN(suit.accessories) ? "
Remove Accessory" : ""]
- [internal ? "
Remove Internal" : ""]
- [istype(wear_id, /obj/item/card/id/dogtag) ? "
Retrieve Info Tag" : ""]
-
Remove Splints
-
-
Refresh
-
Close
-
"}
- show_browser(user, dat, name, "mob[name]")
-
/**
* Handles any storage containers that the human is looking inside when auto-observed.
*/
@@ -428,9 +385,6 @@
/mob/living/carbon/human/Topic(href, href_list)
- if(href_list["refresh"])
- if(interactee&&(in_range(src, usr)))
- show_inv(interactee)
if(href_list["mach_close"])
var/t1 = text("window=[]", href_list["mach_close"])
@@ -475,76 +429,6 @@
what = usr.get_active_hand()
usr.stripPanelEquip(what,src,slot)
- if(href_list["internal"])
-
- if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr))
- attack_log += text("\[[time_stamp()]\] Has had their internals toggled by [key_name(usr)]")
- usr.attack_log += text("\[[time_stamp()]\] Attempted to toggle [key_name(src)]'s' internals")
- if(internal)
- usr.visible_message(SPAN_DANGER("[usr] is trying to disable [src]'s internals"), null, null, 3)
- else
- usr.visible_message(SPAN_DANGER("[usr] is trying to enable [src]'s internals."), null, null, 3)
-
- if(do_after(usr, POCKET_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(internal)
- internal.add_fingerprint(usr)
- internal = null
- visible_message("[src] is no longer running on internals.", null, null, 1)
- else
- if(istype(wear_mask, /obj/item/clothing/mask))
- if(istype(back, /obj/item/tank))
- internal = back
- else if(istype(s_store, /obj/item/tank))
- internal = s_store
- else if(istype(belt, /obj/item/tank))
- internal = belt
- if(internal)
- visible_message(SPAN_NOTICE("[src] is now running on internals."), null, null, 1)
- internal.add_fingerprint(usr)
-
- // Update strip window
- if(usr.interactee == src && Adjacent(usr))
- show_inv(usr)
-
-
- if(href_list["splints"])
- if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr))
- if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group))
- to_chat(usr, SPAN_WARNING("You can't strip a crit or dead member of another faction!"))
- return
- attack_log += text("\[[time_stamp()]\] Has had their splints removed by [key_name(usr)]")
- usr.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(src)]'s' splints ")
- remove_splints(usr)
-
- if(href_list["tie"])
- if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr))
- if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group))
- to_chat(usr, SPAN_WARNING("You can't strip a crit or dead member of another faction!"))
- return
- if(w_uniform && istype(w_uniform, /obj/item/clothing))
- var/obj/item/clothing/under/U = w_uniform
- if(!LAZYLEN(U.accessories))
- return FALSE
- var/obj/item/clothing/accessory/A = LAZYACCESS(U.accessories, 1)
- if(LAZYLEN(U.accessories) > 1)
- A = tgui_input_list(usr, "Select an accessory to remove from [U]", "Remove accessory", U.accessories)
- if(!istype(A))
- return
- attack_log += text("\[[time_stamp()]\] Has had their accessory ([A]) removed by [key_name(usr)]")
- usr.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(src)]'s' accessory ([A])")
- if(istype(A, /obj/item/clothing/accessory/holobadge) || istype(A, /obj/item/clothing/accessory/medal))
- visible_message(SPAN_DANGER("[usr] tears off \the [A] from [src]'s [U]!"), null, null, 5)
- if(U == w_uniform)
- U.remove_accessory(usr, A)
- else
- if(HAS_TRAIT(src, TRAIT_UNSTRIPPABLE) && !is_mob_incapacitated()) //Can't strip the unstrippable!
- to_chat(usr, SPAN_DANGER("[src] has an unbreakable grip on their equipment!"))
- return
- visible_message(SPAN_DANGER("[usr] is trying to take off \a [A] from [src]'s [U]!"), null, null, 5)
- if(do_after(usr, get_strip_delay(usr, src), INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(U == w_uniform)
- U.remove_accessory(usr, A)
-
if(href_list["sensor"])
if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr))
if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group))
diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm
new file mode 100644
index 0000000000..fbf6fb5ce4
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/human_stripping.dm
@@ -0,0 +1,272 @@
+GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list(
+ /datum/strippable_item/mob_item_slot/head,
+ /datum/strippable_item/mob_item_slot/back,
+ /datum/strippable_item/mob_item_slot/mask,
+ /datum/strippable_item/mob_item_slot/eyes,
+ /datum/strippable_item/mob_item_slot/r_ear,
+ /datum/strippable_item/mob_item_slot/l_ear,
+ /datum/strippable_item/mob_item_slot/jumpsuit,
+ /datum/strippable_item/mob_item_slot/suit,
+ /datum/strippable_item/mob_item_slot/gloves,
+ /datum/strippable_item/mob_item_slot/feet,
+ /datum/strippable_item/mob_item_slot/suit_storage,
+ /datum/strippable_item/mob_item_slot/id,
+ /datum/strippable_item/mob_item_slot/belt,
+ /datum/strippable_item/mob_item_slot/pocket/left,
+ /datum/strippable_item/mob_item_slot/pocket/right,
+ /datum/strippable_item/mob_item_slot/hand/left,
+ /datum/strippable_item/mob_item_slot/hand/right,
+ /datum/strippable_item/mob_item_slot/cuffs/handcuffs,
+ /datum/strippable_item/mob_item_slot/cuffs/legcuffs,
+)))
+
+/mob/living/carbon/human/proc/should_strip(mob/user)
+ if (user.pulling == src && user.grab_level == GRAB_AGGRESSIVE && (user.a_intent & INTENT_GRAB))
+ return FALSE //to not interfere with fireman carry
+ return TRUE
+
+/datum/strippable_item/mob_item_slot/head
+ key = STRIPPABLE_ITEM_HEAD
+ item_slot = SLOT_HEAD
+
+/datum/strippable_item/mob_item_slot/back
+ key = STRIPPABLE_ITEM_BACK
+ item_slot = SLOT_BACK
+
+/datum/strippable_item/mob_item_slot/mask
+ key = STRIPPABLE_ITEM_MASK
+ item_slot = SLOT_FACE
+
+/datum/strippable_item/mob_item_slot/mask/get_alternate_action(atom/source, mob/user)
+ var/obj/item/clothing/mask = get_item(source)
+ if (!istype(mask))
+ return
+ if (!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcehuman = source
+ if (istype(sourcehuman.s_store, /obj/item/tank))
+ return "toggle_internals"
+ if (istype(sourcehuman.back, /obj/item/tank))
+ return "toggle_internals"
+ if (istype(sourcehuman.belt, /obj/item/tank))
+ return "toggle_internals"
+ return
+
+/datum/strippable_item/mob_item_slot/mask/alternate_action(atom/source, mob/user)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcehuman = source
+ if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user))
+ return
+ if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcehuman.stat == DEAD || sourcehuman.health < HEALTH_THRESHOLD_CRIT) && !sourcehuman.get_target_lock(user.faction_group))
+ to_chat(user, SPAN_WARNING("You can't toggle internals of a crit or dead member of another faction!"))
+ return
+
+ sourcehuman.attack_log += text("\[[time_stamp()]\] Has had their internals toggled by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] Attempted to toggle [key_name(src)]'s' internals")
+ if(sourcehuman.internal)
+ user.visible_message(SPAN_DANGER("[user] is trying to disable [sourcehuman]'s internals"), null, null, 3)
+ else
+ user.visible_message(SPAN_DANGER("[user] is trying to enable [sourcehuman]'s internals."), null, null, 3)
+
+ if(!do_after(user, POCKET_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, sourcehuman, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ return
+
+ if(sourcehuman.internal)
+ sourcehuman.internal.add_fingerprint(user)
+ sourcehuman.internal = null
+ sourcehuman.visible_message("[sourcehuman] is no longer running on internals.", max_distance = 1)
+ return
+
+ if(!istype(sourcehuman.wear_mask, /obj/item/clothing/mask))
+ return
+
+ if(istype(sourcehuman.back, /obj/item/tank))
+ sourcehuman.internal = sourcehuman.back
+ else if(istype(sourcehuman.s_store, /obj/item/tank))
+ sourcehuman.internal = sourcehuman.s_store
+ else if(istype(sourcehuman.belt, /obj/item/tank))
+ sourcehuman.internal = sourcehuman.belt
+
+ if(!sourcehuman.internal)
+ return
+
+ sourcehuman.visible_message(SPAN_NOTICE("[sourcehuman] is now running on internals."), max_distance = 1)
+ sourcehuman.internal.add_fingerprint(user)
+
+/datum/strippable_item/mob_item_slot/eyes
+ key = STRIPPABLE_ITEM_EYES
+ item_slot = SLOT_EYES
+
+/datum/strippable_item/mob_item_slot/r_ear
+ key = STRIPPABLE_ITEM_R_EAR
+ item_slot = SLOT_EAR
+
+/datum/strippable_item/mob_item_slot/l_ear
+ key = STRIPPABLE_ITEM_L_EAR
+ item_slot = SLOT_EAR
+
+/datum/strippable_item/mob_item_slot/jumpsuit
+ key = STRIPPABLE_ITEM_JUMPSUIT
+ item_slot = SLOT_ICLOTHING
+
+/datum/strippable_item/mob_item_slot/jumpsuit/get_alternate_action(atom/source, mob/user)
+ var/obj/item/clothing/under/uniform = get_item(source)
+ if (!istype(uniform))
+ return null
+ return uniform?.accessories ? "remove_accessory" : null
+
+/datum/strippable_item/mob_item_slot/jumpsuit/alternate_action(atom/source, mob/user)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcemob = source
+ if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user))
+ return
+ if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group))
+ to_chat(user, SPAN_WARNING("You can't strip a crit or dead member of another faction!"))
+ return
+ if(!sourcemob.w_uniform || !istype(sourcemob.w_uniform, /obj/item/clothing))
+ return
+
+ var/obj/item/clothing/under/uniform = sourcemob.w_uniform
+ if(!LAZYLEN(uniform.accessories))
+ return FALSE
+ var/obj/item/clothing/accessory/accessory = LAZYACCESS(uniform.accessories, 1)
+ if(LAZYLEN(uniform.accessories) > 1)
+ accessory = tgui_input_list(user, "Select an accessory to remove from [uniform]", "Remove accessory", uniform.accessories)
+ if(!istype(accessory))
+ return
+ sourcemob.attack_log += text("\[[time_stamp()]\] Has had their accessory ([accessory]) removed by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(sourcemob)]'s' accessory ([accessory])")
+ if(istype(accessory, /obj/item/clothing/accessory/holobadge) || istype(accessory, /obj/item/clothing/accessory/medal))
+ sourcemob.visible_message(SPAN_DANGER("[user] tears off [accessory] from [sourcemob]'s [uniform]!"), null, null, 5)
+ if(uniform == sourcemob.w_uniform)
+ uniform.remove_accessory(user, accessory)
+ return
+
+ if(HAS_TRAIT(sourcemob, TRAIT_UNSTRIPPABLE) && !sourcemob.is_mob_incapacitated()) //Can't strip the unstrippable!
+ to_chat(user, SPAN_DANGER("[sourcemob] has an unbreakable grip on their equipment!"))
+ return
+ sourcemob.visible_message(SPAN_DANGER("[user] is trying to take off \a [accessory] from [source]'s [uniform]!"), null, null, 5)
+
+ if(!do_after(user, sourcemob.get_strip_delay(user, sourcemob), INTERRUPT_ALL, BUSY_ICON_GENERIC, sourcemob, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ return
+
+ if(uniform != sourcemob.w_uniform)
+ return
+
+ uniform.remove_accessory(user, accessory)
+
+/datum/strippable_item/mob_item_slot/suit
+ key = STRIPPABLE_ITEM_SUIT
+ item_slot = SLOT_OCLOTHING
+
+/datum/strippable_item/mob_item_slot/suit/has_no_item_alt_action()
+ return TRUE
+
+/datum/strippable_item/mob_item_slot/suit/get_alternate_action(atom/source, mob/user)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcemob = source
+ for(var/bodypart in list("l_leg","r_leg","l_arm","r_arm","r_hand","l_hand","r_foot","l_foot","chest","head","groin"))
+ var/obj/limb/limb = sourcemob.get_limb(bodypart)
+ if(limb && (limb.status & LIMB_SPLINTED))
+ return "remove_splints"
+ return
+
+/datum/strippable_item/mob_item_slot/suit/alternate_action(atom/source, mob/user)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcemob = source
+ if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user))
+ return
+ if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group))
+ to_chat(user, SPAN_WARNING("You can't remove splints of a crit or dead member of another faction!"))
+ return
+ sourcemob.attack_log += text("\[[time_stamp()]\] Has had their splints removed by [key_name(user)]")
+ user.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(sourcemob)]'s' splints ")
+ sourcemob.remove_splints(user)
+
+/datum/strippable_item/mob_item_slot/gloves
+ key = STRIPPABLE_ITEM_GLOVES
+ item_slot = SLOT_HANDS
+
+/datum/strippable_item/mob_item_slot/feet
+ key = STRIPPABLE_ITEM_FEET
+ item_slot = SLOT_FEET
+
+/datum/strippable_item/mob_item_slot/suit_storage
+ key = STRIPPABLE_ITEM_SUIT_STORAGE
+ item_slot = SLOT_SUIT_STORE
+
+/datum/strippable_item/mob_item_slot/id
+ key = STRIPPABLE_ITEM_ID
+ item_slot = SLOT_ID
+
+/datum/strippable_item/mob_item_slot/id/get_alternate_action(atom/source, mob/user)
+ var/obj/item/card/id/dogtag/tag = get_item(source)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcemob = source
+ if (!istype(tag))
+ return
+ if (!sourcemob.undefibbable && (!skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED) || sourcemob.stat != DEAD))
+ return
+ return tag.dogtag_taken ? null : "retrieve_tag"
+
+/datum/strippable_item/mob_item_slot/id/alternate_action(atom/source, mob/user)
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/sourcemob = source
+ if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user))
+ return
+ if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group))
+ to_chat(user, SPAN_WARNING("You can't strip a crit or dead member of another faction!"))
+ return
+ if(!istype(sourcemob.wear_id, /obj/item/card/id/dogtag))
+ return
+ if (!sourcemob.undefibbable && !skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED))
+ return
+ var/obj/item/card/id/dogtag/tag = sourcemob.wear_id
+ if(tag.dogtag_taken)
+ to_chat(user, SPAN_WARNING("Someone's already taken [sourcemob]'s information tag."))
+ return
+
+ if(sourcemob.stat != DEAD)
+ to_chat(user, SPAN_WARNING("You can't take a dogtag's information tag while its owner is alive."))
+ return
+
+ to_chat(user, SPAN_NOTICE("You take [sourcemob]'s information tag, leaving the ID tag"))
+ tag.dogtag_taken = TRUE
+ tag.icon_state = "dogtag_taken"
+ var/obj/item/dogtag/newtag = new(sourcemob.loc)
+ newtag.fallen_names = list(tag.registered_name)
+ newtag.fallen_assgns = list(tag.assignment)
+ newtag.fallen_blood_types = list(tag.blood_type)
+ user.put_in_hands(newtag)
+
+
+
+/datum/strippable_item/mob_item_slot/belt
+ key = STRIPPABLE_ITEM_BELT
+ item_slot = SLOT_WAIST
+
+/datum/strippable_item/mob_item_slot/pocket/left
+ key = STRIPPABLE_ITEM_LPOCKET
+ item_slot = SLOT_STORE
+
+/datum/strippable_item/mob_item_slot/pocket/right
+ key = STRIPPABLE_ITEM_RPOCKET
+ item_slot = SLOT_STORE
+
+/datum/strippable_item/mob_item_slot/hand/left
+ key = STRIPPABLE_ITEM_LHAND
+
+/datum/strippable_item/mob_item_slot/hand/right
+ key = STRIPPABLE_ITEM_RHAND
+
+/datum/strippable_item/mob_item_slot/cuffs/handcuffs
+ key = STRIPPABLE_ITEM_HANDCUFFS
+
+/datum/strippable_item/mob_item_slot/cuffs/legcuffs
+ key = STRIPPABLE_ITEM_LEGCUFFS
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 22deabce45..871a2cd376 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -504,68 +504,6 @@
/// Final result is overall delay * speed multiplier
return target_delay * user_speed
-/mob/living/carbon/human/stripPanelUnequip(obj/item/interact_item, mob/target_mob, slot_to_process)
- if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated()) //Can't strip the unstrippable!
- to_chat(src, SPAN_DANGER("[target_mob] has an unbreakable grip on their equipment!"))
- return
- if(interact_item.flags_item & ITEM_ABSTRACT)
- return
- if(interact_item.flags_item & NODROP)
- to_chat(src, SPAN_WARNING("You can't remove \the [interact_item.name], it appears to be stuck!"))
- return
- if(interact_item.flags_inventory & CANTSTRIP)
- to_chat(src, SPAN_WARNING("You're having difficulty removing \the [interact_item.name]."))
- return
- target_mob.attack_log += "\[[time_stamp()]\] Has had their [interact_item.name] ([slot_to_process]) attempted to be removed by [key_name(src)]"
- attack_log += "\[[time_stamp()]\] Attempted to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process])"
- log_interact(src, target_mob, "[key_name(src)] tried to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]).")
-
- src.visible_message(SPAN_DANGER("[src] tries to remove [target_mob]'s [interact_item.name]."), \
- SPAN_DANGER("You are trying to remove [target_mob]'s [interact_item.name]."), null, 5)
- interact_item.add_fingerprint(src)
- if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(interact_item && Adjacent(target_mob) && interact_item == target_mob.get_item_by_slot(slot_to_process))
- target_mob.drop_inv_item_on_ground(interact_item)
- log_interact(src, target_mob, "[key_name(src)] removed [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]) successfully.")
-
- if(target_mob)
- if(interactee == target_mob && Adjacent(target_mob))
- target_mob.show_inv(src)
-
-
-/mob/living/carbon/human/stripPanelEquip(obj/item/interact_item, mob/target_mob, slot_to_process)
- if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated())
- to_chat(src, SPAN_DANGER("[target_mob] is too strong to force [interact_item.name] onto them!"))
- return
- if(interact_item && !(interact_item.flags_item & ITEM_ABSTRACT))
- if(interact_item.flags_item & NODROP)
- to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob], it's stuck to your hand!"))
- return
- if(interact_item.flags_inventory & CANTSTRIP)
- to_chat(src, SPAN_WARNING("You're having difficulty putting \the [interact_item.name] on [target_mob]."))
- return
- if(interact_item.flags_item & WIELDED)
- interact_item.unwield(src)
- if(!interact_item.mob_can_equip(target_mob, slot_to_process, TRUE))
- to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob]!"))
- return
- visible_message(SPAN_NOTICE("[src] tries to put \the [interact_item.name] on [target_mob]."), null, null, 5)
- if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(interact_item == get_active_hand() && !target_mob.get_item_by_slot(slot_to_process) && Adjacent(target_mob))
- if(interact_item.flags_item & WIELDED) //to prevent re-wielding it during the do_after
- interact_item.unwield(src)
- if(interact_item.mob_can_equip(target_mob, slot_to_process, TRUE))//Placing an item on the mob
- drop_inv_item_on_ground(interact_item)
- if(interact_item && !QDELETED(interact_item)) //Might be self-deleted?
- target_mob.equip_to_slot_if_possible(interact_item, slot_to_process, 1, 0, 1, 1)
- if(ishuman(target_mob) && target_mob.stat == DEAD)
- var/mob/living/carbon/human/human_target = target_mob
- human_target.disable_lights() // take that powergamers -spookydonut
-
- if(target_mob)
- if(interactee == target_mob && Adjacent(target_mob))
- target_mob.show_inv(src)
-
/mob/living/carbon/human/drop_inv_item_on_ground(obj/item/I, nomoveupdate, force)
remember_dropped_object(I)
return ..()
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
index 8d3367ee68..d00b08f183 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
@@ -261,10 +261,6 @@
move_delay = .
-
-/mob/living/carbon/xenomorph/show_inv(mob/user)
- return
-
/mob/living/carbon/xenomorph/proc/pounced_mob(mob/living/L)
// This should only be called back by a mob that has pounce, so no need to check
var/datum/action/xeno_action/activable/pounce/pounceAction = get_xeno_action_by_type(src, /datum/action/xeno_action/activable/pounce)
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index f1452d95c2..78c96bbbb7 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -110,19 +110,6 @@
/*
* Inventory
*/
-/mob/living/simple_animal/parrot/show_inv(mob/user as mob)
- user.set_interaction(src)
- if(user.stat) return
-
- var/dat = "Inventory of [name]
"
- if(ears)
- dat += "
Headset: [ears] (Remove)"
- else
- dat += "
Headset: Nothing"
-
- user << browse(dat, text("window=mob[];size=325x500", name))
- onclose(user, "mob[real_name]")
- return
/mob/living/simple_animal/parrot/Topic(href, href_list)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index c2cf6402ed..13c47719a7 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -372,25 +372,6 @@
SIGNAL_HANDLER
reset_view(null)
-/mob/proc/show_inv(mob/user)
- user.set_interaction(src)
- var/dat = {"
-
[name]
-
-
Head(Mask): [(wear_mask ? wear_mask : "Nothing")]
-
Left Hand: [(l_hand ? l_hand : "Nothing")]
-
Right Hand: [(r_hand ? r_hand : "Nothing")]
-
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? text(" Set Internal", src) : "")]
-
[(internal ? text("Remove Internal") : "")]
-
Empty Pockets
-
Refresh
-
Close
-
"}
- show_browser(user, dat, name, "mob[name]")
- return
-
-
-
/mob/proc/point_to_atom(atom/A, turf/T)
//Squad Leaders and above have reduced cooldown and get a bigger arrow
if(check_improved_pointing())
@@ -448,21 +429,6 @@
update_flavor_text()
return
-
-/mob/MouseDrop(mob/M)
- ..()
- if(M != usr) return
- if(usr == src) return
- if(!Adjacent(usr)) return
- if(!ishuman(M) && !ismonkey(M)) return
- if(!ishuman(src) && !ismonkey(src)) return
- if(M.is_mob_incapacitated())
- return
- if(M.pulling == src && (M.a_intent & INTENT_GRAB) && M.grab_level == GRAB_AGGRESSIVE)
- return
-
- show_inv(M)
-
/mob/proc/swap_hand()
hand = !hand
diff --git a/code/modules/projectiles/gun_attachables.dm b/code/modules/projectiles/gun_attachables.dm
index 0c6da6f973..6f972bdeb8 100644
--- a/code/modules/projectiles/gun_attachables.dm
+++ b/code/modules/projectiles/gun_attachables.dm
@@ -2445,7 +2445,7 @@ Defined in conflicts.dm of the #defines folder.
/obj/item/attachable/stock/smg/collapsible/brace/apply_on_weapon(obj/item/weapon/gun/G)
if(stock_activated)
- G.flags_item |= NODROP
+ G.flags_item |= NODROP|FORCEDROP_CONDITIONAL
accuracy_mod = -HIT_ACCURACY_MULT_TIER_3
scatter_mod = SCATTER_AMOUNT_TIER_8
recoil_mod = RECOIL_AMOUNT_TIER_2 //Hurts pretty bad if it's wielded.
@@ -2456,7 +2456,7 @@ Defined in conflicts.dm of the #defines folder.
icon_state = "smg_brace_on"
attach_icon = "smg_brace_a_on"
else
- G.flags_item &= ~NODROP
+ G.flags_item &= ~(NODROP|FORCEDROP_CONDITIONAL)
accuracy_mod = 0
scatter_mod = 0
recoil_mod = 0
diff --git a/code/modules/reagents/chemistry_reactions/food_drink.dm b/code/modules/reagents/chemistry_reactions/food_drink.dm
index 7e3fdac324..63c79ea577 100644
--- a/code/modules/reagents/chemistry_reactions/food_drink.dm
+++ b/code/modules/reagents/chemistry_reactions/food_drink.dm
@@ -186,6 +186,66 @@
required_reagents = list("hot_ramen" = 6, "hotsauce" = 1)
result_amount = 6
+/datum/chemical_reaction/banana //WHO DIDN'T MAKE ALL THE JUICES SUBTYPES OF A PARENT ONE AAAAAA
+ name = "Banana Juice"
+ id = "banana"
+ result = "banana"
+ required_reagents = list("dehydrated_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/orangejuice
+ name = "Orange Juice"
+ id = "orangejuice"
+ result = "orangejuice"
+ required_reagents = list("dehydrated_orange_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/applejuice
+ name = "Apple Juice"
+ id = "applejuice"
+ result = "applejuice"
+ required_reagents = list("dehydrated_apple_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/watermelonjuice
+ name = "Watermelon Juice"
+ id = "watermelonjuice"
+ result = "watermelonjuice"
+ required_reagents = list("dehydrated_watermelon_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/grapejuice
+ name = "Grape Juice"
+ id = "grapejuice"
+ result = "grapejuice"
+ required_reagents = list("dehydrated_grape_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/pineapplejuice
+ name = "Pineapple Juice"
+ id = "pineapplejuice"
+ result = "pineapplejuice"
+ required_reagents = list("dehydrated_pineapple_juice" = 1, "water" = 5)
+ result_amount = 5
+
+/datum/chemical_reaction/egg
+ name = "Rehydrated Egg Product"
+ id = "egg"
+ result = "egg"
+ required_reagents = list("dehydrated_egg_powder" = 1, "water" = 5)
+ result_amount = 1
+
+/datum/chemical_reaction/egg/on_reaction(datum/reagents/holder, created_volume)
+ var/location = get_turf(holder.my_atom)
+ new /obj/item/reagent_container/food/snacks/egg/dried(location)
+
+/datum/chemical_reaction/milk
+ name = "Rehydrated Milk"
+ id = "milk"
+ result = "milk"
+ required_reagents = list("dehydrated_milk_powder" = 1, "water" = 5)
+ result_amount = 5
+
//*****************************************************************************************************/
//******************************************Cocktails**************************************************/
diff --git a/code/modules/reagents/chemistry_reagents/drink.dm b/code/modules/reagents/chemistry_reagents/drink.dm
index 9739687dec..e06f898be4 100644
--- a/code/modules/reagents/chemistry_reagents/drink.dm
+++ b/code/modules/reagents/chemistry_reagents/drink.dm
@@ -151,6 +151,17 @@
description = "Made in the modern day with proper pomegranate substitute. Who uses real fruit, anyways?"
color = "#FF004F" // rgb: 255, 0, 79
+/datum/reagent/drink/pineapplejuice
+ name = "Pineapple Juice"
+ id = "pineapplejuice"
+ description = "Refreshingly citric, with a pleasant flavor."
+ color = "#FFC000" // rgb: 255, 192, 0
+
+/datum/reagent/drink/applejuice
+ name = "Apple Juice"
+ id = "applejuice"
+ description = "The pleasantly sweet taste of apples, now in liquid form."
+ color = "#f59a40" // rgb: 245, 154, 64
//MILK//
diff --git a/code/modules/reagents/chemistry_reagents/food.dm b/code/modules/reagents/chemistry_reagents/food.dm
index 0ec3a22502..8e167908b2 100644
--- a/code/modules/reagents/chemistry_reagents/food.dm
+++ b/code/modules/reagents/chemistry_reagents/food.dm
@@ -307,3 +307,83 @@
color = "#FFFF00"
chemclass = CHEM_CLASS_RARE
flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_juice
+ name = "Mystery Juice Powder"
+ id = "dehydrated_juice"
+ description = "Powderized fruit-juice ready to be mixed with water. Smells of artificial bananas."
+ reagent_state = SOLID
+ color = "#863333" // rgb: 175, 175, 0
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_orange_juice
+ name = "Orange Juice Powder"
+ id = "dehydrated_orange_juice"
+ description = "Powderized orange-juice ready to be mixed with water. Smells of, surprise surprise, oranges."
+ reagent_state = SOLID
+ color = "#E78108" // rgb: 231, 129, 8
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_apple_juice
+ name = "Apple Juice Powder"
+ id = "dehydrated_apple_juice"
+ description = "Powderized apple-juice ready to be mixed with water. Smells of, surprise surprise, apples."
+ reagent_state = SOLID
+ color = "#365E30" // rgb: 54, 94, 48
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_watermelon_juice
+ name = "Watermelon Juice Powder"
+ id = "dehydrated_watermelon_juice"
+ description = "Powderized watermelon-juice ready to be mixed with water. Smells of, surprise surprise, watermelons."
+ reagent_state = SOLID
+ color = "#863333" // rgb: 134, 51, 51
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_grape_juice
+ name = "Grape Juice Powder"
+ id = "dehydrated_grape_juice"
+ description = "Powderized grape-juice ready to be mixed with water. Smells of, surprise surprise, grapes."
+ reagent_state = SOLID
+ color = "#863333" // rgb: 134, 51, 51
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_pineapple_juice
+ name = "Pineapple Juice Powder"
+ id = "dehydrated_pineapple_juice"
+ description = "Powderized pineapple-juice ready to be mixed with water. Smells of, surprise surprise, pineapples."
+ reagent_state = SOLID
+ color = "#FFC000" // rgb: 255, 192, 0
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_egg_powder
+ name = "Powdered Egg"
+ id = "dehydrated_egg_powder"
+ description = "Powderized egg-whites and egg-yolk ready to be mixed with water."
+ reagent_state = SOLID
+ color = "#FFFFFF" // rgb: 255,255,255
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
+
+/datum/reagent/dehydrated_milk_powder
+ name = "Dehydrated Milk Powder"
+ id = "dehydrated_milk_powder"
+ description = "Dehydrated milk-product powder waiting to be mixed with water."
+ reagent_state = SOLID
+ color = "#DFDFDF" // rgb: 223, 223, 223
+ chemclass = CHEM_CLASS_COMMON
+ properties = list(PROPERTY_NUTRITIOUS = 2)
+ flags = REAGENT_NO_GENERATION
diff --git a/code/modules/shuttle/shuttles/dropship.dm b/code/modules/shuttle/shuttles/dropship.dm
index 5819ede980..23bb364e85 100644
--- a/code/modules/shuttle/shuttles/dropship.dm
+++ b/code/modules/shuttle/shuttles/dropship.dm
@@ -142,6 +142,9 @@
dwidth = 4
dheight = 8
+/obj/docking_port/mobile/marine_dropship/upp/get_transit_path_type()
+ return /turf/open/space/transit/dropship/upp
+
/obj/docking_port/mobile/marine_dropship/cyclone
name = "Cyclone"
id = DROPSHIP_CYCLONE
@@ -151,6 +154,9 @@
dwidth = 4
dheight = 8
+/obj/docking_port/mobile/marine_dropship/cyclone/get_transit_path_type()
+ return /turf/open/space/transit/dropship/cyclone
+
/obj/docking_port/mobile/marine_dropship/tornado
name = "HLD-Tornado"
id = DROPSHIP_TORNADO
@@ -160,6 +166,9 @@
dwidth = 5
dheight = 9
+/obj/docking_port/mobile/marine_dropship/tornado/get_transit_path_type()
+ return /turf/open/space/transit/dropship/tornado
+
/obj/docking_port/mobile/marine_dropship/typhoon
name = "CMD-Typhoon"
id = DROPSHIP_TYPHOON
@@ -169,6 +178,9 @@
dwidth = 5
dheight = 9
+/obj/docking_port/mobile/marine_dropship/typhoon/get_transit_path_type()
+ return /turf/open/space/transit/dropship/typhoon
+
/obj/docking_port/mobile/marine_dropship/tripoli
name = "Tripoli"
id = DROPSHIP_TRIPOLI
@@ -178,6 +190,9 @@
dwidth = 4
dheight = 8
+/obj/docking_port/mobile/marine_dropship/tripoli/get_transit_path_type()
+ return /turf/open/space/transit/dropship/tripoli
+
/obj/docking_port/mobile/marine_dropship/alamo
name = "Alamo"
id = DROPSHIP_ALAMO
diff --git a/colonialmarines.dme b/colonialmarines.dme
index d5e035d6d2..cadce2ac68 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -101,6 +101,7 @@
#include "code\__DEFINES\stamina.dm"
#include "code\__DEFINES\stats.dm"
#include "code\__DEFINES\status_effects.dm"
+#include "code\__DEFINES\strippable.dm"
#include "code\__DEFINES\STUI.dm"
#include "code\__DEFINES\subsystems.dm"
#include "code\__DEFINES\surgery.dm"
@@ -481,6 +482,7 @@
#include "code\datums\elements\light_blocking.dm"
#include "code\datums\elements\mouth_drop_item.dm"
#include "code\datums\elements\poor_eyesight_correction.dm"
+#include "code\datums\elements\strippable.dm"
#include "code\datums\elements\suturing.dm"
#include "code\datums\elements\yautja_tracked_item.dm"
#include "code\datums\elements\bullet_trait\damage_boost.dm"
@@ -675,6 +677,7 @@
#include "code\datums\weather\weather_map_holder.dm"
#include "code\datums\weather\weather_events\big_red.dm"
#include "code\datums\weather\weather_events\faction_clash.dm"
+#include "code\datums\weather\weather_events\long.dm"
#include "code\datums\weather\weather_events\lv522_chances_claim.dm"
#include "code\datums\weather\weather_events\lv624.dm"
#include "code\datums\weather\weather_events\new_varadero.dm"
@@ -1911,6 +1914,7 @@
#include "code\modules\mob\living\carbon\human\human_dummy.dm"
#include "code\modules\mob\living\carbon\human\human_helpers.dm"
#include "code\modules\mob\living\carbon\human\human_movement.dm"
+#include "code\modules\mob\living\carbon\human\human_stripping.dm"
#include "code\modules\mob\living\carbon\human\inventory.dm"
#include "code\modules\mob\living\carbon\human\life.dm"
#include "code\modules\mob\living\carbon\human\login.dm"
diff --git a/html/changelogs/archive/2024-08.yml b/html/changelogs/archive/2024-08.yml
index 47ca066f3b..09dfee3d69 100644
--- a/html/changelogs/archive/2024-08.yml
+++ b/html/changelogs/archive/2024-08.yml
@@ -90,3 +90,20 @@
2024-08-22:
BonniePandora:
- maptweak: Expanded the galley of the Golden Arrow.
+2024-08-24:
+ BonniePandora:
+ - rscadd: Added various condiment packets of powedered juices, milk & egg to the
+ code
+ - rscadd: Added corresponding reactions for the new powdered substances where needed
+ - rscadd: Sprites for the condiment packets, some taken from Bay, others edited
+ by me from Bay sprites
+ - qol: MRE's now contain a juice packet alongside their typical contents
+ - maptweak: Replaces the magic freezer and it's perishable contents with a locker
+ full of the non-perishable replacements
+ DexterDude:
+ - rscadd: Added something
+ private-tristan:
+ - rscadd: ports TG strip menu
+2024-08-26:
+ sunofang:
+ - admin: Added infinite weather options.
diff --git a/icons/mob/humans/onmob/helmet_garb.dmi b/icons/mob/humans/onmob/helmet_garb.dmi
index 9ed93efa34..31ce025b09 100644
Binary files a/icons/mob/humans/onmob/helmet_garb.dmi and b/icons/mob/humans/onmob/helmet_garb.dmi differ
diff --git a/icons/mob/humans/onmob/mask.dmi b/icons/mob/humans/onmob/mask.dmi
index 0c4ac97807..82a5885c87 100644
Binary files a/icons/mob/humans/onmob/mask.dmi and b/icons/mob/humans/onmob/mask.dmi differ
diff --git a/icons/obj/items/clothing/masks.dmi b/icons/obj/items/clothing/masks.dmi
index 037ee8aba1..f2a3b4ee6d 100644
Binary files a/icons/obj/items/clothing/masks.dmi and b/icons/obj/items/clothing/masks.dmi differ
diff --git a/icons/obj/items/clothing/ties.dmi b/icons/obj/items/clothing/ties.dmi
index a505b419d7..e509f8f2fa 100644
Binary files a/icons/obj/items/clothing/ties.dmi and b/icons/obj/items/clothing/ties.dmi differ
diff --git a/icons/obj/items/clothing/ties_overlay.dmi b/icons/obj/items/clothing/ties_overlay.dmi
index fd6c4204ed..66279502f7 100644
Binary files a/icons/obj/items/clothing/ties_overlay.dmi and b/icons/obj/items/clothing/ties_overlay.dmi differ
diff --git a/icons/obj/items/food.dmi b/icons/obj/items/food.dmi
index 3c2e963e38..5872b7e852 100644
Binary files a/icons/obj/items/food.dmi and b/icons/obj/items/food.dmi differ
diff --git a/icons/obj/items/helmet_garb.dmi b/icons/obj/items/helmet_garb.dmi
index bbebf822c9..6886ec3556 100644
Binary files a/icons/obj/items/helmet_garb.dmi and b/icons/obj/items/helmet_garb.dmi differ
diff --git a/icons/ui_icons/inventory/back.png b/icons/ui_icons/inventory/back.png
new file mode 100644
index 0000000000..736b9d64bf
Binary files /dev/null and b/icons/ui_icons/inventory/back.png differ
diff --git a/icons/ui_icons/inventory/belt.png b/icons/ui_icons/inventory/belt.png
new file mode 100644
index 0000000000..1be89d450a
Binary files /dev/null and b/icons/ui_icons/inventory/belt.png differ
diff --git a/icons/ui_icons/inventory/collar.png b/icons/ui_icons/inventory/collar.png
new file mode 100644
index 0000000000..71803b1b6c
Binary files /dev/null and b/icons/ui_icons/inventory/collar.png differ
diff --git a/icons/ui_icons/inventory/ears.png b/icons/ui_icons/inventory/ears.png
new file mode 100644
index 0000000000..e9a8f3c23c
Binary files /dev/null and b/icons/ui_icons/inventory/ears.png differ
diff --git a/icons/ui_icons/inventory/glasses.png b/icons/ui_icons/inventory/glasses.png
new file mode 100644
index 0000000000..6e6f1ad098
Binary files /dev/null and b/icons/ui_icons/inventory/glasses.png differ
diff --git a/icons/ui_icons/inventory/gloves.png b/icons/ui_icons/inventory/gloves.png
new file mode 100644
index 0000000000..2c8a16cbdb
Binary files /dev/null and b/icons/ui_icons/inventory/gloves.png differ
diff --git a/icons/ui_icons/inventory/hand_l.png b/icons/ui_icons/inventory/hand_l.png
new file mode 100644
index 0000000000..b09228d65f
Binary files /dev/null and b/icons/ui_icons/inventory/hand_l.png differ
diff --git a/icons/ui_icons/inventory/hand_r.png b/icons/ui_icons/inventory/hand_r.png
new file mode 100644
index 0000000000..0e05a487e0
Binary files /dev/null and b/icons/ui_icons/inventory/hand_r.png differ
diff --git a/icons/ui_icons/inventory/head.png b/icons/ui_icons/inventory/head.png
new file mode 100644
index 0000000000..11e2d2254c
Binary files /dev/null and b/icons/ui_icons/inventory/head.png differ
diff --git a/icons/ui_icons/inventory/id.png b/icons/ui_icons/inventory/id.png
new file mode 100644
index 0000000000..4469591d36
Binary files /dev/null and b/icons/ui_icons/inventory/id.png differ
diff --git a/icons/ui_icons/inventory/mask.png b/icons/ui_icons/inventory/mask.png
new file mode 100644
index 0000000000..82e5108937
Binary files /dev/null and b/icons/ui_icons/inventory/mask.png differ
diff --git a/icons/ui_icons/inventory/neck.png b/icons/ui_icons/inventory/neck.png
new file mode 100644
index 0000000000..78ad3ce3b1
Binary files /dev/null and b/icons/ui_icons/inventory/neck.png differ
diff --git a/icons/ui_icons/inventory/pocket.png b/icons/ui_icons/inventory/pocket.png
new file mode 100644
index 0000000000..f42399dca0
Binary files /dev/null and b/icons/ui_icons/inventory/pocket.png differ
diff --git a/icons/ui_icons/inventory/shoes.png b/icons/ui_icons/inventory/shoes.png
new file mode 100644
index 0000000000..d20f7ef4d1
Binary files /dev/null and b/icons/ui_icons/inventory/shoes.png differ
diff --git a/icons/ui_icons/inventory/suit.png b/icons/ui_icons/inventory/suit.png
new file mode 100644
index 0000000000..e9c48e8069
Binary files /dev/null and b/icons/ui_icons/inventory/suit.png differ
diff --git a/icons/ui_icons/inventory/suit_storage.png b/icons/ui_icons/inventory/suit_storage.png
new file mode 100644
index 0000000000..9722eb1029
Binary files /dev/null and b/icons/ui_icons/inventory/suit_storage.png differ
diff --git a/icons/ui_icons/inventory/uniform.png b/icons/ui_icons/inventory/uniform.png
new file mode 100644
index 0000000000..292b3324b5
Binary files /dev/null and b/icons/ui_icons/inventory/uniform.png differ
diff --git a/maps/map_files/golden_arrow/golden_arrow.dmm b/maps/map_files/golden_arrow/golden_arrow.dmm
index e128af5e78..2d72efb78b 100644
--- a/maps/map_files/golden_arrow/golden_arrow.dmm
+++ b/maps/map_files/golden_arrow/golden_arrow.dmm
@@ -7011,15 +7011,18 @@
},
/area/golden_arrow/cryo_cells)
"AV" = (
-/obj/structure/closet/secure_closet/freezer{
- name = "Industrial Freezer";
- desc = "An industrial-grade deep-freezer designed to keep ingredients within 'fresh' over the course of faster-than-light travel. Might impact the taste somewhat. Don't leave the door open too long!"
- },
/obj/item/reagent_container/food/condiment/sugar,
/obj/item/reagent_container/food/condiment/enzyme,
-/obj/item/reagent_container/food/drinks/milk,
-/obj/item/reagent_container/food/drinks/milk,
-/obj/item/storage/fancy/egg_box,
+/obj/item/reagent_container/food/snacks/flour,
+/obj/item/reagent_container/food/snacks/flour,
+/obj/item/reagent_container/food/snacks/flour,
+/obj/item/reagent_container/food/snacks/flour,
+/obj/structure/closet{
+ desc = "It's a fancy storage unit for long-life foodstuffs.";
+ name = "long-life foodstuff storage"
+ },
+/obj/item/storage/box/powderedmilk,
+/obj/item/reagent_container/food/condiment/juice/egg,
/obj/item/reagent_container/food/snacks/flour,
/obj/item/reagent_container/food/snacks/flour,
/obj/item/reagent_container/food/snacks/flour,
diff --git a/tgui/packages/tgui/interfaces/StripMenu.tsx b/tgui/packages/tgui/interfaces/StripMenu.tsx
new file mode 100644
index 0000000000..6790d17a29
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/StripMenu.tsx
@@ -0,0 +1,380 @@
+import { range } from 'common/collections';
+import { BooleanLike } from 'common/react';
+
+import { resolveAsset } from '../assets';
+import { useBackend } from '../backend';
+import { Box, Button, Icon, Image, Stack } from '../components';
+import { Window } from '../layouts';
+
+const ROWS = 5;
+const COLUMNS = 6;
+
+const BUTTON_DIMENSIONS = '64px';
+
+type GridSpotKey = string;
+
+const getGridSpotKey = (spot: [number, number]): GridSpotKey => {
+ return `${spot[0]}/${spot[1]}`;
+};
+
+const CornerText = (props: {
+ readonly align: 'left' | 'right';
+ readonly children: string;
+}): JSX.Element => {
+ const { align, children } = props;
+
+ return (
+
+ {children}
+
+ );
+};
+
+type AlternateAction = {
+ icon: string;
+ text: string;
+};
+
+const ALTERNATE_ACTIONS: Record = {
+ remove_splints: {
+ icon: 'crutch',
+ text: 'Remove splints',
+ },
+
+ remove_accessory: {
+ icon: 'tshirt',
+ text: 'Remove accessory',
+ },
+
+ retrieve_tag: {
+ icon: 'tags',
+ text: 'Retrieve info tag',
+ },
+
+ toggle_internals: {
+ icon: 'mask-face',
+ text: 'Toggle internals',
+ },
+};
+
+type Slot = {
+ displayName: string;
+ gridSpot: GridSpotKey;
+ image?: string;
+ additionalComponent?: JSX.Element;
+ hideEmpty?: boolean;
+};
+
+const SLOTS: Record = {
+ glasses: {
+ displayName: 'glasses',
+ gridSpot: getGridSpotKey([0, 1]),
+ image: 'inventory-glasses.png',
+ },
+
+ head: {
+ displayName: 'headwear',
+ gridSpot: getGridSpotKey([0, 2]),
+ image: 'inventory-head.png',
+ },
+
+ wear_mask: {
+ displayName: 'mask',
+ gridSpot: getGridSpotKey([1, 2]),
+ image: 'inventory-mask.png',
+ },
+
+ wear_r_ear: {
+ displayName: 'right earwear',
+ gridSpot: getGridSpotKey([0, 3]),
+ image: 'inventory-ears.png',
+ },
+
+ wear_l_ear: {
+ displayName: 'left earwear',
+ gridSpot: getGridSpotKey([1, 3]),
+ image: 'inventory-ears.png',
+ },
+
+ handcuffs: {
+ displayName: 'handcuffs',
+ gridSpot: getGridSpotKey([1, 4]),
+ hideEmpty: true,
+ },
+
+ legcuffs: {
+ displayName: 'legcuffs',
+ gridSpot: getGridSpotKey([1, 5]),
+ hideEmpty: true,
+ },
+
+ w_uniform: {
+ displayName: 'uniform',
+ gridSpot: getGridSpotKey([2, 1]),
+ image: 'inventory-uniform.png',
+ },
+
+ wear_suit: {
+ displayName: 'suit',
+ gridSpot: getGridSpotKey([2, 2]),
+ image: 'inventory-suit.png',
+ },
+
+ gloves: {
+ displayName: 'gloves',
+ gridSpot: getGridSpotKey([2, 3]),
+ image: 'inventory-gloves.png',
+ },
+
+ r_hand: {
+ displayName: 'right hand',
+ gridSpot: getGridSpotKey([2, 4]),
+ image: 'inventory-hand_r.png',
+ additionalComponent: R,
+ },
+
+ l_hand: {
+ displayName: 'left hand',
+ gridSpot: getGridSpotKey([2, 5]),
+ image: 'inventory-hand_l.png',
+ additionalComponent: L,
+ },
+
+ shoes: {
+ displayName: 'shoes',
+ gridSpot: getGridSpotKey([3, 2]),
+ image: 'inventory-shoes.png',
+ },
+
+ j_store: {
+ displayName: 'suit storage item',
+ gridSpot: getGridSpotKey([4, 0]),
+ image: 'inventory-suit_storage.png',
+ },
+
+ id: {
+ displayName: 'ID',
+ gridSpot: getGridSpotKey([4, 1]),
+ image: 'inventory-id.png',
+ },
+
+ belt: {
+ displayName: 'belt',
+ gridSpot: getGridSpotKey([4, 2]),
+ image: 'inventory-belt.png',
+ },
+
+ back: {
+ displayName: 'backpack',
+ gridSpot: getGridSpotKey([4, 3]),
+ image: 'inventory-back.png',
+ },
+
+ l_store: {
+ displayName: 'left pocket',
+ gridSpot: getGridSpotKey([4, 4]),
+ image: 'inventory-pocket.png',
+ },
+
+ r_store: {
+ displayName: 'right pocket',
+ gridSpot: getGridSpotKey([4, 5]),
+ image: 'inventory-pocket.png',
+ },
+};
+
+enum ObscuringLevel {
+ Completely = 1,
+ Hidden = 2,
+}
+
+type Interactable = {
+ interacting: BooleanLike;
+};
+
+/**
+ * Some possible options:
+ *
+ * null - No interactions, no item, but is an available slot
+ * { interacting: 1 } - No item, but we're interacting with it
+ * { icon: icon, name: name } - An item with no alternate actions
+ * that we're not interacting with.
+ * { icon, name, interacting: 1 } - An item with no alternate actions
+ * that we're interacting with.
+ */
+type StripMenuItem =
+ | null
+ | Interactable
+ | ((
+ | {
+ icon: string;
+ name: string;
+ alternate: string;
+ }
+ | {
+ obscured: ObscuringLevel;
+ }
+ | {
+ no_item_action: string;
+ }
+ ) &
+ Partial);
+
+type StripMenuData = {
+ items: Record;
+ name: string;
+};
+
+const StripContent = (props: { readonly item: StripMenuItem }) => {
+ if (props.item && 'name' in props.item) {
+ return (
+
+ );
+ }
+ if (props.item && 'obscured' in props.item) {
+ return (
+
+ );
+ }
+ return <> >;
+};
+
+export const StripMenu = (props) => {
+ const { act, data } = useBackend();
+
+ const gridSpots = new Map();
+ for (const key of Object.keys(data.items)) {
+ const item = data.items[key];
+ if (item === null && SLOTS[key].hideEmpty) continue;
+ gridSpots.set(SLOTS[key].gridSpot, key);
+ }
+
+ return (
+
+
+
+ {range(0, ROWS).map((row) => (
+
+
+ {range(0, COLUMNS).map((column) => {
+ const key = getGridSpotKey([row, column]);
+ const keyAtSpot = gridSpots.get(key);
+
+ if (!keyAtSpot) {
+ return (
+
+ );
+ }
+
+ const item = data.items[keyAtSpot];
+ const slot = SLOTS[keyAtSpot];
+
+ let alternateAction: AlternateAction | undefined;
+
+ let content;
+ let tooltip;
+
+ if (item === null) {
+ tooltip = slot.displayName;
+ } else if ('name' in item) {
+ alternateAction = ALTERNATE_ACTIONS[item.alternate];
+ tooltip = item.name;
+ } else if ('obscured' in item) {
+ tooltip = `obscured ${slot.displayName}`;
+ } else if ('no_item_action' in item) {
+ tooltip = slot.displayName;
+ alternateAction = ALTERNATE_ACTIONS[item.no_item_action];
+ }
+
+ return (
+
+
+
+
+ {alternateAction !== undefined && (
+
+ )}
+
+
+ );
+ })}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/interfaces/StripMenu.scss b/tgui/packages/tgui/styles/interfaces/StripMenu.scss
new file mode 100644
index 0000000000..d2c867c0ac
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/StripMenu.scss
@@ -0,0 +1,65 @@
+@use '../base.scss';
+
+$background-color: rgba(0, 0, 0, 0.33) !default;
+
+.StripMenu__cornertext_left {
+ position: relative;
+ left: 2px;
+ text-align: left;
+ text-shadow: 1px 1px 1px #555;
+}
+
+.StripMenu__cornertext_right {
+ position: relative;
+ left: -2px;
+ text-align: right;
+ text-shadow: 1px 1px 1px #555;
+}
+
+.StripMenu__iconbox {
+ height: 100%;
+ width: 100%;
+ -ms-interpolation-mode: nearest-neighbor;
+ vertical-align: middle;
+}
+
+.StripMenu__obscured {
+ text-align: center;
+ height: 100%;
+ width: 100%;
+}
+
+.StripMenu__itembox {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.StripMenu__contentbox {
+ position: relative;
+}
+
+.StripMenu__itembutton {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+}
+
+.StripMenu__itemslot {
+ position: absolute;
+ width: 32px;
+ height: 32px;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%) scale(0.8);
+ opacity: 0.7;
+}
+
+.StripMenu__alternativeaction {
+ background: rgba(0, 0, 0, 0.6);
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ z-index: 2;
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index b51e602746..639365e0e3 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -80,6 +80,7 @@
@include meta.load-css('./interfaces/common/Dpad.scss');
@include meta.load-css('./interfaces/common/ElectricalPanel.scss');
@include meta.load-css('./interfaces/TacticalMap.scss');
+@include meta.load-css('./interfaces/StripMenu.scss');
// Layouts
@include meta.load-css('./layouts/Layout.scss');