diff --git a/code/__DEFINES/storage.dm b/code/__DEFINES/storage.dm
new file mode 100644
index 00000000000..cb657520e33
--- /dev/null
+++ b/code/__DEFINES/storage.dm
@@ -0,0 +1,88 @@
+// storage_flags variable on /datum/component/storage
+
+// Storage limits. These can be combined (and usually are combined).
+/// Check max_items and contents.len when trying to insert
+#define STORAGE_LIMIT_MAX_ITEMS (1<<0)
+/// Check max_combined_w_class.
+#define STORAGE_LIMIT_COMBINED_W_CLASS (1<<1)
+/// Use the new volume system. Will automatically force rendering to use the new volume/baystation scaling UI so this is kind of incompatible with stuff like stack storage etc etc.
+#define STORAGE_LIMIT_VOLUME (1<<2)
+/// Use max_w_class
+#define STORAGE_LIMIT_MAX_W_CLASS (1<<3)
+
+#define STORAGE_FLAGS_LEGACY_DEFAULT (STORAGE_LIMIT_MAX_ITEMS | STORAGE_LIMIT_COMBINED_W_CLASS | STORAGE_LIMIT_MAX_W_CLASS)
+#define STORAGE_FLAGS_VOLUME_DEFAULT (STORAGE_LIMIT_VOLUME | STORAGE_LIMIT_MAX_W_CLASS)
+
+// UI defines
+/// Size of volumetric box icon
+#define VOLUMETRIC_STORAGE_BOX_ICON_SIZE 32
+/// Size of EACH left/right border icon for volumetric boxes
+#define VOLUMETRIC_STORAGE_BOX_BORDER_SIZE 1
+/// Minimum pixels an item must have in volumetric scaled storage UI
+#define MINIMUM_PIXELS_PER_ITEM 8
+/// Maximum number of objects that will be allowed to be displayed using the volumetric display system. Arbitrary number to prevent server lockups.
+#define MAXIMUM_VOLUMETRIC_ITEMS 256
+/// How much padding to give between items
+#define VOLUMETRIC_STORAGE_ITEM_PADDING 3
+/// How much padding to give to edges
+#define VOLUMETRIC_STORAGE_EDGE_PADDING 1
+
+//ITEM INVENTORY WEIGHT, FOR w_class
+/// Usually items smaller then a human hand, ex: Playing Cards, Lighter, Scalpel, Coins/Money
+#define WEIGHT_CLASS_TINY 1
+/// Fits within a small pocket, ex: Flashlight, Multitool, Grenades, GPS Device
+#define WEIGHT_CLASS_SMALL 2
+/// Can be carried in one hand comfortably, ex: Fire extinguisher, Stunbaton, Gas Mask, Metal Sheets
+#define WEIGHT_CLASS_NORMAL 3
+/// Items that can be wielded or equipped, (e.g. defibrillator, space suits). Often fits inside backpacks.
+#define WEIGHT_CLASS_BULKY 4
+/// Usually represents objects that require two hands to operate, (e.g. shotgun, two-handed melee weapons) May fit on some inventory slots
+#define WEIGHT_CLASS_HUGE 5
+/// Essentially means it cannot be picked up or placed in an inventory, ex: Mech Parts, Safe - Can not fit in Boh
+#define WEIGHT_CLASS_GIGANTIC 6
+
+// PLEASE KEEP ALL VOLUME DEFINES IN THIS FILE, it's going to be hell to keep track of them later.
+#define DEFAULT_VOLUME_TINY 1
+#define DEFAULT_VOLUME_SMALL 2
+#define DEFAULT_VOLUME_NORMAL 6
+#define DEFAULT_VOLUME_BULKY 12
+#define DEFAULT_VOLUME_HUGE 24
+#define DEFAULT_VOLUME_GIGANTIC 48
+
+GLOBAL_LIST_INIT(default_weight_class_to_volume, list(
+ "[WEIGHT_CLASS_TINY]" = DEFAULT_VOLUME_TINY,
+ "[WEIGHT_CLASS_SMALL]" = DEFAULT_VOLUME_SMALL,
+ "[WEIGHT_CLASS_NORMAL]" = DEFAULT_VOLUME_NORMAL,
+ "[WEIGHT_CLASS_BULKY]" = DEFAULT_VOLUME_BULKY,
+ "[WEIGHT_CLASS_HUGE]" = DEFAULT_VOLUME_HUGE,
+ "[WEIGHT_CLASS_GIGANTIC]" = DEFAULT_VOLUME_GIGANTIC
+ ))
+
+/// Macro for automatically getting the volume of an item from its w_class.
+#define AUTO_SCALE_VOLUME(w_class) (GLOB.default_weight_class_to_volume["[w_class]"])
+/// Macro for automatically getting the volume of a storage item from its max_w_class and max_combined_w_class.
+#define AUTO_SCALE_STORAGE_VOLUME(w_class, max_combined_w_class) (AUTO_SCALE_VOLUME(w_class) * (max_combined_w_class / w_class))
+
+// Let's keep all of this in one place. given what we put above anyways..
+
+// volume amount for items
+#define ITEM_VOLUME_DISK DEFAULT_VOLUME_TINY
+#define ITEM_VOLUME_CONTAINER_M 12 //makes nested toolboxes & toolbelts less efficient
+#define ITEM_VOLUME_MOB 40//prevents mob stacking
+
+// #define SAMPLE_VOLUME_AMOUNT 2
+
+// max_weight_class for storages
+//
+#define MAX_WEIGHT_CLASS_S_CONTAINER WEIGHT_CLASS_SMALL
+#define MAX_WEIGHT_CLASS_M_CONTAINER WEIGHT_CLASS_NORMAL
+#define MAX_WEIGHT_CLASS_BACKPACK WEIGHT_CLASS_NORMAL
+#define MAX_WEIGHT_CLASS_DUFFEL WEIGHT_CLASS_BULKY
+
+// max_volume for storages
+#define STORAGE_VOLUME_CONTAINER_S DEFAULT_VOLUME_NORMAL //3 small items
+#define STORAGE_VOLUME_CONTAINER_M (DEFAULT_VOLUME_NORMAL * 2) //6 small items
+#define STORAGE_VOLUME_SATCHEL (DEFAULT_VOLUME_NORMAL * 4) //4 normal items
+#define STORAGE_VOLUME_BACKPACK (DEFAULT_VOLUME_NORMAL * 6) //6 normal items, or 3 bulky items
+#define STORAGE_VOLUME_DUFFLEBAG (DEFAULT_VOLUME_NORMAL * 8) // 2 huge items, or 4 bulky items
+#define STORAGE_VOLUME_BAG_OF_HOLDING (DEFAULT_VOLUME_NORMAL * 9) //1.5X backpack
diff --git a/code/_onclick/hud/storage.dm b/code/_onclick/hud/storage.dm
new file mode 100644
index 00000000000..c10375df4e8
--- /dev/null
+++ b/code/_onclick/hud/storage.dm
@@ -0,0 +1,198 @@
+/atom/movable/screen/storage
+ name = "storage"
+ var/insertion_click = FALSE
+
+/atom/movable/screen/storage/Initialize(mapload, new_master)
+ . = ..()
+ master = new_master
+
+/atom/movable/screen/storage/Click(location, control, params)
+ if(!insertion_click)
+ return ..()
+ if(hud?.mymob && (hud.mymob != usr))
+ return
+ // just redirect clicks
+ if(master)
+ var/obj/item/I = usr.get_active_held_item()
+ if(I)
+ master.attackby(null, I, usr, params)
+ return TRUE
+
+/atom/movable/screen/storage/boxes
+ name = "storage"
+ icon_state = "block"
+ screen_loc = "7,7 to 10,8"
+ layer = HUD_LAYER
+ plane = HUD_PLANE
+ insertion_click = TRUE
+
+/atom/movable/screen/storage/close
+ name = "close"
+ layer = ABOVE_HUD_LAYER
+ plane = ABOVE_HUD_PLANE
+ icon_state = "backpack_close"
+
+/atom/movable/screen/storage/close/Click()
+ var/datum/component/storage/S = master
+ S.close(usr)
+ return TRUE
+
+/atom/movable/screen/storage/left
+ icon_state = "storage_start"
+ insertion_click = TRUE
+
+/atom/movable/screen/storage/right
+ icon_state = "storage_end"
+ insertion_click = TRUE
+
+/atom/movable/screen/storage/continuous
+ icon_state = "storage_continue"
+ insertion_click = TRUE
+
+/atom/movable/screen/storage/volumetric_box
+ icon_state = "stored_continue"
+ layer = VOLUMETRIC_STORAGE_BOX_LAYER
+ plane = VOLUMETRIC_STORAGE_BOX_PLANE
+ var/obj/item/our_item
+
+/atom/movable/screen/storage/volumetric_box/Initialize(mapload, new_master, obj/item/our_item)
+ src.our_item = our_item
+ RegisterSignal(our_item, COMSIG_ITEM_MOUSE_ENTER, PROC_REF(on_item_mouse_enter))
+ RegisterSignal(our_item, COMSIG_ITEM_MOUSE_EXIT, PROC_REF(on_item_mouse_exit))
+ return ..()
+
+/atom/movable/screen/storage/volumetric_box/Destroy()
+ makeItemInactive()
+ our_item = null
+ return ..()
+
+/atom/movable/screen/storage/volumetric_box/Click(location, control, params)
+ return our_item.Click(location, control, params)
+
+/atom/movable/screen/storage/volumetric_box/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
+ return our_item.MouseDrop(over, src_location, over_location, src_control, over_control, params)
+
+/atom/movable/screen/storage/volumetric_box/MouseExited(location, control, params)
+ makeItemInactive()
+
+/atom/movable/screen/storage/volumetric_box/MouseEntered(location, control, params)
+ . = ..()
+ makeItemActive()
+
+/atom/movable/screen/storage/volumetric_box/proc/on_item_mouse_enter()
+ makeItemActive()
+
+/atom/movable/screen/storage/volumetric_box/proc/on_item_mouse_exit()
+ makeItemInactive()
+
+/atom/movable/screen/storage/volumetric_box/proc/makeItemInactive()
+ return
+
+/atom/movable/screen/storage/volumetric_box/proc/makeItemActive()
+ return
+
+/atom/movable/screen/storage/volumetric_box/center
+ icon_state = "stored_continue"
+ var/atom/movable/screen/storage/volumetric_edge/stored_left/left
+ var/atom/movable/screen/storage/volumetric_edge/stored_right/right
+ var/atom/movable/screen/storage/item_holder/holder
+ var/pixel_size
+
+/atom/movable/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item)
+ left = new(null, src, our_item)
+ right = new(null, src, our_item)
+ return ..()
+
+/atom/movable/screen/storage/volumetric_box/center/Destroy()
+ QDEL_NULL(left)
+ QDEL_NULL(right)
+ vis_contents.Cut()
+ if(holder)
+ QDEL_NULL(holder)
+ return ..()
+
+/atom/movable/screen/storage/volumetric_box/center/proc/on_screen_objects()
+ return list(src)
+
+
+//Sets the size of this box screen object and regenerates its left/right borders. This includes the actual border's size!
+/atom/movable/screen/storage/volumetric_box/center/proc/set_pixel_size(pixels)
+ if(pixel_size == pixels)
+ return
+ pixel_size = pixels
+ cut_overlays()
+ vis_contents.Cut()
+ //our icon size is 32 pixels.
+ var/multiplier = (pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE
+ transform = matrix(multiplier, 0, 0, 0, 1, 0)
+ if(our_item)
+ if(holder)
+ qdel(holder)
+ holder = new(null, src, our_item)
+ holder.transform = matrix(1 / multiplier, 0, 0, 0, 1, 0)
+ holder.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ holder.appearance_flags &= ~RESET_TRANSFORM
+ makeItemInactive()
+ vis_contents += holder
+ left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
+ right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
+ add_overlay(left)
+ add_overlay(right)
+
+/atom/movable/screen/storage/volumetric_box/center/makeItemInactive()
+ if(!holder)
+ return
+ holder.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
+ holder.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
+
+/atom/movable/screen/storage/volumetric_box/center/makeItemActive()
+ if(!holder)
+ return
+ holder.our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
+ holder.our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
+
+/atom/movable/screen/storage/volumetric_edge
+ layer = VOLUMETRIC_STORAGE_BOX_LAYER
+ plane = VOLUMETRIC_STORAGE_BOX_PLANE
+
+/atom/movable/screen/storage/volumetric_edge/Initialize(mapload, master, our_item)
+ src.master = master
+ return ..()
+
+/atom/movable/screen/storage/volumetric_edge/Click(location, control, params)
+ return master.Click(location, control, params)
+
+/atom/movable/screen/storage/volumetric_edge/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
+ return master.MouseDrop(over, src_location, over_location, src_control, over_control, params)
+
+/atom/movable/screen/storage/volumetric_edge/MouseExited(location, control, params)
+ return master.MouseExited(location, control, params)
+
+/atom/movable/screen/storage/volumetric_edge/MouseEntered(location, control, params)
+ . = ..()
+ return master.MouseEntered(location, control, params)
+
+/atom/movable/screen/storage/volumetric_edge/stored_left
+ icon_state = "stored_start"
+ appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM // Yes I know RESET_TRANSFORM is in APPEARANCE_UI but we're hard-asserting this incase someone changes it.
+
+/atom/movable/screen/storage/volumetric_edge/stored_right
+ icon_state = "stored_end"
+ appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM
+
+/atom/movable/screen/storage/item_holder
+ var/obj/item/our_item
+ vis_flags = NONE
+
+/atom/movable/screen/storage/item_holder/Initialize(mapload, new_master, obj/item/I)
+ . = ..()
+ our_item = I
+ vis_contents += I
+
+/atom/movable/screen/storage/item_holder/Destroy()
+ vis_contents.Cut()
+ our_item = null
+ return ..()
+
+/atom/movable/screen/storage/item_holder/Click(location, control, params)
+ return our_item.Click(location, control, params)
diff --git a/code/controllers/subsystem/processing/movable_physics.dm b/code/controllers/subsystem/processing/movable_physics.dm
new file mode 100644
index 00000000000..65015edbd66
--- /dev/null
+++ b/code/controllers/subsystem/processing/movable_physics.dm
@@ -0,0 +1,24 @@
+///Real fast ticking subsystem for moving movables via modifying pixel_x/y/z
+PROCESSING_SUBSYSTEM_DEF(movablephysics)
+ name = "Movable Physics"
+ wait = 0.05 SECONDS
+ stat_tag = "MP"
+ priority = FIRE_PRIORITY_MOVABLE_PHYSICS
+
+/datum/controller/subsystem/processing/movablephysics/fire(resumed = FALSE)
+ if (!resumed)
+ currentrun = processing.Copy()
+ //cache for sanic speed (lists are references anyways)
+ var/list/current_run = currentrun
+
+ while(current_run.len)
+ var/datum/component/thing = current_run[current_run.len]
+ current_run.len--
+ if(QDELETED(thing))
+ processing -= thing
+ else
+ if(thing.process(wait * 0.1) == PROCESS_KILL)
+ // fully stop so that a future START_PROCESSING will work
+ STOP_PROCESSING(src, thing)
+ if (MC_TICK_CHECK)
+ return
diff --git a/code/datums/components/crafting/recipes/clothing.dm b/code/datums/components/crafting/recipes/clothing.dm
new file mode 100644
index 00000000000..95b37502bca
--- /dev/null
+++ b/code/datums/components/crafting/recipes/clothing.dm
@@ -0,0 +1,192 @@
+// Please separate them based on categories. Made this easier for all of us, god damn it! #Lianvee
+
+// Durathread
+/datum/crafting_recipe/durathread_vest
+ name = "Durathread Vest"
+ result = /obj/item/clothing/suit/armor/vest/durathread
+ reqs = list(
+ /obj/item/stack/sheet/durathread = 5,
+ /obj/item/stack/sheet/leather = 4
+ )
+ time = 50
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/durathread_helmet
+ name = "Durathread Helmet"
+ result = /obj/item/clothing/head/helmet/durathread
+ reqs = list(
+ /obj/item/stack/sheet/durathread = 4,
+ /obj/item/stack/sheet/leather = 5
+ )
+ time = 40
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/durathread_jumpsuit
+ name = "Durathread Jumpsuit"
+ result = /obj/item/clothing/under/misc/durathread
+ reqs = list(/obj/item/stack/sheet/durathread = 4)
+ time = 40
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/durathread_beret
+ name = "Durathread Beret"
+ result = /obj/item/clothing/head/beret/durathread
+ reqs = list(/obj/item/stack/sheet/durathread = 2)
+ time = 40
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/durathread_beanie
+ name = "Durathread Beanie"
+ result = /obj/item/clothing/head/beanie/durathread
+ reqs = list(/obj/item/stack/sheet/durathread = 2)
+ time = 40
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/durathread_bandana
+ name = "Durathread Bandana"
+ result = /obj/item/clothing/mask/bandana/durathread
+ reqs = list(/obj/item/stack/sheet/durathread = 1)
+ time = 25
+ category = CAT_CLOTHING
+
+// Belts
+/datum/crafting_recipe/fannypack
+ name = "Fannypack"
+ result = /obj/item/storage/belt/fannypack
+ reqs = list(
+ /obj/item/stack/sheet/cotton/cloth = 2,
+ /obj/item/stack/sheet/leather = 1
+ )
+ time = 20
+ category = CAT_CLOTHING
+
+// Eyewear
+/datum/crafting_recipe/hudsunsec
+ name = "Security HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/security/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/hud/security = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/hudsunmed
+ name = "Medical HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/health/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/hud/health = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/hudsundiag
+ name = "Diagnostic HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/diagnostic/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/hud/diagnostic = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/sciencesunglasses
+ name = "Science Sunglasses"
+ result = /obj/item/clothing/glasses/sunglasses/chemical
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/science = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/medhudglasses // The prescription HUD glasses. This long to have them... #Lianvee
+ name = "MedicalHUD Prescription Glasses"
+ result = /obj/item/clothing/glasses/hud/health/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/hud/health = 1,
+ /obj/item/clothing/glasses/regular = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/sechudglasses
+ name = "SecurityHUD Prescription Glasses"
+ result = /obj/item/clothing/glasses/hud/security/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/hud/security = 1,
+ /obj/item/clothing/glasses/regular = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/mesonglasses
+ name = "Meson Prescription Glasses"
+ result = /obj/item/clothing/glasses/meson/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/meson = 1,
+ /obj/item/clothing/glasses/regular = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/scienceglasses
+ name = "Science Prescription Glasses"
+ result = /obj/item/clothing/glasses/science/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/glasses/science = 1,
+ /obj/item/clothing/glasses/regular = 1,
+ /obj/item/stack/cable_coil = 5
+ )
+ category = CAT_CLOTHING
+
+// Misc.
+/datum/crafting_recipe/ghostsheet
+ name = "Ghost Sheet"
+ result = /obj/item/clothing/suit/ghost_sheet
+ time = 5
+ tools = list(TOOL_WIRECUTTER)
+ reqs = list(/obj/item/bedsheet = 1)
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/cowboyboots
+ name = "Cowboy Boots"
+ result = /obj/item/clothing/shoes/cowboy
+ reqs = list(/obj/item/stack/sheet/leather = 2)
+ time = 45
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/gripperoffbrand
+ name = "Improvised Gripper Gloves"
+ reqs = list(
+ /obj/item/clothing/gloves/fingerless = 1,
+ /obj/item/stack/tape = 1)
+ result = /obj/item/clothing/gloves/tackler/offbrand
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/scrap_armor
+ name = "Scrap Armor"
+ result = /obj/item/clothing/suit/armor/vest/scrap_armor
+ time = 60
+ reqs = list(
+ /obj/item/stack/sheet/metal = 10,
+ /obj/item/stack/cable_coil = 20,
+ )
+ category = CAT_CLOTHING
diff --git a/code/datums/components/crafting/recipes/drink.dm b/code/datums/components/crafting/recipes/drink.dm
new file mode 100644
index 00000000000..5ca6d14814c
--- /dev/null
+++ b/code/datums/components/crafting/recipes/drink.dm
@@ -0,0 +1,52 @@
+/datum/crafting_recipe/umbrellared
+ name = "Red Drink Umbrella"
+ result = /obj/item/garnish/umbrellared
+ time = 1 SECONDS
+ tools = list(/obj/item/toy/crayon/spraycan)
+ reqs = list(
+ /obj/item/paper = 1,
+ /obj/item/stack/rods = 1)
+ category = CAT_DRINK
+
+/datum/crafting_recipe/umbrellablue
+ name = "Blue Drink Umbrella"
+ result = /obj/item/garnish/umbrellablue
+ time = 1 SECONDS
+ tools = list(/obj/item/toy/crayon/spraycan)
+ reqs = list(
+ /obj/item/paper = 1,
+ /obj/item/stack/rods = 1)
+ category = CAT_DRINK
+
+/datum/crafting_recipe/umbrellagreen
+ name = "Green Drink Umbrella"
+ result = /obj/item/garnish/umbrellagreen
+ time = 1 SECONDS
+ tools = list(/obj/item/toy/crayon/spraycan)
+ reqs = list(
+ /obj/item/paper = 1,
+ /obj/item/stack/rods = 1)
+ category = CAT_DRINK
+
+/datum/crafting_recipe/ash_garnish
+ name = "Ash Garnish"
+ result = /obj/item/garnish/ash
+ reqs = list(/datum/reagent/ash = 10)
+ time = 5
+ category = CAT_DRINK
+
+/datum/crafting_recipe/salt_garnish
+ name = "Salt Garnish"
+ result = /obj/item/garnish/salt
+ reqs = list(/datum/reagent/consumable/sodiumchloride = 10)
+ time = 5
+ category = CAT_DRINK
+
+/datum/crafting_recipe/breakawayflask
+ name = "Breakaway Flask"
+ result = /obj/item/reagent_containers/food/drinks/breakawayflask
+ time = 5 SECONDS
+ reqs = list(/obj/item/stack/sheet/glass = 5,
+ /obj/item/stack/sheet/mineral/plasma = 1)
+ tools = list(TOOL_WELDER)
+ category = CAT_DRINK
diff --git a/code/datums/components/crafting/recipes/misc.dm b/code/datums/components/crafting/recipes/misc.dm
new file mode 100644
index 00000000000..0ed4acb5be7
--- /dev/null
+++ b/code/datums/components/crafting/recipes/misc.dm
@@ -0,0 +1,220 @@
+/datum/crafting_recipe/skateboard
+ name = "Skateboard"
+ result = /obj/vehicle/ridden/scooter/skateboard
+ time = 60
+ reqs = list(/obj/item/stack/sheet/metal = 5,
+ /obj/item/stack/rods = 10)
+ category = CAT_MISC
+
+/datum/crafting_recipe/scooter
+ name = "Scooter"
+ result = /obj/vehicle/ridden/scooter
+ time = 65
+ reqs = list(/obj/item/stack/sheet/metal = 5,
+ /obj/item/stack/rods = 12)
+ category = CAT_MISC
+
+/datum/crafting_recipe/wheelchair
+ name = "Wheelchair"
+ result = /obj/vehicle/ridden/wheelchair
+ reqs = list(/obj/item/stack/sheet/metal = 4,
+ /obj/item/stack/rods = 6)
+ time = 100
+ category = CAT_MISC
+
+/datum/crafting_recipe/motorized_wheelchair
+ name = "Motorized Wheelchair"
+ result = /obj/vehicle/ridden/wheelchair/motorized
+ reqs = list(/obj/item/stack/sheet/metal = 10,
+ /obj/item/stack/rods = 8,
+ /obj/item/stock_parts/manipulator = 2,
+ /obj/item/stock_parts/capacitor = 1)
+ parts = list(/obj/item/stock_parts/manipulator = 2,
+ /obj/item/stock_parts/capacitor = 1)
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
+ time = 200
+ category = CAT_MISC
+
+/datum/crafting_recipe/mousetrap
+ name = "Mouse Trap"
+ result = /obj/item/assembly/mousetrap
+ time = 10
+ reqs = list(/obj/item/stack/sheet/cardboard = 1,
+ /obj/item/stack/rods = 1)
+ category = CAT_MISC
+
+/datum/crafting_recipe/papersack
+ name = "Paper Sack"
+ result = /obj/item/storage/box/papersack
+ time = 10
+ reqs = list(/obj/item/paper = 5)
+ category = CAT_MISC
+
+/datum/crafting_recipe/flashlight_eyes
+ name = "Flashlight Eyes"
+ result = /obj/item/organ/eyes/robotic/flashlight
+ time = 10
+ reqs = list(
+ /obj/item/flashlight = 2,
+ /obj/item/restraints/handcuffs/cable = 1
+ )
+ category = CAT_MISC
+
+/datum/crafting_recipe/paperframes
+ name = "Paper Frames"
+ result = /obj/item/stack/sheet/paperframes/five
+ time = 10
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 5, /obj/item/paper = 20)
+ category = CAT_MISC
+
+/datum/crafting_recipe/naturalpaper
+ name = "Hand-Pressed Paper"
+ time = 30
+ reqs = list(/datum/reagent/water = 50, /obj/item/stack/sheet/mineral/wood = 1)
+ tools = list(/obj/item/hatchet)
+ result = /obj/item/paper_bin/bundlenatural
+ category = CAT_MISC
+
+/datum/crafting_recipe/curtain
+ name = "Curtains"
+ reqs = list(/obj/item/stack/sheet/cotton/cloth = 4, /obj/item/stack/rods = 1)
+ result = /obj/structure/curtain/cloth
+ category = CAT_MISC
+
+/datum/crafting_recipe/showercurtain
+ name = "Shower Curtains"
+ reqs = list(/obj/item/stack/sheet/cotton/cloth = 2, /obj/item/stack/sheet/plastic = 2, /obj/item/stack/rods = 1)
+ result = /obj/structure/curtain
+ category = CAT_MISC
+
+/datum/crafting_recipe/extendohand
+ name = "Extendo-Hand"
+ reqs = list(/obj/item/bodypart/r_arm/robot = 1, /obj/item/clothing/gloves/boxing = 1)
+ result = /obj/item/extendohand
+ category = CAT_MISC
+
+/datum/crafting_recipe/pressureplate
+ name = "Pressure Plate"
+ result = /obj/item/pressure_plate
+ time = 5
+ reqs = list(/obj/item/stack/sheet/metal = 1,
+ /obj/item/stack/tile/plasteel = 1,
+ /obj/item/stack/cable_coil = 2,
+ /obj/item/assembly/igniter = 1)
+ category = CAT_MISC
+
+/datum/crafting_recipe/rcl
+ name = "Makeshift Rapid Pipe Cleaner Layer"
+ result = /obj/item/rcl/ghetto
+ time = 40
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
+ reqs = list(/obj/item/stack/sheet/metal = 15)
+ category = CAT_MISC
+
+/datum/crafting_recipe/guillotine
+ name = "Guillotine"
+ result = /obj/structure/guillotine
+ time = 150 // Building a functioning guillotine takes time
+ reqs = list(/obj/item/stack/sheet/plasteel = 3,
+ /obj/item/stack/sheet/mineral/wood = 20,
+ /obj/item/stack/cable_coil = 10)
+ tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
+ category = CAT_MISC
+
+/datum/crafting_recipe/ghettojetpack
+ name = "Improvised Jetpack"
+ result = /obj/item/tank/jetpack/improvised
+ time = 30
+ reqs = list(/obj/item/tank/internals/oxygen = 2, /obj/item/extinguisher = 1, /obj/item/pipe = 3, /obj/item/stack/cable_coil = MAXCOIL)
+ category = CAT_MISC
+ tools = list(TOOL_WRENCH, TOOL_WELDER, TOOL_WIRECUTTER)
+
+/datum/crafting_recipe/multiduct
+ name = "Multi-layer duct"
+ result = /obj/machinery/duct/multilayered
+ time = 5
+ reqs = list(/obj/item/stack/ducts = 5)
+ category = CAT_MISC
+ tools = list(TOOL_WELDER)
+
+/datum/crafting_recipe/ipickaxe
+ name = "Improvised Pickaxe"
+ reqs = list(
+ /obj/item/crowbar = 1,
+ /obj/item/kitchen/knife = 1,
+ /obj/item/stack/tape = 1)
+ result = /obj/item/pickaxe/improvised
+ category = CAT_MISC
+
+/datum/crafting_recipe/chem_scanner
+ name = "Reagent Scanner"
+ time = 30
+ tools = list(TOOL_WIRECUTTER, TOOL_SCREWDRIVER)
+ reqs = list(
+ /obj/item/healthanalyzer = 1,
+ /obj/item/stack/cable_coil = 5,
+ /obj/item/stock_parts/scanning_module = 1)
+ result = /obj/item/reagent_scanner
+ category = CAT_MISC
+
+/datum/crafting_recipe/filter
+ name = "Seperatory Funnel"
+ time = 40
+ tools = list(TOOL_WELDER, TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/reagent_containers/glass/beaker = 3)
+ result = /obj/item/reagent_containers/glass/filter
+ category = CAT_MISC
+
+/datum/crafting_recipe/splint
+ name = "Makeshift Splint"
+ reqs = list(
+ /obj/item/stack/rods = 2,
+ /obj/item/stack/sheet/cotton/cloth = 4)
+ result = /obj/item/stack/medical/splint/ghetto
+ category = CAT_MISC
+
+/datum/crafting_recipe/portableseedextractor
+ name = "Portable seed extractor"
+ reqs = list(
+ /obj/item/storage/bag/plants = 1,
+ /obj/item/plant_analyzer = 1,
+ /obj/item/stock_parts/manipulator = 1,
+ /obj/item/stack/cable_coil = 2)
+ result = /obj/item/storage/bag/plants/portaseeder //this will probably mean that you can craft portable seed extractors into themselves, sending the other materials into the void, but we still don't have a solution for recipes involving radios stealing your headset, so this is officially not my problem. "no, Tills-The-Soil, adding more analyzers and micro-manipulators to your portable seed extractor does not make it make more seeds. in fact it does exactly nothing."
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ category = CAT_MISC
+
+/datum/crafting_recipe/freezer
+ name = "Freezer"
+ result = /obj/structure/closet/crate/freezer
+ time = 2 SECONDS
+ reqs = list(/datum/reagent/consumable/ice = 25,
+ /obj/item/stack/sheet/metal = 2)
+ category = CAT_MISC
+
+/datum/crafting_recipe/aquarium
+ name = "Aquarium"
+ result = /obj/structure/aquarium
+ time = 10 SECONDS
+ reqs = list(/obj/item/stack/sheet/metal = 15,
+ /obj/item/stack/sheet/glass = 10,
+ /obj/item/aquarium_kit = 1)
+ category = CAT_MISC
+
+/datum/crafting_recipe/mothplush
+ name = "Moth Plushie"
+ result = /obj/item/toy/plush/moth
+ reqs = list(/obj/item/stack/sheet/animalhide/mothroach = 1,
+ /obj/item/organ/heart = 1,
+ /obj/item/stack/sheet/cotton/cloth = 3)
+ category = CAT_MISC
+
+/datum/crafting_recipe/candorupgrade
+ name = "Candor Upgrade"
+ result = /obj/item/gun/ballistic/automatic/pistol/candor/phenex
+ reqs = list(/obj/item/stack/sheet/mineral/hidden = 4,
+ /obj/item/gun/ballistic/automatic/pistol/candor = 1)
+ category = CAT_MISC
diff --git a/code/datums/components/crafting/recipes/robot.dm b/code/datums/components/crafting/recipes/robot.dm
new file mode 100644
index 00000000000..a5558682e86
--- /dev/null
+++ b/code/datums/components/crafting/recipes/robot.dm
@@ -0,0 +1,86 @@
+/datum/crafting_recipe/ed209
+ name = "ED209"
+ result = /mob/living/simple_animal/bot/secbot/ed209
+ reqs = list(/obj/item/robot_suit = 1,
+ /obj/item/clothing/head/helmet = 1,
+ /obj/item/clothing/suit/armor/vest = 1,
+ /obj/item/bodypart/leg/left/robot = 1,
+ /obj/item/bodypart/leg/right/robot = 1,
+ /obj/item/stack/sheet/metal = 1,
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/gun/energy/disabler = 1,
+ /obj/item/assembly/prox_sensor = 1)
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER)
+ time = 60
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/secbot
+ name = "Secbot"
+ result = /mob/living/simple_animal/bot/secbot
+ reqs = list(/obj/item/assembly/signaler = 1,
+ /obj/item/clothing/head/helmet/sec = 1,
+ /obj/item/melee/baton = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/bodypart/r_arm/robot = 1)
+ tools = list(TOOL_WELDER)
+ time = 60
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/cleanbot
+ name = "Cleanbot"
+ result = /mob/living/simple_animal/bot/cleanbot
+ reqs = list(/obj/item/reagent_containers/glass/bucket = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/bodypart/r_arm/robot = 1)
+ time = 40
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/floorbot
+ name = "Floorbot"
+ result = /mob/living/simple_animal/bot/floorbot
+ reqs = list(/obj/item/storage/toolbox = 1,
+ /obj/item/stack/tile/plasteel = 10,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/bodypart/r_arm/robot = 1)
+ time = 40
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/medbot
+ name = "Medbot"
+ result = /mob/living/simple_animal/bot/medbot
+ reqs = list(/obj/item/healthanalyzer = 1,
+ /obj/item/storage/firstaid = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/bodypart/r_arm/robot = 1)
+ time = 40
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/honkbot
+ name = "Honkbot"
+ result = /mob/living/simple_animal/bot/honkbot
+ reqs = list(/obj/item/storage/box/clown = 1,
+ /obj/item/bodypart/r_arm/robot = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/bikehorn/ = 1)
+ time = 40
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/Firebot
+ name = "Firebot"
+ result = /mob/living/simple_animal/bot/firebot
+ reqs = list(/obj/item/extinguisher = 1,
+ /obj/item/bodypart/r_arm/robot = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/clothing/head/hardhat/red = 1)
+ time = 40
+ category = CAT_ROBOT
+
+/datum/crafting_recipe/Vibebot
+ name = "Vibebot"
+ result = /mob/living/simple_animal/bot/vibebot
+ reqs = list(/obj/item/light/bulb = 2,
+ /obj/item/bodypart/head/robot = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/toy/crayon = 1)
+ time = 40
+ category = CAT_ROBOT
diff --git a/code/datums/components/crafting/recipes/tribal.dm b/code/datums/components/crafting/recipes/tribal.dm
new file mode 100644
index 00000000000..83e5c03722b
--- /dev/null
+++ b/code/datums/components/crafting/recipes/tribal.dm
@@ -0,0 +1,236 @@
+/datum/crafting_recipe/bonetalisman
+ name = "Bone Talisman"
+ result = /obj/item/clothing/accessory/talisman
+ time = 20
+ reqs = list(/obj/item/stack/sheet/bone = 2,
+ /obj/item/stack/sheet/sinew = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bonenecklace
+ name = "Hunter's Necklace"
+ result = /obj/item/clothing/accessory/wolftalisman
+ time = 35
+ reqs = list(/obj/item/stack/sheet/bone = 2,
+ /obj/item/stack/sheet/sinew = 3,
+ /obj/item/mob_trophy/wolf_ear = 2,
+ /obj/item/mob_trophy/fang = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bonecodpiece
+ name = "Skull Codpiece"
+ result = /obj/item/clothing/accessory/skullcodpiece
+ time = 20
+ reqs = list(/obj/item/stack/sheet/bone = 2,
+ /obj/item/mob_trophy/legion_skull = 1,
+ /obj/item/stack/sheet/animalhide/goliath_hide = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/skilt
+ name = "Sinew Kilt"
+ result = /obj/item/clothing/accessory/skilt
+ time = 20
+ reqs = list(/obj/item/stack/sheet/bone = 1,
+ /obj/item/stack/sheet/sinew = 2)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bracers
+ name = "Bone Bracers"
+ result = /obj/item/clothing/gloves/bracer
+ time = 20
+ reqs = list(/obj/item/stack/sheet/bone = 2,
+ /obj/item/stack/sheet/sinew = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/goliathcloak
+ name = "Goliath Cloak"
+ result = /obj/item/clothing/suit/hooded/cloak/goliath
+ time = 50
+ reqs = list(/obj/item/stack/sheet/leather = 2,
+ /obj/item/stack/sheet/sinew = 2,
+ /obj/item/stack/sheet/animalhide/goliath_hide = 2) //it takes 4 goliaths to make 1 cloak if the plates are skinned
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/drakecloak
+ name = "Ash Drake Armour"
+ result = /obj/item/clothing/suit/hooded/cloak/drake
+ time = 60
+ reqs = list(/obj/item/stack/sheet/bone = 10,
+ /obj/item/stack/sheet/sinew = 2,
+ /obj/item/stack/sheet/animalhide/ashdrake = 5)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bonespear
+ name = "Bone Spear"
+ result = /obj/item/spear/bonespear
+ time = 30
+ reqs = list(/obj/item/stack/sheet/bone = 4,
+ /obj/item/stack/sheet/sinew = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/boneaxe
+ name = "Bone Axe"
+ result = /obj/item/fireaxe/boneaxe
+ time = 50
+ reqs = list(/obj/item/stack/sheet/bone = 6,
+ /obj/item/stack/sheet/sinew = 3)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bonfire
+ name = "Bonfire"
+ time = 60
+ reqs = list(/obj/item/grown/log = 5)
+ parts = list(/obj/item/grown/log = 5)
+ blacklist = list(/obj/item/grown/log/steel)
+ result = /obj/structure/bonfire
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/headpike
+ name = "Spike Head (Glass Spear)"
+ time = 65
+ reqs = list(/obj/item/spear = 1,
+ /obj/item/bodypart/head = 1)
+ parts = list(/obj/item/bodypart/head = 1,
+ /obj/item/spear = 1)
+ blacklist = list(/obj/item/spear/explosive, /obj/item/spear/bonespear)
+ result = /obj/structure/headpike
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/headpikebone
+ name = "Spike Head (Bone Spear)"
+ time = 65
+ reqs = list(/obj/item/spear/bonespear = 1,
+ /obj/item/bodypart/head = 1)
+ parts = list(/obj/item/bodypart/head = 1,
+ /obj/item/spear/bonespear = 1)
+ result = /obj/structure/headpike/bone
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/lasso
+ name = "Bone Lasso"
+ reqs = list(
+ /obj/item/stack/sheet/bone = 1,
+ /obj/item/stack/sheet/sinew = 5)
+ result = /obj/item/key/lasso
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/heavybonearmor
+ name = "Heavy Bone Armor"
+ result = /obj/item/clothing/suit/hooded/cloak/bone
+ time = 60
+ reqs = list(/obj/item/stack/sheet/bone = 8,
+ /obj/item/stack/sheet/sinew = 3)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/watcherbola
+ name = "Watcher Bola"
+ result = /obj/item/restraints/legcuffs/bola/watcher
+ time = 30
+ reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 2,
+ /obj/item/restraints/handcuffs/cable/sinew = 1)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/goliathshield
+ name = "Goliath shield"
+ result = /obj/item/shield/riot/goliath
+ time = 60
+ reqs = list(/obj/item/stack/sheet/bone = 4,
+ /obj/item/stack/sheet/animalhide/goliath_hide = 3)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bonesword
+ name = "Bone Sword"
+ result = /obj/item/claymore/bone
+ time = 40
+ reqs = list(/obj/item/stack/sheet/bone = 3,
+ /obj/item/stack/sheet/sinew = 2)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/hunterbelt
+ name = "Hunters Belt"
+ result = /obj/item/storage/belt/mining/primitive
+ time = 20
+ reqs = list(/obj/item/stack/sheet/sinew = 2,
+ /obj/item/stack/sheet/animalhide/goliath_hide = 2)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/quiver
+ name = "Quiver"
+ result = /obj/item/storage/bag/quiver/empty
+ time = 80
+ reqs = list(/obj/item/stack/sheet/leather = 3,
+ /obj/item/stack/sheet/sinew = 4)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/bone_bow
+ name = "Bone Bow"
+ result = /obj/item/gun/ballistic/bow/ashen
+ time = 200
+ reqs = list(/obj/item/stack/sheet/bone = 8,
+ /obj/item/stack/sheet/sinew = 4)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/polarbearcloak
+ name = "Polar Cloak"
+ result = /obj/item/clothing/suit/hooded/cloak/goliath/polar
+ time = 50
+ reqs = list(/obj/item/stack/sheet/leather = 2,
+ /obj/item/stack/sheet/sinew = 2,
+ /obj/item/stack/sheet/animalhide/goliath_hide/polar_bear_hide = 2)
+ blacklist = list(/obj/item/stack/sheet/animalhide/goliath_hide)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/distiller
+ name = "Distiller"
+ result = /obj/structure/fermenting_barrel/distiller
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 8, /obj/item/stack/sheet/metal = 5, /datum/reagent/srm_bacteria = 30)
+ time = 50
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/crystalamulet
+ name = "Crystal Amulet"
+ result = /obj/item/clothing/neck/crystal_amulet
+ time = 4 SECONDS
+ reqs = list(/obj/item/strange_crystal = 3)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/crystalspear
+ name = "Crystal Spear"
+ result = /obj/item/spear/crystal
+ time = 4 SECONDS
+ reqs = list(/obj/item/strange_crystal = 2)
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/mushroom_bowl
+ name = "Mushroom Bowl"
+ result = /obj/item/reagent_containers/glass/bowl/mushroom_bowl
+ reqs = list(/obj/item/reagent_containers/food/snacks/grown/ash_flora/shavings = 5)
+ time = 30
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/charcoal_stylus
+ name = "Charcoal Stylus"
+ result = /obj/item/pen/charcoal
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1, /datum/reagent/ash = 30)
+ time = 30
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/mushroom_mortar
+ name = "Mushroom Mortar"
+ result = /obj/item/reagent_containers/glass/mortar/mushroom
+ reqs = list(/obj/item/reagent_containers/food/snacks/grown/ash_flora/shavings = 5)
+ time = 30
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/oar
+ name = "Goliath Bone Oar"
+ result = /obj/item/oar
+ reqs = list(/obj/item/stack/sheet/bone = 2)
+ time = 15
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/boat
+ name = "Goliath Hide Boat"
+ result = /obj/vehicle/ridden/lavaboat
+ reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 3)
+ time = 50
+ category = CAT_PRIMAL
diff --git a/code/datums/components/crafting/recipes/weapon.dm b/code/datums/components/crafting/recipes/weapon.dm
new file mode 100644
index 00000000000..c1dde04b365
--- /dev/null
+++ b/code/datums/components/crafting/recipes/weapon.dm
@@ -0,0 +1,317 @@
+/datum/crafting_recipe/IED
+ name = "IED"
+ result = /obj/item/grenade/iedcasing
+ reqs = list(/datum/reagent/fuel = 50,
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/assembly/igniter = 1,
+ /obj/item/reagent_containers/food/drinks/soda_cans = 1)
+ parts = list(/obj/item/reagent_containers/food/drinks/soda_cans = 1)
+ time = 15
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/lance
+ name = "Explosive Lance (Grenade)"
+ result = /obj/item/spear/explosive
+ reqs = list(/obj/item/spear = 1,
+ /obj/item/grenade = 1)
+ blacklist = list(/obj/item/spear/bonespear)
+ parts = list(/obj/item/spear = 1,
+ /obj/item/grenade = 1)
+ time = 15
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/strobeshield
+ name = "Strobe Shield"
+ result = /obj/item/shield/riot/flash
+ reqs = list(/obj/item/wallframe/flasher = 1,
+ /obj/item/assembly/flash/handheld = 1,
+ /obj/item/shield/riot = 1)
+ time = 40
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/strobeshield/New()
+ ..()
+ blacklist |= subtypesof(/obj/item/shield/riot/)
+
+/datum/crafting_recipe/molotov
+ name = "Molotov"
+ result = /obj/item/reagent_containers/food/drinks/bottle/molotov
+ reqs = list(/obj/item/reagent_containers/glass/rag = 1,
+ /obj/item/reagent_containers/food/drinks/bottle = 1)
+ parts = list(/obj/item/reagent_containers/food/drinks/bottle = 1)
+ time = 40
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/stunprod
+ name = "Stunprod"
+ result = /obj/item/melee/baton/cattleprod
+ reqs = list(/obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/stack/rods = 1,
+ /obj/item/assembly/igniter = 1)
+ time = 40
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/teleprod
+ name = "Teleprod"
+ result = /obj/item/melee/baton/cattleprod/teleprod
+ reqs = list(/obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/stack/rods = 1,
+ /obj/item/assembly/igniter = 1,
+ /obj/item/stack/ore/bluespace_crystal = 1)
+ time = 40
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/bola
+ name = "Bola"
+ result = /obj/item/restraints/legcuffs/bola
+ reqs = list(/obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/stack/sheet/metal = 6)
+ time = 20//15 faster than crafting them by hand!
+ category= CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/gonbola
+ name = "Gonbola"
+ result = /obj/item/restraints/legcuffs/bola/gonbola
+ reqs = list(/obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/stack/sheet/metal = 6,
+ /obj/item/stack/sheet/animalhide/gondola = 1)
+ time = 40
+ category= CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/improvised_pneumatic_cannon //Pretty easy to obtain but
+ name = "Pneumatic Cannon"
+ result = /obj/item/pneumatic_cannon/ghetto
+ tools = list(TOOL_WELDER, TOOL_WRENCH)
+ reqs = list(/obj/item/stack/sheet/metal = 4,
+ /obj/item/stack/packageWrap = 8,
+ /obj/item/pipe = 2)
+ time = 50
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/flamethrower
+ name = "Flamethrower"
+ result = /obj/item/flamethrower
+ reqs = list(/obj/item/weldingtool = 1,
+ /obj/item/assembly/igniter = 1,
+ /obj/item/stack/rods = 1)
+ parts = list(/obj/item/assembly/igniter = 1,
+ /obj/item/weldingtool = 1)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 10
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/meteorslug
+ name = "Meteorslug Shell"
+ result = /obj/item/ammo_casing/shotgun/meteorslug
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
+ /obj/item/rcd_ammo = 1,
+ /obj/item/stock_parts/manipulator = 2)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/pulseslug
+ name = "Pulse Slug Shell"
+ result = /obj/item/ammo_casing/shotgun/pulseslug
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
+ /obj/item/stock_parts/capacitor = 3,
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stock_parts/cell = 1,
+ /datum/reagent/lithium = 5)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/dragonsbreath
+ name = "Dragonsbreath Shell"
+ result = /obj/item/ammo_casing/shotgun/dragonsbreath
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1, /datum/reagent/phosphorus = 10)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/frag12
+ name = "FRAG-12 Shell"
+ result = /obj/item/ammo_casing/shotgun/frag12
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
+ /datum/reagent/glycerol = 5,
+ /datum/reagent/toxin/acid = 5,
+ /datum/reagent/toxin/acid/fluacid = 5)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/ionslug
+ name = "Ion Scatter Shell"
+ result = /obj/item/ammo_casing/shotgun/ion
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/capacitor = 2,
+ /obj/item/stock_parts/scanning_module = 1,
+ /datum/reagent/iron = 5,
+ /datum/reagent/uranium = 5)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/improvisedslug
+ name = "Improvised Shotgun Shell"
+ result = /obj/item/ammo_casing/shotgun/improvised
+ reqs = list(/obj/item/stack/sheet/metal = 2,
+ /obj/item/stack/cable_coil = 1,
+ /datum/reagent/fuel = 10)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 12
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/laserscatter
+ name = "Scatter Laser Shell"
+ result = /obj/item/ammo_casing/shotgun/laserscatter
+ reqs = list(/obj/item/ammo_casing/shotgun/techshell = 1,
+ /obj/item/stock_parts/capacitor = 1,
+ /obj/item/stock_parts/micro_laser = 3,
+ /obj/item/stock_parts/cell = 1,
+ /datum/reagent/lithium = 5)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 5
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/ishotgun
+ name = "Improvised Shotgun"
+ result = /obj/item/gun/ballistic/shotgun/doublebarrel/improvised
+ reqs = list(/obj/item/weaponcrafting/receiver = 1,
+ /obj/item/pipe = 1,
+ /obj/item/weaponcrafting/stock = 1,
+ /obj/item/stack/packageWrap = 5)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 100
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/chainsaw
+ name = "Chainsaw"
+ result = /obj/item/chainsaw
+ reqs = list(/obj/item/circular_saw = 1,
+ /obj/item/stack/cable_coil = 3,
+ /obj/item/stack/sheet/plasteel = 5)
+ tools = list(TOOL_WELDER)
+ time = 50
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/spear
+ name = "Spear"
+ result = /obj/item/spear
+ reqs = list(/obj/item/restraints/handcuffs/cable = 1,
+ /obj/item/shard = 1,
+ /obj/item/stack/rods = 1)
+ parts = list(/obj/item/shard = 1)
+ time = 40
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/chemical_payload
+ name = "Chemical Payload (C4)"
+ result = /obj/item/bombcore/chemical
+ reqs = list(
+ /obj/item/stock_parts/matter_bin = 1,
+ /obj/item/grenade/c4 = 1,
+ /obj/item/grenade/chem_grenade = 2
+ )
+ parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2)
+ time = 30
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/chemical_payload2
+ name = "Chemical Payload (Gibtonite)"
+ result = /obj/item/bombcore/chemical
+ reqs = list(
+ /obj/item/stock_parts/matter_bin = 1,
+ /obj/item/gibtonite = 1,
+ /obj/item/grenade/chem_grenade = 2
+ )
+ parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2)
+ time = 50
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/pipebow
+ name = "Pipe Bow"
+ result = /obj/item/gun/ballistic/bow/pipe
+ reqs = list(/obj/item/pipe = 5,
+ /obj/item/stack/sheet/plastic = 15,
+ /obj/item/weaponcrafting/silkstring = 10)
+ time = 450
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/datum/crafting_recipe/arrow
+ name = "Arrow"
+ result = /obj/item/ammo_casing/caseless/arrow/wood
+ time = 30
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/stack/sheet/silk = 1,
+ /obj/item/stack/rods = 1) //1 metal sheet = 2 rods= 2 arrows
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/bone_arrow
+ name = "Bone Arrow"
+ result = /obj/item/ammo_casing/caseless/arrow/bone
+ time = 30
+ reqs = list(/obj/item/stack/sheet/bone = 1,
+ /obj/item/stack/sheet/sinew = 1,
+ /obj/item/ammo_casing/caseless/arrow/ash = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/ashen_arrow
+ name = "Fire hardened arrow"
+ result = /obj/item/ammo_casing/caseless/arrow/ash
+ tools = list(TOOL_WELDER)
+ time = 30
+ reqs = list(/obj/item/ammo_casing/caseless/arrow/wood = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/bronze_arrow
+ name = "Bronze arrow"
+ result = /obj/item/ammo_casing/caseless/arrow/bronze
+ time = 30
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/silk = 1)
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO
+
+/datum/crafting_recipe/zip_pistol
+ name = "Zip Pistol"
+ result = /obj/item/gun/ballistic/automatic/zip_pistol
+ reqs = list(/obj/item/stack/rods = 4,
+ /obj/item/pipe = 1,
+ /obj/item/stack/cable_coil = 15,
+ /obj/item/weaponcrafting/receiver = 1,
+ /obj/item/floor_painter = 1,
+ /obj/item/stack/packageWrap = 10)
+ tools = list(TOOL_SCREWDRIVER)
+ time = 100
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
diff --git a/code/datums/components/movable_physics.dm b/code/datums/components/movable_physics.dm
new file mode 100644
index 00000000000..114cac29f24
--- /dev/null
+++ b/code/datums/components/movable_physics.dm
@@ -0,0 +1,151 @@
+#define PHYSICS_GRAV_STANDARD 9.80665
+
+///Remove the component as soon as there's zero velocity, useful for movables that will no longer move after being initially moved (blood splatters)
+#define QDEL_WHEN_NO_MOVEMENT (1<<0)
+
+///Stores information related to the movable's physics and keeping track of relevant signals to trigger movement
+/datum/component/movable_physics
+ ///Modifies the pixel_x/pixel_y of an object every process()
+ var/horizontal_velocity
+ ///Modifies the pixel_z of an object every process(), movables aren't Move()'d into another turf if pixel_z exceeds 16, so try not to supply a super high vertical value if you don't want the movable to clip through multiple turfs
+ var/vertical_velocity
+ ///The horizontal_velocity is reduced by this every process(), this doesn't take into account the object being in the air vs gravity pushing it against the ground
+ var/horizontal_friction
+ ///The vertical_velocity is reduced by this every process()
+ var/z_gravity
+ ///The pixel_z that the object will no longer be influenced by gravity for a 32x32 turf, keep this value between -16 to 0 so it's visuals matches up with it physically being in the turf
+ var/z_floor
+ ///The angle of the path the object takes on the x/y plane
+ var/angle_of_movement
+ ///Flags for turning on certain physic properties, see the top of the file for more information on flags
+ var/physic_flags
+ ///The cached animate_movement of the parent; any kind of gliding when doing Move() makes the physics look derpy, so we'll just make Move() be instant
+ var/cached_animate_movement
+ ///The sound effect to play when bouncing off of something
+ var/bounce_sound
+
+ var/numbounce = 1
+
+/datum/component/movable_physics/Initialize(_horizontal_velocity = 0, _vertical_velocity = 0, _horizontal_friction = 0, _z_gravity = 0, _z_floor = 0, _angle_of_movement = 0, _physic_flags = 0, _bounce_sound)
+ . = ..()
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, PROC_REF(throw_impact_ricochet), override = TRUE)
+ horizontal_velocity = _horizontal_velocity
+ vertical_velocity = _vertical_velocity
+ horizontal_friction = _horizontal_friction
+ z_gravity = _z_gravity
+ z_floor = _z_floor
+ angle_of_movement = _angle_of_movement
+ physic_flags = _physic_flags
+ bounce_sound = _bounce_sound
+ if(vertical_velocity || horizontal_velocity)
+ start_movement()
+
+///Let's get moving
+/datum/component/movable_physics/proc/start_movement()
+ var/atom/movable/moving_atom = parent
+ cached_animate_movement = moving_atom.animate_movement
+ moving_atom.animate_movement = NO_STEPS
+ START_PROCESSING(SSmovablephysics, src)
+ moving_atom.SpinAnimation(speed = 1 SECONDS, loops = 1)
+
+///Alright it's time to stop
+/datum/component/movable_physics/proc/stop_movement()
+ var/atom/movable/moving_atom = parent
+ moving_atom.animate_movement = cached_animate_movement
+ STOP_PROCESSING(SSmovablephysics, src)
+ if(physic_flags & QDEL_WHEN_NO_MOVEMENT)
+ qdel(src)
+
+/datum/component/movable_physics/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_MOVABLE_IMPACT)
+
+/datum/component/movable_physics/proc/throw_impact_ricochet(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+ var/atom/movable/atom_source = source
+ ricochet(atom_source, Get_Angle(atom_source, throwingdatum.target_turf))
+
+/datum/component/movable_physics/proc/z_floor_bounce(atom/movable/moving_atom)
+ angle_of_movement += rand(-3000, 3000) / 100
+ var/turf/a_turf = get_turf(moving_atom)
+ if(istype(moving_atom, /obj/item/ammo_casing) && !bounce_sound)
+ playsound(moving_atom, a_turf.bullet_bounce_sound, 50, TRUE)
+ else
+ playsound(moving_atom, bounce_sound, 50, TRUE)
+ moving_atom.SpinAnimation(speed = 1 SECONDS / numbounce, loops = 1)
+ moving_atom.pixel_z = z_floor
+ horizontal_velocity = max(0, horizontal_velocity + (vertical_velocity * -0.8))
+ vertical_velocity = max(0, ((vertical_velocity * -0.8) - 0.2))
+ numbounce += 0.5
+
+/datum/component/movable_physics/proc/ricochet(atom/movable/moving_atom, bounce_angle)
+ angle_of_movement = ((180 - bounce_angle) - angle_of_movement)
+ if(angle_of_movement < 0)
+ angle_of_movement += 360
+ //var/turf/a_turf = get_turf(moving_atom)
+ //playsound(src, a_turf.bullet_bounce_sound, 50, TRUE)
+
+/datum/component/movable_physics/proc/fix_angle(angle, atom/moving_atom)//fixes an angle below 0 or above 360
+ if(!(angle_of_movement > 360) && !(angle_of_movement < 0))
+ return angle //early return if it doesn't need to change
+ var/new_angle
+ if(angle_of_movement > 360)
+ new_angle = angle_of_movement - 360
+ if(angle_of_movement < 0)
+ new_angle = angle_of_movement + 360
+ return new_angle
+
+/datum/component/movable_physics/process(delta_time)
+ var/atom/movable/moving_atom = parent
+ var/turf/location = get_turf(moving_atom)
+
+ angle_of_movement = fix_angle(angle_of_movement, moving_atom)
+ if(horizontal_velocity <= 0 && moving_atom.pixel_z == 0)
+ horizontal_velocity = 0
+ stop_movement()
+ return
+
+ moving_atom.pixel_x += (horizontal_velocity * (sin(angle_of_movement)))
+ moving_atom.pixel_y += (horizontal_velocity * (cos(angle_of_movement)))
+
+ horizontal_velocity = max(0, horizontal_velocity - horizontal_friction)
+
+ moving_atom.pixel_z = max(z_floor, moving_atom.pixel_z + vertical_velocity)
+ if(moving_atom.pixel_z > z_floor)
+ vertical_velocity -= (z_gravity * 0.05)
+
+ if(moving_atom.pixel_z <= z_floor && (vertical_velocity != 0) && moving_atom.has_gravity(location)) //z bounce
+ z_floor_bounce(moving_atom)
+
+ if(moving_atom.pixel_x > 16)
+ if(moving_atom.Move(get_step(moving_atom, EAST)))
+ moving_atom.pixel_x = -16
+ else
+ moving_atom.pixel_x = 16
+ ricochet(moving_atom, 0)
+ return
+
+ if(moving_atom.pixel_x < -16)
+ if(moving_atom.Move(get_step(moving_atom, WEST)))
+ moving_atom.pixel_x = 16
+ else
+ moving_atom.pixel_x = -16
+ ricochet(moving_atom, 0)
+ return
+
+ if(moving_atom.pixel_y > 16)
+ if(moving_atom.Move(get_step(moving_atom, NORTH)))
+ moving_atom.pixel_y = -16
+ else
+ moving_atom.pixel_y = 16
+ ricochet(moving_atom, 180)
+ return
+
+ if(moving_atom.pixel_y < -16)
+ if(moving_atom.Move(get_step(moving_atom, SOUTH)))
+ moving_atom.pixel_y = 16
+ else
+ moving_atom.pixel_y = -16
+ ricochet(moving_atom, 180)
+
diff --git a/code/datums/components/storage/ui.dm b/code/datums/components/storage/ui.dm
new file mode 100644
index 00000000000..e3e4c126d73
--- /dev/null
+++ b/code/datums/components/storage/ui.dm
@@ -0,0 +1,253 @@
+// Generates a list of numbered_display datums for the numerical display system.
+/datum/component/storage/proc/_process_numerical_display()
+ . = list()
+ for(var/obj/item/I in accessible_items())
+ if(QDELETED(I))
+ continue
+ if(!.[I.type])
+ .[I.type] = new /datum/numbered_display(I, 1, src)
+ else
+ var/datum/numbered_display/ND = .[I.type]
+ ND.number++
+
+// Orients all objects in legacy mode, and returns the objects to show to the user.
+/datum/component/storage/proc/orient2hud_legacy(mob/user, maxcolumns)
+ . = list()
+ var/list/accessible_contents = accessible_items()
+ var/adjusted_contents = length(accessible_contents)
+ var/atom/movable/screen/storage/close/ui_close
+ var/atom/movable/screen/storage/boxes/ui_boxes
+
+ //Numbered contents display
+ var/list/datum/numbered_display/numbered_contents
+ if(display_numerical_stacking)
+ numbered_contents = _process_numerical_display()
+ adjusted_contents = numbered_contents.len
+
+ var/columns = limited_random_access_stack_position == 0 ? clamp(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns) : clamp(limited_random_access_stack_position, 1, maxcolumns ? maxcolumns : screen_max_columns)
+ var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
+
+ // First, boxes.
+ ui_boxes = get_ui_boxes()
+ ui_boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+columns-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_boxes
+ // Then, closer.
+ ui_close = get_ui_close()
+ ui_close.screen_loc = "[screen_start_x + columns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
+ . += ui_close
+ // Then orient the actual items.
+ var/cx = screen_start_x
+ var/cy = screen_start_y
+ if(islist(numbered_contents))
+ for(var/type in numbered_contents)
+ var/datum/numbered_display/ND = numbered_contents[type]
+ ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
+ ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
+ ND.sample_object.layer = ABOVE_HUD_LAYER
+ ND.sample_object.plane = ABOVE_HUD_PLANE
+ . += ND.sample_object
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+ else
+ for(var/obj/O in accessible_items())
+ if(QDELETED(O))
+ continue
+ var/atom/movable/screen/storage/item_holder/D = new(null, src, O)
+ D.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
+ D.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ O.maptext = ""
+ O.layer = ABOVE_HUD_LAYER
+ O.plane = ABOVE_HUD_PLANE
+ . += D
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+
+// Orients all objects in .. volumetric mode. Does not support numerical display!
+/datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns)
+ . = list()
+ var/atom/movable/screen/storage/left/ui_left
+ var/atom/movable/screen/storage/continuous/ui_continuous
+ var/atom/movable/screen/storage/close/ui_close
+
+ // Generate ui_item_blocks for missing ones and render+orient.
+ var/list/atom/contents = accessible_items()
+ // our volume
+ var/our_volume = get_max_volume()
+ var/horizontal_pixels = (maxcolumns * world.icon_size) - (VOLUMETRIC_STORAGE_EDGE_PADDING * 2)
+ var/max_horizontal_pixels = horizontal_pixels * screen_max_rows
+ // sigh loopmania time
+ var/used = 0
+ // define outside for performance
+ var/volume
+ var/list/volume_by_item = list()
+ var/list/percentage_by_item = list()
+ for(var/obj/item/I in contents)
+ if(QDELETED(I))
+ continue
+ volume = I.get_w_volume()
+ used += volume
+ volume_by_item[I] = volume
+ percentage_by_item[I] = volume / get_max_volume()
+ var/padding_pixels = ((length(percentage_by_item) - 1) * VOLUMETRIC_STORAGE_ITEM_PADDING) + VOLUMETRIC_STORAGE_EDGE_PADDING * 2
+ var/min_pixels = (MINIMUM_PIXELS_PER_ITEM * length(percentage_by_item)) + padding_pixels
+ // do the check for fallback for when someone has too much gamer gear
+ if((min_pixels) > (max_horizontal_pixels + 4)) // 4 pixel grace zone
+ to_chat(user, "[parent] was showed to you in legacy mode due to your items overrunning the three row limit! Consider not carrying too much or bugging a maintainer to raise this limit!")
+ return orient2hud_legacy(user, maxcolumns)
+ // after this point we are sure we can somehow fit all items into our max number of rows.
+
+ // determine rows
+ var/rows = clamp(CEILING(min_pixels / horizontal_pixels, 1), 1, screen_max_rows)
+
+ var/overrun = FALSE
+ if(used > our_volume)
+ // congratulations we are now in overrun mode. everything will be crammed to minimum storage pixels.
+ to_chat(user, "[parent] rendered in overrun mode due to more items inside than the maximum volume supports.")
+ overrun = TRUE
+
+ // how much we are using
+ var/using_horizontal_pixels = horizontal_pixels * rows
+
+ // item padding
+ using_horizontal_pixels -= padding_pixels
+
+ // define outside for marginal performance boost
+ var/obj/item/I
+ // start at this pixel from screen_start_x.
+ var/current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
+ var/first = TRUE
+ var/row = 1
+
+ for(var/i in percentage_by_item)
+ I = i
+ var/percent = percentage_by_item[I]
+ var/atom/movable/screen/storage/volumetric_box/center/B = new /atom/movable/screen/storage/volumetric_box/center(null, src, I)
+ // SNOWFLAKE: force it to icon until we unfuck storage/click passing
+ I.mouse_opacity = MOUSE_OPACITY_ICON
+ var/pixels_to_use = overrun? MINIMUM_PIXELS_PER_ITEM : max(using_horizontal_pixels * percent, MINIMUM_PIXELS_PER_ITEM)
+ var/addrow = FALSE
+ if(CEILING(pixels_to_use, 1) >= FLOOR(horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING, 1))
+ pixels_to_use = horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING
+ addrow = TRUE
+
+ // now that we have pixels_to_use, place our thing and add it to the returned list.
+ B.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
+ // add the used pixels to pixel after we place the object
+ current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING)
+ first = FALSE //apply padding to everything after this
+
+ // set various things
+ B.set_pixel_size(pixels_to_use)
+ B.name = I.name
+
+ // finally add our things.
+ . += B.on_screen_objects()
+
+ // go up a row if needed
+ if(addrow)
+ row++
+ first = TRUE //first in the row, don't apply between-item padding.
+ current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
+
+ // Then, continuous section.
+ ui_continuous = get_ui_continuous()
+ ui_continuous.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+maxcolumns-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_continuous
+ // Then, left.
+ ui_left = get_ui_left()
+ ui_left.screen_loc = "[screen_start_x]:[screen_pixel_x - 2],[screen_start_y]:[screen_pixel_y] to [screen_start_x]:[screen_pixel_x - 2],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_left
+ // Then, closer, which is also our right element.
+ ui_close = get_ui_close()
+ ui_close.screen_loc = "[screen_start_x + maxcolumns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x + maxcolumns]:[screen_pixel_x],[screen_start_y + row - 1]:[screen_pixel_y]"
+ . += ui_close
+
+
+// Shows our UI to a mob.
+/datum/component/storage/proc/ui_show(mob/M)
+ if(!M.client)
+ return FALSE
+ if(ui_by_mob[M] || LAZYFIND(is_using, M))
+ // something went horribly wrong
+ // hide first
+ ui_hide(M)
+ var/list/cview = getviewsize(M.client.view)
+ // in tiles
+ var/maxallowedscreensize = cview[1]-8
+ // we got screen size, register signal
+ RegisterSignal(M, COMSIG_PARENT_QDELETING, PROC_REF(on_logout), override = TRUE)
+ if(M.active_storage != src)
+ if(M.active_storage)
+ M.active_storage.ui_hide(M)
+ M.active_storage = src
+ LAZYOR(is_using, M)
+ if(volumetric_ui())
+ //new volumetric ui bay-style
+ var/list/objects = orient2hud_volumetric(M, maxallowedscreensize)
+ M.client.screen |= objects
+ ui_by_mob[M] = objects
+ else
+ //old ui
+ var/list/objects = orient2hud_legacy(M, maxallowedscreensize)
+ M.client.screen |= objects
+ ui_by_mob[M] = objects
+ return TRUE
+
+// VV hooked to ensure no lingering screen objects.
+/datum/component/storage/vv_edit_var(var_name, var_value)
+ var/list/old
+ if(var_name == NAMEOF(src, storage_flags))
+ old = is_using.Copy()
+ for(var/i in is_using)
+ ui_hide(i)
+ . = ..()
+ if(old)
+ for(var/i in old)
+ ui_show(i)
+
+// Proc triggered by signal to ensure logging out clients don't linger.
+/datum/component/storage/proc/on_logout(datum/source, client/C)
+ ui_hide(source)
+
+// Hides our UI from a mob
+/datum/component/storage/proc/ui_hide(mob/M)
+ if(!M.client)
+ return TRUE
+ UnregisterSignal(M, list(COMSIG_PARENT_QDELETING))
+ M.client.screen -= ui_by_mob[M]
+ var/list/objects = ui_by_mob[M]
+ QDEL_LIST(objects)
+ if(M.active_storage == src)
+ M.active_storage = null
+ LAZYREMOVE(is_using, M)
+ return TRUE
+
+// Returns TRUE if we are using volumetric UI instead of box UI
+/datum/component/storage/proc/volumetric_ui()
+ var/atom/real_location = real_location()
+ return (storage_flags & STORAGE_LIMIT_VOLUME) && (length(real_location.contents) <= MAXIMUM_VOLUMETRIC_ITEMS) && !display_numerical_stacking
+
+// Gets our ui_boxes, making it if it doesn't exist.
+/datum/component/storage/proc/get_ui_boxes()
+ return new /atom/movable/screen/storage/boxes(null, src)
+
+// Gets our ui_left, making it if it doesn't exist.
+/datum/component/storage/proc/get_ui_left()
+ return new /atom/movable/screen/storage/left(null, src)
+
+// Gets our ui_close, making it if it doesn't exist.
+/datum/component/storage/proc/get_ui_close()
+ return new /atom/movable/screen/storage/close(null, src)
+
+// Gets our ui_continuous, making it if it doesn't exist.
+/datum/component/storage/proc/get_ui_continuous()
+ return new /atom/movable/screen/storage/continuous(null, src)
diff --git a/code/datums/elements/world_icon.dm b/code/datums/elements/world_icon.dm
new file mode 100644
index 00000000000..bcb0129c6c6
--- /dev/null
+++ b/code/datums/elements/world_icon.dm
@@ -0,0 +1,121 @@
+/////////////////////////////////////////////////////////////
+////////// WORLD ICON ELEMENT DIRECTORY //////////
+/////////////////////////////////////////////////////////////
+//PORTED FROM MOJAVE SUN//
+
+// Slap onto something to give it a world icon that differs from the inventory one (allows for realistically sized objects and all that) //
+// To fix 25/06/2021 : Blood Decals, Mutable Overlays and other baked in bitch ass overlays that need to be remade when the icon changes //
+// Fixed 07/05/2022: Now you can deal with the above by handling everything with attached_proc instead
+// Fixed 12/04/2023: Icon states, Needs major tuning up by someone who can properly make it work
+
+/datum/element/world_icon
+ id_arg_index = 2
+ element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
+ //If we want COMPLEX world icon behavior, this proc will handle icon updating when the item is NOT in the inventory.
+ //I just assumed that the default update_icon is for inventory sprites because ss13 basically focuses on how the sprites
+ //look on your hand, not how they realistically look in the world.
+ var/attached_proc
+ /// Only used if attached_proc doesn't exist, simply changes the icon of target to this when it's in the inventory
+ var/inventory_icon
+ /// Only used if attached_proc doesn't exist, simply changes the icon of target to this when it's NOT in the inventory
+ var/world_icon
+ /// Only used when inventory state icon is different from original
+ var/inventory_icon_state
+ /// Only used when world state icon is different from original, pretty much just the original "icon_state" but if you for some reason need to flip the standard icon states for this element around you can use this
+ var/world_icon_state
+
+/datum/element/world_icon/Attach(obj/item/target, attached_proc, world_icon, inventory_icon, world_icon_state, inventory_icon_state)
+ . = ..()
+ if(!istype(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.attached_proc = attached_proc
+ src.world_icon = world_icon
+ src.world_icon_state = world_icon_state
+ src.inventory_icon = inventory_icon
+ src.inventory_icon_state = inventory_icon_state
+ RegisterSignal(target, COMSIG_ATOM_UPDATE_ICON, PROC_REF(update_icon))
+ RegisterSignal(target, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(update_icon_state))
+ RegisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_STORAGE_ENTERED, COMSIG_ITEM_DROPPED, COMSIG_STORAGE_EXITED), PROC_REF(inventory_updated))
+ target.update_appearance(UPDATE_ICON)
+ target.update_appearance(UPDATE_ICON_STATE)
+
+/datum/element/world_icon/Detach(obj/item/source)
+ . = ..()
+ UnregisterSignal(source, COMSIG_ATOM_UPDATE_ICON)
+ UnregisterSignal(source, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(update_icon_state))
+ UnregisterSignal(source, list(COMSIG_ITEM_EQUIPPED, COMSIG_STORAGE_ENTERED, COMSIG_ITEM_DROPPED, COMSIG_STORAGE_EXITED))
+ source.update_appearance(UPDATE_ICON)
+ source.update_appearance(UPDATE_ICON_STATE)
+
+/datum/element/world_icon/proc/update_icon(obj/item/source, updates)
+ SIGNAL_HANDLER
+
+ if((source.item_flags & IN_INVENTORY) || (source.loc && SEND_SIGNAL(source.loc, COMSIG_CONTAINS_STORAGE)))
+ if(attached_proc)
+ return
+ return default_inventory_icon(source)
+
+ if(attached_proc)
+ return call(source, attached_proc)(updates)
+ else
+ return default_world_icon(source)
+
+/datum/element/world_icon/proc/update_icon_state(obj/item/source, updates)
+ SIGNAL_HANDLER
+
+ if((source.item_flags & IN_INVENTORY) || (source.loc && SEND_SIGNAL(source.loc, COMSIG_CONTAINS_STORAGE)))
+ if(attached_proc)
+ return
+ return default_inventory_icon_state(source)
+
+ if(attached_proc)
+ return call(source, attached_proc)(updates)
+ else
+ return default_world_icon_state(source)
+
+/datum/element/world_icon/proc/inventory_updated(obj/item/source)
+ SIGNAL_HANDLER
+
+ source.update_appearance(UPDATE_ICON)
+ source.update_appearance(UPDATE_ICON_STATE)
+
+/datum/element/world_icon/proc/default_inventory_icon(obj/item/source)
+ SIGNAL_HANDLER
+
+ source.icon = inventory_icon
+
+/datum/element/world_icon/proc/default_world_icon(obj/item/source)
+ SIGNAL_HANDLER
+
+ source.icon = world_icon
+
+/datum/element/world_icon/proc/default_inventory_icon_state(obj/item/source)
+ SIGNAL_HANDLER
+
+ if(!inventory_icon_state)
+ source.icon_state = source.icon_state
+ return
+
+ INVOKE_ASYNC(src, PROC_REF(check_inventory_state), source)
+
+/datum/element/world_icon/proc/default_world_icon_state(obj/item/source)
+ SIGNAL_HANDLER
+
+ if(!world_icon_state)
+ source.icon_state = source.icon_state
+ return
+
+ INVOKE_ASYNC(src, PROC_REF(check_world_icon_state), source)
+
+/datum/element/world_icon/proc/check_inventory_state(obj/item/source)
+ SIGNAL_HANDLER
+
+ inventory_icon_state = source.inventory_state
+ source.icon_state = inventory_icon_state
+
+/datum/element/world_icon/proc/check_world_icon_state(obj/item/source)
+ SIGNAL_HANDLER
+
+ world_icon_state = source.world_state
+ source.icon_state = world_icon_state
diff --git a/code/modules/autowiki/pages/reactions.dm b/code/modules/autowiki/pages/reactions.dm
new file mode 100644
index 00000000000..2e1a07b806e
--- /dev/null
+++ b/code/modules/autowiki/pages/reactions.dm
@@ -0,0 +1,65 @@
+/*
+Templates:
+
+Autowiki/Reaction
+{{{chems|ERROR}}} {{#if: {{{temperature|}}} |
Temperature {{{temperature}}} | }} {{#if: {{{container|}}} |
Needs container "{{{container}}}" | }}
Makes {{{volume|1}}}u
+
+Autowiki/Reagent
+{{#if: {{{tooltip|}}} | {{Tooltip|{{{volume}}} part [[#{{{name}}}|{{{name}}}]]|{{{tooltip}}}|FEF6E7}} | {{{volume}}} part {{{name}}} }}
+
+*/
+
+/datum/autowiki/reactions
+ page = "Template:Autowiki/Content/Reactions"
+
+/datum/autowiki/reactions/generate()
+ var/list/output = list()
+
+ var/list/mixable_reagents = list()
+ var/list/all_reactions = list()
+ for(var/type in subtypesof(/datum/chemical_reaction))
+ var/datum/chemical_reaction/reaction = new type
+ all_reactions += reaction
+ mixable_reagents |= reaction.results
+
+ for(var/datum/chemical_reaction/reaction as anything in all_reactions)
+ var/required_chems = ""
+ for(var/datum/reagent/required_chem_type as anything in reaction.required_reagents)
+ var/has_tooltip = (required_chem_type in mixable_reagents) && !(required_chem_type in reaction.results) && !(required_chem_type in GLOB.base_reagents)
+ required_chems += format_required_reagent(required_chem_type, reaction.required_reagents[required_chem_type], has_tooltip)
+
+ for(var/datum/reagent/required_catalyst_type as anything in reaction.required_catalysts)
+ var/has_tooltip = (required_catalyst_type in mixable_reagents) && !(required_catalyst_type in reaction.results) && !(required_catalyst_type in GLOB.base_reagents)
+ required_chems += format_required_reagent(required_catalyst_type, reaction.required_catalysts[required_catalyst_type], has_tooltip, "Catalyst")
+
+ for(var/datum/reagent/result_chem_type as anything in reaction.results)
+ var/result_name = escape_value(initial(result_chem_type.name))
+ var/list/details = list("volume" = reaction.results[result_chem_type], "chems" = required_chems, "name" = result_name)
+
+ if(reaction.required_temp > 0)
+ details["temperature"] = "[reaction.is_cold_recipe ? "below" : "above"] [reaction.required_temp]K"
+
+ if(reaction.required_container)
+ details["container"] = "[escape_value(initial(reaction.required_container.name))]"
+
+ var/description = include_template("Autowiki/Reaction", details)
+ if(result_name in output)
+ output[result_name] += "
OR
[description]"
+ else
+ output[result_name] = description
+
+ return output
+
+/datum/autowiki/reactions/proc/format_required_reagent(datum/reagent/required_reagent_type, volume, has_tooltip = FALSE, type)
+ var/list/details = list(
+ "volume" = volume,
+ "name" = escape_value(initial(required_reagent_type.name))
+ )
+
+ if(has_tooltip)
+ details["tooltip"] = include_template("Autowiki/Content/Reactions/[initial(required_reagent_type.name)]")
+
+ if(type)
+ details["type"] = type
+
+ return include_template("Autowiki/Reagent", details)
diff --git a/code/modules/cargo/blackmarket/blackmarket_items/emergency.dm b/code/modules/cargo/blackmarket/blackmarket_items/emergency.dm
new file mode 100644
index 00000000000..b609da87945
--- /dev/null
+++ b/code/modules/cargo/blackmarket/blackmarket_items/emergency.dm
@@ -0,0 +1,52 @@
+/datum/blackmarket_item/emergency
+ category = "Emergency"
+
+/datum/blackmarket_item/emergency/plasma
+ name = "Ten Plasma Sheets"
+ desc = "Low on fuel? We can part with some plasma... for a reasonable price."
+ item = /obj/item/stack/sheet/mineral/plasma/ten
+
+ price_min = 1750
+ price_max = 2250
+ availability_prob = 100
+ unlimited = TRUE
+
+/datum/blackmarket_item/emergency/uranium
+ name = "Ten Uranium Sheets"
+ desc = "Fuel? Dirty Bomb? Fancy nightlight? Doesn't matter, we'll supply."
+ item = /obj/item/stack/sheet/mineral/uranium/ten
+
+ price_min = 1750
+ price_max = 2250
+ availability_prob = 100
+ unlimited = TRUE
+
+/datum/blackmarket_item/emergency/ion_thruster
+ name = "Ion Thruster"
+ desc = "Need a boost? We have a leftover engine board or two from a ship we happened to find. If you're lucky, you won't be the next."
+ item = /obj/item/circuitboard/machine/shuttle/engine/electric
+
+ price_min = 2000
+ price_max = 3000
+ stock_max = 5
+ availability_prob = 100
+
+/datum/blackmarket_item/emergency/oyxgen
+ name = "Oxygen Canister"
+ desc = "What keeps us all breathing. It'll keep you breathing too, if you know what's good for you."
+ item = /obj/machinery/portable_atmospherics/canister/oxygen
+
+ price_min = 2000
+ price_max = 3000
+ stock_max = 3
+ availability_prob = 100
+
+/datum/blackmarket_item/emergency/metal_foam
+ name = "Metal Foam Grenade"
+ desc = "Poor piloting blow a hole in the side of your hull? These metal foam grenades should keep everything important in."
+ item = /obj/item/grenade/chem_grenade/metalfoam
+
+ price_min = 300
+ price_max = 750
+ availability_prob = 100
+ unlimited = TRUE
diff --git a/code/modules/cargo/blackmarket/blackmarket_items/explosives.dm b/code/modules/cargo/blackmarket/blackmarket_items/explosives.dm
new file mode 100644
index 00000000000..7fe78cdcd05
--- /dev/null
+++ b/code/modules/cargo/blackmarket/blackmarket_items/explosives.dm
@@ -0,0 +1,88 @@
+/datum/blackmarket_item/explosive
+ category = "Explosives"
+
+/datum/blackmarket_item/explosive/emp_grenade
+ name = "EMP Grenade"
+ desc = "Use this grenade for SHOCKING results!"
+ item = /obj/item/grenade/empgrenade
+
+ price_min = 100
+ price_max = 400
+ stock_max = 5
+ availability_prob = 50
+
+/datum/blackmarket_item/explosive/h_e
+ name = "HE Grenade"
+ desc = "These high explosive grenades are sure to get some bang for your buck."
+ item = /obj/item/grenade/syndieminibomb/concussion
+
+ price_min = 100
+ price_max = 500
+ stock_min = 2
+ stock_max = 5
+ availability_prob = 25
+
+/datum/blackmarket_item/explosive/frag
+ name = "Fragmentation Grenade"
+ desc = "Pull the pin, count to three, and throw for best results."
+ item = /obj/item/grenade/frag
+
+ price_min = 100
+ price_max = 500
+ stock_min = 3
+ stock_max = 5
+ availability_prob = 40
+
+/datum/blackmarket_item/explosive/c4
+ name = "C4"
+ desc = "Looking to make an explosive entrance? These plastic explosives are perfect for the job."
+ item = /obj/item/grenade/c4
+
+ price_min = 100
+ price_max = 400
+ stock_min = 5
+ stock_max = 10
+ availability_prob = 50
+
+/datum/blackmarket_item/explosive/x4
+ name = "X4"
+ desc = "X4 Plastic Explosives! Better than W4, worse than Y4."
+ item = /obj/item/grenade/c4/x4
+
+ price_min = 400
+ price_max = 700
+ stock_min = 2
+ stock_max = 4
+ availability_prob = 25
+
+/datum/blackmarket_item/explosive/slipocalypse
+ name = "Slipocalyse Cluster Bomb"
+ desc = "Wash away the opposition with sudstastic grenade!"
+ item = /obj/item/grenade/clusterbuster/soap
+
+ price_min = 500
+ price_max = 1500
+ stock = 1
+ availability_prob = 10
+
+/datum/blackmarket_item/explosive/rusted_mine
+ name = "Landmine"
+ desc = "Recovered from a decades old ICW battlefield by our best EOD tech, Nicky Nine Fingers."
+ item = /obj/item/mine/pressure/explosive/rusty
+
+ price_min = 250
+ price_max = 500
+ stock_max = 7
+ availability_prob = 50
+
+/datum/blackmarket_item/explosive/rpg
+ name = "PML-9 RPG"
+ desc = "Offically, it's an anti-armor RPG launcher. Technically, it's anti-everything. Most things don't enjoy being hit in the face with high explosives."
+ item = /obj/item/gun/ballistic/rocketlauncher
+
+ price_min = 3500
+ price_max = 6500
+ stock_min = 2
+ stock_max = 5
+ availability_prob = 20
+
diff --git a/code/modules/clothing/factions/ngr.dm b/code/modules/clothing/factions/ngr.dm
new file mode 100644
index 00000000000..7892a098b50
--- /dev/null
+++ b/code/modules/clothing/factions/ngr.dm
@@ -0,0 +1,244 @@
+//////////////
+//Jumpsuits//
+/////////////
+
+/obj/item/clothing/under/syndicate/ngr
+ name = "\improper NGR uniform"
+ desc = "A button-up in a tasteful beige with black pants, used as the basic uniform of the New Gorlex Republic."
+ icon_state = "ngr"
+ item_state = "ngr"
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ can_adjust = FALSE
+ icon = 'icons/obj/clothing/faction/ngr/uniforms.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/uniforms.dmi'
+
+/obj/item/clothing/under/syndicate/ngr/fatigues
+ name = "\improper NGR fatigues"
+ desc = "Beige fatigues used primarily by the ship and mech pilots of the New Gorlex Republic."
+ icon_state = "ngr_fatigues"
+ item_state = "ngr_fatigues"
+
+/obj/item/clothing/under/syndicate/ngr/jumpsuit
+ name = "\improper NGR jumpsuit"
+ desc = "A beige jumpsuit with black overalls used by wreckers of the New Gorlex Republic. A reminder of Gorlex VII's history as a mining colony, prior to its destruction."
+ icon_state = "ngr_jumpsuit"
+ item_state = "ngr_jumpsuit"
+
+/obj/item/clothing/under/syndicate/ngr/officer
+ name = "\improper NGR officer uniform"
+ desc = "A button-up in a tasteful black with beige pants, used by officers of the New Gorlex Republic."
+ icon_state = "ngr_officer"
+ item_state = "ngr_officer"
+
+/obj/item/clothing/under/plasmaman/ngr
+ name = "\improper NGR phorid envirosuit"
+ desc = "A button-up envirosuit with use intended for phorids of the New Gorlex Republic. Ensures they don't die of combustion."
+ icon_state = "ngr_envirosuit"
+ item_state = "ngr_envirosuit"
+ icon = 'icons/obj/clothing/faction/ngr/uniforms.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/uniforms.dmi'
+
+
+////////////////////
+//Unarmored suits//
+///////////////////
+
+/obj/item/clothing/suit/ngr
+ name = "foreman's jacket"
+ desc = "A beige high-visibility jacket worn by the Foreman of the New Gorlex Republic."
+ icon = 'icons/obj/clothing/faction/ngr/suits.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/suits.dmi'
+ icon_state = "ngr_foreman"
+ item_state = "blackcloth"
+
+/obj/item/clothing/suit/ngr/smock
+ name = "blood red smock"
+ desc = "A blood-red surgical smock typically worn by field medics of the New Gorlex Republic. It hides red blood really well!"
+ icon_state = "ngr_apron"
+ item_state = "redcloth"
+
+/obj/item/clothing/suit/hazardvest/ngr
+ name = "blood-red hazard vest"
+ desc = "A blood-red high-visibility vest typically used in work zones by the New Gorlex Republic."
+ icon = 'icons/obj/clothing/faction/ngr/suits.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/suits.dmi'
+ icon_state = "ngr_hazard"
+ item_state = "redcloth"
+
+//////////////////
+//Armored suits//
+/////////////////
+
+/obj/item/clothing/suit/armor/ngr
+ name = "NGR armor vest"
+ desc = "A slim Type I armored vest, utilized by the 2nd Battlegroup of the New Gorlex Republic that provides decent protection against most types of damage."
+ icon_state = "ngr_vest"
+ item_state = "armor"
+ icon = 'icons/obj/clothing/faction/ngr/suits.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/suits.dmi'
+ blood_overlay_type = "armor"
+
+/obj/item/clothing/suit/armor/ngr/lieutenant
+ name = "\improper 2nd Battlegroup overcoat"
+ desc = "An armored overcoat worn by the lieutenants of the New Gorlex Republic's 2nd Battlegroup."
+ body_parts_covered = CHEST|GROIN|ARMS
+ icon_state = "ngr_lieutenant"
+ item_state = "ngr_lieutenant"
+ blood_overlay_type = "coat"
+ armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+
+/obj/item/clothing/suit/armor/ngr/captain
+ name = "\improper 2nd Battlegroup coat"
+ desc = "An armored coat worn by captains the New Gorlex Republic's 2nd Battlegroup."
+ body_parts_covered = CHEST|GROIN|ARMS
+ icon_state = "ngr_captain"
+ item_state = "ngr_captain"
+ blood_overlay_type = "coat"
+ armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+
+///////////////
+//Spacesuits//
+//////////////
+
+/obj/item/clothing/head/helmet/space/hardsuit/syndi/ngr
+ name = "beige-red hardsuit helmet"
+ desc = "A standardized dual-mode helmet derived from ICW-era advanced special operations helmets, its red partly replaced by beige. It is in EVA mode. Manufactured by Second Battlegroup."
+ alt_desc = "A standardized dual-mode helmet derived from ICW-era advanced special operations helmets, its red partly replaced by beige. It is in combat mode. Manufactured by Second Battlegroup."
+ icon_state = "hardsuit1-ngr"
+ item_state = "hardsuit1-ngr"
+ icon = 'icons/obj/clothing/faction/ngr/head.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/head.dmi'
+ hardsuit_type = "ngr"
+
+/obj/item/clothing/suit/space/hardsuit/syndi/ngr
+ name = "beige-red hardsuit"
+ desc = "A standardized dual-mode hardsuit derived from ICW-era advanced special operations hardsuits, its red partly replaced by beige. It is in EVA mode. Manufactured by Second Battlegroup."
+ alt_desc = "A standardized dual-mode hardsuit derived from ICW-era advanced special operations hardsuits, its red partly replaced by beige. It is in combat mode. Manufactured by the Second Battlegroup."
+ icon_state = "hardsuit1-ngr"
+ item_state = "hardsuit1-ngr"
+ hardsuit_type = "ngr"
+ icon = 'icons/obj/clothing/faction/ngr/suits.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/suits.dmi'
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/ngr
+ lightweight = 1
+ jetpack = null
+
+/obj/item/clothing/head/helmet/space/plasmaman/ngr
+ name = "NGR phorid envirosuit helmet"
+ desc = "An envirohelmet designed for phorids of the New Gorlex Republic, with intimidating blood-red stripes."
+ icon_state = "ngr_envirohelm"
+ item_state = "ngr_envirohelm"
+ icon = 'icons/obj/clothing/faction/ngr/head.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/head.dmi'
+
+/////////
+//Hats//
+////////
+
+/obj/item/clothing/head/ngr
+ name = "beige garrison cap"
+ desc = "A garrison cap used by low-ranking members of the New Gorlex Republic's 2nd Battlegroup when off-duty."
+ icon_state = "ngr_garrison"
+ icon = 'icons/obj/clothing/faction/ngr/head.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/head.dmi'
+ armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+
+/obj/item/clothing/head/ngr/flap
+ name = "beige flap cap"
+ desc = "A flap cap used by soldiers of the New Gorlex Republic's 2nd Battlegroup in desert environments."
+ icon_state = "ngr_flap"
+
+/obj/item/clothing/head/ngr/surgical
+ name = "blood-red surgical cap"
+ desc = "A surgical cap used by field medics of the New Gorlex Republic's 2nd Battlegroup."
+ icon_state = "ngr_surgery"
+
+/obj/item/clothing/head/hardhat/ngr
+ name = "blood-red hard hat"
+ desc = "A blood-red hardhat typically used by Wreckers and Ship Engineers of the New Gorlex Republic."
+ icon_state = "ngr_hardhat"
+ icon = 'icons/obj/clothing/faction/ngr/head.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/head.dmi'
+
+/obj/item/clothing/head/hardhat/ngr/foreman
+ name = "beige hard hat"
+ desc = "A beige hardhat used exclusively by the Foreman of the New Gorlex Republic."
+ icon_state = "ngr_foreman"
+
+/obj/item/clothing/head/ngr/peaked
+ name = "2nd Battlegroup peaked cap"
+ desc = "A cap worn by officers of the New Gorlex Republic's 2nd Battlegroup."
+ icon_state = "ngr_officer"
+ item_state = "ngr_officer"
+
+/obj/item/clothing/head/helmet/ngr
+ name = "\improper NGR X-11 helmet"
+ desc = "A well-armored helmet utilized by the New Gorlex Republic's 2nd Battlegroup, far better at protecting one's head than the softer caps."
+ icon = 'icons/obj/clothing/faction/ngr/head.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/head.dmi'
+ armor = list("melee" = 40, "bullet" = 60, "laser" = 35, "energy" = 35, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) // The guys who specialize in ballistics would probably have better bullet armor. Maybe.
+ icon_state = "ngr_x11"
+ item_state = "ngr_x11"
+
+/obj/item/clothing/head/helmet/ngr/swat
+ name = "\improper NGR pilot helmet"
+ desc = "A modified X-11 helmet utilized by the pilots of the New Gorlex Republic's 2nd Battlegroup. The attached visor helps protect against sudden flashes from explosions."
+ flash_protect = FLASH_PROTECTION_WELDER
+ icon_state = "ngr_pilot"
+ item_state = "ngr_pilot"
+
+////////////
+//Glasses//
+///////////
+
+/obj/item/clothing/glasses/hud/security/sunglasses/ngr
+ name = "NGR modified mesons"
+ desc = "A modified version of widely-used optical meson scanners, with a flash-proof tint and integrated security HUD. Unfortunately, the opaque visor disables the meson functionality."
+ icon_state = "ngr_goggles"
+ item_state = "ngr_goggles"
+ icon = 'icons/obj/clothing/faction/ngr/eyes.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/eyes.dmi'
+ glass_colour_type = /datum/client_colour/glass_colour/green
+
+//////////
+//Masks//
+/////////
+
+/obj/item/clothing/mask/gas/sechailer/balaclava/ngr
+ name = "NGR combat balaclava"
+ desc = "A surprisingly advanced balaclava. while it doesn't muffle your voice it has a miniature rebreather for internals. Comfy to boot! This version is commonly used by the soldiers of the New Gorlex Republic to protect against sandstorms."
+ icon_state = "ngr_balaclava"
+ item_state = "ngr_balaclava"
+ icon = 'icons/obj/clothing/faction/ngr/mask.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/mask.dmi'
+
+/obj/item/clothing/mask/gas/syndicate/ngr
+ name = "NGR face mask"
+ desc = "A face mask that covers the nose, mouth and neck of those who wear it. Favored by field medics over the balaclava due to lessened heat while wearing."
+ icon_state = "ngr_facemask"
+ item_state = "ngr_facemask"
+ icon = 'icons/obj/clothing/faction/ngr/mask.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/mask.dmi'
+
+//////////
+//Neck//
+/////////
+
+/obj/item/clothing/neck/shemagh/ngr
+ name = "shemagh"
+ desc = "An oversized shemagh, in a tacticool blood-red for use in the 2nd Battlegroup."
+ icon_state = "ngr_shemagh"
+ icon = 'icons/obj/clothing/faction/ngr/neck.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/neck.dmi'
+
+//////////
+//Belts//
+/////////
+
+/obj/item/storage/belt/security/webbing/ngr
+ name = "NGR webbing"
+ desc = "A set of tactical webbing for operators of the New Gorlex Republic, can hold security gear."
+ icon_state = "ngr_webbing"
+ item_state = "ngr_webbing"
+ icon = 'icons/obj/clothing/faction/ngr/belt.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/faction/ngr/belt.dmi'
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord_outfits.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord_outfits.dm
new file mode 100644
index 00000000000..0dca4c21dad
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord_outfits.dm
@@ -0,0 +1,479 @@
+/datum/outfit/generic/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ . = ..()
+ uniform = pickweight(list(
+ /obj/item/clothing/under/utility = 5,
+ /obj/item/clothing/under/utility/skirt = 5,
+ /obj/item/clothing/under/color/black = 1,
+ /obj/item/clothing/under/color/white = 1,
+ /obj/item/clothing/under/color/random = 1,
+ /obj/item/clothing/under/suit/white = 1,
+ /obj/item/clothing/under/suit/tan = 1,
+ /obj/item/clothing/under/suit/black_really = 1,
+ /obj/item/clothing/under/suit/navy = 1,
+ /obj/item/clothing/under/suit/burgundy = 1,
+ /obj/item/clothing/under/suit/charcoal = 1,
+ /obj/item/clothing/under/rank/civilian/lawyer/galaxy = 1,
+ /obj/item/clothing/under/suit/black/skirt = 1,
+ /obj/item/clothing/under/suit/black = 1,
+ /obj/item/clothing/under/dress/sailor = 1,
+ /obj/item/clothing/under/dress/striped = 1,
+ /obj/item/clothing/under/dress/skirt/blue = 1,
+ /obj/item/clothing/under/syndicate/tacticool = 1,
+ )
+ )
+ suit = pickweight(list(
+ /obj/item/clothing/suit/hooded/wintercoat = 1,
+ /obj/item/clothing/suit/jacket = 1,
+ /obj/item/clothing/suit/jacket/leather = 1,
+ /obj/item/clothing/suit/jacket/leather/overcoat = 1,
+ /obj/item/clothing/suit/jacket/leather/duster = 1,
+ /obj/item/clothing/suit/jacket/miljacket = 1,
+ /obj/item/clothing/suit/jacket/puffer = 1,
+ /obj/item/clothing/suit/gothcoat = 1,
+ /obj/item/clothing/suit/toggle/industrial = 1,
+ /obj/item/clothing/suit/toggle/hazard = 1,
+ )
+ )
+ back = pickweight(list(
+ /obj/item/storage/backpack = 1,
+ /obj/item/storage/backpack/satchel = 1,
+ /obj/item/storage/backpack/duffelbag = 1,
+ /obj/item/storage/backpack/messenger = 1,
+ /obj/item/storage/backpack/satchel/leather = 1
+ )
+ )
+ if (prob(10))
+ belt = pickweight(list(
+ /obj/item/gun/ballistic/automatic/pistol/candor = 2,
+ /obj/item/gun/ballistic/automatic/pistol/commander = 1,
+ /obj/item/gun/ballistic/automatic/pistol = 1,
+ /obj/item/gun/ballistic/revolver = 1,
+ /obj/item/gun/ballistic/revolver/firebrand = 1,
+ )
+ )
+ if(prob(50))
+ gloves = pickweight(list(
+ /obj/item/clothing/gloves/color/black = 1,
+ /obj/item/clothing/gloves/fingerless = 1,
+ /obj/item/clothing/gloves/color/white = 1,
+ )
+ )
+ shoes = pickweight(list(
+ /obj/item/clothing/shoes/laceup = 1,
+ /obj/item/clothing/shoes/sandal = 1,
+ /obj/item/clothing/shoes/winterboots = 1,
+ /obj/item/clothing/shoes/workboots/mining = 1,
+ /obj/item/clothing/shoes/workboots = 1,
+ /obj/item/clothing/shoes/sneakers/black = 1,
+ /obj/item/clothing/shoes/sneakers/brown = 1,
+ /obj/item/clothing/shoes/sneakers/white = 1
+ )
+ )
+ if(prob(50))
+ head = pickweight(list(
+ /obj/item/clothing/head/beret = 3,
+ /obj/item/clothing/head/beret/grey = 3,
+ /obj/item/clothing/head/flatcap = 3,
+ /obj/item/clothing/head/beanie = 3,
+ /obj/item/clothing/head/cowboy = 3,
+ /obj/item/clothing/head/trapper = 2,
+ /obj/item/clothing/head/hardhat = 2,
+ /obj/item/clothing/head/hardhat/orange = 2,
+ /obj/item/clothing/head/hardhat/dblue = 2,
+ /obj/item/clothing/head/pirate = 1,
+ /obj/item/clothing/head/foilhat = 1
+ )
+ )
+ if(prob(50))
+ mask = pickweight(list(
+ /obj/item/clothing/mask/balaclava = 1,
+ /obj/item/clothing/mask/bandana/red = 1,
+ /obj/item/clothing/mask/gas = 3,
+ /obj/item/clothing/mask/breath = 3,
+ )
+ )
+ if(prob(25))
+ neck = pickweight(list(
+ /obj/item/clothing/neck/scarf/red = 1,
+ /obj/item/clothing/neck/scarf/green = 1,
+ /obj/item/clothing/neck/scarf/darkblue = 1,
+ /obj/item/clothing/neck/shemagh = 1,
+ /obj/item/clothing/neck/stripedredscarf = 1,
+ /obj/item/clothing/neck/stripedgreenscarf = 1,
+ /obj/item/clothing/neck/stripedbluescarf = 1
+ )
+ )
+ ears = pick(/obj/item/radio/headset, /obj/item/radio/headset/alt)
+ if(prob(50))
+ glasses = pickweight(list(
+ /obj/item/clothing/glasses/regular = 1,
+ /obj/item/clothing/glasses/regular/circle = 1,
+ /obj/item/clothing/glasses/regular/jamjar = 1,
+ /obj/item/clothing/glasses/eyepatch = 1,
+ /obj/item/clothing/glasses/cheapsuns = 1,
+ /obj/item/clothing/glasses/regular/hipster = 1,
+ /obj/item/clothing/glasses/cold = 1,
+ /obj/item/clothing/glasses/heat = 1,
+ /obj/item/clothing/glasses/orange = 1,
+ )
+ )
+ if(prob(75))
+ r_pocket = /obj/item/tank/internals/emergency_oxygen
+ if(prob(75))
+ l_pocket = pick(/obj/item/radio, /obj/item/flashlight)
+ id = /obj/item/card/id
+ backpack_contents = list()
+ backpack_contents += pickweight(list(
+ /obj/item/dice/d20 = 1,
+ /obj/item/lipstick = 1,
+ /obj/item/clothing/mask/vape = 1,
+ /obj/item/clothing/mask/vape/cigar = 1,
+ /obj/item/reagent_containers/food/drinks/flask = 1,
+ /obj/item/lighter = 1,
+ /obj/item/toy/cards/deck = 1,
+ /obj/item/toy/eightball = 1,
+ /obj/item/storage/wallet = 1,
+ /obj/item/paicard = 1,
+ /obj/item/pen/fourcolor = 1,
+ /obj/item/paper_bin = 1,
+ /obj/item/cane = 1,
+ /obj/item/radio = 1,
+ /obj/item/dyespray = 1,
+ /obj/item/table_bell/brass = 1,
+ /obj/item/flashlight = 1,
+ /obj/item/crowbar/red = 1
+ )
+ )
+
+/datum/outfit/generic
+ name = "Generic (Legion)"
+ box = /obj/item/storage/box/survival
+ random = TRUE
+
+/datum/outfit/generic/miner/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(prob(75))
+ uniform = pickweight(list(
+ /obj/item/clothing/under/rank/cargo/miner/lavaland = 5,
+ /obj/item/clothing/under/rank/cargo/miner = 4,
+ /obj/item/clothing/under/rank/cargo/miner/lavaland/old = 1,
+ )
+ )
+ if(prob(25))
+ suit = pickweight(list(
+ /obj/item/clothing/suit/hooded/explorer = 18,
+ /obj/item/clothing/suit/hooded/explorer/old = 1,
+ /obj/item/clothing/suit/hooded/cloak/goliath = 1
+ )
+ )
+ if(prob(75))
+ back = /obj/item/storage/backpack/explorer
+ if(prob(75))
+ belt = pickweight(list(
+ /obj/item/storage/belt/mining = 2,
+ /obj/item/storage/belt/mining/alt = 2
+ )
+ )
+ else if(prob(75))
+ belt = pickweight(list(
+ /obj/item/pickaxe = 16,
+ /obj/item/pickaxe/mini = 8,
+ /obj/item/pickaxe/silver = 4,
+ /obj/item/pickaxe/diamond = 2,
+ /obj/item/gun/energy/kinetic_accelerator = 2,
+ /obj/item/kinetic_crusher/old = 1
+ )
+ )
+ if(prob(75))
+ gloves = pickweight(list(
+ /obj/item/clothing/gloves/color/black = 9,
+ /obj/item/clothing/gloves/explorer/old = 1
+ )
+ )
+ if(prob(75))
+ shoes = /obj/item/clothing/shoes/workboots/mining
+ if(prob(75))
+ mask = pickweight(list(
+ /obj/item/clothing/mask/gas/explorer = 9,
+ /obj/item/clothing/mask/gas/explorer/old = 1
+ )
+ )
+ if(prob(50))
+ glasses = /obj/item/clothing/glasses/meson
+ if(prob(50))
+ r_pocket = pickweight(list(
+ /obj/item/stack/marker_beacon = 20,
+ /obj/item/spacecash/bundle/mediumrand = 7,
+ /obj/item/reagent_containers/hypospray/medipen/survival = 2,
+ /obj/item/borg/upgrade/modkit/damage = 1
+ )
+ )
+ if(prob(25))
+ l_pocket = pickweight(list(
+ /obj/item/spacecash/bundle/mediumrand = 5,
+ /obj/item/reagent_containers/hypospray/medipen/survival = 2,
+ /obj/item/borg/upgrade/modkit/cooldown = 1
+ )
+ )
+ if(prob(75))
+ for(var/count in 1 to 3)
+ if(prob(70))
+ backpack_contents += pickweight(list(
+ /obj/item/borg/upgrade/modkit/damage = 1,
+ /obj/item/borg/upgrade/modkit/trigger_guard = 1,
+ /obj/item/soap/nanotrasen = 1,
+ /obj/item/wormhole_jaunter = 1,
+ /obj/item/fulton_core = 1,
+ /obj/item/extraction_pack = 2,
+ /obj/item/stack/sheet/animalhide/goliath_hide = 3,
+ /obj/item/hivelordstabilizer = 2,
+ /obj/item/stack/marker_beacon/ten = 2,
+ /obj/item/mining_scanner = 2,
+ /obj/item/extinguisher/mini = 2,
+ /obj/item/kitchen/knife/combat/survival = 3,
+ /obj/item/flashlight/seclite = 3,
+ /obj/item/stack/sheet/sinew = 3,
+ /obj/item/stack/sheet/bone = 3
+ )
+ )
+ if(prob(30))
+ backpack_contents += list(
+ /obj/item/reagent_containers/hypospray/medipen/survival = pickweight(list(
+ 1 = 3,
+ 2 = 2,
+ 3 = 1
+ )
+ )
+ )
+ else if (prob(75))
+ backpack_contents = list()
+ back = pickweight(list(
+ /obj/item/kinetic_crusher = 9,
+ /obj/item/kinetic_crusher/old = 1
+ )
+ )
+
+/datum/outfit/generic/miner
+ name = "Miner (Legion)"
+
+/datum/outfit/generic/engineer/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(prob(75))
+ uniform = pick(/obj/item/clothing/under/rank/engineering/engineer, /obj/item/clothing/under/rank/engineering/engineer/hazard, /obj/item/clothing/under/rank/security/officer/military/eng)
+ if(prob(75))
+ suit = pick(/obj/item/clothing/suit/toggle/hazard, /obj/item/clothing/suit/hazardvest, /obj/item/clothing/suit/hooded/wintercoat/engineering)
+ if(prob(75))
+ gloves = pick(/obj/item/clothing/gloves/color/yellow, /obj/item/clothing/gloves/color/fyellow, /obj/item/clothing/gloves/color/fyellow/old)
+ if(prob(75))
+ belt = pick(/obj/item/storage/belt/utility/full, /obj/item/storage/belt/utility)
+ if(prob(50))
+ head = /obj/item/clothing/head/welding
+ if(prob(75))
+ ears = /obj/item/radio/headset/headset_eng
+ else if(prob(50))
+ glasses = /obj/item/clothing/glasses/welding
+ if(prob(75))
+ back = pick(/obj/item/storage/backpack/industrial, /obj/item/storage/backpack/satchel/eng, /obj/item/storage/backpack/duffelbag/engineering, /obj/item/storage/backpack/messenger/engi)
+ if(prob(10))
+ back = /obj/item/fireaxe
+ for(var/i = 1 to 3)
+ if(prob(75))
+ backpack_contents += pickweight(list(
+ /obj/item/stack/tape/industrial/electrical = 1,
+ /obj/item/electronics/apc = 1,
+ /obj/item/multitool = 1,
+ /obj/item/pipe_dispenser = 1,
+ /obj/item/tank/internals/emergency_oxygen/engi = 1,
+ /obj/item/holosign_creator/engineering = 1,
+ /obj/item/extinguisher/advanced = 1,
+ /obj/item/stack/sheet/metal/twenty = 1
+ )
+ )
+ if(prob(75))
+ accessory = /obj/item/clothing/accessory/armband/engine
+
+/datum/outfit/generic/engineer
+ name = "Mechanic (Legion)"
+ box = /obj/item/storage/box/survival/engineer
+
+/datum/outfit/generic/doctor/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(prob(75))
+ uniform = pick(/obj/item/clothing/under/rank/medical/doctor, /obj/item/clothing/under/rank/medical/doctor/blue)
+ if(prob(75))
+ suit = pick(/obj/item/clothing/suit/toggle/labcoat, /obj/item/clothing/suit/apron/surgical ,/obj/item/clothing/suit/hooded/wintercoat/medical)
+ if(prob(75))
+ back = pick(/obj/item/storage/backpack/medic, /obj/item/storage/backpack/satchel/med, /obj/item/storage/backpack/duffelbag/med, /obj/item/storage/backpack/messenger/med)
+ else if (prob(75))
+ back = /obj/item/defibrillator/loaded
+ if(prob(75))
+ belt = pickweight(list(/obj/item/storage/belt/medical = 5, /obj/item/defibrillator/compact/loaded = 1))
+ if(prob(75))
+ gloves = pick(/obj/item/clothing/gloves/color/white, /obj/item/clothing/gloves/color/latex/nitrile)
+ if(prob(75))
+ mask = /obj/item/clothing/mask/surgical
+ if(prob(75))
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ if(prob(75))
+ head = /obj/item/clothing/head/beret/med
+ if(prob(75))
+ ears = /obj/item/radio/headset/headset_med
+ if(prob(75))
+ glasses = pick(/obj/item/clothing/glasses/hud/health, /obj/item/clothing/glasses/hud/health/prescription)
+ for(var/i = 1 to 3)
+ if(prob(75))
+ backpack_contents += pickweight(list(
+ /obj/item/reagent_containers/pill/patch/styptic = 5,
+ /obj/item/reagent_containers/pill/patch/silver_sulf = 5,
+ /obj/item/storage/firstaid/medical = 3,
+ /obj/item/reagent_containers/syringe = 3,
+ /obj/item/reagent_containers/glass/beaker = 2,
+ /obj/item/reagent_containers/dropper = 2,
+ /obj/item/reagent_containers/pill/charcoal = 2,
+ /obj/item/reagent_containers/medigel/styptic = 2,
+ /obj/item/reagent_containers/medigel/silver_sulf = 2,
+ /obj/item/reagent_containers/medigel/sterilizine = 1,
+ /obj/item/flashlight/pen = 1,
+ /obj/item/hypospray/mkii = 1,
+ /obj/item/healthanalyzer = 1,
+ )
+ )
+ if(prob(75))
+ accessory = /obj/item/clothing/accessory/armband/medblue
+
+/datum/outfit/generic/doctor
+ name = "Medical Doctor (Legion)"
+ box = /obj/item/storage/box/survival/medical
+
+/datum/outfit/generic/science/pre_equip(mob/living/carbon/human/H)
+ ..()
+ if(prob(75))
+ uniform = pick(/obj/item/clothing/under/rank/rnd/scientist, /obj/item/clothing/under/rank/rnd/roboticist)
+ if(prob(75))
+ suit = pick(/obj/item/clothing/suit/toggle/labcoat/science, /obj/item/clothing/suit/hooded/wintercoat/science)
+ if(prob(75))
+ back = pick(/obj/item/storage/backpack/science, /obj/item/storage/backpack/satchel/tox, /obj/item/storage/backpack/messenger/tox)
+ if(prob(75))
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ if(prob(75))
+ gloves = /obj/item/clothing/gloves/color/white
+ if(prob(75))
+ head = /obj/item/clothing/head/beret/sci
+ if(prob(75))
+ ears = /obj/item/radio/headset/headset_sci
+ if(prob(75))
+ glasses = pick(/obj/item/clothing/glasses/hud/diagnostic, /obj/item/clothing/glasses/science)
+ if(prob(1))
+ neck = /obj/item/clothing/neck/tie/horrible
+ for(var/i = 1 to 3)
+ if(prob(75))
+ backpack_contents += pickweight(list(
+ /obj/item/research_notes/loot/tiny = 3,
+ /obj/item/research_notes/loot/small = 3,
+ /obj/item/reagent_scanner = 3,
+ /obj/item/assembly/flash/handheld = 3,
+ /obj/item/stock_parts/capacitor/adv = 2,
+ /obj/item/stock_parts/scanning_module/adv = 2,
+ /obj/item/stock_parts/manipulator/nano = 2,
+ /obj/item/stock_parts/micro_laser/high = 2,
+ /obj/item/stock_parts/matter_bin/adv = 2,
+ /obj/item/survey_handheld = 1,
+ /obj/item/weldingtool/experimental = 1,
+ /obj/item/mmi/posibrain = 1,
+ /obj/item/reagent_containers/glass/beaker/plastic = 1,
+ /obj/item/organ/eyes/robotic/shield = 1,
+ /obj/item/organ/eyes/robotic/glow = 1,
+ )
+ )
+ if(prob(75))
+ accessory = /obj/item/clothing/accessory/armband/science
+
+
+/datum/outfit/generic/science
+ name = "Scientist (Legion)"
+
+/datum/outfit/generic/cargo/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(prob(75))
+ uniform = pick(/obj/item/clothing/under/rank/cargo/tech, /obj/item/clothing/under/shorts/grey)
+ if(prob(75))
+ suit = pick(/obj/item/clothing/suit/hazardvest, /obj/item/clothing/suit/hooded/wintercoat/cargo)
+ if(prob(25))
+ belt = /obj/item/gun/ballistic/automatic/zip_pistol
+ if(prob(75))
+ gloves = /obj/item/clothing/gloves/fingerless
+ if(prob(75))
+ shoes = /obj/item/clothing/shoes/sneakers/black
+ if(prob(75))
+ head = /obj/item/clothing/head/soft
+ if(prob(75))
+ ears = /obj/item/radio/headset/headset_cargo
+ for(var/i = 1 to 3)
+ if(prob(75))
+ backpack_contents += pickweight(list(
+ /obj/item/spacecash/bundle/mediumrand = 5,
+ /obj/item/ammo_box/magazine/illestren_a850r = 5,
+ /obj/item/ammo_box/magazine/zip_ammo_9mm = 5,
+ /obj/item/modular_computer/tablet/preset/cargo = 3,
+ /obj/item/stack/tape = 3,
+ /obj/item/stack/tape/industrial = 3,
+ /obj/item/stack/sheet/plastic/five = 3,
+ /obj/item/grenade/frag = 1
+ )
+ )
+ if(prob(75))
+ accessory = /obj/item/clothing/accessory/armband/cargo
+ if(prob(25))
+ suit = /obj/item/clothing/suit/armor/vest/scrap_armor
+ suit_store = /obj/item/gun/ballistic/rifle/illestren
+
+/datum/outfit/generic/cargo
+ name = "Cargo Technician (Legion)"
+
+/datum/outfit/generic/security/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(prob(75))
+ uniform = /obj/item/clothing/under/rank/security/officer
+ if(prob(75))
+ suit = pick(/obj/item/clothing/suit/armor/vest, /obj/item/clothing/suit/armor/vest/security/officer, /obj/item/clothing/suit/armor/vest/bulletproof, /obj/item/clothing/suit/armor/vest/blueshirt)
+ if(prob(75))
+ back = pick(/obj/item/storage/backpack/security, /obj/item/storage/backpack/satchel/sec, /obj/item/storage/backpack/duffelbag/sec, /obj/item/storage/backpack/messenger/sec)
+ if(prob(75))
+ belt = pick(/obj/item/storage/belt/security, /obj/item/storage/belt/security/webbing)
+ if(prob(75))
+ gloves = pick(/obj/item/clothing/gloves/color/black, /obj/item/clothing/gloves/tackler)
+ if(prob(75))
+ shoes = /obj/item/clothing/shoes/jackboots
+ if(prob(75))
+ head = pick(/obj/item/clothing/head/helmet/sec, /obj/item/clothing/head/helmet/blueshirt, /obj/item/clothing/head/helmet/bulletproof)
+ if(prob(75))
+ mask = /obj/item/clothing/mask/gas/sechailer
+ if(prob(75))
+ ears = /obj/item/radio/headset/headset_sec
+ if(prob(75))
+ glasses = pick(/obj/item/clothing/glasses/hud/security, /obj/item/clothing/glasses/sunglasses)
+ if(prob(75))
+ r_pocket = pick(/obj/item/flashlight/seclite, /obj/item/assembly/flash/handheld, /obj/item/restraints/handcuffs)
+ if(prob(50))
+ suit_store = pick(/obj/item/gun/energy/e_gun, /obj/item/gun/energy/e_gun/smg, /obj/item/gun/energy/e_gun/iot)
+ for(var/i = 1 to 3)
+ if(prob(75))
+ backpack_contents += pickweight(list(
+ /obj/item/restraints/handcuffs = 8,
+ /obj/item/assembly/flash/handheld = 5,
+ /obj/item/storage/box/evidence = 6,
+ /obj/item/flashlight/seclite = 4,
+ /obj/item/ammo_box/c9mm/rubbershot = 3,
+ /obj/item/ammo_box/c9mm = 1,
+ /obj/item/stock_parts/cell/gun = 3,
+ /obj/item/coin/antagtoken = 1,
+ /obj/item/grenade/stingbang = 1
+ )
+ )
+ if(prob(75))
+ accessory = /obj/item/clothing/accessory/armband/deputy
+
+/datum/outfit/generic/security
+ name = "Security Officer (Legion)"
+ box = /obj/item/storage/box/survival/security
+
diff --git a/code/modules/projectiles/boxes_magazines/generic_ammo_box.dm b/code/modules/projectiles/boxes_magazines/generic_ammo_box.dm
new file mode 100644
index 00000000000..2c88824623e
--- /dev/null
+++ b/code/modules/projectiles/boxes_magazines/generic_ammo_box.dm
@@ -0,0 +1,53 @@
+/obj/item/ammo_box/generic
+ name = "generic ammo box"
+ desc = "A generic, unbranded box of ammo. It doesn't have great capacity, but it can hold a variety of different calibers."
+ max_ammo = 20
+ start_empty = TRUE
+ icon_state = "generic-ammo"
+ /// Does the box currently have an ammo type set?
+ var/ammo_set = FALSE
+ /// Name of the currently set ammo type
+ var/ammo_name
+
+/obj/item/ammo_box/generic/update_ammo_count()
+ . = ..()
+ if(LAZYLEN(stored_ammo) == 0)
+ ammo_set = FALSE
+ ammo_type = /obj/item/ammo_casing
+
+/obj/item/ammo_box/generic/proc/update_max_ammo(obj/item/ammo_casing/ammo)
+ if(ammo.bullet_per_box)
+ max_ammo = round(ammo.bullet_per_box)
+ else
+ max_ammo = 10
+
+ return
+
+/obj/item/ammo_box/generic/attackby(obj/item/attacking_obj, mob/user, params, silent, replace_spent)
+ . = ..()
+
+ if(!ammo_set && istype(attacking_obj, /obj/item/ammo_casing))
+ var/obj/item/ammo_casing/ammo_load = attacking_obj.type
+ ammo_type = ammo_load
+ ammo_set = TRUE
+ ammo_name = attacking_obj.name
+ update_max_ammo(attacking_obj)
+ to_chat(user, span_notice("You set the box to hold [attacking_obj]!"))
+
+ if(istype(attacking_obj, /obj/item/pen))
+ if(!user.is_literate())
+ to_chat(user, span_notice("You scribble illegibly on the cover of [src]!"))
+ return
+ var/inputvalue = stripped_input(user, "What would you like to label the box?", "Box Labelling", "", MAX_NAME_LEN)
+
+ if(!inputvalue)
+ return
+
+ if(user.canUseTopic(src, BE_CLOSE))
+ name = "[initial(src.name)][(inputvalue ? " - '[inputvalue]'" : null)]"
+
+/obj/item/ammo_box/generic/examine(mob/user)
+ . = ..()
+ . += span_notice("[ammo_set ? "It's set to hold [ammo_name]\s. The box can hold up to [max_ammo] rounds." : "It doesn't have an ammo type set. Use a bullet on the box to set it."]")
+ . += span_notice("You can use a pen on it to rename the box.")
+
diff --git a/code/modules/ruins/lavalandruin_code/biodome_winter.dm b/code/modules/ruins/lavalandruin_code/biodome_winter.dm
new file mode 100644
index 00000000000..85fed2a7432
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/biodome_winter.dm
@@ -0,0 +1,9 @@
+
+/obj/item/paper/pamphlet/biodomelore
+ name = "Solarian Frontier Project Pamphlet"
+ default_raw_text = "