diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm
new file mode 100644
index 0000000000..a83f02135c
--- /dev/null
+++ b/code/__DEFINES/ai.dm
@@ -0,0 +1,6 @@
+///Mob the MOD is trying to attach to
+#define BB_MOD_TARGET "BB_mod_target"
+///The implant the AI was created from
+#define BB_MOD_IMPLANT "BB_mod_implant"
+///Range for a MOD AI controller.
+#define MOD_AI_RANGE 100
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
new file mode 100644
index 0000000000..be1e9b31c8
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -0,0 +1,3 @@
+/// from base of atom/movable/Process_Spacemove(): (movement_dir)
+#define COMSIG_MOVABLE_SPACEMOVE "spacemove"
+ #define COMSIG_MOVABLE_STOP_SPACEMOVE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
new file mode 100644
index 0000000000..be1e9b31c8
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -0,0 +1,3 @@
+/// from base of atom/movable/Process_Spacemove(): (movement_dir)
+#define COMSIG_MOVABLE_SPACEMOVE "spacemove"
+ #define COMSIG_MOVABLE_STOP_SPACEMOVE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index 5d2a471c0a..0fd09d9063 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -1,2 +1,6 @@
///From base of mob/living/MobBump() (mob/living)
#define COMSIG_LIVING_MOB_BUMP "living_mob_bump"
+
+///From base of mob/living/ZImpactDamage() (mob/living, levels, turf/t)
+#define COMSIG_LIVING_Z_IMPACT "living_z_impact"
+ #define NO_Z_IMPACT_DAMAGE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_mod.dm b/code/__DEFINES/dcs/signals/signals_mod.dm
new file mode 100644
index 0000000000..4c8ec4d6de
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mod.dm
@@ -0,0 +1,7 @@
+//MODsuit signals
+/// Called when a module is selected to be the active one from on_select()
+#define COMSIG_MOD_MODULE_SELECTED "mod_module_selected"
+/// Called when a MOD activation is called from toggle_activate(mob/user)
+#define COMSIG_MOD_ACTIVATE "mod_activate"
+ /// Cancels the suit's activation
+ #define MOD_CANCEL_ACTIVATE (1 << 0)
diff --git a/code/__DEFINES/mod.dm b/code/__DEFINES/mod.dm
new file mode 100644
index 0000000000..03163497d4
--- /dev/null
+++ b/code/__DEFINES/mod.dm
@@ -0,0 +1,31 @@
+/// Default value for the max_complexity var on MODsuits
+#define DEFAULT_MAX_COMPLEXITY 15
+
+/// Default cell drain per process on MODsuits
+#define DEFAULT_CELL_DRAIN 5
+
+/// Passive module, just acts when put in naturally.
+#define MODULE_PASSIVE 0
+/// Usable module, does something when you press a button.
+#define MODULE_USABLE 1
+/// Toggle module, you turn it on/off and it does stuff.
+#define MODULE_TOGGLE 2
+/// Actively usable module, you may only have one selected at a time.
+#define MODULE_ACTIVE 3
+
+//Defines used by the theme for clothing flags and similar
+#define HELMET_LAYER "helmet_layer"
+#define HELMET_FLAGS "helmet_flags"
+#define CHESTPLATE_FLAGS "chestplate_flags"
+#define GAUNTLETS_FLAGS "gauntlets_flags"
+#define BOOTS_FLAGS "boots_flags"
+
+#define UNSEALED_CLOTHING "unsealed_clothing"
+#define SEALED_CLOTHING "sealed_clothing"
+#define UNSEALED_INVISIBILITY "unsealed_invisibility"
+#define SEALED_INVISIBILITY "sealed_invisibility"
+#define UNSEALED_COVER "unsealed_cover"
+#define SEALED_COVER "sealed_cover"
+
+/// Global list of all /datum/mod_theme
+GLOBAL_LIST_INIT(mod_themes, setup_mod_themes())
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 222dc5cd39..af5dccb3fd 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -158,6 +158,22 @@
#define TRAIT_CALCIUM_HEALER "calcium_healer"
#define TRAIT_MAGIC_CHOKE "magic_choke"
#define TRAIT_CAPTAIN_METABOLISM "captain-metabolism"
+/// Prevents plasmamen from self-igniting
+#define TRAIT_NOSELFIGNITION "no_selfignition"
+/// Like antimagic, but doesn't block the user from casting
+#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
+/// Gives us turf, mob and object vision through walls
+#define TRAIT_XRAY_VISION "xray_vision"
+/// Gives us mob vision through walls and slight night vision
+#define TRAIT_THERMAL_VISION "thermal_vision"
+/// Gives us turf vision through walls and slight night vision
+#define TRAIT_MESON_VISION "meson_vision"
+/// Gives us Night vision
+#define TRAIT_TRUE_NIGHT_VISION "true_night_vision"
+/// Negates our gravity, letting us move normally on floors in 0-g
+#define TRAIT_NEGATES_GRAVITY "negates_gravity"
+/// Lets us scan reagents
+#define TRAIT_REAGENT_SCANNER "reagent_scanner"
#define TRAIT_ABDUCTOR_TRAINING "abductor-training"
#define TRAIT_ABDUCTOR_SCIENTIST_TRAINING "abductor-scientist-training"
#define TRAIT_SURGEON "surgeon"
@@ -377,3 +393,5 @@
#define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint"
#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
+/// Trait applied by MODsuits.
+#define MOD_TRAIT "mod"
diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm
new file mode 100644
index 0000000000..c242bcc7d6
--- /dev/null
+++ b/code/__DEFINES/wiremod.dm
@@ -0,0 +1,4 @@
+#define SHELL_FLAG_CIRCUIT_UNREMOVABLE (1<<0)
+
+/// Whether a circuit is not able to be modified
+#define SHELL_FLAG_CIRCUIT_UNMODIFIABLE (1<<5)
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index b41b7356a8..9aa0f8be0a 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -51,6 +51,7 @@ GLOBAL_LIST_INIT(maintenance_loot, list(
/obj/item/airlock_painter = 1,
/obj/item/stack/cable_coil/random = 4,
/obj/item/stack/cable_coil/random/five = 6,
+ /obj/item/mod/construction/broken_core = 5,
/obj/item/stack/medical/suture = 1,
/obj/item/stack/rods/ten = 9,
/obj/item/stack/rods/twentyfive = 1,
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 935379b721..b438afaec8 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -91,6 +91,8 @@ GLOBAL_LIST_EMPTY(radial_menus)
else
py_shift = 32
restrict_to_dir(NORTH) //I was going to parse screen loc here but that's more effort than it's worth.
+ else if(hudfix_method && AM.loc)
+ anchor = get_atom_on_turf(anchor)
//Sets defaults
//These assume 45 deg min_angle
diff --git a/code/datums/ai/objects/mod.dm b/code/datums/ai/objects/mod.dm
new file mode 100644
index 0000000000..ced2ffceb6
--- /dev/null
+++ b/code/datums/ai/objects/mod.dm
@@ -0,0 +1,48 @@
+/// An AI controller for the MODsuit pathfinder module. It's activated by implant and attaches itself to the user.
+/datum/ai_controller/mod
+ blackboard = list(
+ BB_MOD_TARGET,
+ BB_MOD_IMPLANT,
+ )
+ max_target_distance = MOD_AI_RANGE //a little spicy but its one specific item that summons it, and it doesnt run otherwise
+ ai_movement = /datum/ai_movement/jps
+ ///ID card generated from the suit's required access. Used for pathing.
+ var/obj/item/card/id/advanced/id_card
+
+/datum/ai_controller/mod/TryPossessPawn(atom/new_pawn)
+ if(!istype(new_pawn, /obj/item/mod/control))
+ return AI_CONTROLLER_INCOMPATIBLE
+ var/obj/item/mod/control/mod = new_pawn
+ id_card = new /obj/item/card/id/advanced/simple_bot()
+ if(length(mod.req_access))
+ id_card.set_access(mod.req_access)
+ return ..() //Run parent at end
+
+/datum/ai_controller/mod/UnpossessPawn(destroy)
+ QDEL_NULL(id_card)
+ return ..() //Run parent at end
+
+/datum/ai_controller/mod/SelectBehaviors(delta_time)
+ current_behaviors = list()
+ if(blackboard[BB_MOD_TARGET] && blackboard[BB_MOD_IMPLANT])
+ queue_behavior(/datum/ai_behavior/mod_attach)
+
+/datum/ai_controller/mod/get_access()
+ return id_card
+
+/datum/ai_behavior/mod_attach
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_MOVE_AND_PERFORM
+
+/datum/ai_behavior/mod_attach/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ if(!controller.pawn.Adjacent(controller.blackboard[BB_MOD_TARGET]))
+ return
+ var/obj/item/implant/mod/implant = controller.blackboard[BB_MOD_IMPLANT]
+ implant.module.attach(controller.blackboard[BB_MOD_TARGET])
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/mod_attach/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ controller.blackboard[BB_MOD_TARGET] = null
+ var/obj/item/implant/mod/implant = controller.blackboard[BB_MOD_IMPLANT]
+ implant.end_recall(succeeded)
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index ad37f770ed..7f6408f583 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -554,6 +554,19 @@
subcategory = CAT_MISCELLANEOUS
category = CAT_MISCELLANEOUS
+/datum/crafting_recipe/mod_core
+ name = "MOD core"
+ result = /obj/item/mod/construction/core
+ tool_behaviors = list(TOOL_SCREWDRIVER)
+ time = 10 SECONDS
+ reqs = list(/obj/item/stack/cable_coil = 5,
+ /obj/item/stack/rods = 2,
+ /obj/item/stack/sheet/glass = 1,
+ /obj/item/organ/heart = 1
+ )
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISCELLANEOUS
+
//////////////
//Banners/////
//////////////
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index c0101140d6..1218e8b9af 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -369,9 +369,10 @@
//Tries to dump content
/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M)
var/atom/A = parent
- var/atom/dump_destination = dest_object.get_dumping_location()
- if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
+ var/atom/dump_destination = get_dumping_location(dest_object)
+ if(M.CanReach(A) && dump_destination && M.CanReach(dump_destination))
if(check_locked(null, M, TRUE))
+ to_chat(M, "[parent] seems to be locked!")
return FALSE
if(dump_destination.storage_contents_dump_act(src, M))
playsound(A, "rustle", 50, 1, -5)
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index c54f6c971a..a4d5591653 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -348,12 +348,21 @@
/datum/preset_holoimage/engineer/rig
outfit_type = /datum/outfit/job/engineer/gloved/rig
+/datum/preset_holoimage/engineer/mod
+ outfit_type = /datum/outfit/job/engineer/mod
+
/datum/preset_holoimage/engineer/ce
outfit_type = /datum/outfit/job/ce
+/datum/preset_holoimage/engineer/ce/mod
+ outfit_type = /datum/outfit/job/ce/mod
+
/datum/preset_holoimage/engineer/ce/rig
outfit_type = /datum/outfit/job/engineer/gloved/rig
+/datum/preset_holoimage/engineer/atmos/mod
+ outfit_type = /datum/outfit/job/atmos/mod
+
/datum/preset_holoimage/engineer/atmos
outfit_type = /datum/outfit/job/atmos
@@ -449,6 +458,7 @@
SAY Oh, shit!
DELAY 10
PRESET /datum/preset_holoimage/engineer/atmos/rig
+ PRESET /datum/preset_holoimage/engineer/atmos/mod
LANGUAGE /datum/language/narsie
NAME Unknown
SAY RISE, MY LORD!!
@@ -456,6 +466,7 @@
LANGUAGE /datum/language/common
NAME Plastic
PRESET /datum/preset_holoimage/engineer/rig
+ PRESET /datum/preset_holoimage/engineer/mod
SAY Fuck, fuck, fuck!
DELAY 20
SAY It's loose! CALL THE FUCKING SHUTT-
diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm
index 4c17874f97..c5221bdaf8 100755
--- a/code/datums/outfit.dm
+++ b/code/datums/outfit.dm
@@ -79,6 +79,10 @@
//Type path of item to go in left hand
var/r_hand = null
+
+ ///ID of the slot containing a gas tank
+ var/internals_slot = null
+
/// Any clothing accessory item
var/accessory = null
@@ -140,6 +144,15 @@
//to be overridden for toggling internals, id binding, access etc
return
+#define EQUIP_OUTFIT_ITEM(item_path, slot_name) if(##item_path) { \
+ H.equip_to_slot_or_del(SSwardrobe.provide_type(##item_path), ##slot_name, TRUE); \
+ var/obj/item/outfit_item = H.get_item_by_slot(##slot_name); \
+ if (outfit_item && outfit_item.type == ##item_path) { \
+ outfit_item.on_outfit_equip(H, visualsOnly, ##slot_name); \
+ } \
+}
+
+
/**
* Equips all defined types and paths to the mob passed in
*
@@ -153,31 +166,32 @@
//Start with uniform,suit,backpack for additional slots
if(uniform)
- H.equip_to_slot_or_del(new uniform(H), ITEM_SLOT_ICLOTHING, TRUE)
+ EQUIP_OUTFIT_ITEM(uniform, ITEM_SLOT_ICLOTHING)
if(suit)
- H.equip_to_slot_or_del(new suit(H), ITEM_SLOT_OCLOTHING, TRUE)
- if(back)
- H.equip_to_slot_or_del(new back(H), ITEM_SLOT_BACK, TRUE)
+ EQUIP_OUTFIT_ITEM(suit, ITEM_SLOT_OCLOTHING)
if(belt)
- H.equip_to_slot_or_del(new belt(H), ITEM_SLOT_BELT, TRUE)
+ EQUIP_OUTFIT_ITEM(belt, ITEM_SLOT_BELT)
if(gloves)
- H.equip_to_slot_or_del(new gloves(H), ITEM_SLOT_GLOVES, TRUE)
+ EQUIP_OUTFIT_ITEM(gloves, ITEM_SLOT_GLOVES)
if(shoes)
- H.equip_to_slot_or_del(new shoes(H), ITEM_SLOT_FEET, TRUE)
+ EQUIP_OUTFIT_ITEM(shoes, ITEM_SLOT_FEET)
if(head)
- H.equip_to_slot_or_del(new head(H), ITEM_SLOT_HEAD, TRUE)
+ EQUIP_OUTFIT_ITEM(head, ITEM_SLOT_HEAD)
if(mask)
- H.equip_to_slot_or_del(new mask(H), ITEM_SLOT_MASK, TRUE)
+ EQUIP_OUTFIT_ITEM(mask, ITEM_SLOT_MASK)
if(neck)
- H.equip_to_slot_or_del(new neck(H), ITEM_SLOT_NECK, TRUE)
+ EQUIP_OUTFIT_ITEM(neck, ITEM_SLOT_NECK)
if(ears)
- H.equip_to_slot_or_del(new ears(H), ITEM_SLOT_EARS, TRUE)
+ EQUIP_OUTFIT_ITEM(ears, ITEM_SLOT_EARS)
if(glasses)
- H.equip_to_slot_or_del(new glasses(H), ITEM_SLOT_EYES, TRUE)
+ EQUIP_OUTFIT_ITEM(glasses, ITEM_SLOT_EYES)
+ if(back)
+ EQUIP_OUTFIT_ITEM(back, ITEM_SLOT_BACK)
if(id)
- H.equip_to_slot_or_del(new id(H), ITEM_SLOT_ID, TRUE)
+ EQUIP_OUTFIT_ITEM(id, ITEM_SLOT_ID)
if(suit_store)
- H.equip_to_slot_or_del(new suit_store(H), ITEM_SLOT_SUITSTORE, TRUE)
+ EQUIP_OUTFIT_ITEM(suit_store, ITEM_SLOT_SUITSTORE)
+
if(undershirt)
H.undershirt = initial(undershirt.name)
@@ -195,9 +209,9 @@
if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon.
if(l_pocket)
- H.equip_to_slot_or_del(new l_pocket(H), ITEM_SLOT_LPOCKET, TRUE)
+ EQUIP_OUTFIT_ITEM(l_pocket, ITEM_SLOT_LPOCKET)
if(r_pocket)
- H.equip_to_slot_or_del(new r_pocket(H), ITEM_SLOT_RPOCKET, TRUE)
+ EQUIP_OUTFIT_ITEM(r_pocket, ITEM_SLOT_RPOCKET)
if(box)
if(!backpack_contents)
@@ -211,11 +225,7 @@
if(!isnum(number))//Default to 1
number = 1
for(var/i in 1 to number)
- H.equip_to_slot_or_del(new path(H), ITEM_SLOT_BACKPACK, TRUE)
-
- if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit))
- var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
- HS.ToggleHelmet()
+ EQUIP_OUTFIT_ITEM(path, ITEM_SLOT_BACKPACK)
post_equip(H, visualsOnly, preference_source)
@@ -232,6 +242,9 @@
H.update_body()
return TRUE
+#undef EQUIP_OUTFIT_ITEM
+
+
/**
* Apply a fingerprint from the passed in human to all items in the outfit
*
diff --git a/code/datums/wires/mod.dm b/code/datums/wires/mod.dm
new file mode 100644
index 0000000000..b5805557ea
--- /dev/null
+++ b/code/datums/wires/mod.dm
@@ -0,0 +1,57 @@
+/datum/wires/mod
+ holder_type = /obj/item/mod/control
+ proper_name = "MOD control unit"
+
+/datum/wires/mod/New(atom/holder)
+ wires = list(WIRE_HACK, WIRE_DISABLE, WIRE_SHOCK, WIRE_INTERFACE)
+ add_duds(2)
+ ..()
+
+/datum/wires/mod/interactable(mob/user)
+ if(!..())
+ return FALSE
+ var/obj/item/mod/control/mod = holder
+ return mod.open
+
+/datum/wires/mod/get_status()
+ var/obj/item/mod/control/mod = holder
+ var/list/status = list()
+ status += "The orange light is [mod.seconds_electrified ? "on" : "off"]."
+ status += "The red light is [mod.malfunctioning ? "off" : "blinking"]."
+ status += "The green light is [mod.locked ? "on" : "off"]."
+ status += "The yellow light is [mod.interface_break ? "off" : "on"]."
+ return status
+
+/datum/wires/mod/on_pulse(wire)
+ var/obj/item/mod/control/mod = holder
+ switch(wire)
+ if(WIRE_HACK)
+ mod.locked = !mod.locked
+ if(WIRE_DISABLE)
+ mod.malfunctioning = TRUE
+ if(WIRE_SHOCK)
+ mod.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME
+ if(WIRE_INTERFACE)
+ mod.interface_break = !mod.interface_break
+
+/datum/wires/mod/on_cut(wire, mend)
+ var/obj/item/mod/control/mod = holder
+ switch(wire)
+ if(WIRE_HACK)
+ if(!mend)
+ mod.req_access = list()
+ if(WIRE_DISABLE)
+ mod.malfunctioning = !mend
+ if(WIRE_SHOCK)
+ if(mend)
+ mod.seconds_electrified = MACHINE_NOT_ELECTRIFIED
+ else
+ mod.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT
+ if(WIRE_INTERFACE)
+ mod.interface_break = !mend
+
+/datum/wires/mod/ui_act(action, params)
+ var/obj/item/mod/control/mod = holder
+ if(!issilicon(usr) && mod.seconds_electrified && mod.shock(usr))
+ return FALSE
+ return ..()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index e7d9898dd2..fd10507d59 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -911,7 +911,8 @@
user.active_storage.ui_show(user)
return TRUE
-/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
+///Get the best place to dump the items contained in the source storage item?
+/atom/proc/get_dumping_location()
return null
//This proc is called on the location of an atom when the atom is Destroy()'d
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index ef47a0e7f6..9dd54e043f 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -381,7 +381,7 @@
/obj/machinery/door/morgue
icon = 'icons/obj/doors/doormorgue.dmi'
-/obj/machinery/door/get_dumping_location(obj/item/storage/source,mob/user)
+/obj/machinery/door/get_dumping_location()
return null
/obj/machinery/door/proc/lock()
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 11ae19f7fa..634ffec74a 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -1,6 +1,6 @@
/obj/machinery/recharge_station
- name = "cyborg recharging station"
- desc = "This device recharges cyborgs and resupplies them with materials."
+ name = "recharging station"
+ desc = "This device recharges energy dependent lifeforms, like cyborgs, ethereals and MODsuit users."
icon = 'icons/obj/objects.dmi'
icon_state = "borgcharger0"
density = FALSE
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 74a05a1e7b..5e509a637b 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -10,11 +10,15 @@
var/obj/item/clothing/suit/space/suit = null
var/obj/item/clothing/head/helmet/space/helmet = null
var/obj/item/clothing/mask/mask = null
+ var/obj/item/mod/control/mod = null
var/obj/item/storage = null
var/suit_type = null
var/helmet_type = null
var/mask_type = null
+ /// What type of MOD the unit starts with when spawned.
+ var/mod_type = null
+ /// What type of additional item the unit starts with when spawned.
var/storage_type = null
state_open = FALSE
@@ -38,21 +42,50 @@
mask_type = /obj/item/clothing/mask/gas/sechailer
storage_type = /obj/item/tank/jetpack/oxygen/captain
+/obj/machinery/suit_storage_unit/captainmod
+ mask_type = /obj/item/clothing/mask/gas/atmos/captain
+ storage_type = /obj/item/tank/jetpack/oxygen/captain
+ mod_type = /obj/item/mod/control/pre_equipped/magnate
+
/obj/machinery/suit_storage_unit/engine
suit_type = /obj/item/clothing/suit/space/hardsuit/engine
mask_type = /obj/item/clothing/mask/breath
storage_type= /obj/item/clothing/shoes/magboots
+/obj/machinery/suit_storage_unit/enginemod
+ mask_type = /obj/item/clothing/mask/breath
+ mod_type = /obj/item/mod/control/pre_equipped/engineering
+
+/obj/machinery/suit_storage_unit/atmos
+ suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos
+ mask_type = /obj/item/clothing/mask/gas/atmos
+ storage_type = /obj/item/watertank/atmos
+
+/obj/machinery/suit_storage_unit/atmosmod
+ mask_type = /obj/item/clothing/mask/gas/atmos
+ storage_type = /obj/item/watertank/atmos
+ mod_type = /obj/item/mod/control/pre_equipped/atmospheric
+
/obj/machinery/suit_storage_unit/ce
suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite
mask_type = /obj/item/clothing/mask/breath
storage_type= /obj/item/clothing/shoes/magboots/advance
+/obj/machinery/suit_storage_unit/cemod
+ mask_type = /obj/item/clothing/mask/breath
+ storage_type = /obj/item/clothing/shoes/magboots/advance
+ mod_type = /obj/item/mod/control/pre_equipped/advanced
+
/obj/machinery/suit_storage_unit/security
suit_type = /obj/item/clothing/suit/space/hardsuit/security
mask_type = /obj/item/clothing/mask/gas/sechailer
storage_type = /obj/item/tank/jetpack/oxygen/security
+/obj/machinery/suit_storage_unit/securitymod
+ mask_type = /obj/item/clothing/mask/gas/sechailer
+ mod_type = /obj/item/mod/control/pre_equipped/security
+
+
/obj/machinery/suit_storage_unit/hos
suit_type = /obj/item/clothing/suit/space/hardsuit/security/hos
mask_type = /obj/item/clothing/mask/gas/sechailer
@@ -63,6 +96,11 @@
mask_type = /obj/item/clothing/mask/gas
storage_type = /obj/item/watertank/atmos
+/obj/machinery/suit_storage_unit/hosmod
+ mask_type = /obj/item/clothing/mask/gas/sechailer
+ storage_type = /obj/item/tank/internals/oxygen
+ mod_type = /obj/item/mod/control/pre_equipped/safeguard
+
/obj/machinery/suit_storage_unit/mining
suit_type = /obj/item/clothing/suit/hooded/explorer/standard
mask_type = /obj/item/clothing/mask/gas/explorer
@@ -71,6 +109,16 @@
suit_type = /obj/item/clothing/suit/space/hardsuit/mining
mask_type = /obj/item/clothing/mask/breath
+/obj/machinery/suit_storage_unit/mining/evahos
+ suit_type = null
+ mask_type = /obj/item/clothing/mask/breath
+ mod_type = /obj/item/mod/control/pre_equipped/mining
+
+/obj/machinery/suit_storage_unit/medicalmod
+ mask_type = /obj/item/clothing/mask/breath/medical
+ storage_type = /obj/item/tank/internals/oxygen
+ mod_type = /obj/item/mod/control/pre_equipped/medical
+
/obj/machinery/suit_storage_unit/cmo
suit_type = /obj/item/clothing/suit/space/hardsuit/medical
mask_type = /obj/item/clothing/mask/breath
@@ -81,15 +129,29 @@
helmet_type = /obj/item/clothing/head/helmet/space/eva/paramedic
mask_type = /obj/item/clothing/mask/breath
+/obj/machinery/suit_storage_unit/cmomod
+ mask_type = /obj/item/clothing/mask/breath/medical
+ storage_type = /obj/item/tank/internals/oxygen
+ mod_type = /obj/item/mod/control/pre_equipped/rescue
+
/obj/machinery/suit_storage_unit/rd
suit_type = /obj/item/clothing/suit/space/hardsuit/rd
mask_type = /obj/item/clothing/mask/breath
+/obj/machinery/suit_storage_unit/rdmod
+ mask_type = /obj/item/clothing/mask/breath
+ mod_type = /obj/item/mod/control/pre_equipped/research
+
/obj/machinery/suit_storage_unit/syndicate
suit_type = /obj/item/clothing/suit/space/hardsuit/syndi
mask_type = /obj/item/clothing/mask/gas/syndicate
storage_type = /obj/item/tank/jetpack/oxygen/harness
+/obj/machinery/suit_storage_unit/syndicatemod
+ mask_type = /obj/item/clothing/mask/gas/syndicate
+ storage_type = /obj/item/tank/jetpack/oxygen/harness
+ mod_type = /obj/item/mod/control/pre_equipped/nuclear
+
/obj/machinery/suit_storage_unit/ert/command
suit_type = /obj/item/clothing/suit/space/hardsuit/ert
mask_type = /obj/item/clothing/mask/breath
@@ -129,6 +191,8 @@
helmet = new helmet_type(src)
if(mask_type)
mask = new mask_type(src)
+ if(mod_type)
+ mod = new mod_type(src)
if(storage_type)
storage = new storage_type(src)
update_icon()
@@ -137,6 +201,7 @@
QDEL_NULL(suit)
QDEL_NULL(helmet)
QDEL_NULL(mask)
+ QDEL_NULL(mod)
QDEL_NULL(storage)
return ..()
@@ -154,7 +219,7 @@
. += "broken"
else
. += "open"
- if(suit)
+ if(suit || mod)
. += "suit"
if(helmet)
. += "helm"
@@ -175,6 +240,7 @@
helmet = null
suit = null
mask = null
+ mod = null
storage = null
occupant = null
@@ -241,6 +307,8 @@
qdel(suit) // Delete everything but the occupant.
mask = null
qdel(mask)
+ mod = null
+ qdel(mod)
storage = null
qdel(storage)
// The wires get damaged too.
@@ -262,6 +330,9 @@
if(mask)
things_to_clear += mask
things_to_clear += mask.GetAllContents()
+ if(mod)
+ things_to_clear += mod
+ things_to_clear += mod.get_all_contents()
if(storage)
things_to_clear += storage
things_to_clear += storage.GetAllContents()
@@ -279,13 +350,25 @@
if(occupant)
dump_contents()
-/obj/machinery/suit_storage_unit/proc/shock(mob/user, prb)
- if(!prob(prb))
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(5, 1, src)
- s.start()
- if(electrocute_mob(user, src, src, 1, TRUE))
- return 1
+/obj/machinery/suit_storage_unit/process(delta_time)
+ var/obj/item/stock_parts/cell/cell
+ if(suit)
+ if(!istype(suit))
+ return
+ if(!suit.cell)
+ return
+ cell = suit.cell
+ else if(mod)
+ if(!istype(mod))
+ return
+ if(!mod.cell)
+ return
+ cell = mod.cell
+ else
+ return
+
+ use_power(charge_rate * delta_time)
+ cell.give(charge_rate * delta_time)
/obj/machinery/suit_storage_unit/relaymove(mob/user)
if(locked)
@@ -353,6 +436,13 @@
if(!user.transferItemToLoc(I, src))
return
mask = I
+ else if(istype(I, /obj/item/mod/control))
+ if(mod)
+ to_chat(user, span_warning("The unit already contains a MOD!"))
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ mod = I
else
if(storage)
to_chat(user, "The auxiliary storage compartment is full!")
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 0dcc23f3aa..5fd55eeb45 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1271,3 +1271,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
/obj/item/proc/update_action_buttons(status_only = FALSE, force = FALSE)
for(var/datum/action/current_action as anything in actions)
current_action.UpdateButtonIcon(status_only, force)
+
+/// Special stuff you want to do when an outfit equips this item.
+/obj/item/proc/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
+ return
diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm
index 503e2a9473..52ec7f796f 100644
--- a/code/game/objects/items/devices/aicard.dm
+++ b/code/game/objects/items/devices/aicard.dm
@@ -26,18 +26,22 @@
user.visible_message("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!")
return BRUTELOSS
-/obj/item/aicard/afterattack(atom/target, mob/user, proximity)
- . = ..()
- if(!proximity || !target)
- return
+/obj/item/aicard/pre_attack(atom/target, mob/living/user, params)
if(AI) //AI is on the card, implies user wants to upload it.
- log_combat(user, AI, "carded", src)
+ var/our_ai = AI
target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src)
+ else //No AI on the card, therefore the user wants to download one.
+ target.transfer_ai(AI_TRANS_TO_CARD, user, null, src)
+ if(AI)
+ log_combat(user, our_ai, "uploaded", src, "to [target].")
+ return TRUE
else //No AI on the card, therefore the user wants to download one.
target.transfer_ai(AI_TRANS_TO_CARD, user, null, src)
if(AI)
log_combat(user, AI, "carded", src)
- update_icon() //Whatever happened, update the card's state (icon, name) to match.
+ return TRUE
+ update_appearance() //Whatever happened, update the card's state (icon, name) to match.
+ return ..()
/obj/item/aicard/update_icon()
cut_overlays()
diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm
index b9d66a5bd0..77dee74711 100644
--- a/code/game/objects/items/singularityhammer.dm
+++ b/code/game/objects/items/singularityhammer.dm
@@ -53,7 +53,13 @@
var/atom/movable/A = X
if(A == wielder)
continue
- if(A && !A.anchored && !ishuman(X))
+ if(isliving(A))
+ var/mob/living/vortexed_mob = A
+ if(vortexed_mob.mob_negates_gravity())
+ continue
+ else
+ vortexed_mob.Paralyze(2 SECONDS)
+ if(!A.anchored && !isobserver(A))
step_towards(A,pull)
step_towards(A,pull)
step_towards(A,pull)
diff --git a/code/game/objects/items/storage/_storage.dm b/code/game/objects/items/storage/_storage.dm
index cbaa1775eb..c7aa36a350 100644
--- a/code/game/objects/items/storage/_storage.dm
+++ b/code/game/objects/items/storage/_storage.dm
@@ -5,9 +5,6 @@
w_class = WEIGHT_CLASS_NORMAL
var/component_type = /datum/component/storage/concrete
-/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user)
- return src
-
/obj/item/storage/Initialize(mapload)
. = ..()
PopulateContents()
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 06c38ead9f..76bfe63d27 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -19,6 +19,25 @@
ion_trail = new
ion_trail.set_up(src)
+/obj/item/tank/jetpack/Destroy()
+ QDEL_NULL(ion_trail)
+ return ..()
+
+/obj/item/tank/jetpack/item_action_slot_check(slot)
+ if(slot == ITEM_SLOT_BACK)
+ return TRUE
+
+/obj/item/tank/jetpack/equipped(mob/user, slot, initial)
+ . = ..()
+ if(on && slot != ITEM_SLOT_BACK)
+ turn_off(user)
+
+/obj/item/tank/jetpack/dropped(mob/user, silent)
+ . = ..()
+ if(on)
+ turn_off(user)
+
+
/obj/item/tank/jetpack/populate_gas()
if(gas_type)
air_contents.set_moles(gas_type, ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C)))
@@ -53,6 +72,7 @@
icon_state = "[initial(icon_state)]-on"
ion_trail.start()
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/move_react)
+ RegisterSignal(user, COMSIG_MOVABLE_SPACEMOVE, .proc/spacemove_react)
if(full_speed)
user.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
else
@@ -90,6 +110,13 @@
else
..()
+/obj/item/tank/jetpack/proc/spacemove_react(mob/user, movement_dir)
+ SIGNAL_HANDLER
+
+ if(on && (movement_dir || stabilizers))
+ return COMSIG_MOVABLE_STOP_SPACEMOVE
+
+
/obj/item/tank/jetpack/improvised
name = "improvised jetpack"
desc = "A jetpack made from two air tanks, a fire extinguisher and some atmospherics equipment. It doesn't look like it can hold much."
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 4ea25bec96..7ed99f895b 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -237,7 +237,7 @@
if(!anchored || current_size >= STAGE_FIVE)
step_towards(src,S)
-/obj/get_dumping_location(datum/component/storage/source,mob/user)
+/obj/get_dumping_location()
return get_turf(src)
/**
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index bf06f06e6f..67d17eecfd 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -133,7 +133,7 @@
new mineral(loc)
qdel(src)
-/obj/structure/falsewall/get_dumping_location(obj/item/storage/source,mob/user)
+/obj/structure/falsewall/get_dumping_location()
return null
/obj/structure/falsewall/examine_status(mob/user) //So you can't detect falsewalls by examine.
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 998285dd58..129744ab1c 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -273,7 +273,7 @@
C.add_delayedload(C.newavail() * 0.0375) // you can gain up to 3.5 via the 4x upgrades power is halved by the pole so thats 2x then 1X then .5X for 3.5x the 3 bounces shock.
return ..()
-/obj/structure/grille/get_dumping_location(datum/component/storage/source,mob/user)
+/obj/structure/grille/get_dumping_location()
return null
/obj/structure/grille/broken // Pre-broken grilles for map placement
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index cf5e34fd8b..625d36f5c2 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -567,7 +567,7 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
take_damage(round(exposed_volume / 100), BURN, 0, 0)
..()
-/obj/structure/window/get_dumping_location(obj/item/storage/source,mob/user)
+/obj/structure/window/get_dumping_location()
return null
/obj/structure/window/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index cac03b0637..deda368e66 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -278,7 +278,7 @@
if(.)
ChangeTurf(/turf/closed/wall/clockwork)
-/turf/closed/wall/get_dumping_location(obj/item/storage/source, mob/user)
+/turf/closed/wall/get_dumping_location()
return null
/turf/closed/wall/acid_act(acidpwr, acid_volume)
diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm
index 125bfe2034..6ec6554693 100644
--- a/code/modules/cargo/packs/science.dm
+++ b/code/modules/cargo/packs/science.dm
@@ -238,3 +238,15 @@
contains = list(/obj/item/raw_anomaly_core/pyro)
crate_name = "raw pyro anomaly"
crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/mod_core
+ name = "MOD core Crate"
+ desc = "Three cores, perfect for any MODsuit construction! Naturally harvested™, of course."
+ cost = CARGO_CRATE_VALUE * 3
+ access = ACCESS_ROBOTICS
+ access_view = ACCESS_ROBOTICS
+ contains = list(/obj/item/mod/construction/core,
+ /obj/item/mod/construction/core,
+ /obj/item/mod/construction/core)
+ crate_name = "MOD core crate"
+ crate_type = /obj/structure/closet/crate/secure/science
diff --git a/tgstation.dme b/tgstation.dme
index c45ca0f83f..1ced56efb1 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -26,6 +26,7 @@
#include "code\__DEFINES\achievements.dm"
#include "code\__DEFINES\actionspeed_modifiers.dm"
#include "code\__DEFINES\admin.dm"
+#include "code\__DEFINES\ai.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\atmospherics.dm"
#include "code\__DEFINES\bitfields.dm"
@@ -80,6 +81,7 @@
#include "code\__DEFINES\menu.dm"
#include "code\__DEFINES\misc.dm"
#include "code\__DEFINES\mobs.dm"
+#include "code\__DEFINES\mod.dm"
#include "code\__DEFINES\monkeys.dm"
#include "code\__DEFINES\move_force.dm"
#include "code\__DEFINES\movement.dm"
@@ -136,6 +138,7 @@
#include "code\__DEFINES\vote.dm"
#include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\wall_dents.dm"
+#include "code\__DEFINES\wiremod.dm"
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\wounds.dm"
#include "code\__DEFINES\_flags\_flags.dm"
@@ -156,8 +159,11 @@
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals.dm"
+#include "code\__DEFINES\dcs\signals\signals_mod.dm"
#include "code\__DEFINES\dcs\signals\signals_subsystem.dm"
+#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movable.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm"
#include "code\__DEFINES\mapping\maploader.dm"
#include "code\__DEFINES\material\worth.dm"
@@ -500,6 +506,7 @@
#include "code\datums\achievements\misc_scores.dm"
#include "code\datums\achievements\skill_achievements.dm"
#include "code\datums\actions\beam_rifle.dm"
+#include "code\datums\ai\objects\mod.dm"
#include "code\datums\announcers\_announcer.dm"
#include "code\datums\announcers\default_announcer.dm"
#include "code\datums\announcers\intern_announcer.dm"
@@ -807,6 +814,7 @@
#include "code\datums\wires\emitter.dm"
#include "code\datums\wires\explosive.dm"
#include "code\datums\wires\microwave.dm"
+#include "code\datums\wires\mod.dm"
#include "code\datums\wires\mulebot.dm"
#include "code\datums\wires\particle_accelerator.dm"
#include "code\datums\wires\r_n_d.dm"