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..948bd9613e 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),
@@ -140,7 +140,7 @@
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 Camouflage Wrap", round(scale * 10), /obj/item/prop/helmetgarb/camocover, 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),
@@ -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),
@@ -452,8 +444,9 @@
list("Machete Scabbard (Full)", round(scale * 5), /obj/item/storage/large_holster/machete/full, VENDOR_ITEM_REGULAR),
list("Binoculars", round(scale * 1), /obj/item/device/binoculars, VENDOR_ITEM_REGULAR),
list("AN/PSQ-55 Sentry Console", round(scale * 1), /obj/item/device/sentry_computer, VENDOR_ITEM_REGULAR),
- list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit, VENDOR_ITEM_REGULAR),
+ list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit/advanced, VENDOR_ITEM_REGULAR),
list("Rail Flashlight", round(scale * 5), /obj/item/attachable/flashlight, VENDOR_ITEM_REGULAR),
+ list("M5 'Night Raider' bayonet", round(scale * 5), /obj/item/attachable/bayonet, VENDOR_ITEM_REGULAR),
)
/obj/structure/machinery/cm_vending/sorted/cargo_guns/squad/upp
@@ -510,8 +503,9 @@
list("Roller Bed", round(scale * 2), /obj/item/roller, VENDOR_ITEM_REGULAR),
list("Machete Scabbard (Full)", round(scale * 5), /obj/item/storage/large_holster/machete/full, VENDOR_ITEM_REGULAR),
list("Binoculars", round(scale * 1), /obj/item/device/binoculars, VENDOR_ITEM_REGULAR),
- list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit, VENDOR_ITEM_REGULAR),
+ list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit/advanced, VENDOR_ITEM_REGULAR),
list("Rail Flashlight", round(scale * 5), /obj/item/attachable/flashlight, VENDOR_ITEM_REGULAR),
+ list("Type 80 Bayonet", round(scale * 5), /obj/item/attachable/bayonet/upp, null, VENDOR_ITEM_REGULAR),
list("CLOTHING", -1, null, null),
list("Cap", round(scale * 5), /obj/item/clothing/head/uppcap, VENDOR_ITEM_REGULAR),
@@ -571,8 +565,9 @@
list("Roller Bed", round(scale * 2), /obj/item/roller, VENDOR_ITEM_REGULAR),
list("Machete Scabbard (Full)", round(scale * 5), /obj/item/storage/large_holster/machete/full, VENDOR_ITEM_REGULAR),
list("Binoculars", round(scale * 1), /obj/item/device/binoculars, VENDOR_ITEM_REGULAR),
- list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit, VENDOR_ITEM_REGULAR),
+ list("Spare PDT/L Battle Buddy Kit", round(scale * 3), /obj/item/storage/box/pdt_kit/advanced, VENDOR_ITEM_REGULAR),
list("Rail Flashlight", round(scale * 5), /obj/item/attachable/flashlight, VENDOR_ITEM_REGULAR),
+ list("M5 'Night Raider' bayonet", round(scale * 5), /obj/item/attachable/bayonet, VENDOR_ITEM_REGULAR),
list("CLOTHING", -1, null, null),
list("Poncho (green)", round(scale * 10), /obj/item/clothing/accessory/poncho/green, 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..558c8b235c 100644
--- a/code/game/objects/items/props/helmetgarb.dm
+++ b/code/game/objects/items/props/helmetgarb.dm
@@ -52,6 +52,15 @@
desc = "The standard M10 combat helmet is already water-resistant at depths of up to 10 meters. This makes the top potentially water-proof. At least it's something."
icon_state = "raincover"
+/obj/item/prop/helmetgarb/camocover
+ name = "camocover"
+ desc = "A cover that goes over the top of an M10 pattern helmet to camoflauge it without needing the use of paints."
+ icon_state = "camocover"
+
+/obj/item/prop/helmetgarb/camocover/Initialize(mapload, ...)
+ . = ..()
+ select_gamemode_skin(/obj/item/prop/helmetgarb/camocover)
+
/obj/item/prop/helmetgarb/rabbitsfoot
name = "Rabbit's Foot"
desc = "Lucky for you, but not the rabbit, didn't really do it much good."
@@ -493,7 +502,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 +529,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..92d9830ba4 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -283,6 +283,7 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list(
/obj/item/prop/helmetgarb/cartridge = "cartridge",
/obj/item/prop/helmetgarb/prescription_bottle = "prescription_bottle",
/obj/item/prop/helmetgarb/raincover = "raincover",
+ /obj/item/prop/helmetgarb/camocover = "camocover",
/obj/item/prop/helmetgarb/rabbitsfoot = "rabbitsfoot",
/obj/item/prop/helmetgarb/rosary = "helmet_rosary", // This one was already in the game for some reason, but never had an object
/obj/item/prop/helmetgarb/lucky_feather = "lucky_feather",
@@ -294,6 +295,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 b5c29ef21b..98af79c511 100644
--- a/code/modules/cm_marines/equipment/guncases.dm
+++ b/code/modules/cm_marines/equipment/guncases.dm
@@ -211,10 +211,12 @@
desc = "A gun case containing the M37A2 Pump Shotgun."
icon_state = "guncase_red"
storage_slots = 4
- can_hold = list(/obj/item/weapon/gun/shotgun/pump, /obj/item/ammo_magazine/shotgun/buckshot, /obj/item/ammo_magazine/shotgun/flechette, /obj/item/ammo_magazine/shotgun/slugs)
+ can_hold = list(/obj/item/weapon/gun/shotgun/pump, /obj/item/ammo_magazine/shotgun/buckshot, /obj/item/ammo_magazine/shotgun/flechette, /obj/item/ammo_magazine/shotgun/slugs, /obj/item/storage/pouch/shotgun)
/obj/item/storage/box/guncase/pumpshotgun/fill_preset_inventory()
new /obj/item/weapon/gun/shotgun/pump(src)
+ new /obj/item/storage/pouch/shotgun(src)
+ new /obj/item/storage/large_holster/m37(src)
for(var/i = 1 to 3)
var/random_pick = rand(1, 3)
switch(random_pick)
@@ -231,6 +233,8 @@
/obj/item/storage/box/guncase/pumpshotgun/special/fill_preset_inventory()
new /obj/item/weapon/gun/shotgun/pump/special(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]
-
"
- 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]
-