diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index 20c68c015b..0cbba32b96 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -35,6 +35,7 @@ #define MEDAL_HOT_DAMN "Hot Damn!" #define MEDAL_CAYENNE_DISK "Very Important Piscis" #define MEDAL_TRAM_SURFER "Tram Surfer" +#define MEDAL_SPRINGLOCK "The Man Inside the Modsuit" //Skill medal hub IDs #define MEDAL_LEGENDARY_MINER "Legendary Miner" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 93e3d40507..23b547da56 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -477,6 +477,8 @@ #define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params) #define COMSIG_ITEM_ALT_AFTERATTACK "item_alt_afterattack" //from base of obj/item/altafterattack(): (atom/target, mob/user, proximity, params) #define COMSIG_ITEM_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot) +/// A mob has just unequipped an item. +#define COMSIG_MOB_UNEQUIPPED_ITEM "mob_unequipped_item" // Do not grant actions on equip. #define COMPONENT_NO_GRANT_ACTIONS 1 #define COMSIG_ITEM_DROPPED "item_drop" //from base of obj/item/dropped(): (mob/user) diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm new file mode 100644 index 0000000000..801eb6b41b --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -0,0 +1,14 @@ +/// From /datum/surgery/New(): (datum/surgery/surgery, surgery_location (body zone), obj/item/bodypart/targeted_limb) +#define COMSIG_MOB_SURGERY_STARTED "mob_surgery_started" + +/// From /datum/surgery_step/success(): (datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) +#define COMSIG_MOB_SURGERY_STEP_SUCCESS "mob_surgery_step_success" + +/// From /obj/item/shockpaddles/proc/do_success(): (obj/item/shockpaddles/source) +#define COMSIG_DEFIBRILLATOR_SUCCESS "defib_success" + #define COMPONENT_DEFIB_STOP (1<<0) + +/// From /datum/surgery/can_start(): (mob/source, datum/surgery/surgery, mob/living/patient) +#define COMSIG_SURGERY_STARTING "surgery_starting" + #define COMPONENT_CANCEL_SURGERY (1<<0) + #define COMPONENT_FORCE_SURGERY (1<<1) diff --git a/code/__DEFINES/dcs/signals/signals_mod.dm b/code/__DEFINES/dcs/signals/signals_mod.dm new file mode 100644 index 0000000000..2533b69852 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mod.dm @@ -0,0 +1,35 @@ +//MODsuit signals +/// Called when a module is selected to be the active one from on_select(obj/item/mod/module/module) +#define COMSIG_MOD_MODULE_SELECTED "mod_module_selected" +/// Called when a MOD deploys one or more of its parts. +#define COMSIG_MOD_DEPLOYED "mod_deployed" +/// Called when a MOD retracts one or more of its parts. +#define COMSIG_MOD_RETRACTED "mod_retracted" +/// Called when a MOD is finished toggling itself. +#define COMSIG_MOD_TOGGLED "mod_toggled" +/// 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) +/// Called when a MOD finishes having a module removed from it. +#define COMSIG_MOD_MODULE_REMOVED "mod_module_removed" +/// Called when a MOD finishes having a module added to it. +#define COMSIG_MOD_MODULE_ADDED "mod_module_added" +/// Called when a MOD is having modules removed from crowbar_act(mob/user, obj/crowbar) +#define COMSIG_MOD_MODULE_REMOVAL "mod_module_removal" + /// Cancels the removal of modules + #define MOD_CANCEL_REMOVAL (1 << 0) +/// Called when a module attempts to activate, however it does. At the end of checks so you can add some yourself, or work on trigger behavior (mob/user) +#define COMSIG_MODULE_TRIGGERED "mod_module_triggered" + /// Cancels activation, with no message. Include feedback on your cancel. + #define MOD_ABORT_USE (1<<0) +/// Called when a module activates, after all checks have passed and cooldown started. +#define COMSIG_MODULE_ACTIVATED "mod_module_activated" +/// Called when a module deactivates, after all checks have passed. +#define COMSIG_MODULE_DEACTIVATED "mod_module_deactivated" +/// Called when a module is used, after all checks have passed and cooldown started. +#define COMSIG_MODULE_USED "mod_module_used" +/// Called when the MODsuit wearer is set. +#define COMSIG_MOD_WEARER_SET "mod_wearer_set" +/// Called when the MODsuit wearer is unset. +#define COMSIG_MOD_WEARER_UNSET "mod_wearer_unset" diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm new file mode 100644 index 0000000000..848458f91c --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_reagent.dm @@ -0,0 +1,2 @@ +///Sent by /datum/reagents/proc/reaction, used for /obj/item/mod/module/springlock +#define COMSIG_ATOM_EXPOSE_REAGENTS "atom_expose_reagents" diff --git a/code/__DEFINES/mod.dm b/code/__DEFINES/mod.dm new file mode 100644 index 0000000000..0e380f9e95 --- /dev/null +++ b/code/__DEFINES/mod.dm @@ -0,0 +1,40 @@ +/// Default value for the max_complexity var on MODsuits +#define DEFAULT_MAX_COMPLEXITY 15 + +/// Default cell drain per process on MODsuits +#define DEFAULT_CHARGE_DRAIN 5 + +/// Default time for a part to seal +#define MOD_ACTIVATION_STEP_TIME (2 SECONDS) + +/// 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 CONTROL_LAYER "control_layer" +#define HELMET_FLAGS "helmet_flags" +#define CHESTPLATE_FLAGS "chestplate_flags" +#define GAUNTLETS_FLAGS "gauntlets_flags" +#define BOOTS_FLAGS "boots_flags" + +#define UNSEALED_LAYER "unsealed_layer" +#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" +#define CAN_OVERSLOT "can_overslot" + +//Defines used to override MOD clothing's icon and worn icon files in the skin. +#define MOD_ICON_OVERRIDE "mod_icon_override" +#define MOD_WORN_ICON_OVERRIDE "mod_worn_icon_override" + +/// 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 920a93a906..dbacbcbda5 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -158,6 +158,18 @@ #define TRAIT_CALCIUM_HEALER "calcium_healer" #define TRAIT_MAGIC_CHOKE "magic_choke" #define TRAIT_CAPTAIN_METABOLISM "captain-metabolism" +/// 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" +/// 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" @@ -213,8 +225,6 @@ #define TRAIT_AUTO_CATCH_ITEM "auto_catch_item" #define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman. #define TRAIT_FREESPRINT "free_sprinting" -#define TRAIT_XRAY_VISION "xray_vision" -#define TRAIT_THERMAL_VISION "thermal_vision" #define TRAIT_NO_TELEPORT "no-teleport" //you just can't #define TRAIT_NO_INTERNALS "no-internals" #define TRAIT_TOXIC_ALCOHOL "alcohol_intolerance" @@ -387,3 +397,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/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index cec25c0047..d02ebb659e 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -53,6 +53,8 @@ GLOBAL_LIST_INIT(maintenance_loot, list( /obj/item/airlock_painter/decal/tile = 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/mod/module/springlock = 1, /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 8f237c4a71..761a1bad4a 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/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index 5ad337445b..ace74c13a8 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -171,3 +171,9 @@ desc = "Lights out, guerilla radio!" database_id = MEDAL_TRAM_SURFER icon = "tram_surfer" + +/datum/award/achievement/misc/springlock + name = "The Man Inside the MODsuit" + desc = "Ignore the warning label on a springlock MODsuit." + database_id = MEDAL_SPRINGLOCK + icon = "springlock" diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm index ad37f770ed..fd72964505 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 + tools = 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/gps.dm b/code/datums/components/gps.dm index f3ff912321..38b6376716 100644 --- a/code/datums/components/gps.dm +++ b/code/datums/components/gps.dm @@ -20,14 +20,22 @@ GLOBAL_LIST_EMPTY(GPS_list) /datum/component/gps/item var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown + /// UI state of GPS, altering when it can be used. + var/datum/ui_state/state = null -/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE, starton = TRUE) +/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE, starton = TRUE, state = null, overlay_state = "working") . = ..() if(. == COMPONENT_INCOMPATIBLE || !isitem(parent)) return COMPONENT_INCOMPATIBLE + + if(isnull(state)) + state = GLOB.default_state + src.state = state + var/atom/A = parent if(starton) - A.add_overlay("working") + if(overlay_state) + A.add_overlay(overlay_state) else tracking = FALSE A.name = "[initial(A.name)] ([gpstag])" @@ -93,6 +101,9 @@ GLOBAL_LIST_EMPTY(GPS_list) ui.open() ui.set_autoupdate(updating) +/datum/component/gps/item/ui_state(mob/user) + return state + /datum/component/gps/item/ui_data(mob/user) var/list/data = list() data["power"] = tracking diff --git a/code/datums/wires/mod.dm b/code/datums/wires/mod.dm new file mode 100644 index 0000000000..7949d4d5f7 --- /dev/null +++ b/code/datums/wires/mod.dm @@ -0,0 +1,56 @@ +/datum/wires/mod + holder_type = /obj/item/mod/control + proper_name = "MOD control unit" + req_knowledge = JOB_SKILL_MASTER + req_skill = JOB_SKILL_TRAINED + +/datum/wires/mod/New(atom/holder) + wires = list(WIRE_DISABLE, WIRE_SHOCK, WIRE_INTERFACE) + add_duds(3) + ..() + +/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 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_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/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 11ae19f7fa..1bd795e4f1 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 @@ -10,7 +10,7 @@ req_access = list(ACCESS_ROBOTICS) state_open = TRUE circuit = /obj/item/circuitboard/machine/cyborgrecharger - occupant_typecache = list(/mob/living/silicon/robot) + occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) var/recharge_speed var/repairs diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index 966985c427..3ca926c055 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -11,6 +11,7 @@ var/obj/item/clothing/head/helmet/space/helmet = null var/obj/item/clothing/mask/mask = null var/obj/item/clothing/shoes/shoes = null + var/obj/item/mod/control/mod = null var/obj/item/storage = null // if you add more storage slots, update cook() to clear their radiation too. @@ -22,6 +23,8 @@ var/mask_type = null /// What type of shoes the unit starts with when spawned. var/shoes_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 @@ -47,6 +50,8 @@ var/message_cooldown /// How long it takes to break out of the SSU. var/breakout_time = 300 + /// How fast it charges cells in a suit + var/charge_rate = 250 /obj/machinery/suit_storage_unit/standard_unit suit_type = /obj/item/clothing/suit/space/eva @@ -58,6 +63,11 @@ 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/sechailer + 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 @@ -68,16 +78,40 @@ mask_type = /obj/item/clothing/mask/breath storage_type = /obj/item/watertank/atmos +/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/breath + storage_type = /obj/item/watertank/atmos + +/obj/machinery/suit_storage_unit/atmosmod + mask_type = /obj/item/clothing/mask/breath + 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 shoes_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 @@ -88,6 +122,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 @@ -96,6 +135,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 @@ -106,15 +155,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 @@ -156,6 +219,8 @@ mask = new mask_type(src) if(shoes_type) shoes = new shoes_type(src) + if(mod_type) + mod = new mod_type(src) if(storage_type) storage = new storage_type(src) update_icon() @@ -165,6 +230,7 @@ QDEL_NULL(helmet) QDEL_NULL(mask) QDEL_NULL(shoes) + QDEL_NULL(mod) QDEL_NULL(storage) return ..() @@ -182,7 +248,7 @@ . += "broken" else . += "open" - if(suit) + if(suit || mod) . += "suit" if(helmet) . += "helm" @@ -203,6 +269,7 @@ helmet = null suit = null mask = null + mod = null storage = null occupant = null @@ -281,6 +348,8 @@ qdel(mask) shoes = null qdel(shoes) + mod = null + qdel(mod) storage = null qdel(storage) // The wires get damaged too. @@ -305,6 +374,9 @@ if(shoes) things_to_clear += shoes things_to_clear += shoes.GetAllContents() + if(mod) + things_to_clear += mod + things_to_clear += mod.GetAllContents() if(storage) things_to_clear += storage things_to_clear += storage.GetAllContents() @@ -322,6 +394,20 @@ if(occupant) dump_contents() +/obj/machinery/suit_storage_unit/process(delta_time) + var/obj/item/stock_parts/cell/cell + 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/proc/shock(mob/user, prb) if(!prob(prb)) var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread @@ -403,6 +489,13 @@ if(!user.transferItemToLoc(I, src)) return shoes = 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!") @@ -459,6 +552,10 @@ data["shoes"] = shoes.name else data["shoes"] = null + if(mod) + data["mod"] = mod.name + else + data["mod"] = null if(storage) data["storage"] = storage.name else @@ -489,7 +586,7 @@ if("uv") if(occupant && safeties) return - else if(!helmet && !mask && !suit && !storage && !occupant) + else if(!helmet && !mask && !mod && !suit && !storage && !occupant) return else if(occupant) @@ -501,7 +598,7 @@ if(!state_open) return - var/static/list/valid_items = list("helmet", "suit", "mask", "shoes", "storage") + var/static/list/valid_items = list("helmet", "suit", "mask", "shoes", "mod", "storage") var/item_name = params["item"] if(item_name in valid_items) var/obj/item/I = vars[item_name] diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 96a26e704c..20aeb628df 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -399,7 +399,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(throwing) throwing.finalize(FALSE) if(loc == user) - if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) + if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(I = src)) return . = FALSE @@ -1285,3 +1285,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/defib.dm b/code/game/objects/items/defib.dm index f680d0034b..aea961860d 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -461,6 +461,8 @@ cooldown = TRUE busy = FALSE update_icon() + if(SEND_SIGNAL(src, COMSIG_DEFIBRILLATOR_SUCCESS) & COMPONENT_DEFIB_STOP) + return if(req_defib) defib.cooldowncheck(user) else @@ -514,6 +516,8 @@ cooldown = TRUE busy = FALSE update_icon() + if(SEND_SIGNAL(src, COMSIG_DEFIBRILLATOR_SUCCESS) & COMPONENT_DEFIB_STOP) + return if(!req_defib) recharge(60) if(req_defib && (defib.cooldowncheck(user))) @@ -632,6 +636,8 @@ defib.deductcharge(revivecost) cooldown = 1 update_icon() + if(SEND_SIGNAL(src, COMSIG_DEFIBRILLATOR_SUCCESS) & COMPONENT_DEFIB_STOP) + return if(req_defib) defib.cooldowncheck(user) else diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index dfd53b3a0d..25e2f1a292 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -239,9 +239,13 @@ return /mob/living/carbon/get_jetpack() - var/obj/item/tank/jetpack/J = back - if(istype(J)) - return J + var/obj/item/I = back + if(istype(I, /obj/item/tank/jetpack)) + return I + else if(istype(I, /obj/item/mod/control)) + var/obj/item/mod/control/C = I + for(var/obj/item/mod/module/jetpack/J in C.modules) + return J /mob/living/carbon/human/get_jetpack() var/obj/item/tank/jetpack/J = ..() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index fb518602fb..da9dc6acea 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -31,6 +31,8 @@ new /obj/item/clothing/head/beret/ce/white(src) new /obj/item/storage/bag/construction(src) new /obj/item/storage/bag/material(src) + new /obj/item/mod/construction/armor/advanced(src) + new /obj/item/mod/module/rad_protection(src) /obj/structure/closet/secure_closet/engineering_electrical name = "electrical supplies locker" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index babb047c8d..ce9ae2e280 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -103,6 +103,8 @@ new /obj/item/storage/lockbox/medal/medical(src) new /obj/item/clothing/suit/hooded/wintercoat/cmo(src) new /obj/item/clothing/head/beret/cmo/blue(src) + new /obj/item/mod/construction/armor/rescue(src) + new /obj/item/mod/module/health_analyzer(src) /obj/structure/closet/secure_closet/animal name = "animal control" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 997132313d..e843514035 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -33,3 +33,4 @@ new /obj/item/clothing/suit/hooded/wintercoat/rd(src) new /obj/item/analyzer/hilbertsanalyzer(src) + new /obj/item/mod/construction/armor/research(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index d96ad3b0d6..a2239caa92 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -17,6 +17,8 @@ new /obj/item/gun/energy/e_gun(src) new /obj/item/door_remote/captain(src) new /obj/item/storage/photo_album/Captain(src) + new /obj/item/mod/construction/armor/magnate(src) + new /obj/item/mod/module/holster(src) /obj/structure/closet/secure_closet/hop name = "\proper head of personnel's locker" @@ -86,6 +88,9 @@ new /obj/item/circuitboard/machine/techfab/department/security(src) new /obj/item/storage/photo_album/HoS(src) new /obj/item/clothing/suit/hooded/wintercoat/hos(src) + new /obj/item/mod/construction/armor/safeguard(src) + new /obj/item/mod/module/jetpack(src) + new /obj/item/mod/module/holster(src) /obj/structure/closet/secure_closet/warden name = "\proper warden's locker" diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm index 125bfe2034..96d9a97b5f 100644 --- a/code/modules/cargo/packs/science.dm +++ b/code/modules/cargo/packs/science.dm @@ -238,3 +238,14 @@ 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 + 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/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 7ce29db178..6ec7d2c438 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -22,7 +22,6 @@ Assistant threat = 0.2 family_heirlooms = list( - /obj/item/storage/toolbox/mechanical/old/heirloom, /obj/item/clothing/gloves/cut/family ) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 31380ea121..7c0c7d2f76 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -337,6 +337,7 @@ on_item_dropped(I) if(I.dropped(src) == ITEM_RELOCATED_BY_DROPPED) return FALSE + SEND_SIGNAL(src, COMSIG_MOB_UNEQUIPPED_ITEM, I, force, newloc, no_move, invdrop, silent) return TRUE //This is a SAFE proc. Use this instead of equip_to_slot()! diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index c5c135f8a5..3ec63cea1a 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -28,8 +28,8 @@ if(prob(40)) if(prob(25)) audible_message("You hear something rumbling inside [src]'s stomach...", \ - "You hear something rumbling.", 4,\ - "Something is rumbling inside your stomach!") + "You hear something rumbling.", 4,\ + "Something is rumbling inside your stomach!") var/obj/item/I = user.get_active_held_item() if(I && I.force) var/d = rand(round(I.force / 4), I.force) @@ -626,12 +626,21 @@ if(M.name == XRAY) sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) see_in_dark = max(see_in_dark, 8) + + if(HAS_TRAIT(src, TRAIT_TRUE_NIGHT_VISION)) + lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + see_in_dark = max(see_in_dark, 8) + + if(HAS_TRAIT(src, TRAIT_MESON_VISION)) + sight |= SEE_TURFS + lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + if(HAS_TRAIT(src, TRAIT_THERMAL_VISION)) - sight |= (SEE_MOBS) + sight |= SEE_MOBS lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) if(HAS_TRAIT(src, TRAIT_XRAY_VISION)) - sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS see_in_dark = max(see_in_dark, 8) if(see_override) diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index 45b7831a77..508c4fced9 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -16,9 +16,15 @@ if(istype(T) && movement_dir && T.allow_thrust(0.01)) return 1 - var/obj/item/tank/jetpack/J = get_jetpack() - if(istype(J) && (movement_dir || J.stabilizers) && J.allow_thrust(0.01, src)) - return 1 + var/obj/item/I = get_jetpack() + if(istype(I, /obj/item/tank/jetpack)) + var/obj/item/tank/jetpack/J = I + if((movement_dir || J.stabilizers) && J.allow_thrust(0.01, src)) + return 1 + else if(istype(I, /obj/item/mod/module/jetpack)) + var/obj/item/mod/module/jetpack/J = I + if((movement_dir || J.stabilizers) && J.allow_thrust()) + return 1 /mob/living/carbon/Moved() . = ..() diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index e5cab7a6f5..af3a0693dd 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -374,7 +374,14 @@ if(equip_to_slot_if_possible(thing, ITEM_SLOT_BACK)) update_inv_hands() return - if(!SEND_SIGNAL(equipped_back, COMSIG_CONTAINS_STORAGE)) // not a storage item + var/datum/component/storage/storage = equipped_back.GetComponent(/datum/component/storage) + if(istype(equipped_back, /obj/item/mod/control)) + var/obj/item/mod/control/C = equipped_back + for(var/obj/item/mod/module/storage/S in C.modules) + if(S.stored) + equipped_back = S.stored + storage = S.stored.GetComponent(/datum/component/storage) + if(!storage) if(!thing) equipped_back.attack_hand(src) else @@ -384,10 +391,11 @@ if(!SEND_SIGNAL(equipped_back, COMSIG_TRY_STORAGE_INSERT, thing, src)) to_chat(src, "You can't fit anything in!") return - if(!equipped_back.contents.len) // nothing to take out - to_chat(src, "There's nothing in your backpack to take out!") + var/atom/real_location = storage.real_location() + if(!real_location.contents.len) // nothing to take out + to_chat(src, "There's nothing in your [equipped_back.name] to take out!") return - var/obj/item/stored = equipped_back.contents[equipped_back.contents.len] + var/obj/item/stored = real_location.contents[real_location.contents.len] if(!stored || stored.on_found(src)) return stored.attack_hand(src) // take out thing from backpack diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 5a9891a787..cee739cb79 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -38,8 +38,8 @@ var/can_be_carded = TRUE var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) var/viewalerts = 0 - var/icon/holo_icon//Female is assigned when AI is created. - var/obj/vehicle/sealed/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. + var/icon/holo_icon //Female is assigned when AI is created. + var/obj/controlled_equipment //A piece of equipment, to determine whether to relaymove or use the AI eye. var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. radiomod = ";" //AIs will, by default, state their laws on the internal radio. var/obj/item/pda/ai/aiPDA @@ -99,6 +99,13 @@ var/display_icon_override var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it. + var/datum/robot_control/robot_control + /// Station alert datum for showing alerts UI + var/datum/station_alert/alert_control + ///remember AI's last location + var/atom/lastloc + interaction_range = INFINITY + /mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) . = ..() if(!target_ai) //If there is no player/brain inside. @@ -173,10 +180,21 @@ GLOB.ai_list -= src GLOB.shuttle_caller_list -= src SSshuttle.autoEvac() - qdel(eyeobj) // No AI, no Eye + QDEL_NULL(eyeobj) // No AI, no Eye + QDEL_NULL(spark_system) + QDEL_NULL(malf_picker) + QDEL_NULL(doomsday_device) + // TODO: Why these no work? + // QDEL_NULL(robot_control) + QDEL_NULL(aiMulti) + // QDEL_NULL(alert_control) malfhack = null - - . = ..() + current = null + Bot = null + controlled_equipment = null + linked_core = null + apc_override = null + return ..() /mob/living/silicon/ai/IgniteMob() fire_stacks = 0 @@ -410,7 +428,7 @@ if (href_list["ai_take_control"]) //Mech domination var/obj/vehicle/sealed/mecha/M = locate(href_list["ai_take_control"]) - if(controlled_mech) + if(controlled_equipment) to_chat(src, "You are already loaded into an onboard computer!") return if(!GLOB.cameranet.checkCameraVis(M)) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 6c799607be..435d1853f6 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -199,6 +199,13 @@ /mob/living/silicon/pai/restrained(ignore_grab) . = FALSE +/mob/living/silicon/pai/can_interact_with(atom/target) + if(istype(target, /obj/item/mod/control)) // A poor workaround for enabling MODsuit control + var/obj/item/mod/control/C = target + if(C.ai == src) + return TRUE + return ..() + // See software.dm for Topic() /mob/living/silicon/pai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index cb815bb2c3..b28d8643cb 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -783,7 +783,7 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) return pick(protection_sources) else return src - if((magic && HAS_TRAIT(src, TRAIT_ANTIMAGIC)) || (holy && HAS_TRAIT(src, TRAIT_HOLY))) + if((magic && HAS_TRAIT(src, TRAIT_ANTIMAGIC)) || (!self && magic && HAS_TRAIT(src, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) || (holy && HAS_TRAIT(src, TRAIT_HOLY))) return src //You can buckle on mobs if you're next to them since most are dense diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 41cf8b4522..1c110fcd53 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -588,7 +588,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp //Can the mob see reagents inside of containers? /mob/proc/can_see_reagents() - return stat == DEAD || silicon_privileges //Dead guys and silicons can always see reagents + return stat == DEAD || silicon_privileges || HAS_TRAIT(src, TRAIT_REAGENT_SCANNER) //Dead guys and silicons can always see reagents /mob/proc/is_blind() SHOULD_BE_PURE(TRUE) diff --git a/code/modules/mod/mod_actions.dm b/code/modules/mod/mod_actions.dm new file mode 100644 index 0000000000..ee28771ef1 --- /dev/null +++ b/code/modules/mod/mod_actions.dm @@ -0,0 +1,94 @@ +/datum/action/item_action/mod + background_icon_state = "bg_tech_blue" + icon_icon = 'icons/mob/actions/actions_mod.dmi' + check_flags = AB_CHECK_CONSCIOUS + var/obj/item/mod/control/mod + /// Whether this action is intended for the AI. Stuff breaks a lot if this is done differently. + var/ai_action = FALSE + +/datum/action/item_action/mod/New(Target) + ..() + if(!istype(Target, /obj/item/mod/control)) + qdel(src) + return + if(ai_action) + background_icon_state = "bg_tech" + +/datum/action/item_action/mod/Grant(mob/user) + mod = target + if(ai_action && user != mod.ai) + return + else if(!ai_action && user == mod.ai) + return + return ..() + +/datum/action/item_action/mod/Remove(mob/user) + if(ai_action && mod && user != mod.ai) + return + else if(!ai_action && mod && user == mod.ai) + return + return ..() + +/datum/action/item_action/mod/Trigger(trigger_flags) + if(!IsAvailable()) + return FALSE + if(mod.malfunctioning && prob(75)) + mod.balloon_alert(usr, "button malfunctions!") + return FALSE + return TRUE + +/datum/action/item_action/mod/deploy + name = "Deploy MODsuit" + desc = "Deploy/Conceal a part of the MODsuit." + button_icon_state = "deploy" + +/datum/action/item_action/mod/deploy/Trigger() + if(!IsAvailable()) + return FALSE + mod.choose_deploy(usr) + return TRUE + +/datum/action/item_action/mod/deploy/ai + ai_action = TRUE + +/datum/action/item_action/mod/activate + name = "Activate MODsuit" + desc = "Activate/Deactivate the MODsuit." + button_icon_state = "activate" + +/datum/action/item_action/mod/activate/Trigger() + if(!IsAvailable()) + return FALSE + mod.toggle_activate(usr) + return TRUE + +/datum/action/item_action/mod/activate/ai + ai_action = TRUE + +/datum/action/item_action/mod/module + name = "Toggle Module" + desc = "Toggle a MODsuit module." + button_icon_state = "module" + +/datum/action/item_action/mod/module/Trigger() + if(!IsAvailable()) + return FALSE + mod.quick_module(usr) + return TRUE + +/datum/action/item_action/mod/module/ai + ai_action = TRUE + +/datum/action/item_action/mod/panel + name = "MODsuit Panel" + desc = "Open the MODsuit's panel." + button_icon_state = "panel" + +/datum/action/item_action/mod/panel/Trigger() + if(!IsAvailable()) + return FALSE + mod.ui_interact(usr) + return TRUE + +/datum/action/item_action/mod/panel/ai + ai_action = TRUE diff --git a/code/modules/mod/mod_activation.dm b/code/modules/mod/mod_activation.dm new file mode 100644 index 0000000000..80cf0a3d95 --- /dev/null +++ b/code/modules/mod/mod_activation.dm @@ -0,0 +1,242 @@ +/// Creates a radial menu from which the user chooses parts of the suit to deploy/retract. Repeats until all parts are extended or retracted. +/obj/item/mod/control/proc/choose_deploy(mob/user) + if(!length(mod_parts)) + return + var/list/display_names = list() + var/list/items = list() + for(var/obj/item/piece as anything in mod_parts) + display_names[piece.name] = REF(piece) + var/image/piece_image = image(icon = piece.icon, icon_state = piece.icon_state) + items += list(piece.name = piece_image) + var/pick = show_radial_menu(user, src, items, custom_check = FALSE, require_near = TRUE, tooltips = TRUE) + if(!pick) + return + var/part_reference = display_names[pick] + var/obj/item/part = locate(part_reference) in mod_parts + if(!istype(part) || user.incapacitated()) + return + if((active && part != helmet) || activating) // SKYRAT EDIT - Let the hair flow - ORIGINAL: if(active || activating) + balloon_alert(user, "deactivate the suit first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + var/parts_to_check = mod_parts - part + if(part.loc == src) + deploy(user, part) + for(var/obj/item/piece as anything in parts_to_check) + if(piece.loc != src) + continue + choose_deploy(user) + break + else + conceal(user, part) + for(var/obj/item/piece as anything in parts_to_check) + if(piece.loc == src) + continue + choose_deploy(user) + break + +/// Deploys a part of the suit onto the user. +/obj/item/mod/control/proc/deploy(mob/user, part) + var/obj/item/piece = part + if(piece == gauntlets && wearer.gloves) + gauntlets.overslot = wearer.gloves + wearer.transferItemToLoc(gauntlets.overslot, gauntlets, force = TRUE) + if(piece == boots && wearer.shoes) + boots.overslot = wearer.shoes + wearer.transferItemToLoc(boots.overslot, boots, force = TRUE) + if(wearer.equip_to_slot_if_possible(piece, piece.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE)) + ADD_TRAIT(piece, TRAIT_NODROP, MOD_TRAIT) + if(!user) + return TRUE + wearer.visible_message(span_notice("[wearer]'s [piece] deploy[piece.p_s()] with a mechanical hiss."), + span_notice("[piece] deploy[piece.p_s()] with a mechanical hiss."), + span_hear("You hear a mechanical hiss.")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + return TRUE + else if(piece.loc != src) + if(!user) + return FALSE + balloon_alert(user, "[piece] already deployed!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + else + if(!user) + return FALSE + balloon_alert(user, "bodypart clothed!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + +/// Retract a part of the suit from the user +/obj/item/mod/control/proc/conceal(mob/user, part) + var/obj/item/piece = part + REMOVE_TRAIT(piece, TRAIT_NODROP, MOD_TRAIT) + if(wearer) + wearer.transferItemToLoc(piece, src, force = TRUE) + if(piece == gauntlets) + gauntlets.show_overslot() + if(piece == boots) + boots.show_overslot() + if(!user) + return + wearer.visible_message(span_notice("[wearer]'s [piece] retract[piece.p_s()] back into [src] with a mechanical hiss."), + span_notice("[piece] retract[piece.p_s()] back into [src] with a mechanical hiss."), + span_hear("You hear a mechanical hiss.")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + +/// Starts the activation sequence, where parts of the suit activate one by one until the whole suit is on +/obj/item/mod/control/proc/toggle_activate(mob/user, force_deactivate = FALSE) + if(!wearer) + if(!force_deactivate) + balloon_alert(user, "put suit on back!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(!force_deactivate && (SEND_SIGNAL(src, COMSIG_MOD_ACTIVATE, user) & MOD_CANCEL_ACTIVATE)) + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + for(var/obj/item/part as anything in mod_parts) + if(!force_deactivate && part.loc == src) + balloon_alert(user, "deploy all parts first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(!cell?.charge && !force_deactivate) + balloon_alert(user, "suit not powered!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(open && !force_deactivate) + balloon_alert(user, "close the suit panel!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(activating) + if(!force_deactivate) + balloon_alert(user, "suit already [active ? "shutting down" : "starting up"]!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + for(var/obj/item/mod/module/module as anything in modules) + if(!module.active || module.allowed_inactive) + continue + module.on_deactivation() + activating = TRUE + to_chat(wearer, span_notice("MODsuit [active ? "shutting down" : "starting up"].")) + if(ai) + to_chat(ai, span_notice("MODsuit [active ? "shutting down" : "starting up"].")) + + if(force_deactivate) + seal_part(boots, seal = FALSE) + seal_part(gauntlets, seal = FALSE) + seal_part(chestplate,seal = FALSE) + seal_part(helmet, seal = FALSE) + finish_activation(on = FALSE) + activating = FALSE + to_chat(wearer, span_notice("Systems shut down. Parts unsealed. Goodbye, [wearer].")) + if(ai) + to_chat(ai, span_notice("SYSTEMS DEACTIVATED. GOODBYE: \"[ai]\"")) + playsound(src, 'sound/machines/synth_no.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000) + return TRUE + + if(do_after(wearer, activation_step_time, target = wearer, required_mobility_flags = NONE)) + to_chat(wearer, span_notice("[boots] [active ? "relax their grip on your legs" : "seal around your feet"].")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + seal_part(boots, seal = !active) + else + return toggle_activate_fail() + if(do_after(wearer, activation_step_time, target = wearer, required_mobility_flags = NONE)) + to_chat(wearer, span_notice("[gauntlets] [active ? "become loose around your fingers" : "tighten around your fingers and wrists"].")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + seal_part(gauntlets, seal = !active) + else + return toggle_activate_fail() + if(do_after(wearer, activation_step_time, target = wearer, required_mobility_flags = NONE)) + to_chat(wearer, span_notice("[chestplate] [active ? "releases your chest" : "cinches tightly against your chest"].")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + seal_part(chestplate,seal = !active) + else + return toggle_activate_fail() + if(do_after(wearer, activation_step_time, target = wearer, required_mobility_flags = NONE)) + to_chat(wearer, span_notice("[helmet] hisses [active ? "open" : "closed"].")) + playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + seal_part(helmet, seal = !active) + else + return toggle_activate_fail() + if(do_after(wearer, activation_step_time, target = wearer, required_mobility_flags = NONE)) + to_chat(wearer, span_notice("Systems [active ? "shut down. Parts unsealed. Goodbye" : "started up. Parts sealed. Welcome"], [wearer].")) + if(ai) + to_chat(ai, span_notice("SYSTEMS [active ? "DEACTIVATED. GOODBYE" : "ACTIVATED. WELCOME"]: \"[ai]\"")) + finish_activation(on = !active) + if(active) + playsound(src, 'sound/machines/synth_yes.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000) + SEND_SOUND(wearer, sound('sound/mecha/nominal.ogg',volume=50)) + else + playsound(src, 'sound/machines/synth_no.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000) + else + return toggle_activate_fail() + activating = FALSE + return TRUE + +/obj/item/mod/control/proc/toggle_activate_fail() + seal_part(boots, seal = active) + seal_part(gauntlets, seal = active) + seal_part(chestplate,seal = active) + seal_part(helmet, seal = active) + to_chat(wearer, span_warning("[active ? "Shut down" : "Start up"] cancelled.")) + finish_activation(on = active) + activating = FALSE + return FALSE + +///Seals or unseals the given part +/obj/item/mod/control/proc/seal_part(obj/item/clothing/part, seal) + if(seal) + part.clothing_flags |= part.visor_flags + part.flags_inv |= part.visor_flags_inv + part.flags_cover |= part.visor_flags_cover + part.heat_protection = initial(part.heat_protection) + part.cold_protection = initial(part.cold_protection) + else + part.flags_cover &= ~part.visor_flags_cover + part.flags_inv &= ~part.visor_flags_inv + part.clothing_flags &= ~part.visor_flags + part.heat_protection = NONE + part.cold_protection = NONE + part.icon_state = "[skin]-[initial(part.icon_state)][seal ? "-sealed" : ""]" + part.item_state = "[skin]-[initial(part.item_state)][seal ? "-sealed" : ""]" + if(part == boots) + wearer.update_inv_shoes() + if(part == gauntlets) + wearer.update_inv_gloves() + if(part == chestplate) + wearer.update_inv_wear_suit() + wearer.update_inv_w_uniform() + if(part == helmet) + if(seal) + helmet.alternate_worn_layer = null + else + helmet.alternate_worn_layer = helmet.alternate_layer + wearer.update_inv_head() + wearer.update_inv_wear_mask() + wearer.update_hair() + +/// Finishes the suit's activation, starts processing +/obj/item/mod/control/proc/finish_activation(on) + icon_state = "[skin]-control[on ? "-sealed" : ""]" + slowdown = on ? slowdown_active : slowdown_inactive + if(on) + for(var/obj/item/mod/module/module as anything in modules) + module.on_suit_activation() + START_PROCESSING(SSobj, src) + else + for(var/obj/item/mod/module/module as anything in modules) + module.on_suit_deactivation() + STOP_PROCESSING(SSobj, src) + wearer.update_equipment_speed_mods() + active = on + wearer.update_inv_back() + +/// Quickly deploys all the suit parts and if successful, seals them and turns on the suit. Intended mostly for outfits. +/obj/item/mod/control/proc/quick_activation() + var/seal = TRUE + for(var/obj/item/part in mod_parts) + if(!deploy(null, part)) + seal = FALSE + if(!seal) + return + for(var/obj/item/part in mod_parts) + seal_part(part, seal = TRUE) + finish_activation(on = TRUE) diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm new file mode 100644 index 0000000000..46da99f052 --- /dev/null +++ b/code/modules/mod/mod_ai.dm @@ -0,0 +1,199 @@ +/obj/item/mod/control/transfer_ai(interaction, mob/user, mob/living/silicon/ai/intAI, obj/item/aicard/card) + . = ..() + if(!.) + return + if(!open) //mod must be open + balloon_alert(user, "suit must be open to transfer!") + return + switch(interaction) + if(AI_TRANS_TO_CARD) + if(!ai) + balloon_alert(user, "no AI in suit!") + return + if(!isAI(ai)) + balloon_alert(user, "onboard AI cannot fit in this card!") + return + balloon_alert(user, "transferring to card...") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return + if(!ai) + return + intAI = ai + intAI.ai_restore_power()//So the AI initially has power. + intAI.control_disabled = TRUE + intAI.radio_enabled = FALSE + intAI.disconnect_shell() + intAI.forceMove(card) + card.AI = intAI + for(var/datum/action/action as anything in actions) + action.Remove(intAI) + intAI.controlled_equipment = null + intAI.remote_control = null + balloon_alert(intAI, "transferred to a card") + balloon_alert(user, "AI transferred to card") + ai = null + + if(AI_TRANS_FROM_CARD) //Using an AI card to upload to the suit. + intAI = card.AI + if(!intAI) + balloon_alert(user, "no AI in card!") + return + if(ai) + balloon_alert(user, "already has AI!") + return + if(intAI.deployed_shell) //Recall AI if shelled so it can be checked for a client + intAI.disconnect_shell() + if(intAI.stat || !intAI.client) + balloon_alert(user, "AI unresponsive!") + return + balloon_alert(user, "transferring to suit...") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return + if(ai) + return + balloon_alert(user, "AI transferred to suit") + ai_enter_mod(intAI) + card.AI = null + +/obj/item/mod/control/proc/ai_enter_mod(mob/living/silicon/ai/new_ai) + new_ai.control_disabled = FALSE + new_ai.radio_enabled = TRUE + new_ai.ai_restore_power() + new_ai.cancel_camera() + new_ai.controlled_equipment = src + new_ai.remote_control = src + new_ai.forceMove(src) + ai = new_ai + balloon_alert(new_ai, "transferred to a suit") + for(var/datum/action/action as anything in actions) + action.Grant(ai) + +/** + * Simple proc to insert the pAI into the MODsuit. + * + * user - The person trying to put the pAI into the MODsuit. + * card - The pAI card we're slotting in the MODsuit. + */ + +/obj/item/mod/control/proc/insert_pai(mob/user, obj/item/paicard/card) + if(ai) + balloon_alert(user, "AI already installed!") + return + if(!card.pai || !card.pai.mind) + balloon_alert(user, "pAI unresponsive!") + return + balloon_alert(user, "transferring to suit...") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return FALSE + if(!user.transferItemToLoc(card, src)) + return + + card.pai.canholo = FALSE + ai = card.pai + balloon_alert(user, "pAI transferred to suit") + balloon_alert(ai, "transferred to a suit") + ai.remote_control = src + for(var/datum/action/action as anything in actions) + action.Grant(ai) + return TRUE + +/** + * Simple proc to extract the pAI from the MODsuit. It's the proc to call if you want to take it out, + * remove_pai() is there so atom_destruction() doesn't have any risk of sleeping. + * + * user - The person trying to take out the pAI from the MODsuit. + * forced - Whether or not we skip the checks and just eject the pAI. Defaults to FALSE. + * feedback - Whether to give feedback via balloon alerts or not. Defaults to TRUE. + */ +/obj/item/mod/control/proc/extract_pai(mob/user, forced = FALSE, feedback = TRUE) + if(!ai) + if(user && feedback) + balloon_alert(user, "no pAI to remove!") + return + if(!ispAI(ai)) + if(user && feedback) + balloon_alert(user, "onboard AI cannot fit in this card!") + return + if(!forced) + if(!open) + if(user && feedback) + balloon_alert(user, "open the suit panel!") + return FALSE + if(!do_after(user, 5 SECONDS, target = src)) + if(user && feedback) + balloon_alert(user, "interrupted!") + return FALSE + + remove_pai(feedback) + + if(feedback && user) + balloon_alert(user, "pAI removed from the suit") + +/** + * Simple proc that handles the safe removal of the pAI from a MOD control unit. + * + * Arguments: + * * feedback - Whether or not we want to give balloon alert feedback to the ai. Defaults to FALSE. + */ +/obj/item/mod/control/proc/remove_pai(feedback = FALSE) + if(!ispAI(ai)) + return + var/mob/living/silicon/pai/pai = ai + var/turf/drop_off = get_turf(src) + if(drop_off) // In case there's no drop_off, the pAI will simply get deleted. + pai.card.forceMove(drop_off) + + for(var/datum/action/action as anything in actions) + if(action.owner == pai) + action.Remove(pai) + + if(feedback) + balloon_alert(pai, "removed from a suit") + pai.remote_control = null + pai.canholo = TRUE + pai = null + ai = null + +#define MOVE_DELAY 2 +#define WEARER_DELAY 1 +#define LONE_DELAY 5 +#define CELL_PER_STEP (DEFAULT_CHARGE_DRAIN * 2.5) +#define AI_FALL_TIME (1 SECONDS) + +/obj/item/mod/control/relaymove(mob/user, direction) + if((!active && wearer) || !cell || cell.charge < CELL_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer?.pulledby?.grab_state > GRAB_PASSIVE)) + return FALSE + var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? SQRT_2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY) + if(wearer && !wearer.Process_Spacemove(direction)) + return FALSE + else if(!wearer && (!has_gravity() || !isturf(loc))) + return FALSE + COOLDOWN_START(src, cooldown_mod_move, movedelay * timemodifier + slowdown) + cell.charge = max(0, cell.charge - CELL_PER_STEP) + playsound(src, 'sound/mecha/mechmove01.ogg', 25, TRUE) + if(ismovable(wearer?.loc)) + return wearer.loc.relaymove(wearer, direction) + else if(wearer) + ADD_TRAIT(wearer, TRAIT_MOBILITY_NOREST, MOD_TRAIT) + addtimer(CALLBACK(src, .proc/ai_fall), AI_FALL_TIME, TIMER_UNIQUE | TIMER_OVERRIDE) + var/atom/movable/mover = wearer || src + return step(mover, direction) + +#undef MOVE_DELAY +#undef WEARER_DELAY +#undef LONE_DELAY +#undef CELL_PER_STEP +#undef AI_FALL_TIME + +/obj/item/mod/control/proc/ai_fall() + if(!wearer) + return + REMOVE_TRAIT(wearer, TRAIT_MOBILITY_NOREST, MOD_TRAIT) + +/obj/item/mod/control/ui_state(mob/user) + if(user == ai) + return GLOB.contained_state + return ..() diff --git a/code/modules/mod/mod_clothes.dm b/code/modules/mod/mod_clothes.dm new file mode 100644 index 0000000000..a7e4c0cb1d --- /dev/null +++ b/code/modules/mod/mod_clothes.dm @@ -0,0 +1,138 @@ +/obj/item/clothing/head/helmet/space/mod + name = "MOD helmet" + desc = "A helmet for a MODsuit." + icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi' + mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi' + anthro_mob_worn_overlay = 'icons/mob/clothing/modsuit/mod_clothing_anthro.dmi' + icon_state = "helmet" + item_state = "helmet" + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10) + body_parts_covered = HEAD + heat_protection = HEAD + cold_protection = HEAD + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + clothing_flags = THICKMATERIAL + resistance_flags = NONE + flash_protect = 0 + clothing_flags = NONE + flags_inv = HIDEFACIALHAIR + flags_cover = NONE + visor_flags = THICKMATERIAL|STOPSPRESSUREDAMAGE + visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT + visor_flags_cover = HEADCOVERSMOUTH|HEADCOVERSEYES + var/alternate_layer = NECK_LAYER + var/obj/item/mod/control/mod + mutantrace_variation = STYLE_MUZZLE + +/obj/item/clothing/head/helmet/space/mod/Destroy() + if(!QDELETED(mod)) + mod.helmet = null + mod.mod_parts -= src + QDEL_NULL(mod) + return ..() + +/obj/item/clothing/suit/space/mod + name = "MOD chestplate" + desc = "A chestplate for a MODsuit." + icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi' + mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi' + anthro_mob_worn_overlay = 'icons/mob/clothing/modsuit/mod_clothing_anthro.dmi' + icon_state = "chestplate" + item_state = "chestplate" + blood_overlay_type = "armor" + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10) + body_parts_covered = CHEST|GROIN + heat_protection = CHEST|GROIN + cold_protection = CHEST|GROIN + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + clothing_flags = THICKMATERIAL + flags_inv = HIDETAUR + visor_flags = STOPSPRESSUREDAMAGE + visor_flags_inv = HIDEJUMPSUIT + allowed = list(/obj/item/flashlight, /obj/item/tank/internals) + resistance_flags = NONE + var/obj/item/mod/control/mod + mutantrace_variation = STYLE_DIGITIGRADE + +/obj/item/clothing/suit/space/mod/Destroy() + if(!QDELETED(mod)) + mod.chestplate = null + mod.mod_parts -= src + QDEL_NULL(mod) + return ..() + +/obj/item/clothing/gloves/mod + name = "MOD gauntlets" + desc = "A pair of gauntlets for a MODsuit." + icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi' + mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi' + icon_state = "gauntlets" + item_state = "gauntlets" + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10) + body_parts_covered = HANDS|ARMS + heat_protection = HANDS|ARMS + cold_protection = HANDS|ARMS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + clothing_flags = THICKMATERIAL + resistance_flags = NONE + var/obj/item/mod/control/mod + var/obj/item/clothing/overslot + mutantrace_variation = STYLE_NO_ANTHRO_ICON + +/obj/item/clothing/gloves/mod/Destroy() + if(!QDELETED(mod)) + mod.gauntlets = null + mod.mod_parts -= src + QDEL_NULL(mod) + return ..() + +/// Replaces these gloves on the wearer with the overslot ones + +/obj/item/clothing/gloves/mod/proc/show_overslot() + if(!overslot) + return + if(!mod.wearer.equip_to_slot_if_possible(overslot, overslot.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE)) + mod.wearer.dropItemToGround(overslot, force = TRUE) + overslot = null + +/obj/item/clothing/shoes/mod + name = "MOD boots" + desc = "A pair of boots for a MODsuit." + icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi' + mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi' + anthro_mob_worn_overlay = 'icons/mob/clothing/modsuit/mod_clothing_anthro.dmi' + icon_state = "boots" + item_state = "boots" + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10) + body_parts_covered = FEET|LEGS + heat_protection = FEET|LEGS + cold_protection = FEET|LEGS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + clothing_flags = THICKMATERIAL + resistance_flags = NONE + item_flags = NONE + var/obj/item/mod/control/mod + var/obj/item/clothing/overslot + mutantrace_variation = STYLE_DIGITIGRADE + +/obj/item/clothing/shoes/mod/Destroy() + if(!QDELETED(mod)) + mod.boots = null + mod.mod_parts -= src + QDEL_NULL(mod) + return ..() + +/// Replaces these shoes on the wearer with the overslot ones +/obj/item/clothing/shoes/mod/proc/show_overslot() + if(!overslot) + return + if(!mod.wearer.equip_to_slot_if_possible(overslot, overslot.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE)) + mod.wearer.dropItemToGround(overslot, force = TRUE) + overslot = null + +/obj/item/clothing/shoes/mod/negates_gravity() + return clothing_flags & NOSLIP diff --git a/code/modules/mod/mod_construction.dm b/code/modules/mod/mod_construction.dm new file mode 100644 index 0000000000..11eef7cb11 --- /dev/null +++ b/code/modules/mod/mod_construction.dm @@ -0,0 +1,288 @@ +/obj/item/mod/construction + desc = "A part used in MOD construction." + icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + item_state = "rack_parts" + +/obj/item/mod/construction/helmet + name = "MOD helmet" + icon_state = "helmet" + +/obj/item/mod/construction/chestplate + name = "MOD chestplate" + icon_state = "chestplate" + +/obj/item/mod/construction/gauntlets + name = "MOD gauntlets" + icon_state = "gauntlets" + +/obj/item/mod/construction/boots + name = "MOD boots" + icon_state = "boots" + +/obj/item/mod/construction/core + name = "MOD core" + icon_state = "mod-core-standard" + desc = "Growing in the most lush, fertile areas of the planet Sprout, there is a crystal known as the Heartbloom. \ + These rare, organic piezoelectric crystals are of incredible cultural significance to the artist castes of the Ethereals, \ + owing to their appearance; which is exactly similar to that of an Ethereal's heart. \n\ + Which one you have in your suit is unclear, but either way, \ + it's been repurposed to be an internal power source for a Modular Outerwear Device." + +/obj/item/mod/construction/broken_core + name = "broken MOD core" + icon_state = "mod-core" + desc = "An internal power source for a Modular Outerwear Device. You don't seem to be able to source any power from this one, though." + +/obj/item/mod/construction/broken_core/examine(mob/user) + . = ..() + . += span_notice("You could repair it with a screwdriver...") + +/obj/item/mod/construction/broken_core/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(!tool.use_tool(src, user, 5 SECONDS, volume = 30)) + return + new /obj/item/mod/construction/core(drop_location()) + qdel(src) + +/obj/item/mod/construction/armor + name = "MOD armor plates" + desc = "Armor plates used to finish a MOD." + icon_state = "standard-plating" + var/datum/mod_theme/theme = /datum/mod_theme + +/obj/item/mod/construction/armor/Initialize(mapload) + . = ..() + var/datum/mod_theme/used_theme = GLOB.mod_themes[theme] + name = "MOD [used_theme.name] armor plates" + desc = "[desc] [used_theme.desc]" + icon_state = "[used_theme.default_skin]-plating" + +/obj/item/mod/construction/armor/engineering + theme = /datum/mod_theme/engineering + +/obj/item/mod/construction/armor/atmospheric + theme = /datum/mod_theme/atmospheric + +/obj/item/mod/construction/armor/advanced + theme = /datum/mod_theme/advanced + +/obj/item/mod/construction/armor/mining + theme = /datum/mod_theme/mining + +/obj/item/mod/construction/armor/medical + theme = /datum/mod_theme/medical + +/obj/item/mod/construction/armor/rescue + theme = /datum/mod_theme/rescue + +/obj/item/mod/construction/armor/security + theme = /datum/mod_theme/security + +/obj/item/mod/construction/armor/safeguard + theme = /datum/mod_theme/safeguard + +/obj/item/mod/construction/armor/research + theme = /datum/mod_theme/research + +/obj/item/mod/construction/armor/cosmohonk + theme = /datum/mod_theme/cosmohonk + +/obj/item/mod/construction/armor/magnate + theme = /datum/mod_theme/magnate + +#define START_STEP "start" +#define CORE_STEP "core" +#define SCREWED_CORE_STEP "screwed_core" +#define HELMET_STEP "helmet" +#define CHESTPLATE_STEP "chestplate" +#define GAUNTLETS_STEP "gauntlets" +#define BOOTS_STEP "boots" +#define WRENCHED_ASSEMBLY_STEP "wrenched_assembly" +#define SCREWED_ASSEMBLY_STEP "screwed_assembly" + +/obj/item/mod/construction/shell + name = "MOD shell" + icon_state = "mod-construction_start" + desc = "A MOD shell." + var/obj/item/core + var/obj/item/helmet + var/obj/item/chestplate + var/obj/item/gauntlets + var/obj/item/boots + var/step = START_STEP + +/obj/item/mod/construction/shell/examine(mob/user) + . = ..() + var/display_text + switch(step) + if(START_STEP) + display_text = "It looks like it's missing a MOD core..." + if(CORE_STEP) + display_text = "The core seems loose..." + if(SCREWED_CORE_STEP) + display_text = "It looks like it's missing a helmet..." + if(HELMET_STEP) + display_text = "It looks like it's missing a chestplate..." + if(CHESTPLATE_STEP) + display_text = "It looks like it's missing gauntlets..." + if(GAUNTLETS_STEP) + display_text = "It looks like it's missing boots..." + if(BOOTS_STEP) + display_text = "The assembly seems unsecured..." + if(WRENCHED_ASSEMBLY_STEP) + display_text = "The assembly seems loose..." + if(SCREWED_ASSEMBLY_STEP) + display_text = "All it's missing is external armor..." + . += span_notice(display_text) + +/obj/item/mod/construction/shell/attackby(obj/item/part, mob/user, params) + . = ..() + switch(step) + if(START_STEP) + if(!istype(part, /obj/item/mod/construction/core)) + return + if(!user.transferItemToLoc(part, src)) + balloon_alert(user, "core stuck to your hand!") + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "core inserted") + core = part + step = CORE_STEP + if(CORE_STEP) + if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "core screwed") + step = SCREWED_CORE_STEP + else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + core.forceMove(drop_location()) + balloon_alert(user, "core taken out") + step = START_STEP + if(SCREWED_CORE_STEP) + if(istype(part, /obj/item/mod/construction/helmet)) //Construct + if(!user.transferItemToLoc(part, src)) + balloon_alert(user, "helmet stuck to your hand!") + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "helmet added") + helmet = part + step = HELMET_STEP + else if(part.tool_behaviour == TOOL_SCREWDRIVER) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "core unscrewed") + step = CORE_STEP + if(HELMET_STEP) + if(istype(part, /obj/item/mod/construction/chestplate)) //Construct + if(!user.transferItemToLoc(part, src)) + balloon_alert(user, "chestplate stuck to your hand!") + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "chestplate added") + chestplate = part + step = CHESTPLATE_STEP + else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + helmet.forceMove(drop_location()) + balloon_alert(user, "helmet removed") + helmet = null + step = SCREWED_CORE_STEP + if(CHESTPLATE_STEP) + if(istype(part, /obj/item/mod/construction/gauntlets)) //Construct + if(!user.transferItemToLoc(part, src)) + balloon_alert(user, "gauntlets stuck to your hand!") + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "gauntlets added") + gauntlets = part + step = GAUNTLETS_STEP + else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + chestplate.forceMove(drop_location()) + balloon_alert(user, "chestplate removed") + chestplate = null + step = HELMET_STEP + if(GAUNTLETS_STEP) + if(istype(part, /obj/item/mod/construction/boots)) //Construct + if(!user.transferItemToLoc(part, src)) + balloon_alert(user, "boots added") + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "You fit [part] onto [src].") + boots = part + step = BOOTS_STEP + else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + gauntlets.forceMove(drop_location()) + balloon_alert(user, "gauntlets removed") + gauntlets = null + step = CHESTPLATE_STEP + if(BOOTS_STEP) + if(part.tool_behaviour == TOOL_WRENCH) //Construct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "assembly secured") + step = WRENCHED_ASSEMBLY_STEP + else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + boots.forceMove(drop_location()) + balloon_alert(user, "boots removed") + boots = null + step = GAUNTLETS_STEP + if(WRENCHED_ASSEMBLY_STEP) + if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "assembly screwed") + step = SCREWED_ASSEMBLY_STEP + else if(part.tool_behaviour == TOOL_WRENCH) //Deconstruct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "assembly unsecured") + step = BOOTS_STEP + if(SCREWED_ASSEMBLY_STEP) + if(istype(part, /obj/item/mod/construction/armor)) //Construct + var/obj/item/mod/construction/armor/external_armor = part + if(!user.transferItemToLoc(part, src)) + return + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + balloon_alert(user, "suit finished") + var/obj/item/modsuit = new /obj/item/mod/control(drop_location(), external_armor.theme) + qdel(src) + user.put_in_hands(modsuit) + else if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct + if(part.use_tool(src, user, 0, volume=30)) + balloon_alert(user, "assembly unscrewed") + step = SCREWED_ASSEMBLY_STEP + update_icon_state() + +/obj/item/mod/construction/shell/update_icon_state() + . = ..() + icon_state = "mod-construction_[step]" + +/obj/item/mod/construction/shell/Destroy() + QDEL_NULL(core) + QDEL_NULL(helmet) + QDEL_NULL(chestplate) + QDEL_NULL(gauntlets) + QDEL_NULL(boots) + return ..() + +/obj/item/mod/construction/shell/handle_atom_del(atom/deleted_atom) + if(deleted_atom == core) + core = null + if(deleted_atom == helmet) + helmet = null + if(deleted_atom == chestplate) + chestplate = null + if(deleted_atom == gauntlets) + gauntlets = null + if(deleted_atom == boots) + boots = null + return ..() + +#undef START_STEP +#undef CORE_STEP +#undef SCREWED_CORE_STEP +#undef HELMET_STEP +#undef CHESTPLATE_STEP +#undef GAUNTLETS_STEP +#undef BOOTS_STEP +#undef WRENCHED_ASSEMBLY_STEP +#undef SCREWED_ASSEMBLY_STEP diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm new file mode 100644 index 0000000000..0853d04f82 --- /dev/null +++ b/code/modules/mod/mod_control.dm @@ -0,0 +1,575 @@ +/// MODsuits, trade-off between armor and utility +/obj/item/mod + name = "Base MOD" + desc = "You should not see this, yell at a coder!" + icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi' + mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi' + icon_state = "standard-control" + item_state = "standard-control" + mutantrace_variation = STYLE_NO_ANTHRO_ICON + +/obj/item/mod/control + name = "MOD control unit" + desc = "The control unit of a Modular Outerwear Device, a powered, back-mounted suit that protects against various environments." + icon_state = "control" + item_state = "control" + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + strip_delay = 10 SECONDS + slowdown = 2 + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10, RAD = 0) + actions_types = list( + /datum/action/item_action/mod/deploy, + /datum/action/item_action/mod/activate, + /datum/action/item_action/mod/module, + /datum/action/item_action/mod/panel, + /datum/action/item_action/mod/deploy/ai, + /datum/action/item_action/mod/activate/ai, + /datum/action/item_action/mod/module/ai, + /datum/action/item_action/mod/panel/ai, + ) + resistance_flags = NONE + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + permeability_coefficient = 0.01 + siemens_coefficient = 0.5 + alternate_worn_layer = BODY_FRONT_LAYER + /// The MOD's theme, decides on some stuff like armor and statistics. + var/datum/mod_theme/theme = /datum/mod_theme + /// Looks of the MOD. + var/skin = "standard" + /// Theme of the MOD TGUI + var/ui_theme = "ntos" + /// If the suit is deployed and turned on. + var/active = FALSE + /// If the suit wire/module hatch is open. + var/open = FALSE + /// If the suit is malfunctioning. + var/malfunctioning = FALSE + /// If the suit is currently activating/deactivating. + var/activating = FALSE + /// How long the MOD is electrified for. + var/seconds_electrified = MACHINE_NOT_ELECTRIFIED + /// If the suit interface is broken. + var/interface_break = FALSE + /// How much module complexity can this MOD carry. + var/complexity_max = DEFAULT_MAX_COMPLEXITY + /// How much module complexity this MOD is carrying. + var/complexity = 0 + /// Power usage of the MOD. + var/cell_drain = DEFAULT_CHARGE_DRAIN + /// Slowdown of the MOD when not active. + var/slowdown_inactive = 2 + /// Slowdown of the MOD when active. + var/slowdown_active = 1 + /// How long this MOD takes each part to seal. + var/activation_step_time = MOD_ACTIVATION_STEP_TIME + /// MOD cell. + var/obj/item/stock_parts/cell/cell + /// MOD helmet. + var/obj/item/clothing/head/helmet/space/mod/helmet + /// MOD chestplate. + var/obj/item/clothing/suit/space/mod/chestplate + /// MOD gauntlets. + var/obj/item/clothing/gloves/mod/gauntlets + /// MOD boots. + var/obj/item/clothing/shoes/mod/boots + /// List of parts (helmet, chestplate, gauntlets, boots). + var/list/mod_parts = list() + /// Modules the MOD should spawn with. + var/list/initial_modules = list() + /// Modules the MOD currently possesses. + var/list/modules = list() + /// Currently used module. + var/obj/item/mod/module/selected_module + /// AI/pAI mob inhabiting the MOD. + var/mob/living/silicon/ai + /// Delay between moves as AI. + var/movedelay = 0 + /// Cooldown for AI moves. + COOLDOWN_DECLARE(cooldown_mod_move) + /// Person wearing the MODsuit. + var/mob/living/carbon/human/wearer + +/obj/item/mod/control/Initialize(mapload, new_theme, new_skin) + . = ..() + if(new_theme) + theme = new_theme + theme = GLOB.mod_themes[theme] + slowdown_inactive = theme.slowdown_inactive + slowdown_active = theme.slowdown_active + slowdown = slowdown_inactive + complexity_max = theme.complexity_max + skin = new_skin || theme.default_skin + ui_theme = theme.ui_theme + cell_drain = theme.cell_drain + initial_modules += theme.inbuilt_modules + wires = new /datum/wires/mod(src) + if(ispath(cell)) + cell = new cell(src) + helmet = new /obj/item/clothing/head/helmet/space/mod(src) + helmet.mod = src + mod_parts += helmet + chestplate = new /obj/item/clothing/suit/space/mod(src) + chestplate.mod = src + mod_parts += chestplate + gauntlets = new /obj/item/clothing/gloves/mod(src) + gauntlets.mod = src + mod_parts += gauntlets + boots = new /obj/item/clothing/shoes/mod(src) + boots.mod = src + mod_parts += boots + var/list/all_parts = mod_parts.Copy() + src + for(var/obj/item/piece as anything in all_parts) + piece.name = "[theme.name] [piece.name]" + piece.desc = "[piece.desc] [theme.desc]" + piece.armor = getArmor(arglist(theme.armor)) + piece.resistance_flags = theme.resistance_flags + piece.heat_protection = NONE + piece.cold_protection = NONE + piece.max_heat_protection_temperature = theme.max_heat_protection_temperature + piece.min_cold_protection_temperature = theme.min_cold_protection_temperature + piece.permeability_coefficient = theme.permeability_coefficient + piece.siemens_coefficient = theme.siemens_coefficient + piece.icon_state = "[skin]-[initial(piece.icon_state)]" + piece.item_state = "[skin]-[initial(piece.item_state)]" + update_flags() + for(var/obj/item/mod/module/module as anything in initial_modules) + module = new module(src) + install(module) + RegisterSignal(src, COMSIG_ATOM_EXITED, .proc/on_exit) + movedelay = CONFIG_GET(number/movedelay/run_delay) + +/obj/item/mod/control/Destroy() + if(active) + STOP_PROCESSING(SSobj, src) + var/atom/deleting_atom + if(!QDELETED(helmet)) + deleting_atom = helmet + helmet.mod = null + helmet = null + mod_parts -= deleting_atom + qdel(deleting_atom) + if(!QDELETED(chestplate)) + deleting_atom = chestplate + chestplate.mod = null + chestplate = null + mod_parts -= deleting_atom + qdel(deleting_atom) + if(!QDELETED(gauntlets)) + deleting_atom = gauntlets + gauntlets.mod = null + gauntlets = null + mod_parts -= deleting_atom + qdel(deleting_atom) + if(!QDELETED(boots)) + deleting_atom = boots + boots.mod = null + boots = null + mod_parts -= deleting_atom + qdel(deleting_atom) + for(var/obj/item/mod/module/module as anything in modules) + module.mod = null + modules -= module + QDEL_NULL(ai) + QDEL_NULL(wires) + QDEL_NULL(cell) + return ..() + +/obj/item/mod/control/process(delta_time) + if(seconds_electrified > MACHINE_NOT_ELECTRIFIED) + seconds_electrified-- + if((!cell || !cell.charge) && active && !activating) + power_off() + return PROCESS_KILL + var/malfunctioning_charge_drain = 0 + if(malfunctioning) + malfunctioning_charge_drain = rand(1,20) + cell.charge = max(0, cell.charge - (cell_drain + malfunctioning_charge_drain)*delta_time) + update_cell_alert() + for(var/obj/item/mod/module/module as anything in modules) + if(malfunctioning && module.active && DT_PROB(5, delta_time)) + module.on_deactivation() + module.on_process(delta_time) + +/obj/item/mod/control/equipped(mob/user, slot) + ..() + if(slot == ITEM_SLOT_BACK) + set_wearer(user) + else if(wearer) + unset_wearer() + +/obj/item/mod/control/dropped(mob/user) + . = ..() + if(wearer) + unset_wearer() + +/obj/item/mod/control/item_action_slot_check(slot) + if(slot == ITEM_SLOT_BACK) + return TRUE + +/obj/item/mod/control/allow_attack_hand_drop(mob/user) + var/mob/living/carbon/carbon_user = user + if(!istype(carbon_user) || src != carbon_user.back) + return ..() + for(var/obj/item/part in mod_parts) + if(part.loc != src) + balloon_alert(carbon_user, "retract parts first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE, SILENCED_SOUND_EXTRARANGE) + return FALSE + return ..() + +/obj/item/mod/control/MouseDrop(atom/over_object) + if(src != wearer?.back || !istype(over_object, /atom/movable/screen/inventory/hand)) + return ..() + for(var/obj/item/part in mod_parts) + if(part.loc != src) + balloon_alert(wearer, "retract parts first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE, SILENCED_SOUND_EXTRARANGE) + return + if(!wearer.incapacitated()) + var/atom/movable/screen/inventory/hand/ui_hand = over_object + if(wearer.putItemFromInventoryInHandIfPossible(src, ui_hand.held_index)) + add_fingerprint(usr) + return ..() + +/obj/item/mod/control/attack_hand(mob/user) + if(seconds_electrified && cell?.charge) + if(shock(user)) + return + if(open && loc == user) + if(!cell) + balloon_alert(user, "no cell!") + return + balloon_alert(user, "removing cell...") + if(!do_after(user, 1.5 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return + balloon_alert(user, "cell removed") + playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + if(!user.put_in_hands(cell)) + cell.forceMove(drop_location()) + update_cell_alert() + return + return ..() + +/obj/item/mod/control/AltClick(mob/user) + if(seconds_electrified && cell?.charge) + if(shock(user)) + return + if(!open) + for(var/obj/item/mod/module/storage/S in modules) + if(S.stored) + playsound(user, "rustle", 50, 1, -5) + SEND_SIGNAL(S.stored, COMSIG_TRY_STORAGE_SHOW, wearer, TRUE) + return + . = ..() + +/obj/item/mod/control/screwdriver_act(mob/living/user, obj/item/screwdriver) + . = ..() + if(.) + return TRUE + if(active || activating) + balloon_alert(user, "deactivate suit first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + balloon_alert(user, "[open ? "closing" : "opening"] panel...") + screwdriver.play_tool_sound(src, 100) + if(screwdriver.use_tool(src, user, 1 SECONDS)) + if(active || activating) + balloon_alert(user, "deactivate suit first!") + screwdriver.play_tool_sound(src, 100) + balloon_alert(user, "panel [open ? "closed" : "opened"]") + open = !open + else + balloon_alert(user, "interrupted!") + return TRUE + +/obj/item/mod/control/crowbar_act(mob/living/user, obj/item/crowbar) + . = ..() + if(!open) + balloon_alert(user, "open the panel first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(!allowed(user)) + balloon_alert(user, "insufficient access!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + if(length(modules)) + var/list/removable_modules = list() + for(var/obj/item/mod/module/module as anything in modules) + if(!module.removable) + continue + removable_modules += module + var/obj/item/mod/module/module_to_remove = tgui_input_list(user, "Which module to remove?", "Module Removal", removable_modules) + if(!module_to_remove?.mod) + return FALSE + uninstall(module_to_remove) + module_to_remove.forceMove(drop_location()) + crowbar.play_tool_sound(src, 100) + return TRUE + balloon_alert(user, "no modules!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + +/obj/item/mod/control/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/paicard)) + if(!open) //mod must be open + balloon_alert(user, "suit must be open to transfer!") + return FALSE + insert_pai(user, attacking_item) + return TRUE + if(istype(attacking_item, /obj/item/mod/module)) + if(!open) + balloon_alert(user, "open the panel first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + install(attacking_item, user) + return TRUE + else if(istype(attacking_item, /obj/item/stock_parts/cell)) + if(!open) + balloon_alert(user, "open the panel first!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + if(cell) + balloon_alert(user, "cell already installed!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + attacking_item.forceMove(src) + cell = attacking_item + balloon_alert(user, "cell installed") + playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + update_cell_alert() + return TRUE + else if(is_wire_tool(attacking_item) && open) + wires.interact(user) + return TRUE + else if(open && attacking_item.GetID()) + update_access(user, attacking_item) + return TRUE + return ..() + +/obj/item/mod/control/get_cell() + if(open) + return cell + +/obj/item/mod/control/emp_act(severity) + . = ..() + to_chat(wearer, span_notice("[severity > 1 ? "Light" : "Strong"] electromagnetic pulse detected!")) + if(!active || !wearer || . & EMP_PROTECT_CONTENTS) + return + selected_module = null + wearer.apply_damage(10 / severity, BURN, spread_damage=TRUE) + to_chat(wearer, span_danger("You feel [src] heat up from the EMP, burning you slightly.")) + if (wearer.stat < UNCONSCIOUS && prob(10)) + wearer.emote("scream") + +/obj/item/mod/control/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot) + if(visuals_only) + set_wearer(outfit_wearer) //we need to set wearer manually since it doesnt call equipped + quick_activation() + +/obj/item/mod/control/doStrip(mob/stripper, mob/owner) + if(active && !toggle_activate(stripper, force_deactivate = TRUE)) + return + for(var/obj/item/part in mod_parts) + conceal(null, part) + return ..() + +/obj/item/mod/control/worn_overlays(isinhands = FALSE, icon_file) + . = ..() + if(!active) + return + for(var/obj/item/mod/module/module as anything in modules) + var/list/module_icons = module.generate_worn_overlay() + if(!length(module_icons)) + continue + . += module_icons + +/obj/item/mod/control/proc/set_wearer(mob/user) + wearer = user + RegisterSignal(wearer, COMSIG_ATOM_EXITED, .proc/on_exit) + RegisterSignal(wearer, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/on_borg_charge) + update_cell_alert() + for(var/obj/item/mod/module/module as anything in modules) + module.on_equip() + +/obj/item/mod/control/proc/unset_wearer() + for(var/obj/item/mod/module/module as anything in modules) + module.on_unequip() + UnregisterSignal(wearer, list(COMSIG_ATOM_EXITED, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)) + wearer.clear_alert("mod_charge") + wearer = null + +/obj/item/mod/control/proc/update_flags() + var/list/used_skin = theme.skins[skin] + for(var/obj/item/clothing/part as anything in mod_parts) + var/used_category + if(part == helmet) + used_category = HELMET_FLAGS + helmet.alternate_worn_layer = used_skin["HELMET_LAYER"] + helmet.alternate_layer = used_skin["HELMET_LAYER"] + if(part == chestplate) + used_category = CHESTPLATE_FLAGS + if(part == gauntlets) + used_category = GAUNTLETS_FLAGS + if(part == boots) + used_category = BOOTS_FLAGS + var/list/category = used_skin[used_category] + part.clothing_flags = category[UNSEALED_CLOTHING] || NONE + part.visor_flags = category[SEALED_CLOTHING] || NONE + part.flags_inv = category[UNSEALED_INVISIBILITY] || NONE + part.visor_flags_inv = category[SEALED_INVISIBILITY] || NONE + part.flags_cover = category[UNSEALED_COVER] || NONE + part.visor_flags_cover = category[SEALED_COVER] || NONE + +/obj/item/mod/control/proc/quick_module(mob/user) + if(!length(modules)) + return + var/list/display_names = list() + var/list/items = list() + for(var/obj/item/mod/module/module as anything in modules) + if(module.module_type == MODULE_PASSIVE) + continue + display_names[module.name] = REF(module) + var/image/module_image = image(icon = module.icon, icon_state = module.icon_state) + items += list(module.name = module_image) + if(!length(items)) + return + var/pick = show_radial_menu(user, src, items, custom_check = FALSE, require_near = TRUE) + if(!pick) + return + var/module_reference = display_names[pick] + var/obj/item/mod/module/selected_module = locate(module_reference) in modules + if(!istype(selected_module) || user.incapacitated()) + return + selected_module.on_select() + +/obj/item/mod/control/proc/set_mod_color(new_color) + var/list/all_parts = mod_parts + src + for(var/obj/item/part as anything in all_parts) + part.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + part.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY) + wearer?.regenerate_icons() + +/obj/item/mod/control/proc/set_mod_skin(new_skin) + if(active) + CRASH("[src] tried to set skin while active!") + skin = new_skin + var/list/used_skin = theme.skins[new_skin] + if(used_skin[CONTROL_LAYER]) + alternate_worn_layer = used_skin[CONTROL_LAYER] + var/list/skin_updating = mod_parts.Copy() + src + for(var/obj/item/piece as anything in skin_updating) + piece.icon_state = "[skin]-[initial(piece.icon_state)]" + update_flags() + wearer?.regenerate_icons() + +/obj/item/mod/control/proc/shock(mob/living/user) + if(!istype(user) || cell?.charge < 1) + return FALSE + do_sparks(5, TRUE, src) + var/check_range = TRUE + return electrocute_mob(user, cell, src, 0.7, check_range) + +/obj/item/mod/control/proc/install(module, mob/user) + var/obj/item/mod/module/new_module = module + for(var/obj/item/mod/module/old_module as anything in modules) + if(is_type_in_list(new_module, old_module.incompatible_modules) || is_type_in_list(old_module, new_module.incompatible_modules)) + if(user) + balloon_alert(user, "[new_module] incompatible with [old_module]!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + if(is_type_in_list(module, theme.module_blacklist)) + if(user) + balloon_alert(user, "[src] doesn't accept [new_module]!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + var/complexity_with_module = complexity + complexity_with_module += new_module.complexity + if(complexity_with_module > complexity_max) + if(user) + balloon_alert(user, "[new_module] would make [src] too complex!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + new_module.forceMove(src) + modules += new_module + complexity += new_module.complexity + new_module.mod = src + new_module.on_install() + if(wearer) + new_module.on_equip() + if(user) + balloon_alert(user, "[new_module] added") + playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + +/obj/item/mod/control/proc/uninstall(module) + var/obj/item/mod/module/old_module = module + modules -= old_module + complexity -= old_module.complexity + if(active) + old_module.on_suit_deactivation() + if(old_module.active) + old_module.on_deactivation() + if(wearer) + old_module.on_unequip() + old_module.on_uninstall() + old_module.mod = null + +/obj/item/mod/control/proc/update_access(mob/user, obj/item/card/id/card) + if(!allowed(user)) + balloon_alert(user, "insufficient access!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + req_access = card.access.Copy() + balloon_alert(user, "access updated") + +/obj/item/mod/control/proc/update_cell_alert() + if(!wearer) + return + if(!cell) + wearer.throw_alert("mod_charge", /atom/movable/screen/alert/nocell) + return + var/remaining_cell = cell.charge/cell.maxcharge + switch(remaining_cell) + if(0.75 to INFINITY) + wearer.clear_alert("mod_charge") + if(0.5 to 0.75) + wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 1) + if(0.25 to 0.5) + wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 2) + if(0.01 to 0.25) + wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 3) + else + wearer.throw_alert("mod_charge", /atom/movable/screen/alert/emptycell) + +/obj/item/mod/control/proc/power_off() + balloon_alert(wearer, "no power!") + toggle_activate(wearer, force_deactivate = TRUE) + +/obj/item/mod/control/proc/on_exit(datum/source, atom/movable/part, direction) + SIGNAL_HANDLER + + if(part.loc == src) + return + if(part == cell) + cell = null + update_cell_alert() + return + if(part.loc == wearer) + return + if(modules.Find(part)) + uninstall(part) + return + if(mod_parts.Find(part)) + conceal(wearer, part) + if(active) + INVOKE_ASYNC(src, .proc/toggle_activate, wearer, TRUE) + return + +/obj/item/mod/control/proc/on_borg_charge(datum/source, amount) + SIGNAL_HANDLER + + if(!cell) + return + cell.give(amount) diff --git a/code/modules/mod/mod_paint.dm b/code/modules/mod/mod_paint.dm new file mode 100644 index 0000000000..44f4923002 --- /dev/null +++ b/code/modules/mod/mod_paint.dm @@ -0,0 +1,207 @@ +#define MODPAINT_MAX_COLOR_VALUE 1.25 +#define MODPAINT_MIN_COLOR_VALUE 0 +#define MODPAINT_MAX_SECTION_COLORS 2 +#define MODPAINT_MIN_SECTION_COLORS 0.25 +#define MODPAINT_MAX_OVERALL_COLORS 4 +#define MODPAINT_MIN_OVERALL_COLORS 1.5 +#define MODPAINT_MODE_RESKIN "reskin" +#define MODPAINT_MODE_REPAINT "repaint" + +/obj/item/mod/paint + name = "MOD paint kit" + desc = "This kit will repaint your MODsuit to something unique." + icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + icon_state = "paintkit" + var/obj/item/mod/control/editing_mod + var/atom/movable/screen/map_view/proxy_view + var/list/current_color + var/mode = MODPAINT_MODE_RESKIN + +/obj/item/mod/paint/Initialize(mapload) + . = ..() + current_color = color_matrix_identity() + +/obj/item/mod/paint/examine(mob/user) + . = ..() + . += span_notice("It is currently on [mode] mode.") + +/obj/item/mod/paint/attack_self(mob/user) + switch(mode) + if(MODPAINT_MODE_RESKIN) + mode = MODPAINT_MODE_REPAINT + if(MODPAINT_MODE_REPAINT) + mode = MODPAINT_MODE_RESKIN + to_chat(user, span_notice("Switched to [mode] mode.")) + +/obj/item/mod/paint/pre_attack(atom/attacked_atom, mob/living/user, params) + if(!istype(attacked_atom, /obj/item/mod/control)) + return ..() + var/obj/item/mod/control/mod = attacked_atom + if(mod.active || mod.activating) + balloon_alert(user, "suit is active!") + return STOP_ATTACK_PROC_CHAIN + switch(mode) + if(MODPAINT_MODE_RESKIN) + paint_skin(mod, user) + if(MODPAINT_MODE_REPAINT) + if(editing_mod) + return STOP_ATTACK_PROC_CHAIN + editing_mod = mod + var/map_name = "color_matrix_proxy_[REF(user.client)]" + proxy_view = new() + proxy_view.name = "screen" + proxy_view.assigned_map = map_name + proxy_view.del_on_map_removal = FALSE + proxy_view.screen_loc = "[map_name]:1,1" + + proxy_view.appearance = editing_mod.appearance + proxy_view.color = null + user.client.register_map_obj(proxy_view) + ui_interact(user) + return STOP_ATTACK_PROC_CHAIN + +/obj/item/mod/paint/ui_interact(mob/user, datum/tgui/ui) + if(!editing_mod) + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MODpaint", name) + ui.open() + +/obj/item/mod/paint/ui_host() + return editing_mod + +/obj/item/mod/paint/ui_close(mob/user) + . = ..() + editing_mod = null + QDEL_NULL(proxy_view) + current_color = color_matrix_identity() + +/obj/item/mod/paint/ui_status(mob/user) + if(check_menu(editing_mod, user)) + return ..() + return UI_CLOSE + +/obj/item/mod/paint/ui_static_data(mob/user) + var/list/data = list() + data["mapRef"] = proxy_view.assigned_map + return data + +/obj/item/mod/paint/ui_data(mob/user) + var/list/data = list() + data["currentColor"] = current_color + return data + +/obj/item/mod/paint/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("transition_color") + current_color = params["color"] + animate(proxy_view, time = 0.5 SECONDS, color = current_color) + if("confirm") + if(length(current_color) != 20) //20 is the length of a matrix identity list + return + for(var/color_value in current_color) + if(isnum(color_value)) + continue + return + var/total_color_value = 0 + var/list/total_colors = current_color.Copy() + total_colors.Cut(13, length(total_colors)) // 13 to 20 are just a and c, dont want to count them + var/red_value = current_color[1] + current_color[5] + current_color[9] //rr + gr + br + var/green_value = current_color[2] + current_color[6] + current_color[10] //rg + gg + bg + var/blue_value = current_color[3] + current_color[7] + current_color[11] //rb + gb + bb + if(red_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total red too high! ([red_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(red_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total red too low! ([red_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + if(green_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total green too high! ([green_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(green_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total green too low! ([green_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + if(blue_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total blue too high! ([blue_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(blue_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total blue too low! ([blue_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + for(var/color_value in total_colors) + total_color_value += color_value + if(color_value > MODPAINT_MAX_COLOR_VALUE) + balloon_alert(usr, "one of colors too high! ([color_value*100]%/[MODPAINT_MAX_COLOR_VALUE*100]%") + return + else if(color_value < MODPAINT_MIN_COLOR_VALUE) + balloon_alert(usr, "one of colors too low! ([color_value*100]%/[MODPAINT_MIN_COLOR_VALUE*100]%") + return + if(total_color_value > MODPAINT_MAX_OVERALL_COLORS) + balloon_alert(usr, "total colors too high! ([total_color_value*100]%/[MODPAINT_MAX_OVERALL_COLORS*100]%)") + return + else if(total_color_value < MODPAINT_MIN_OVERALL_COLORS) + balloon_alert(usr, "total colors too low! ([total_color_value*100]%/[MODPAINT_MIN_OVERALL_COLORS*100]%)") + return + editing_mod.set_mod_color(current_color) + SStgui.close_uis(src) + +/obj/item/mod/paint/proc/paint_skin(obj/item/mod/control/mod, mob/user) + if(length(mod.theme.skins) <= 1) + balloon_alert(user, "no alternate skins!") + return + var/list/skins = list() + for(var/mod_skin in mod.theme.skins) + skins[mod_skin] = image(icon = mod.icon, icon_state = "[mod_skin]-control") + var/pick = show_radial_menu(user, mod, skins, custom_check = CALLBACK(src, .proc/check_menu, mod, user), require_near = TRUE) + if(!pick) + balloon_alert(user, "no skin picked!") + return + mod.set_mod_skin(pick) + +/obj/item/mod/paint/proc/check_menu(obj/item/mod/control/mod, mob/user) + if(user.incapacitated() || !user.is_holding(src) || !mod || mod.active || mod.activating) + return FALSE + return TRUE + +#undef MODPAINT_MAX_COLOR_VALUE +#undef MODPAINT_MIN_COLOR_VALUE +#undef MODPAINT_MAX_SECTION_COLORS +#undef MODPAINT_MIN_SECTION_COLORS +#undef MODPAINT_MAX_OVERALL_COLORS +#undef MODPAINT_MIN_OVERALL_COLORS +#undef MODPAINT_MODE_RESKIN +#undef MODPAINT_MODE_REPAINT + +/obj/item/mod/skin_applier + name = "MOD skin applier" + desc = "This one-use skin applier will add a skin to MODsuits of a specific type." + icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + icon_state = "skinapplier" + var/skin = "civilian" + var/compatible_theme = /datum/mod_theme + +/obj/item/mod/skin_applier/Initialize(mapload) + . = ..() + name = "MOD [skin] skin applier" + +/obj/item/mod/skin_applier/pre_attack(atom/attacked_atom, mob/living/user, params) + if(!istype(attacked_atom, /obj/item/mod/control)) + return ..() + var/obj/item/mod/control/mod = attacked_atom + if(mod.active || mod.activating) + balloon_alert(user, "suit is active!") + return TRUE + if(!istype(mod.theme, compatible_theme)) + balloon_alert(user, "incompatible theme!") + return TRUE + mod.set_mod_skin(skin) + balloon_alert(user, "skin applied") + qdel(src) + return TRUE + +/obj/item/mod/skin_applier/honkerative + skin = "honkerative" + compatible_theme = /datum/mod_theme/syndicate diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm new file mode 100644 index 0000000000..050badf5d9 --- /dev/null +++ b/code/modules/mod/mod_theme.dm @@ -0,0 +1,854 @@ +/// Global proc that sets up all MOD themes as singletons in a list and returns it. +/proc/setup_mod_themes() + . = list() + for(var/path in typesof(/datum/mod_theme)) + var/datum/mod_theme/new_theme = new path() + .[path] = new_theme + +/// MODsuit theme, instanced once and then used by MODsuits to grab various statistics. +/datum/mod_theme + /// Theme name for the MOD. + var/name = "standard" + /// Description added to the MOD. + var/desc = "A civilian class suit by Nakamura Engineering, doesn't offer much other than slightly quicker movement." + /// Default skin of the MOD. + var/default_skin = "standard" + /// Armor shared across the MOD pieces. + var/armor = list(MELEE = 10, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 5, RAD = 0) + /// Resistance flags shared across the MOD pieces. + var/resistance_flags = NONE + /// Max heat protection shared across the MOD pieces. + var/max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + /// Max cold protection shared across the MOD pieces. + var/min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + /// Permeability shared across the MOD pieces. + var/permeability_coefficient = 0.01 + /// Siemens shared across the MOD pieces. + var/siemens_coefficient = 0.5 + /// How much modules can the MOD carry without malfunctioning. + var/complexity_max = DEFAULT_MAX_COMPLEXITY + /// How much battery power the MOD uses by just being on + var/cell_drain = DEFAULT_CHARGE_DRAIN + /// Slowdown of the MOD when not active. + var/slowdown_inactive = 1.25 + /// Slowdown of the MOD when active. + var/slowdown_active = 0.75 + /// Theme used by the MOD TGUI. + var/ui_theme = "ntos" + /// List of inbuilt modules. These are different from the pre-equipped suits, you should mainly use these for unremovable modules with 0 complexity. + var/list/inbuilt_modules = list() + /// Modules blacklisted from the MOD. + var/list/module_blacklist = list() + /// List of skins with their appropriate clothing flags. + var/list/skins = list( + "standard" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + "civilian" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/engineering + name = "engineering" + desc = "An engineer-fit suit with heat and shock resistance. Nakamura Engineering's classic." + default_skin = "engineering" + armor = list(MELEE = 10, BULLET = 5, LASER = 20, ENERGY = 10, BOMB = 10, BIO = 100, FIRE = 100, ACID = 25, WOUND = 10, RAD = 20) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 1.5 + slowdown_active = 1 + skins = list( + "engineering" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/atmospheric + name = "atmospheric" + desc = "An atmospheric-resistant suit by Nakamura Engineering, offering extreme heat resistance compared to the engineer suit." + default_skin = "atmospheric" + armor = list(MELEE = 10, BULLET = 5, LASER = 10, ENERGY = 15, BOMB = 10, BIO = 100, FIRE = 100, ACID = 75, WOUND = 10, RAD = 0) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + slowdown_inactive = 1.5 + slowdown_active = 1 + skins = list( + "atmospheric" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/advanced + name = "advanced" + desc = "An advanced version of Nakamura Engineering's classic suit, shining with a white, acid and fire resistant polish." + default_skin = "advanced" + armor = list(MELEE = 15, BULLET = 5, LASER = 20, ENERGY = 15, BOMB = 50, BIO = 100, FIRE = 100, ACID = 90, WOUND = 10, RAD = 35) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 1 + slowdown_active = 0.5 + inbuilt_modules = list(/obj/item/mod/module/magboot/advanced) + skins = list( + "advanced" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/mining + name = "mining" + desc = "A high-power Nanotrasen mining suit, supporting more complexity at a bigger drain." + default_skin = "mining" + armor = list(MELEE = 15, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 30, BIO = 100, FIRE = 100, ACID = 75, WOUND = 15, RAD = 0) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cell_drain = DEFAULT_CHARGE_DRAIN * 2 + complexity_max = DEFAULT_MAX_COMPLEXITY + 5 + skins = list( + "mining" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/medical + name = "medical" + desc = "A lightweight suit by DeForest Medical Corporation, allows for easier movement." + default_skin = "medical" + armor = list(MELEE = 5, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 10, BIO = 100, FIRE = 60, ACID = 75, WOUND = 5, RAD = 0) + cell_drain = DEFAULT_CHARGE_DRAIN * 1.5 + slowdown_inactive = 1 + slowdown_active = 0.5 + skins = list( + "medical" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + "corpsman" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/rescue + name = "rescue" + desc = "An advanced version of DeForest Medical Corporation's medical suit, designed for quick rescue of bodies from the most dangerous environments." + default_skin = "rescue" + armor = list(MELEE = 10, BULLET = 10, LASER = 5, ENERGY = 5, BOMB = 10, BIO = 100, FIRE = 100, ACID = 100, WOUND = 5, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cell_drain = DEFAULT_CHARGE_DRAIN * 1.5 + slowdown_inactive = 0.75 + slowdown_active = 0.25 + inbuilt_modules = list(/obj/item/mod/module/quick_carry/advanced) + skins = list( + "rescue" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/research + name = "research" + desc = "A private military EOD suit by Aussec Armory, intended for explosive research. Bulky, but expansive." + default_skin = "research" + armor = list(MELEE = 20, BULLET = 15, LASER = 5, ENERGY = 5, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100, WOUND = 15, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + complexity_max = DEFAULT_MAX_COMPLEXITY + 5 + slowdown_inactive = 2 + slowdown_active = 1.5 + inbuilt_modules = list(/obj/item/mod/module/reagent_scanner/advanced) + skins = list( + "research" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/security + name = "security" + desc = "An Apadyne Technologies security suit, offering shock protection and quicker speed, at the cost of carrying capacity." + default_skin = "security" + armor = list(MELEE = 15, BULLET = 15, LASER = 15, ENERGY = 15, BOMB = 25, BIO = 100, FIRE = 75, ACID = 75, WOUND = 15, RAD = 0) + siemens_coefficient = 0 + complexity_max = DEFAULT_MAX_COMPLEXITY - 5 + slowdown_inactive = 1 + slowdown_active = 0.5 + skins = list( + "security" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/safeguard + name = "safeguard" + desc = "An Apadyne Technologies advanced security suit, offering greater speed and fire protection than the standard security model." + default_skin = "safeguard" + armor = list(MELEE = 15, BULLET = 15, LASER = 15, ENERGY = 15, BOMB = 40, BIO = 100, FIRE = 100, ACID = 95, WOUND = 15, RAD = 0) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + siemens_coefficient = 0 + complexity_max = DEFAULT_MAX_COMPLEXITY - 5 + slowdown_inactive = 0.75 + slowdown_active = 0.25 + skins = list( + "safeguard" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/magnate + name = "magnate" + desc = "A fancy, very protective suit for Nanotrasen's captains. Shock, fire and acid-proof while also having a large capacity and high speed." + default_skin = "magnate" + armor = list(MELEE = 20, BULLET = 15, LASER = 15, ENERGY = 15, BOMB = 50, BIO = 100, FIRE = 100, ACID = 100, WOUND = 15, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + complexity_max = DEFAULT_MAX_COMPLEXITY + 5 + slowdown_inactive = 0.75 + slowdown_active = 0.25 + skins = list( + "magnate" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/cosmohonk + name = "cosmohonk" + desc = "A suit by Honk Ltd. Protects against low humor environments. Most of the tech went to lower the power cost." + default_skin = "cosmohonk" + armor = list(MELEE = 5, BULLET = 5, LASER = 20, ENERGY = 20, BOMB = 10, BIO = 100, FIRE = 60, ACID = 30, WOUND = 5, RAD = 0) + cell_drain = DEFAULT_CHARGE_DRAIN * 0.25 + slowdown_inactive = 1.75 + slowdown_active = 1.25 + skins = list( + "cosmohonk" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR, + SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/syndicate + name = "syndicate" + desc = "A suit designed by Gorlex Marauders, offering armor ruled illegal in most of Spinward Stellar." + default_skin = "syndicate" + armor = list(MELEE = 15, BULLET = 20, LASER = 15, ENERGY = 15, BOMB = 35, BIO = 100, FIRE = 50, ACID = 90, WOUND = 25, RAD = 0) + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 1 + slowdown_active = 0.5 + ui_theme = "syndicate" + inbuilt_modules = list() + skins = list( + "syndicate" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/elite + name = "elite" + desc = "An elite suit upgraded by Cybersun Industries, offering upgraded armor values." + default_skin = "elite" + armor = list(MELEE = 35, BULLET = 30, LASER = 35, ENERGY = 35, BOMB = 55, BIO = 100, FIRE = 100, ACID = 100, WOUND = 25, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 0.75 + slowdown_active = 0.25 + ui_theme = "syndicate" + inbuilt_modules = list() + skins = list( + "elite" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/enchanted + name = "enchanted" + desc = "The Wizard Federation's relatively low-tech MODsuit. Is very protective, though." + default_skin = "enchanted" + armor = list(MELEE = 40, BULLET = 40, LASER = 50, ENERGY = 50, BOMB = 35, BIO = 100, FIRE = 100, ACID = 100, WOUND = 30, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + complexity_max = DEFAULT_MAX_COMPLEXITY - 5 + slowdown_inactive = 0.75 + slowdown_active = 0.25 + ui_theme = "wizard" + inbuilt_modules = list(/obj/item/mod/module/anti_magic/wizard) + skins = list( + "enchanted" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/prototype + name = "prototype" + desc = "A prototype modular suit powered by locomotives. While it is comfortable and has a big capacity, it remains very bulky and power-inefficient." + default_skin = "prototype" + armor = list(MELEE = 20, BULLET = 5, LASER = 10, ENERGY = 10, BOMB = 50, BIO = 100, FIRE = 100, ACID = 75, WOUND = 5, RAD = 0) + resistance_flags = FIRE_PROOF + complexity_max = DEFAULT_MAX_COMPLEXITY + 10 + slowdown_inactive = 2.5 + slowdown_active = 2 + ui_theme = "hackerman" + inbuilt_modules = list(/obj/item/mod/module/kinesis) + skins = list( + "prototype" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/responsory + name = "responsory" + desc = "A high-speed rescue suit by Nanotrasen, intended for its' emergency response teams." + default_skin = "responsory" + armor = list(MELEE = 50, BULLET = 40, LASER = 50, ENERGY = 50, BOMB = 50, BIO = 100, FIRE = 100, ACID = 90, WOUND = 10, RAD = 0) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 0.5 + slowdown_active = 0 + skins = list( + "responsory" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = NONE, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + "inquisitory" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/apocryphal + name = "apocryphal" + desc = "A high-tech, only technically legal, armored suit created by a collaboration effort between Nanotrasen and Apadyne Technologies." + default_skin = "apocryphal" + armor = list(MELEE = 80, BULLET = 80, LASER = 50, ENERGY = 60, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100, WOUND = 25, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + complexity_max = DEFAULT_MAX_COMPLEXITY + 10 + skins = list( + "apocryphal" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR, + SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/corporate + name = "corporate" + desc = "A fancy, high-tech suit for Nanotrasen's high ranking officers." + default_skin = "corporate" + armor = list(MELEE = 50, BULLET = 40, LASER = 50, ENERGY = 50, BOMB = 50, BIO = 100, FIRE = 100, ACID = 100, WOUND = 15, RAD = 0) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 0.5 + slowdown_active = 0 + skins = list( + "corporate" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/debug + name = "debug" + desc = "Strangely nostalgic." + default_skin = "debug" + armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100, WOUND = 0, RAD = 25) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + complexity_max = 50 + slowdown_inactive = 0.5 + slowdown_active = 0 + skins = list( + "debug" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + +/datum/mod_theme/administrative + name = "administrative" + desc = "A suit made of adminium. Who comes up with these stupid mineral names?" + default_skin = "debug" + armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100, WOUND = 100, RAD = 100) + resistance_flags = INDESTRUCTIBLE|LAVA_PROOF|FIRE_PROOF|UNACIDABLE|ACID_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + complexity_max = 1000 + cell_drain = DEFAULT_CHARGE_DRAIN * 0 + slowdown_inactive = 0 + slowdown_active = 0 + skins = list( + "debug" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + SEALED_INVISIBILITY = HIDEJUMPSUIT, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + ), + ), + ) diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm new file mode 100644 index 0000000000..565dcdc2f8 --- /dev/null +++ b/code/modules/mod/mod_types.dm @@ -0,0 +1,273 @@ +/obj/item/mod/control/pre_equipped + cell = /obj/item/stock_parts/cell/high + var/applied_skin + +/obj/item/mod/control/pre_equipped/Initialize(mapload, new_theme, new_skin) + new_skin = applied_skin + return ..() + +/obj/item/mod/control/pre_equipped/standard + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + ) + +/obj/item/mod/control/pre_equipped/engineering + theme = /datum/mod_theme/engineering + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/rad_protection, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/magboot, + ) + +/obj/item/mod/control/pre_equipped/atmospheric + theme = /datum/mod_theme/atmospheric + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/rad_protection, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/t_ray, + ) + +/obj/item/mod/control/pre_equipped/advanced + theme = /datum/mod_theme/advanced + cell = /obj/item/stock_parts/cell/super + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/rad_protection, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/jetpack, + ) + +/obj/item/mod/control/pre_equipped/mining + theme = /datum/mod_theme/mining + cell = /obj/item/stock_parts/cell/high/plus + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/orebag, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/magboot, + /obj/item/mod/module/drill, + ) + +/obj/item/mod/control/pre_equipped/medical + theme = /datum/mod_theme/medical + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/health_analyzer, + /obj/item/mod/module/quick_carry, + ) + +/obj/item/mod/control/pre_equipped/rescue + theme = /datum/mod_theme/rescue + cell = /obj/item/stock_parts/cell/super + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/health_analyzer, + ) + +/obj/item/mod/control/pre_equipped/research + theme = /datum/mod_theme/research + cell = /obj/item/stock_parts/cell/super + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/t_ray, + ) + +/obj/item/mod/control/pre_equipped/security + theme = /datum/mod_theme/security + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/holster, + ) + +/obj/item/mod/control/pre_equipped/safeguard + theme = /datum/mod_theme/safeguard + cell = /obj/item/stock_parts/cell/super + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/jetpack, + /obj/item/mod/module/holster, + ) + +/obj/item/mod/control/pre_equipped/magnate + theme = /datum/mod_theme/magnate + cell = /obj/item/stock_parts/cell/hyper + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/holster, + /obj/item/mod/module/jetpack/advanced, + ) + +/obj/item/mod/control/pre_equipped/traitor + theme = /datum/mod_theme/syndicate + cell = /obj/item/stock_parts/cell/super + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/dna_lock, + /obj/item/mod/module/jetpack, + ) + +/obj/item/mod/control/pre_equipped/nuclear + theme = /datum/mod_theme/syndicate + cell = /obj/item/stock_parts/cell/hyper + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/visor/thermal, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/jetpack/advanced, + /obj/item/mod/module/holster, + ) + +/obj/item/mod/control/pre_equipped/elite + theme = /datum/mod_theme/elite + cell = /obj/item/stock_parts/cell/bluespace + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/emp_shield, + /obj/item/mod/module/visor/thermal, + /obj/item/mod/module/jetpack/advanced, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/holster, + ) + +/obj/item/mod/control/pre_equipped/prototype + theme = /datum/mod_theme/prototype + cell = /obj/item/stock_parts/cell/high/plus + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/rad_protection, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/tether, + ) + +/obj/item/mod/control/pre_equipped/responsory + theme = /datum/mod_theme/responsory + cell = /obj/item/stock_parts/cell/hyper + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/emp_shield, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/holster, + ) + var/insignia_type = /obj/item/mod/module/insignia + +/obj/item/mod/control/pre_equipped/responsory/Initialize(mapload, new_theme, new_skin) + initial_modules.Insert(1, insignia_type) + return ..() + +/obj/item/mod/control/pre_equipped/responsory/commander + insignia_type = /obj/item/mod/module/insignia/commander + +/obj/item/mod/control/pre_equipped/responsory/security + insignia_type = /obj/item/mod/module/insignia/security + +/obj/item/mod/control/pre_equipped/responsory/engineer + insignia_type = /obj/item/mod/module/insignia/engineer + +/obj/item/mod/control/pre_equipped/responsory/medic + insignia_type = /obj/item/mod/module/insignia/medic + +/obj/item/mod/control/pre_equipped/responsory/janitor + insignia_type = /obj/item/mod/module/insignia/janitor + +/obj/item/mod/control/pre_equipped/responsory/clown + insignia_type = /obj/item/mod/module/insignia/clown + +/obj/item/mod/control/pre_equipped/responsory/chaplain + insignia_type = /obj/item/mod/module/insignia/chaplain + +/obj/item/mod/control/pre_equipped/responsory/inquisitory + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/anti_magic, + /obj/item/mod/module/welding, + /obj/item/mod/module/emp_shield, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/holster, + ) + applied_skin = "inquisitory" + +/obj/item/mod/control/pre_equipped/responsory/inquisitory/commander + insignia_type = /obj/item/mod/module/insignia/commander + +/obj/item/mod/control/pre_equipped/responsory/inquisitory/security + insignia_type = /obj/item/mod/module/insignia/security + +/obj/item/mod/control/pre_equipped/responsory/inquisitory/medic + insignia_type = /obj/item/mod/module/insignia/medic + +/obj/item/mod/control/pre_equipped/responsory/inquisitory/chaplain + insignia_type = /obj/item/mod/module/insignia/chaplain + +/obj/item/mod/control/pre_equipped/apocryphal + theme = /datum/mod_theme/apocryphal + cell = /obj/item/stock_parts/cell/bluespace + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/emp_shield, + /obj/item/mod/module/holster, + /obj/item/mod/module/jetpack, + ) + +/obj/item/mod/control/pre_equipped/corporate + theme = /datum/mod_theme/corporate + cell = /obj/item/stock_parts/cell/bluespace + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/holster, + ) + +/obj/item/mod/control/pre_equipped/debug + theme = /datum/mod_theme/debug + cell = /obj/item/stock_parts/cell/bluespace + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/bikehorn, + /obj/item/mod/module/rad_protection, + /obj/item/mod/module/tether, + ) //one of every type of module, for testing if they all work correctly + +/obj/item/mod/control/pre_equipped/administrative + theme = /datum/mod_theme/administrative + cell = /obj/item/stock_parts/cell/infinite/abductor + initial_modules = list( + /obj/item/mod/module/storage, + /obj/item/mod/module/welding, + /obj/item/mod/module/quick_carry/advanced, + /obj/item/mod/module/magboot/advanced, + /obj/item/mod/module/jetpack/advanced, + ) + +//these exist for the prefs menu +/obj/item/mod/control/pre_equipped/syndicate_empty + theme = /datum/mod_theme/syndicate + +/obj/item/mod/control/pre_equipped/syndicate_empty/elite + theme = /datum/mod_theme/elite + +INITIALIZE_IMMEDIATE(/obj/item/mod/control/pre_equipped/syndicate_empty) diff --git a/code/modules/mod/mod_ui.dm b/code/modules/mod/mod_ui.dm new file mode 100644 index 0000000000..1923db6f29 --- /dev/null +++ b/code/modules/mod/mod_ui.dm @@ -0,0 +1,78 @@ +/obj/item/mod/control/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MODsuit", name) + ui.open() + +/obj/item/mod/control/ui_data(mob/user) + var/data = list() + data["interface_break"] = interface_break + data["malfunctioning"] = malfunctioning + data["open"] = open + data["active"] = active + data["complexity"] = complexity + data["selected_module"] = selected_module?.name + data["wearer_name"] = wearer ? (wearer.get_authentification_name("Unknown") || "Unknown") : "No Occupant" + data["wearer_job"] = wearer ? wearer.get_assignment("Unknown", "Unknown", FALSE) : "No Job" + data["AI"] = ai?.name + data["is_pAI"] = ai ? ispAI(ai) : FALSE + data["is_user_AI"] = ai ? user == ai : FALSE + data["cell"] = cell?.name + data["charge"] = cell ? round(cell.percent(), 1) : 0 + data["modules"] = list() + for(var/obj/item/mod/module/module as anything in modules) + var/list/module_data = list( + name = module.name, + description = module.desc, + module_type = module.module_type, + active = module.active, + idle_power = module.idle_power_cost, + active_power = module.active_power_cost, + use_power = module.use_power_cost, + complexity = module.complexity, + cooldown_time = module.cooldown_time, + cooldown = round(COOLDOWN_TIMELEFT(module, cooldown_timer), 1 SECONDS), + id = module.tgui_id, + ref = REF(module), + configuration_data = module.get_configuration() + ) + module_data += module.add_ui_data() + data["modules"] += list(module_data) + return data + +/obj/item/mod/control/ui_static_data(mob/user) + var/data = list() + data["ui_theme"] = ui_theme + data["control"] = name + data["complexity_max"] = complexity_max + data["helmet"] = helmet?.name + data["chestplate"] = chestplate?.name + data["gauntlets"] = gauntlets?.name + data["boots"] = boots?.name + return data + +/obj/item/mod/control/ui_act(action, params) + . = ..() + if(.) + return + if(malfunctioning && prob(75)) + balloon_alert(usr, "button malfunctions!") + return + switch(action) + if("activate") + toggle_activate(usr) + if("select") + var/obj/item/mod/module/module = locate(params["ref"]) in modules + if(!module) + return + module.on_select() + if("configure") + var/obj/item/mod/module/module = locate(params["ref"]) in modules + if(!module) + return + module.configure_edit(params["key"], params["value"]) + if("remove_pai") + if(ishuman(usr)) // Only the MODsuit's wearer should be removing the pAI. + var/mob/user = usr + extract_pai(user) + return TRUE diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm new file mode 100644 index 0000000000..2d008cad1c --- /dev/null +++ b/code/modules/mod/modules/_module.dm @@ -0,0 +1,357 @@ +/obj/item/mod/module + name = "MOD module" + icon = 'icons/obj/clothing/modsuit/mod_modules.dmi' + icon_state = "module" + /// If it can be removed + var/removable = TRUE + /// If it's passive, togglable, usable or active + var/module_type = MODULE_PASSIVE + /// Is the module active + var/active = FALSE + /// How much space it takes up in the MOD + var/complexity = 0 + /// Power use when idle + var/idle_power_cost = DEFAULT_CHARGE_DRAIN * 0 + /// Power use when active + var/active_power_cost = DEFAULT_CHARGE_DRAIN * 0 + /// Power use when used, we call it manually + var/use_power_cost = DEFAULT_CHARGE_DRAIN * 0 + /// ID used by their TGUI + var/tgui_id + /// Linked MODsuit + var/obj/item/mod/control/mod + /// If we're an active module, what item are we? + var/obj/item/device + /// Overlay given to the user when the module is inactive + var/overlay_state_inactive + /// Overlay given to the user when the module is active + var/overlay_state_active + /// Overlay given to the user when the module is used, lasts until cooldown finishes + var/overlay_state_use + /// Icon file for the overlay. + var/overlay_icon_file = 'icons/mob/clothing/modsuit/mod_modules.dmi' + /// Does the overlay use the control unit's colors? + var/use_mod_colors = FALSE + /// What modules are we incompatible with? + var/list/incompatible_modules = list() + /// Cooldown after use + var/cooldown_time = 0 + /// The mouse button needed to use this module + var/used_signal + /// If we're allowed to use this module while phased out. + var/allowed_in_phaseout = FALSE + /// If we're allowed to use this module while the suit is disabled. + var/allowed_inactive = FALSE + /// Timer for the cooldown + COOLDOWN_DECLARE(cooldown_timer) + +/obj/item/mod/module/Initialize(mapload) + . = ..() + if(module_type != MODULE_ACTIVE) + return + if(ispath(device)) + device = new device(src) + ADD_TRAIT(device, TRAIT_NODROP, MOD_TRAIT) + RegisterSignal(device, COMSIG_PARENT_PREQDELETED, .proc/on_device_deletion) + RegisterSignal(src, COMSIG_ATOM_EXITED, .proc/on_exit) + +/obj/item/mod/module/Destroy() + mod?.uninstall(src) + if(device) + UnregisterSignal(device, COMSIG_PARENT_PREQDELETED) + QDEL_NULL(device) + return ..() + +/obj/item/mod/module/examine(mob/user) + . = ..() + if(user.hud_list[DIAG_HUD] && user.client.images & user.hud_list[DIAG_HUD]) + . += span_notice("Complexity level: [complexity]") + +/// Called from MODsuit's install() proc, so when the module is installed. +/obj/item/mod/module/proc/on_install() + return + +/// Called from MODsuit's uninstall() proc, so when the module is uninstalled. +/obj/item/mod/module/proc/on_uninstall() + return + +/// Called when the MODsuit is activated +/obj/item/mod/module/proc/on_suit_activation() + return + +/// Called when the MODsuit is deactivated +/obj/item/mod/module/proc/on_suit_deactivation() + return + +/// Called when the MODsuit is equipped +/obj/item/mod/module/proc/on_equip() + return + +/// Called when the MODsuit is unequipped +/obj/item/mod/module/proc/on_unequip() + return + +/// Called when the module is selected from the TGUI +/obj/item/mod/module/proc/on_select() + if(((!mod.active || mod.activating) && !allowed_inactive) || module_type == MODULE_PASSIVE) + if(mod.wearer) + balloon_alert(mod.wearer, "not active!") + return + if(module_type != MODULE_USABLE) + if(active) + on_deactivation() + else + on_activation() + else + on_use() + SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED, src) + +/// Called when the module is activated +/obj/item/mod/module/proc/on_activation() + if(!COOLDOWN_FINISHED(src, cooldown_timer)) + balloon_alert(mod.wearer, "on cooldown!") + return FALSE + if(!mod.active || mod.activating || !mod.cell?.charge) + balloon_alert(mod.wearer, "unpowered!") + return FALSE + if(!allowed_in_phaseout && istype(mod.wearer.loc, /obj/effect/dummy/phased_mob)) + //specifically a to_chat because the user is phased out. + to_chat(mod.wearer, span_warning("You cannot activate this right now.")) + return FALSE + if(module_type == MODULE_ACTIVE) + if(mod.selected_module && !mod.selected_module.on_deactivation()) + return + mod.selected_module = src + if(device) + if(mod.wearer.put_in_hands(device)) + balloon_alert(mod.wearer, "[device] extended") + RegisterSignal(mod.wearer, COMSIG_ATOM_EXITED, .proc/on_exit) + else + balloon_alert(mod.wearer, "can't extend [device]!") + return + else + update_signal() + balloon_alert(mod.wearer, "[src] activated, alt-click to use") + active = TRUE + mod.wearer.update_inv_back() + return TRUE + +/// Called when the module is deactivated +/obj/item/mod/module/proc/on_deactivation() + active = FALSE + if(module_type == MODULE_ACTIVE) + mod.selected_module = null + if(device) + mod.wearer.transferItemToLoc(device, src, TRUE) + balloon_alert(mod.wearer, "[device] retracted") + UnregisterSignal(mod.wearer, COMSIG_ATOM_EXITED) + else + balloon_alert(mod.wearer, "[src] deactivated") + UnregisterSignal(mod.wearer, used_signal) + used_signal = null + mod.wearer.update_inv_back() + return TRUE + +/// Called when the module is used +/obj/item/mod/module/proc/on_use() + if(!COOLDOWN_FINISHED(src, cooldown_timer)) + return FALSE + if(!check_power(use_power_cost)) + return FALSE + if(!allowed_in_phaseout && istype(mod.wearer.loc, /obj/effect/dummy/phased_mob)) + //specifically a to_chat because the user is phased out. + to_chat(mod.wearer, span_warning("You cannot activate this right now.")) + return FALSE + COOLDOWN_START(src, cooldown_timer, cooldown_time) + addtimer(CALLBACK(mod.wearer, /mob.proc/update_inv_back), cooldown_time) + mod.wearer.update_inv_back() + return TRUE + +/// Called when an activated module without a device is used +/obj/item/mod/module/proc/on_select_use(atom/target) + if(mod.wearer.incapacitated(ignore_grab = TRUE)) + return FALSE + mod.wearer.face_atom(target) + if(!on_use()) + return FALSE + return TRUE + +/// Called when an activated module without a device is active and the user alt/middle-clicks +/obj/item/mod/module/proc/on_special_click(mob/source, atom/target) + SIGNAL_HANDLER + on_select_use(target) + return COMSIG_MOB_CANCEL_CLICKON + +/// Called on the MODsuit's process +/obj/item/mod/module/proc/on_process(delta_time) + if(active) + if(!drain_power(active_power_cost * delta_time)) + on_deactivation() + return FALSE + on_active_process(delta_time) + else + drain_power(idle_power_cost * delta_time) + return TRUE + +/// Called on the MODsuit's process if it is an active module +/obj/item/mod/module/proc/on_active_process(delta_time) + return + +/// Drains power from the suit cell +/obj/item/mod/module/proc/drain_power(amount) + if(!check_power(amount)) + return FALSE + mod.cell.charge = max(0, mod.cell.charge - amount) + return TRUE + +/obj/item/mod/module/proc/check_power(amount) + if(!mod.cell || (mod.cell.charge < amount)) + return FALSE + return TRUE + +/// Adds additional things to the MODsuit ui_data() +/obj/item/mod/module/proc/add_ui_data() + return list() + +/// Creates a list of configuring options for this module +/obj/item/mod/module/proc/get_configuration() + return list() + +/// Generates an element of the get_configuration list with a display name, type and value +/obj/item/mod/module/proc/add_ui_configuration(display_name, type, value, list/values) + return list("display_name" = display_name, "type" = type, "value" = value, "values" = values) + +/// Receives configure edits from the TGUI and edits the vars +/obj/item/mod/module/proc/configure_edit(key, value) + return + +/// Called when the device moves to a different place on active modules +/obj/item/mod/module/proc/on_exit(datum/source, atom/movable/part, direction) + SIGNAL_HANDLER + + if(!active) + return + if(part.loc == src) + return + if(part.loc == mod.wearer) + return + if(part == device) + on_deactivation() + +/// Called when the device gets deleted on active modules +/obj/item/mod/module/proc/on_device_deletion(datum/source) + SIGNAL_HANDLER + + if(source == device) + device = null + qdel(src) + +/// Generates an icon to be used for the suit's worn overlays +/obj/item/mod/module/proc/generate_worn_overlay() + . = list() + if(!mod.active) + return + var/used_overlay + if(overlay_state_use && !COOLDOWN_FINISHED(src, cooldown_timer)) + used_overlay = overlay_state_use + else if(overlay_state_active && active) + used_overlay = overlay_state_active + else if(overlay_state_inactive) + used_overlay = overlay_state_inactive + else + return + var/mutable_appearance/module_icon = mutable_appearance(overlay_icon_file, used_overlay) + if(!use_mod_colors) + module_icon.appearance_flags |= RESET_COLOR + . += module_icon + +/// Updates the signal used by active modules to be activated +/obj/item/mod/module/proc/update_signal() + mod.selected_module.used_signal = COMSIG_MOB_ALTCLICKON + RegisterSignal(mod.wearer, mod.selected_module.used_signal, /obj/item/mod/module.proc/on_special_click) + +/obj/item/mod/module/anomaly_locked + name = "MOD anomaly locked module" + desc = "A form of a module, locked behind an anomalous core to function." + incompatible_modules = list(/obj/item/mod/module/anomaly_locked) + /// The core item the module runs off. + var/obj/item/assembly/signaler/anomaly/core + /// Accepted types of anomaly cores. + var/list/accepted_anomalies = list(/obj/item/assembly/signaler/anomaly) + /// If this one starts with a core in. + var/prebuilt = FALSE + +/obj/item/mod/module/anomaly_locked/Initialize(mapload) + . = ..() + if(!prebuilt || !length(accepted_anomalies)) + return + var/core_path = pick(accepted_anomalies) + core = new core_path(src) + update_icon_state() + +/obj/item/mod/module/anomaly_locked/Destroy() + QDEL_NULL(core) + return ..() + +/obj/item/mod/module/anomaly_locked/examine(mob/user) + . = ..() + if(!length(accepted_anomalies)) + return + if(core) + . += span_notice("There is a [core.name] installed in it. You could remove it with a screwdriver...") + else + var/list/core_list = list() + for(var/path in accepted_anomalies) + var/atom/core_path = path + core_list += initial(core_path.name) + . += span_notice("You need to insert \a [english_list(core_list, and_text = " or ")] for this module to function.") + +/obj/item/mod/module/anomaly_locked/on_select() + if(!core) + balloon_alert(mod.wearer, "no core!") + return + return ..() + +/obj/item/mod/module/anomaly_locked/on_process(delta_time) + . = ..() + if(!core) + return FALSE + +/obj/item/mod/module/anomaly_locked/on_active_process(delta_time) + if(!core) + return FALSE + return TRUE + +/obj/item/mod/module/anomaly_locked/attackby(obj/item/item, mob/living/user, params) + if(item.type in accepted_anomalies) + if(core) + balloon_alert(user, "core already in!") + return + if(!user.transferItemToLoc(item, src)) + return + core = item + balloon_alert(user, "core installed") + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + update_icon_state() + else + return ..() + +/obj/item/mod/module/anomaly_locked/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(!core) + balloon_alert(user, "no core!") + return + balloon_alert(user, "removing core...") + if(!do_after(user, 3 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return + balloon_alert(user, "core removed") + core.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(core) + core = null + update_icon_state() + +/obj/item/mod/module/anomaly_locked/update_icon_state() + icon_state = initial(icon_state) + (core ? "-core" : "") + return ..() diff --git a/code/modules/mod/modules/modules.dm b/code/modules/mod/modules/modules.dm new file mode 100644 index 0000000000..0e0da3572b --- /dev/null +++ b/code/modules/mod/modules/modules.dm @@ -0,0 +1,96 @@ +/obj/item/mod/module/anti_magic/on_suit_activation() + ADD_TRAIT(mod.wearer, TRAIT_ANTIMAGIC, MOD_TRAIT) + ADD_TRAIT(mod.wearer, TRAIT_HOLY, MOD_TRAIT) + +/obj/item/mod/module/anti_magic/on_suit_deactivation() + REMOVE_TRAIT(mod.wearer, TRAIT_ANTIMAGIC, MOD_TRAIT) + REMOVE_TRAIT(mod.wearer, TRAIT_HOLY, MOD_TRAIT) + +/obj/item/mod/module/anti_magic/wizard + name = "MOD magic neutralizer module" + desc = "The caster wielding this spell gains an invisible barrier around them, channeling arcane power through \ + specialized runes engraved onto the surface of the suit to generate anti-magic field. \ + The field will neutralize all magic that comes into contact with the user. \ + It will not protect the caster from social ridicule." + icon_state = "magic_neutralizer" + +/obj/item/mod/module/anti_magic/wizard/on_suit_activation() + ADD_TRAIT(mod.wearer, TRAIT_ANTIMAGIC_NO_SELFBLOCK, MOD_TRAIT) + +/obj/item/mod/module/anti_magic/wizard/on_suit_deactivation() + REMOVE_TRAIT(mod.wearer, TRAIT_ANTIMAGIC_NO_SELFBLOCK, MOD_TRAIT) + +/obj/item/mod/module/kinesis //TODO POST-MERGE MAKE NOT SUCK ASS, MAKE BALLER AS FUCK + name = "MOD kinesis module" + desc = "A modular plug-in to the forearm, this module was presumed lost for many years, \ + despite the suits it used to be mounted on still seeing some circulation. \ + This piece of technology allows the user to generate precise anti-gravity fields, \ + letting them move objects as small as a titanium rod to as large as industrial machinery. \ + Oddly enough, it doesn't seem to work on living creatures." + icon_state = "kinesis" +// module_type = MODULE_ACTIVE + module_type = MODULE_TOGGLE +// complexity = 3 + complexity = 0 + active_power_cost = DEFAULT_CHARGE_DRAIN*0.75 +// use_power_cost = DEFAULT_CHARGE_DRAIN*3 + removable = FALSE + incompatible_modules = list(/obj/item/mod/module/kinesis) + cooldown_time = 0.5 SECONDS + var/has_tk = FALSE + +/obj/item/mod/module/kinesis/on_activation() + . = ..() + if(!.) + return + if(mod.wearer.dna.check_mutation(TK)) + has_tk = TRUE + else + mod.wearer.dna.add_mutation(TK) + +/obj/item/mod/module/kinesis/on_deactivation() + . = ..() + if(!.) + return + if(has_tk) + has_tk = FALSE + return + mod.wearer.dna.remove_mutation(TK) + +/obj/item/mod/module/insignia + name = "MOD insignia module" + desc = "Despite the existence of IFF systems, radio communique, and modern methods of deductive reasoning involving \ + the wearer's own eyes, colorful paint jobs remain a popular way for different factions in the galaxy to display who \ + they are. This system utilizes a series of tiny moving paint sprayers to both apply and remove different \ + color patterns to and from the suit." + icon_state = "insignia" + removable = FALSE + incompatible_modules = list(/obj/item/mod/module/insignia) + overlay_state_inactive = "insignia" + +/obj/item/mod/module/insignia/generate_worn_overlay() + overlay_state_inactive = "[initial(overlay_state_inactive)]-[mod.skin]" + . = ..() + for(var/mutable_appearance/appearance as anything in .) + appearance.color = color + +/obj/item/mod/module/insignia/commander + color = "#4980a5" + +/obj/item/mod/module/insignia/security + color = "#b30d1e" + +/obj/item/mod/module/insignia/engineer + color = "#e9c80e" + +/obj/item/mod/module/insignia/medic + color = "#ebebf5" + +/obj/item/mod/module/insignia/janitor + color = "#7925c7" + +/obj/item/mod/module/insignia/clown + color = "#ff1fc7" + +/obj/item/mod/module/insignia/chaplain + color = "#f0a00c" diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm new file mode 100644 index 0000000000..cc0a6812f1 --- /dev/null +++ b/code/modules/mod/modules/modules_engineering.dm @@ -0,0 +1,215 @@ +//Engineering modules for MODsuits + +///Welding Protection - Makes the helmet protect from flashes and welding. +/obj/item/mod/module/welding + name = "MOD welding protection module" + desc = "A module installed into the visor of the suit, this projects a \ + polarized, holographic overlay in front of the user's eyes. It's rated high enough for \ + immunity against extremities such as spot and arc welding, solar eclipses, and handheld flashlights." + icon_state = "welding" + complexity = 1 + incompatible_modules = list(/obj/item/mod/module/welding) + overlay_state_inactive = "module_welding" + +/obj/item/mod/module/welding/on_suit_activation() + mod.helmet.flash_protect = 2 + +/obj/item/mod/module/welding/on_suit_deactivation(deleting = FALSE) + if(deleting) + return + mod.helmet.flash_protect = initial(mod.helmet.flash_protect) + +///T-Ray Scan - Scans the terrain for undertile objects. +/obj/item/mod/module/t_ray + name = "MOD t-ray scan module" + desc = "A module installed into the visor of the suit, allowing the user to use a pulse of terahertz radiation \ + to essentially echolocate things beneath the floor, mostly cables and pipes. \ + A staple of atmospherics work, and counter-smuggling work." + icon_state = "tray" + module_type = MODULE_TOGGLE + complexity = 1 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + incompatible_modules = list(/obj/item/mod/module/t_ray) + cooldown_time = 0.5 SECONDS + /// T-ray scan range. + var/range = 4 + +/obj/item/mod/module/t_ray/on_active_process(delta_time) + t_ray_scan(mod.wearer, 0.8 SECONDS, range) + +///Magnetic Stability - Gives the user a slowdown but makes them negate gravity and be immune to slips. +/obj/item/mod/module/magboot + name = "MOD magnetic stability module" + desc = "These are powerful electromagnets fitted into the suit's boots, allowing users both \ + excellent traction no matter the condition indoors, and to essentially hitch a ride on the exterior of a hull. \ + However, these basic models do not feature computerized systems to automatically toggle them on and off, \ + so numerous users report a certain stickiness to their steps." + icon_state = "magnet" + module_type = MODULE_TOGGLE + complexity = 2 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + incompatible_modules = list(/obj/item/mod/module/magboot) + cooldown_time = 0.5 SECONDS + /// Slowdown added onto the suit. + var/slowdown_active = 2 + +/obj/item/mod/module/magboot/on_activation() + . = ..() + if(!.) + return + mod.boots.clothing_flags |= NOSLIP + ADD_TRAIT(mod.wearer, TRAIT_NOSLIPWATER, MOD_TRAIT) + mod.slowdown += slowdown_active + mod.wearer.update_gravity(mod.wearer.has_gravity()) + mod.wearer.update_equipment_speed_mods() + +/obj/item/mod/module/magboot/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + mod.boots.clothing_flags &= ~NOSLIP + REMOVE_TRAIT(mod.wearer, TRAIT_NOSLIPWATER, MOD_TRAIT) + mod.slowdown -= slowdown_active + mod.wearer.update_gravity(mod.wearer.has_gravity()) + mod.wearer.update_equipment_speed_mods() + +/obj/item/mod/module/magboot/advanced + name = "MOD advanced magnetic stability module" + removable = FALSE + complexity = 0 + slowdown_active = 0 + +///Emergency Tether - Shoots a grappling hook projectile in 0g that throws the user towards it. +/obj/item/mod/module/tether + name = "MOD emergency tether module" + desc = "A custom-built grappling-hook powered by a winch capable of hauling the user. \ + While some older models of cargo-oriented grapples have capacities of a few tons, \ + these are only capable of working in zero-gravity environments, a blessing to some Engineers." + icon_state = "tether" + module_type = MODULE_ACTIVE + complexity = 3 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/tether) + cooldown_time = 1.5 SECONDS + +/obj/item/mod/module/tether/on_use() + if(mod.wearer.has_gravity(get_turf(src))) + balloon_alert(mod.wearer, "too much gravity!") + playsound(src, "gun_dry_fire", 25, TRUE) + return FALSE + return ..() + +/obj/item/mod/module/tether/on_select_use(atom/target) + . = ..() + if(!.) + return + var/obj/item/projectile/tether = new /obj/item/projectile/tether(mod.wearer.loc) + tether.preparePixelProjectile(target, mod.wearer) + tether.firer = mod.wearer + playsound(src, 'sound/weapons/batonextend.ogg', 25, TRUE) + INVOKE_ASYNC(tether, /obj/item/projectile.proc/fire) + drain_power(use_power_cost) + +/obj/item/projectile/tether + name = "tether" + icon_state = "tether_projectile" + icon = 'icons/obj/clothing/modsuit/mod_modules.dmi' + damage = 0 + nodamage = TRUE + range = 10 + hitsound = 'sound/weapons/batonextend.ogg' + hitsound_wall = 'sound/weapons/batonextend.ogg' + suppressed = SUPPRESSED_VERY + hit_threshhold = LATTICE_LAYER + /// Reference to the beam following the projectile. + var/line + +/obj/item/projectile/tether/fire(setAngle) + if(firer) + line = firer.Beam(src, "line", 'icons/obj/clothing/modsuit/mod_modules.dmi') + ..() + +/obj/item/projectile/tether/on_hit(atom/target) + . = ..() + if(firer) + firer.throw_at(target, 10, 1, firer, FALSE, FALSE, null, MOVE_FORCE_NORMAL, TRUE) + +/obj/item/projectile/tether/Destroy() + QDEL_NULL(line) + return ..() + +///Radiation Protection - Protects the user from radiation, gives them a geiger counter and rad info in the panel. +/obj/item/mod/module/rad_protection + name = "MOD radiation protection module" + desc = "A module utilizing polymers and reflective shielding to protect the user against ionizing radiation." + icon_state = "radshield" + complexity = 2 + idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + incompatible_modules = list(/obj/item/mod/module/rad_protection) + +/obj/item/mod/module/rad_protection/on_suit_activation() + mod.armor = mod.armor.modifyRating(rad = 65) + mod.rad_flags = RAD_PROTECT_CONTENTS|RAD_NO_CONTAMINATE + for(var/obj/item/part in mod.mod_parts) + part.armor = mod.armor + part.rad_flags = mod.rad_flags + +/obj/item/mod/module/rad_protection/on_suit_deactivation(deleting = FALSE) + mod.armor = mod.armor.modifyRating(rad = -65) + mod.rad_flags = NONE + for(var/obj/item/part in mod.mod_parts) + part.armor = mod.armor + part.rad_flags = mod.rad_flags + +///Constructor - Lets you build quicker and create RCD holograms. +/obj/item/mod/module/constructor + name = "MOD constructor module" + desc = "This module entirely occupies the wearer's forearm, notably causing conflict with \ + advanced arm servos meant to carry crewmembers. However, it contains the \ + latest engineering schematics combined with inbuilt memory to help the user build walls." + icon_state = "constructor" + module_type = MODULE_USABLE + complexity = 2 + idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 2 + incompatible_modules = list(/obj/item/mod/module/constructor, /obj/item/mod/module/quick_carry) + cooldown_time = 11 SECONDS + +/obj/item/mod/module/constructor/on_suit_activation() + ADD_TRAIT(mod.wearer, TRAIT_QUICK_BUILD, MOD_TRAIT) + +/obj/item/mod/module/constructor/on_suit_deactivation(deleting = FALSE) + REMOVE_TRAIT(mod.wearer, TRAIT_QUICK_BUILD, MOD_TRAIT) + +///Mister - Sprays water over an area. +/obj/item/mod/module/mister + name = "MOD water mister module" + desc = "A module containing a mister, able to spray it over areas." + icon_state = "mister" + module_type = MODULE_ACTIVE + complexity = 2 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + device = /obj/item/reagent_containers/spray/mister + incompatible_modules = list(/obj/item/mod/module/mister) + cooldown_time = 0.5 SECONDS + /// Volume of our reagent holder. + var/volume = 500 + +/obj/item/mod/module/mister/Initialize(mapload) + create_reagents(volume, OPENCONTAINER) + return ..() + +///Resin Mister - Sprays resin over an area. +/obj/item/mod/module/mister/atmos + name = "MOD resin mister module" + desc = "An atmospheric resin mister, able to fix up areas quickly." + device = /obj/item/extinguisher/mini/nozzle/mod + volume = 250 + +/obj/item/mod/module/mister/atmos/Initialize(mapload) + . = ..() + reagents.add_reagent(/datum/reagent/water, volume) + +/obj/item/extinguisher/mini/nozzle/mod + name = "MOD atmospheric mister" + desc = "An atmospheric resin mister with three modes, mounted as a module." diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm new file mode 100644 index 0000000000..1fa49f57e9 --- /dev/null +++ b/code/modules/mod/modules/modules_general.dm @@ -0,0 +1,395 @@ +//General modules for MODsuits + +///Storage - Adds a storage component to the suit. +/obj/item/mod/module/storage + name = "MOD storage containment module" + desc = "What amounts to a series of integrated storage compartments and specialized pockets installed across \ + the surface of the suit, useful for storing various bits, and or bobs." + icon_state = "storage" + complexity = 3 + incompatible_modules = list(/obj/item/mod/module/storage) + module_type = MODULE_USABLE + cooldown_time = 0.5 SECONDS + allowed_inactive = TRUE + /// Bag we have stored. + var/obj/item/storage/backpack/stored + +/obj/item/mod/module/storage/attackby(obj/item/I, mob/user, params) + if(!istype(I, /obj/item/storage/backpack)) + return ..() + var/obj/item/storage/backpack/B = I + if(stored) + balloon_alert(user, "backpack already installed!") + return + if(!user.transferItemToLoc(B, src)) + return + stored = B + balloon_alert(user, "backpack installed") + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + +/obj/item/mod/module/storage/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(!stored) + balloon_alert(user, "no backpack!") + return + balloon_alert(user, "removing backpack...") + if(!do_after(user, 3 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + return + balloon_alert(user, "backpack removed") + stored.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(stored) + stored = null + +/obj/item/mod/module/storage/on_use() + . = ..() + if(!.) + return + if(!stored) + var/obj/item/storage/backpack/holding = mod.wearer.get_active_held_item() + if(!holding) + balloon_alert(mod.wearer, "no backpack installed!") + return + if(!istype(holding)) + balloon_alert(mod.wearer, "it doesn't fit!") + return + if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE)) + stored = holding + balloon_alert(mod.wearer, "backpack stored") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else if(mod.wearer.put_in_active_hand(stored, forced = FALSE, ignore_animation = TRUE)) + balloon_alert(mod.wearer, "backpack retrieved") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else + balloon_alert(mod.wearer, "backpack storage full!") + +/obj/item/mod/module/storage/Exited(atom/movable/gone, direction) + . = ..() + if(gone == stored) + stored = null + +/obj/item/mod/module/storage/Destroy() + QDEL_NULL(stored) + return ..() + +///Ion Jetpack - Lets the user fly freely through space using battery charge. +/obj/item/mod/module/jetpack + name = "MOD ion jetpack module" + desc = "A series of electric thrusters installed across the suit, this is a module highly anticipated by trainee Engineers. \ + Rather than using gasses for combustion thrust, these jets are capable of accelerating ions using \ + charge from the suit's charge. Some say this isn't Nakamura Engineering's first foray into jet-enabled suits." + icon_state = "jetpack" + module_type = MODULE_TOGGLE + complexity = 3 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/jetpack) + cooldown_time = 0.5 SECONDS + overlay_state_inactive = "module_jetpack" + overlay_state_active = "module_jetpack_on" + /// Do we stop the wearer from gliding in space. + var/stabilizers = FALSE + /// Do we give the wearer a speed buff. + var/full_speed = FALSE + var/datum/effect_system/trail_follow/ion/ion_trail + +/obj/item/mod/module/jetpack/Initialize(mapload) + . = ..() + ion_trail = new + ion_trail.set_up(src) + +/obj/item/mod/module/jetpack/Destroy() + QDEL_NULL(ion_trail) + return ..() + +/obj/item/mod/module/jetpack/on_activation() + . = ..() + if(!.) + return + ion_trail.start() + RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, .proc/move_react) + if(full_speed) + mod.wearer.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed) + else + mod.wearer.add_movespeed_modifier(/datum/movespeed_modifier/jetpack) + +/obj/item/mod/module/jetpack/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + stabilizers = FALSE + ion_trail.stop() + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + mod.wearer.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed) + mod.wearer.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack) + +/obj/item/mod/module/jetpack/get_configuration() + . = ..() + .["stabilizers"] = add_ui_configuration("Stabilizers", "bool", stabilizers) + +/obj/item/mod/module/jetpack/configure_edit(key, value) + switch(key) + if("stabilizers") + stabilizers = text2num(value) + +/obj/item/mod/module/jetpack/proc/move_react(mob/user) + allow_thrust() + +/obj/item/mod/module/jetpack/proc/allow_thrust(use_fuel = TRUE) + if(!active) + return FALSE + if(!use_fuel) + return check_power(use_power_cost) + if(!drain_power(use_power_cost)) + return FALSE + return TRUE + +/obj/item/mod/module/jetpack/advanced + name = "MOD advanced ion jetpack module" + desc = "An improvement on the previous model of electric thrusters. This one achieves higher speeds through \ + mounting of more jets and a red paint applied on it." + icon_state = "jetpack_advanced" + overlay_state_inactive = "module_jetpackadv" + overlay_state_active = "module_jetpackadv_on" + full_speed = TRUE + +///Eating Apparatus - Lets the user eat/drink with the suit on. +/obj/item/mod/module/mouthhole + name = "MOD eating apparatus module" + desc = "A favorite by Miners, this modification to the helmet utilizes a nanotechnology barrier infront of the mouth \ + to allow eating and drinking while retaining protection and atmosphere. However, it won't free you from masks, \ + lets pepper spray pass through and it will do nothing to improve the taste of a goliath steak." + icon_state = "apparatus" + complexity = 1 + incompatible_modules = list(/obj/item/mod/module/mouthhole) + overlay_state_inactive = "module_apparatus" + /// Former flags of the helmet. + var/former_flags = NONE + /// Former visor flags of the helmet. + var/former_visor_flags = NONE + +/obj/item/mod/module/mouthhole/on_install() + former_flags = mod.helmet.flags_cover + former_visor_flags = mod.helmet.visor_flags_cover + mod.helmet.flags_cover &= ~HEADCOVERSMOUTH + mod.helmet.visor_flags_cover &= ~HEADCOVERSMOUTH + +/obj/item/mod/module/mouthhole/on_uninstall(deleting = FALSE) + if(deleting) + return + mod.helmet.flags_cover |= former_flags + mod.helmet.visor_flags_cover |= former_visor_flags + +///EMP Shield - Protects the suit from EMPs. +/obj/item/mod/module/emp_shield + name = "MOD EMP shield module" + desc = "A field inhibitor installed into the suit, protecting it against feedback such as \ + electromagnetic pulses that would otherwise damage the electronic systems of the suit or it's modules. \ + However, it will take from the suit's power to do so." + icon_state = "empshield" + complexity = 1 + idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + incompatible_modules = list(/obj/item/mod/module/emp_shield) + +/obj/item/mod/module/emp_shield/on_install() + mod.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS) + +/obj/item/mod/module/emp_shield/on_uninstall(deleting = FALSE) + mod.RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS) + +/obj/item/mod/module/emp_shield/advanced + name = "MOD advanced EMP shield module" + desc = "An advanced field inhibitor installed into the suit, protecting it against feedback such as \ + electromagnetic pulses that would otherwise damage the electronic systems of the suit or electronic devices on the wearer, \ + including augmentations. However, it will take from the suit's power to do so." + complexity = 2 + +/obj/item/mod/module/emp_shield/advanced/on_suit_activation() + mod.wearer.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS) + +/obj/item/mod/module/emp_shield/advanced/on_suit_deactivation(deleting) + mod.wearer.RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS) + +///Flashlight - Gives the suit a customizable flashlight. +/obj/item/mod/module/flashlight + name = "MOD flashlight module" + desc = "A simple pair of configurable flashlights installed on the left and right sides of the helmet, \ + useful for providing light in a variety of ranges and colors. \ + Some survivalists prefer the color green for their illumination, for reasons unknown." + icon_state = "flashlight" + module_type = MODULE_TOGGLE + complexity = 1 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + incompatible_modules = list(/obj/item/mod/module/flashlight) + cooldown_time = 0.5 SECONDS + overlay_state_inactive = "module_light" + light_color = COLOR_WHITE + light_range = 4 + light_power = 1 + /// Charge drain per range amount. + var/base_power = DEFAULT_CHARGE_DRAIN * 0.1 + /// Minimum range we can set. + var/min_range = 2 + /// Maximum range we can set. + var/max_range = 5 + +/obj/item/mod/module/flashlight/on_activation() + . = ..() + if(!.) + return + mod.set_light(light_range, light_power, light_color) + active_power_cost = base_power * light_range + +/obj/item/mod/module/flashlight/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + mod.set_light(0, 0) + +/obj/item/mod/module/flashlight/on_process(delta_time) + active_power_cost = base_power * light_range + return ..() + +/obj/item/mod/module/flashlight/generate_worn_overlay() + . = ..() + if(!active) + return + var/mutable_appearance/light_icon = mutable_appearance(overlay_icon_file, "module_light_on") + light_icon.appearance_flags = RESET_COLOR + light_icon.color = light_color + . += light_icon + +/obj/item/mod/module/flashlight/get_configuration() + . = ..() + .["light_color"] = add_ui_configuration("Light Color", "color", light_color) + .["light_range"] = add_ui_configuration("Light Range", "number", light_range) + +/obj/item/mod/module/flashlight/configure_edit(key, value) + switch(key) + if("light_color") + value = input(usr, "Pick new light color", "Flashlight Color") as color|null + if(!value) + return + var/list/hsl = rgb2hsl(hex2num(copytext(value,2,4)),hex2num(copytext(value,4,6)),hex2num(copytext(value,6,8))) + if(hsl[3] < 0.5) + balloon_alert(mod.wearer, "too dark!") + return + mod.set_light_color(value) + mod.wearer.regenerate_icons() + light_color = value + if("light_range") + mod.set_light_range(clamp(value, min_range, max_range)) + light_range = clamp(value, min_range, max_range) + +///Dispenser - Dispenses an item after a time passes. +/obj/item/mod/module/dispenser + name = "MOD burger dispenser module" + desc = "A rare piece of technology reverse-engineered from a prototype found in a Donk Corporation vessel. \ + This can draw incredible amounts of power from the suit's charge to create edible organic matter in the \ + palm of the wearer's glove; however, research seemed to have entirely stopped at burgers. \ + Notably, all attempts to get it to dispense Earl Grey tea have failed." + icon_state = "dispenser" + module_type = MODULE_USABLE + complexity = 3 + use_power_cost = DEFAULT_CHARGE_DRAIN * 2 + incompatible_modules = list(/obj/item/mod/module/dispenser) + cooldown_time = 5 SECONDS + /// Path we dispense. + var/dispense_type = /obj/item/reagent_containers/food/snacks/burger/plain + /// Time it takes for us to dispense. + var/dispense_time = 0 SECONDS + +/obj/item/mod/module/dispenser/on_use() + . = ..() + if(!.) + return + if(dispense_time && !do_after(mod.wearer, dispense_time, target = mod)) + balloon_alert(mod.wearer, "interrupted!") + return FALSE + var/obj/item/dispensed = new dispense_type(mod.wearer.loc) + mod.wearer.put_in_hands(dispensed) + balloon_alert(mod.wearer, "[dispensed] dispensed") + playsound(src, 'sound/machines/click.ogg', 100, TRUE) + drain_power(use_power_cost) + return dispensed + +///Longfall + +///Thermal Regulator - Naw. + +///DNA Lock - Prevents people without the set DNA from activating the suit. +/obj/item/mod/module/dna_lock + name = "MOD DNA lock module" + desc = "A module which engages with the various locks and seals tied to the suit's systems, \ + enabling it to only be worn by someone corresponding with the user's exact DNA profile; \ + however, this incredibly sensitive module is shorted out by EMPs. Luckily, cloning has been outlawed." + icon_state = "dnalock" + module_type = MODULE_USABLE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 3 + incompatible_modules = list(/obj/item/mod/module/dna_lock) + cooldown_time = 0.5 SECONDS + /// The DNA we lock with. + var/dna = null + +/obj/item/mod/module/dna_lock/on_install() + RegisterSignal(mod, COMSIG_MOD_ACTIVATE, .proc/on_mod_activation) + RegisterSignal(mod, COMSIG_MOD_MODULE_REMOVAL, .proc/on_mod_removal) + RegisterSignal(mod, COMSIG_ATOM_EMP_ACT, .proc/on_emp) + RegisterSignal(mod, COMSIG_ATOM_EMAG_ACT, .proc/on_emag) + +/obj/item/mod/module/dna_lock/on_uninstall(deleting = FALSE) + UnregisterSignal(mod, COMSIG_MOD_ACTIVATE) + UnregisterSignal(mod, COMSIG_MOD_MODULE_REMOVAL) + UnregisterSignal(mod, COMSIG_ATOM_EMP_ACT) + UnregisterSignal(mod, COMSIG_ATOM_EMAG_ACT) + +/obj/item/mod/module/dna_lock/on_use() + . = ..() + if(!.) + return + dna = mod.wearer.dna.unique_enzymes + balloon_alert(mod.wearer, "dna updated") + drain_power(use_power_cost) + +/obj/item/mod/module/dna_lock/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + on_emp(src, severity) + +/obj/item/mod/module/dna_lock/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + on_emag(src, user, emag_card) + +/obj/item/mod/module/dna_lock/proc/dna_check(mob/user) + if(!iscarbon(user)) + return FALSE + var/mob/living/carbon/carbon_user = user + if(!dna || (carbon_user.has_dna() && carbon_user.dna.unique_enzymes == dna)) + return TRUE + balloon_alert(user, "dna locked!") + return FALSE + +/obj/item/mod/module/dna_lock/proc/on_emp(datum/source, severity) + SIGNAL_HANDLER + + dna = null + +/obj/item/mod/module/dna_lock/proc/on_emag(datum/source, mob/user, obj/item/card/emag/emag_card) + SIGNAL_HANDLER + + dna = null + +/obj/item/mod/module/dna_lock/proc/on_mod_activation(datum/source, mob/user) + SIGNAL_HANDLER + + if(!dna_check(user)) + return MOD_CANCEL_ACTIVATE + +/obj/item/mod/module/dna_lock/proc/on_mod_removal(datum/source, mob/user) + SIGNAL_HANDLER + + if(!dna_check(user)) + return MOD_CANCEL_REMOVAL + +///Sign Language Translator - I want, but no diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm new file mode 100644 index 0000000000..65a5be5dc6 --- /dev/null +++ b/code/modules/mod/modules/modules_maint.dm @@ -0,0 +1,157 @@ +//Maint modules for MODsuits + +///Springlock Mechanism - allows your modsuit to activate faster, but reagents are very dangerous. +/obj/item/mod/module/springlock + name = "MOD springlock module" + desc = "A module that spans the entire size of the MOD unit, sitting under the outer shell. \ + This mechanical exoskeleton pushes out of the way when the user enters and it helps in booting \ + up, but was taken out of modern suits because of the springlock's tendency to \"snap\" back \ + into place when exposed to humidity. You know what it's like to have an entire exoskeleton enter you?" + icon_state = "springlock" + complexity = 3 // it is inside every part of your suit, so + incompatible_modules = list(/obj/item/mod/module/springlock) + +/obj/item/mod/module/springlock/on_install() + mod.activation_step_time *= 0.5 + +/obj/item/mod/module/springlock/on_uninstall(deleting = FALSE) + mod.activation_step_time *= 2 + +/obj/item/mod/module/springlock/on_suit_activation() + RegisterSignal(mod.wearer, COMSIG_ATOM_EXPOSE_REAGENTS, .proc/on_wearer_exposed) + +/obj/item/mod/module/springlock/on_suit_deactivation(deleting = FALSE) + UnregisterSignal(mod.wearer, COMSIG_ATOM_EXPOSE_REAGENTS) + +///Signal fired when wearer is exposed to reagents +/obj/item/mod/module/springlock/proc/on_wearer_exposed(atom/source, list/reagents, datum/reagents/source_reagents, methods, volume_modifier, show_message, from_gas) + SIGNAL_HANDLER + + if(!reagents.len) + return + if(!(methods == VAPOR || methods == PATCH || methods == TOUCH)) + return //remove non-touch reagent exposure + to_chat(mod.wearer, span_danger("[src] makes an ominous click sound...")) + playsound(src, 'sound/items/modsuit/springlock.ogg', 75, TRUE) + addtimer(CALLBACK(src, .proc/snap_shut), rand(3 SECONDS, 5 SECONDS)) + RegisterSignal(mod, COMSIG_MOD_ACTIVATE, .proc/on_activate_spring_block) + +///Signal fired when wearer attempts to activate/deactivate suits +/obj/item/mod/module/springlock/proc/on_activate_spring_block(datum/source, user) + SIGNAL_HANDLER + + balloon_alert(user, "springlocks aren't responding...?") + return MOD_CANCEL_ACTIVATE + +///Delayed death proc of the suit after the wearer is exposed to reagents +/obj/item/mod/module/springlock/proc/snap_shut() + UnregisterSignal(mod, COMSIG_MOD_ACTIVATE) + if(!mod.wearer) //while there is a guaranteed user when on_wearer_exposed() fires, that isn't the same case for this proc + return + mod.wearer.visible_message("[src] inside [mod.wearer]'s [mod.name] snaps shut, mutilating the user inside!", span_userdanger("*SNAP*")) + mod.wearer.emote("scream") + playsound(mod.wearer, 'sound/effects/snap.ogg', 75, TRUE, frequency = 0.5) + playsound(mod.wearer, 'sound/effects/splat.ogg', 50, TRUE, frequency = 0.5) + mod.wearer.client?.give_award(/datum/award/achievement/misc/springlock, mod.wearer) + mod.wearer.apply_damage(500, BRUTE, forced = TRUE, spread_damage = TRUE, sharpness = SHARP_POINTY) //boggers, bogchamp, etc + if(!HAS_TRAIT(mod.wearer, TRAIT_NODEATH)) + mod.wearer.death() //just in case, for some reason, they're still alive + flash_color(mod.wearer, flash_color = "#FF0000", flash_time = 10 SECONDS) + +///Rave Visor - Pointless + +///Tanner - Maybe another time + +///Balloon Blower - Blows a balloon. +/obj/item/mod/module/balloon + name = "MOD balloon blower module" + desc = "A strange module invented years ago by some ingenious mimes. It blows balloons." + icon_state = "bloon" + module_type = MODULE_USABLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + incompatible_modules = list(/obj/item/mod/module/balloon) + cooldown_time = 15 SECONDS + +/obj/item/mod/module/balloon/on_use() + . = ..() + if(!.) + return + if(!do_after(mod.wearer, 10 SECONDS, target = mod)) + return FALSE + mod.wearer.adjustOxyLoss(20) + playsound(src, 'sound/items/modsuit/inflate_bloon.ogg', 50, TRUE) + var/obj/item/toy/balloon/balloon = new(get_turf(src)) + mod.wearer.put_in_hands(balloon) + drain_power(use_power_cost) + +///Paper Dispenser - Dispenses (sometimes burning) paper sheets. +/obj/item/mod/module/paper_dispenser + name = "MOD paper dispenser module" + desc = "A simple module designed by the bureaucrats of Torch Bay. \ + It dispenses 'warm, clean, and crisp sheets of paper' onto a nearby table. Usually." + icon_state = "paper_maker" + module_type = MODULE_USABLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + incompatible_modules = list(/obj/item/mod/module/paper_dispenser) + cooldown_time = 5 SECONDS + /// The total number of sheets created by this MOD. The more sheets, them more likely they set on fire. + var/num_sheets_dispensed = 0 + +/obj/item/mod/module/paper_dispenser/on_use() + . = ..() + if(!.) + return + if(!do_after(mod.wearer, 1 SECONDS, target = mod)) + return FALSE + + var/obj/item/paper/crisp_paper = new(get_turf(src)) + crisp_paper.desc = "It's crisp and warm to the touch. Must be fresh." + + var/obj/structure/table/nearby_table = locate() in range(1, mod.wearer) + playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE) + balloon_alert(mod.wearer, "dispensed paper[nearby_table ? " onto table":""]") + + mod.wearer.put_in_hands(crisp_paper) + if(nearby_table) + mod.wearer.transferItemToLoc(crisp_paper, nearby_table.drop_location(), silent = FALSE) + + // Up to a 30% chance to set the sheet on fire, +2% per sheet made + if(prob(min(num_sheets_dispensed * 2, 30))) + if(crisp_paper in mod.wearer.held_items) + mod.wearer.dropItemToGround(crisp_paper, force = TRUE) + crisp_paper.balloon_alert(mod.wearer, "PC LOAD LETTER!") + crisp_paper.visible_message(span_warning("[crisp_paper] bursts into flames, it's too crisp!")) + crisp_paper.fire_act(1000, 100) + + drain_power(use_power_cost) + num_sheets_dispensed++ + + +///Stamper - Extends a stamp that can switch between accept/deny modes. +/obj/item/mod/module/stamp + name = "MOD stamper module" + desc = "A module installed into the wrist of the suit, this functions as a high-power stamp, \ + able to switch between accept and deny modes." + icon_state = "stamp" + module_type = MODULE_ACTIVE + complexity = 1 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + device = /obj/item/stamp/mod + incompatible_modules = list(/obj/item/mod/module/stamp) + cooldown_time = 0.5 SECONDS + +/obj/item/stamp/mod + name = "MOD electronic stamp" + desc = "A high-power stamp, able to switch between accept and deny mode when used." + +/obj/item/stamp/mod/attack_self(mob/user, modifiers) + . = ..() + if(icon_state == "stamp-ok") + icon_state = "stamp-deny" + else + icon_state = "stamp-ok" + balloon_alert(user, "switched mode") + +///Atrocinator - Perhaps another time diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm new file mode 100644 index 0000000000..c7e1510152 --- /dev/null +++ b/code/modules/mod/modules/modules_medical.dm @@ -0,0 +1,189 @@ +//Medical modules for MODsuits + +#define HEALTH_SCAN "Health" +#define WOUND_SCAN "Wound" +#define CHEM_SCAN "Chemical" + +///Health Analyzer - Gives the user a ranged health analyzer and their health status in the panel. +/obj/item/mod/module/health_analyzer + name = "MOD health analyzer module" + desc = "A module installed into the glove of the suit. This is a high-tech biological scanning suite, \ + allowing the user indepth information on the vitals and injuries of others even at a distance, \ + all with the flick of the wrist. Data is displayed in a convenient package on HUD in the helmet, \ + but it's up to you to do something with it." + icon_state = "health" + module_type = MODULE_ACTIVE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/health_analyzer) + cooldown_time = 0.5 SECONDS + tgui_id = "health_analyzer" + /// Scanning mode, changes how we scan something. + var/mode = HEALTH_SCAN + /// List of all scanning modes. + var/static/list/modes = list(HEALTH_SCAN, WOUND_SCAN, CHEM_SCAN) + +/obj/item/mod/module/health_analyzer/add_ui_data() + . = ..() + .["userhealth"] = mod.wearer?.health || 0 + .["usermaxhealth"] = mod.wearer?.getMaxHealth() || 0 + .["userbrute"] = mod.wearer?.getBruteLoss() || 0 + .["userburn"] = mod.wearer?.getFireLoss() || 0 + .["usertoxin"] = mod.wearer?.getToxLoss() || 0 + .["useroxy"] = mod.wearer?.getOxyLoss() || 0 + +/obj/item/mod/module/health_analyzer/on_select_use(atom/target) + . = ..() + if(!.) + return + if(!isliving(target) || !mod.wearer.can_read(src)) + return + switch(mode) + if(HEALTH_SCAN) + healthscan(mod.wearer, target) + if(WOUND_SCAN) + woundscan(mod.wearer, target) + if(CHEM_SCAN) + chemscan(mod.wearer, target) + drain_power(use_power_cost) + +/obj/item/mod/module/health_analyzer/get_configuration() + . = ..() + .["mode"] = add_ui_configuration("Scan Mode", "list", mode, modes) + +/obj/item/mod/module/health_analyzer/configure_edit(key, value) + switch(key) + if("mode") + mode = value + +#undef HEALTH_SCAN +#undef WOUND_SCAN +#undef CHEM_SCAN + +///Quick Carry - Lets the user carry bodies quicker. +/obj/item/mod/module/quick_carry + name = "MOD quick carry module" + desc = "A suite of advanced servos, redirecting power from the suit's arms to help carry the wounded; \ + or simply for fun. However, Nanotrasen has locked the module's ability to assist in hand-to-hand combat." + icon_state = "carry" + complexity = 1 + idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + incompatible_modules = list(/obj/item/mod/module/quick_carry, /obj/item/mod/module/constructor) + +/obj/item/mod/module/quick_carry/on_suit_activation() + ADD_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) + +/obj/item/mod/module/quick_carry/on_suit_deactivation(deleting = FALSE) + REMOVE_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) + +/obj/item/mod/module/quick_carry/advanced + name = "MOD advanced quick carry module" + removable = FALSE + complexity = 0 + +///Injector - No piercing syringes, replace another time + +///Organ Thrower + +///Patrient Transport + +///Defibrillator - Gives the suit an extendable pair of shock paddles. +/obj/item/mod/module/defibrillator + name = "MOD defibrillator module" + desc = "A module built into the gauntlets of the suit; commonly known as the 'Healing Hands' by medical professionals. \ + The user places their palms above the patient. Onboard computers in the suit calculate the necessary voltage, \ + and a modded targeting computer determines the best position for the user to push. \ + Twenty five pounds of force are applied to the patient's skin. Shocks travel from the suit's gloves \ + and counter-shock the heart, and the wearer returns to Medical a hero. Don't you even think about using it as a weapon; \ + regulations on manufacture and software locks expressly forbid it." + icon_state = "defibrillator" + module_type = MODULE_ACTIVE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 25 + device = /obj/item/shockpaddles/mod + overlay_state_inactive = "module_defibrillator" + overlay_state_active = "module_defibrillator_active" + incompatible_modules = list(/obj/item/mod/module/defibrillator) + cooldown_time = 0.5 SECONDS + var/defib_cooldown = 5 SECONDS + +/obj/item/mod/module/defibrillator/Initialize(mapload) + . = ..() + RegisterSignal(device, COMSIG_DEFIBRILLATOR_SUCCESS, .proc/on_defib_success) + +/obj/item/mod/module/defibrillator/Destroy() + UnregisterSignal(device, COMSIG_DEFIBRILLATOR_SUCCESS) + . = ..() + +/obj/item/mod/module/defibrillator/proc/on_defib_success(obj/item/shockpaddles/source) + drain_power(use_power_cost) + source.recharge(defib_cooldown) + return COMPONENT_DEFIB_STOP + +/obj/item/shockpaddles/mod + name = "MOD defibrillator gauntlets" + req_defib = FALSE + icon_state = "defibgauntlets0" + item_state = "defibgauntlets0" + base_icon_state = "defibgauntlets" + +/obj/item/mod/module/defibrillator/combat + name = "MOD combat defibrillator module" + desc = "A module built into the gauntlets of the suit; commonly known as the 'Healing Hands' by medical professionals. \ + The user places their palms above the patient. Onboard computers in the suit calculate the necessary voltage, \ + and a modded targeting computer determines the best position for the user to push. \ + Twenty five pounds of force are applied to the patient's skin. Shocks travel from the suit's gloves \ + and counter-shock the heart, and the wearer returns to Medical a hero. \ + Interdyne Pharmaceutics marketed the domestic version of the Healing Hands as foolproof and unusable as a weapon. \ + But when it came time to provide their operatives with usable medical equipment, they didn't hesitate to remove \ + those in-built safeties. Operatives in the field can benefit from what they dub as 'Stun Gloves', able to apply shocks \ + straight to a victims heart to disable them, or maybe even outright stop their heart with enough power." + complexity = 1 + module_type = MODULE_ACTIVE + overlay_state_inactive = "module_defibrillator_combat" + overlay_state_active = "module_defibrillator_combat_active" + device = /obj/item/shockpaddles/syndicate/mod + defib_cooldown = 2.5 SECONDS + +/obj/item/shockpaddles/syndicate/mod + name = "MOD combat defibrillator gauntlets" + req_defib = FALSE + icon_state = "syndiegauntlets0" + item_state = "syndiegauntlets0" + base_icon_state = "syndiegauntlets" + +///Thread Ripper + +///Surgical Processor - Lets you do advanced surgeries portably. +/obj/item/mod/module/surgical_processor + name = "MOD surgical processor module" + desc = "A module using an onboard surgical computer which can be connected to other computers to download and \ + perform advanced surgeries on the go." + icon_state = "surgical_processor" + module_type = MODULE_ACTIVE + complexity = 2 + active_power_cost = DEFAULT_CHARGE_DRAIN + device = /obj/item/surgical_processor/mod + incompatible_modules = list(/obj/item/mod/module/surgical_processor) + cooldown_time = 0.5 SECONDS + +/obj/item/surgical_processor/mod + name = "MOD surgical processor" + +/obj/item/mod/module/surgical_processor/preloaded + desc = "A module using an onboard surgical computer which can be connected to other computers to download and \ + perform advanced surgeries on the go. This one came pre-loaded with some advanced surgeries." + device = /obj/item/surgical_processor/mod/preloaded + +/obj/item/surgical_processor/mod/preloaded + advanced_surgeries = list( + /datum/surgery/advanced/pacify, + /datum/surgery/healing/combo/upgraded/femto, + /datum/surgery/advanced/brainwashing, + /datum/surgery/advanced/bioware/nerve_splicing, + /datum/surgery/advanced/bioware/nerve_grounding, + /datum/surgery/advanced/bioware/vein_threading, + /datum/surgery/advanced/bioware/muscled_veins, + /datum/surgery/advanced/bioware/ligament_hook, + /datum/surgery/advanced/bioware/ligament_reinforcement + ) diff --git a/code/modules/mod/modules/modules_science.dm b/code/modules/mod/modules/modules_science.dm new file mode 100644 index 0000000000..7208210b76 --- /dev/null +++ b/code/modules/mod/modules/modules_science.dm @@ -0,0 +1,135 @@ +//Science modules for MODsuits + +///Reagent Scanner - Lets the user scan reagents. +/obj/item/mod/module/reagent_scanner + name = "MOD reagent scanner module" + desc = "A module based off research-oriented Nanotrasen HUDs, this is capable of scanning the contents of \ + containers and projecting the information in an easy-to-read format on the wearer's display. \ + It cannot detect flavors, so that's up to you." + icon_state = "scanner" + module_type = MODULE_TOGGLE + complexity = 1 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.2 + incompatible_modules = list(/obj/item/mod/module/reagent_scanner) + cooldown_time = 0.5 SECONDS + +/obj/item/mod/module/reagent_scanner/on_activation() + . = ..() + if(!.) + return + ADD_TRAIT(mod.wearer, TRAIT_REAGENT_SCANNER, MOD_TRAIT) + +/obj/item/mod/module/reagent_scanner/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + REMOVE_TRAIT(mod.wearer, TRAIT_REAGENT_SCANNER, MOD_TRAIT) + +/obj/item/mod/module/reagent_scanner/advanced + name = "MOD advanced reagent scanner module" + complexity = 0 + removable = FALSE + var/explosion_detection_dist = 21 + var/had_research_scanner = FALSE + +/obj/item/mod/module/reagent_scanner/advanced/on_activation() + . = ..() + if(!.) + return + had_research_scanner = mod.wearer.research_scanner + mod.wearer.research_scanner = TRUE + RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) + +/obj/item/mod/module/reagent_scanner/advanced/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + mod.wearer.research_scanner = had_research_scanner + had_research_scanner = FALSE + UnregisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION) + +/obj/item/mod/module/reagent_scanner/advanced/proc/sense_explosion(datum/source, turf/epicenter, + devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) + SIGNAL_HANDLER + var/turf/wearer_turf = get_turf(mod.wearer) + if(wearer_turf.z == epicenter.z) + return + if(get_dist(epicenter, wearer_turf) > explosion_detection_dist) + return + to_chat(mod.wearer, span_notice("Explosion detected! Epicenter: [devastation_range], Outer: [heavy_impact_range], Shock: [light_impact_range]")) + +///Anti-Gravity - Makes the user weightless. +/obj/item/mod/module/anomaly_locked/antigrav + name = "MOD anti-gravity module" + desc = "A module that uses a gravitational core to make the user completely weightless." + icon_state = "antigrav" + module_type = MODULE_TOGGLE + complexity = 3 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.7 + incompatible_modules = list(/obj/item/mod/module/anomaly_locked) + cooldown_time = 0.5 SECONDS + accepted_anomalies = list(/obj/item/assembly/signaler/anomaly/grav) + +/obj/item/mod/module/anomaly_locked/antigrav/on_activation() + . = ..() + if(!.) + return + if(mod.wearer.has_gravity()) + new /obj/effect/temp_visual/mook_dust(get_turf(src)) + mod.wearer.AddElement(/datum/element/forced_gravity, 0) + mod.wearer.update_gravity(mod.wearer.has_gravity()) + playsound(src, 'sound/effects/gravhit.ogg', 50) + +/obj/item/mod/module/anomaly_locked/antigrav/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + mod.wearer.RemoveElement(/datum/element/forced_gravity, 0) + mod.wearer.update_gravity(mod.wearer.has_gravity()) + if(deleting) + return + if(mod.wearer.has_gravity()) + new /obj/effect/temp_visual/mook_dust(get_turf(src)) + playsound(src, 'sound/effects/gravhit.ogg', 50) + +/obj/item/mod/module/anomaly_locked/antigrav/prebuilt + prebuilt = TRUE + +///Teleporter - Lets the user teleport to a nearby location. +/obj/item/mod/module/anomaly_locked/teleporter + name = "MOD teleporter module" + desc = "A module that uses a bluespace core to let the user transport their particles elsewhere." + icon_state = "teleporter" + module_type = MODULE_ACTIVE + complexity = 3 + use_power_cost = DEFAULT_CHARGE_DRAIN * 5 + cooldown_time = 5 SECONDS + accepted_anomalies = list(/obj/item/assembly/signaler/anomaly/bluespace) + /// Time it takes to teleport + var/teleport_time = 3 SECONDS + +/obj/item/mod/module/anomaly_locked/teleporter/on_select_use(atom/target) + . = ..() + if(!.) + return + var/turf/open/target_turf = get_turf(target) + if(!istype(target_turf) || is_blocked_turf(target_turf) || !(target_turf in view(mod.wearer))) + balloon_alert(mod.wearer, "invalid target!") + return + balloon_alert(mod.wearer, "teleporting...") + var/matrix/pre_matrix = matrix() + pre_matrix.Scale(4, 0.25) + var/matrix/post_matrix = matrix() + post_matrix.Scale(0.25, 4) + animate(mod.wearer, teleport_time, color = COLOR_CYAN, transform = pre_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_OUT) + if(!do_after(mod.wearer, teleport_time, target = mod)) + balloon_alert(mod.wearer, "interrupted!") + animate(mod.wearer, teleport_time*0.1, color = null, transform = post_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_IN) + return + animate(mod.wearer, teleport_time*0.1, color = null, transform = post_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_IN) + if(!do_teleport(mod.wearer, target_turf, asoundin = 'sound/effects/phasein.ogg')) + return + drain_power(use_power_cost) + +/obj/item/mod/module/anomaly_locked/teleporter/prebuilt + prebuilt = TRUE diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm new file mode 100644 index 0000000000..098abf8015 --- /dev/null +++ b/code/modules/mod/modules/modules_security.dm @@ -0,0 +1,207 @@ +//Security modules for MODsuits + +///Cloaking - Lowers the user's visibility, can be interrupted by being touched or attacked. +/obj/item/mod/module/stealth + name = "MOD prototype cloaking module" + desc = "A complete retrofitting of the suit, this is a form of visual concealment tech employing esoteric technology \ + to bend light around the user, as well as mimetic materials to make the surface of the suit match the \ + surroundings based off sensor data. For some reason, this tech is rarely seen." + icon_state = "cloak" + module_type = MODULE_TOGGLE + complexity = 4 + active_power_cost = DEFAULT_CHARGE_DRAIN * 2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 10 + incompatible_modules = list(/obj/item/mod/module/stealth) + cooldown_time = 5 SECONDS + /// Whether or not the cloak turns off on bumping. + var/bumpoff = TRUE + /// The alpha applied when the cloak is on. + var/stealth_alpha = 50 + +/obj/item/mod/module/stealth/on_activation() + . = ..() + if(!.) + return + if(bumpoff) + RegisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP, .proc/unstealth) + RegisterSignal(mod.wearer, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_unarmed_attack) + RegisterSignal(mod.wearer, COMSIG_ATOM_BULLET_ACT, .proc/on_bullet_act) + RegisterSignal(mod.wearer, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW), .proc/unstealth) + animate(mod.wearer, alpha = stealth_alpha, time = 1.5 SECONDS) + drain_power(use_power_cost) + +/obj/item/mod/module/stealth/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + if(bumpoff) + UnregisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP) + UnregisterSignal(mod.wearer, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW)) + animate(mod.wearer, alpha = 255, time = 1.5 SECONDS) + +/obj/item/mod/module/stealth/proc/unstealth(datum/source) + SIGNAL_HANDLER + + to_chat(mod.wearer, span_warning("[src] gets discharged from contact!")) + do_sparks(2, TRUE, src) + drain_power(use_power_cost) + on_deactivation(display_message = TRUE, deleting = FALSE) + +/obj/item/mod/module/stealth/proc/on_unarmed_attack(datum/source, atom/target) + SIGNAL_HANDLER + + if(!isliving(target)) + return + unstealth(source) + +/obj/item/mod/module/stealth/proc/on_bullet_act(datum/source, obj/item/projectile/projectile) + SIGNAL_HANDLER + + if(projectile.nodamage) + return + unstealth(source) + +///Magnetic Harness - Automatically puts guns in your suit storage when you drop them. +/obj/item/mod/module/magnetic_harness + name = "MOD magnetic harness module" + desc = "Based off old TerraGov harness kits, this magnetic harness automatically attaches dropped guns back to the wearer." + icon_state = "mag_harness" + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/magnetic_harness) + /// Time before we activate the magnet. + var/magnet_delay = 0.8 SECONDS + /// The typecache of all guns we allow. + var/static/list/guns_typecache + /// The guns already allowed by the modsuit chestplate. + var/list/already_allowed_guns = list() + +/obj/item/mod/module/magnetic_harness/Initialize(mapload) + . = ..() + if(!guns_typecache) + guns_typecache = typecacheof(list(/obj/item/gun/ballistic, /obj/item/gun/energy, /obj/item/gun/grenadelauncher, /obj/item/gun/chem, /obj/item/gun/syringe)) + +/obj/item/mod/module/magnetic_harness/on_install() + already_allowed_guns = guns_typecache & mod.chestplate.allowed + mod.chestplate.allowed |= guns_typecache + +/obj/item/mod/module/magnetic_harness/on_uninstall(deleting = FALSE) + if(deleting) + return + mod.chestplate.allowed -= (guns_typecache - already_allowed_guns) + +/obj/item/mod/module/magnetic_harness/on_suit_activation() + RegisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM, .proc/check_dropped_item) + +/obj/item/mod/module/magnetic_harness/on_suit_deactivation(deleting = FALSE) + UnregisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM) + +/obj/item/mod/module/magnetic_harness/proc/check_dropped_item(datum/source, obj/item/dropped_item, force, new_location) + SIGNAL_HANDLER + + if(!is_type_in_typecache(dropped_item, guns_typecache)) + return + if(new_location != get_turf(src)) + return + addtimer(CALLBACK(src, .proc/pick_up_item, dropped_item), magnet_delay) + +/obj/item/mod/module/magnetic_harness/proc/pick_up_item(obj/item/item) + if(!isturf(item.loc) || !item.Adjacent(mod.wearer)) + return + if(!mod.wearer.equip_to_slot_if_possible(item, ITEM_SLOT_SUITSTORE, qdel_on_fail = FALSE, disable_warning = TRUE)) + return + playsound(src, 'sound/items/modsuit/magnetic_harness.ogg', 50, TRUE) + balloon_alert(mod.wearer, "[item] reattached") + drain_power(use_power_cost) + +///Pepper Shoulders + +///Holster - Instantly holsters any not huge gun. +/obj/item/mod/module/holster + name = "MOD holster module" + desc = "Based off typical storage compartments, this system allows the suit to holster a \ + standard firearm across its surface and allow for extremely quick retrieval. \ + While some users prefer the chest, others the forearm for quick deployment, \ + some law enforcement prefer the holster to extend from the thigh." + icon_state = "holster" + module_type = MODULE_USABLE + complexity = 2 + incompatible_modules = list(/obj/item/mod/module/holster) + cooldown_time = 0.5 SECONDS + allowed_inactive = TRUE + /// Gun we have holstered. + var/obj/item/gun/holstered + +/obj/item/mod/module/holster/on_use() + . = ..() + if(!.) + return + if(!holstered) + var/obj/item/gun/holding = mod.wearer.get_active_held_item() + if(!holding) + balloon_alert(mod.wearer, "nothing to holster!") + return + if(!istype(holding) || holding.w_class > WEIGHT_CLASS_BULKY) + balloon_alert(mod.wearer, "it doesn't fit!") + return + if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE)) + holstered = holding + balloon_alert(mod.wearer, "weapon holstered") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else if(mod.wearer.put_in_active_hand(holstered, forced = FALSE, ignore_animation = TRUE)) + balloon_alert(mod.wearer, "weapon drawn") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else + balloon_alert(mod.wearer, "holster full!") + +/obj/item/mod/module/holster/on_uninstall(deleting = FALSE) + if(holstered) + holstered.forceMove(drop_location()) + +/obj/item/mod/module/holster/Exited(atom/movable/gone, direction) + . = ..() + if(gone == holstered) + holstered = null + +/obj/item/mod/module/holster/Destroy() + QDEL_NULL(holstered) + return ..() + +///Megaphone - Lets you speak loud. +/obj/item/mod/module/megaphone + name = "MOD megaphone module" + desc = "A microchip megaphone linked to a MODsuit, for very important purposes, like: loudness." + icon_state = "megaphone" + module_type = MODULE_TOGGLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + incompatible_modules = list(/obj/item/mod/module/megaphone) + cooldown_time = 0.5 SECONDS + /// List of spans we add to the speaker. + var/list/voicespan = list(SPAN_COMMAND) + +/obj/item/mod/module/megaphone/on_activation() + . = ..() + if(!.) + return + RegisterSignal(mod.wearer, COMSIG_MOB_SAY, .proc/handle_speech) + +/obj/item/mod/module/megaphone/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + UnregisterSignal(mod.wearer, COMSIG_MOB_SAY) + +/obj/item/mod/module/megaphone/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + + speech_args[SPEECH_SPANS] |= voicespan + drain_power(use_power_cost) + +///Criminal Capture + +///Mirage grenade dispenser + +///Projectile Dampener + +///Active Sonar diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm new file mode 100644 index 0000000000..85ebfea132 --- /dev/null +++ b/code/modules/mod/modules/modules_service.dm @@ -0,0 +1,66 @@ +//Service modules for MODsuits + +///Bike Horn - Plays a bike horn sound. +/obj/item/mod/module/bikehorn + name = "MOD bike horn module" + desc = "A shoulder-mounted piece of heavy sonic artillery, this module uses the finest femto-manipulator technology to \ + precisely deliver an almost lethal squeeze to... a bike horn, producing a significantly memorable sound." + icon_state = "bikehorn" + module_type = MODULE_USABLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/bikehorn) + cooldown_time = 1 SECONDS + +/obj/item/mod/module/bikehorn/on_use() + . = ..() + if(!.) + return + playsound(src, 'sound/items/bikehorn.ogg', 100, FALSE) + drain_power(use_power_cost) + +///Microwave Beam - Microwaves items instantly. +/obj/item/mod/module/microwave_beam + name = "MOD microwave beam module" + desc = "An oddly domestic device, this module is installed into the user's palm, \ + hooking up with culinary scanners located in the helmet to blast food with precise microwave radiation, \ + allowing them to cook food from a distance, with the greatest of ease. Not recommended for use against grapes." + icon_state = "microwave_beam" + module_type = MODULE_ACTIVE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 5 + incompatible_modules = list(/obj/item/mod/module/microwave_beam) + cooldown_time = 10 SECONDS + var/obj/machinery/microwave/microwave + +/obj/item/mod/module/microwave_beam/Initialize(mapload) + microwave = new() + . = ..() + +/obj/item/mod/module/microwave_beam/Destroy() + QDEL_NULL(microwave) + . = ..() + +/obj/item/mod/module/microwave_beam/on_select_use(atom/target) + . = ..() + if(!.) + return + if(!isitem(target)) + return + if(!isturf(target.loc)) + balloon_alert(mod.wearer, "must be on the floor!") + return + var/obj/item/microwave_target = target + var/turf/microwave_target_loc = target.loc + var/datum/effect_system/spark_spread/spark_effect = new() + spark_effect.set_up(2, 1, mod.wearer) + spark_effect.start() + mod.wearer.Beam(target,icon_state="lightning[rand(1,12)]", time = 5) + if(microwave_target.microwave_act(microwave)) + playsound(src, 'sound/machines/microwave/microwave-end.ogg', 50, FALSE) + else + balloon_alert(mod.wearer, "can't be microwaved!") + var/datum/effect_system/spark_spread/spark_effect_two = new() + spark_effect_two.set_up(2, 1, microwave_target_loc) + spark_effect_two.start() + drain_power(use_power_cost) diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm new file mode 100644 index 0000000000..e3c70fa570 --- /dev/null +++ b/code/modules/mod/modules/modules_supply.dm @@ -0,0 +1,229 @@ +//Supply modules for MODsuits + +///Internal GPS - Extends a GPS you can use. +/obj/item/mod/module/gps + name = "MOD internal GPS module" + desc = "This module uses common Nanotrasen technology to calculate the user's position anywhere in space, \ + down to the exact coordinates. This information is fed to a central database viewable from the device itself, \ + though using it to help people is up to you." + icon_state = "gps" + module_type = MODULE_USABLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN * 0.2 + incompatible_modules = list(/obj/item/mod/module/gps) + cooldown_time = 0.5 SECONDS + allowed_inactive = TRUE + +/obj/item/mod/module/gps/Initialize(mapload) + . = ..() + AddComponent(/datum/component/gps/item, "MOD0", state = GLOB.deep_inventory_state, overlay_state = FALSE) + +/obj/item/mod/module/gps/on_use() + . = ..() + if(!.) + return + attack_self(mod.wearer) + +///Hydraulic Clamp - Lets you pick up and drop crates. +/obj/item/mod/module/clamp + name = "MOD hydraulic clamp module" + desc = "A series of actuators installed into both arms of the suit, boasting a lifting capacity of almost a ton. \ + However, this design has been locked by Nanotrasen to be primarily utilized for lifting various crates. \ + A lot of people would say that loading cargo is a dull job, but you could not disagree more." + icon_state = "clamp" + module_type = MODULE_ACTIVE + complexity = 3 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/clamp) + cooldown_time = 0.5 SECONDS + overlay_state_inactive = "module_clamp" + overlay_state_active = "module_clamp_on" + /// Time it takes to load a crate. + var/load_time = 3 SECONDS + /// The max amount of crates you can carry. + var/max_crates = 3 + /// The crates stored in the module. + var/list/stored_crates = list() + +/obj/item/mod/module/clamp/on_select_use(atom/target) + . = ..() + if(!.) + return + if(!mod.wearer.Adjacent(target)) + return + if(istype(target, /obj/structure/closet) || istype(target, /obj/structure/bigDelivery)) + var/atom/movable/picked_crate = target + if(!check_crate_pickup(picked_crate)) + return + playsound(src, 'sound/mecha/hydraulic.ogg', 25, TRUE) + if(!do_after(mod.wearer, load_time, target = target)) + balloon_alert(mod.wearer, "interrupted!") + return + if(!check_crate_pickup(picked_crate)) + return + stored_crates += picked_crate + picked_crate.forceMove(src) + balloon_alert(mod.wearer, "picked up [picked_crate]") + drain_power(use_power_cost) + else if(length(stored_crates)) + var/turf/target_turf = get_turf(target) + if(is_blocked_turf(target_turf)) + return + playsound(src, 'sound/mecha/hydraulic.ogg', 25, TRUE) + if(!do_after(mod.wearer, load_time, target = target)) + balloon_alert(mod.wearer, "interrupted!") + return + if(is_blocked_turf(target_turf)) + return + var/atom/movable/dropped_crate = pop(stored_crates) + dropped_crate.forceMove(target_turf) + balloon_alert(mod.wearer, "dropped [dropped_crate]") + drain_power(use_power_cost) + else + balloon_alert(mod.wearer, "invalid target!") + +/obj/item/mod/module/clamp/on_suit_deactivation(deleting = FALSE) + if(deleting) + return + for(var/atom/movable/crate as anything in stored_crates) + crate.forceMove(drop_location()) + stored_crates -= crate + +/obj/item/mod/module/clamp/proc/check_crate_pickup(atom/movable/target) + if(length(stored_crates) >= max_crates) + balloon_alert(mod.wearer, "too many crates!") + return FALSE + for(var/mob/living/mob in target.GetAllContents()) + if(mob.mob_size < MOB_SIZE_HUMAN) + continue + balloon_alert(mod.wearer, "crate too heavy!") + return FALSE + return TRUE + +/obj/item/mod/module/clamp/loader + name = "MOD loader hydraulic clamp module" + icon_state = "clamp_loader" + complexity = 0 + removable = FALSE + overlay_state_inactive = null + overlay_state_active = "module_clamp_loader" + load_time = 1 SECONDS + max_crates = 5 + use_mod_colors = TRUE + +///Drill - Lets you dig through rock and basalt. +/obj/item/mod/module/drill // TODO: Would be cooler with a built-in drill, but meh + name = "MOD pickaxe/drill storage module" + desc = "Provides a convenient storage compartment for pickaxes and drills." + icon_state = "drill" + complexity = 2 + incompatible_modules = list(/obj/item/mod/module/drill) + cooldown_time = 0.5 SECONDS + allowed_inactive = TRUE + module_type = MODULE_USABLE + /// Pickaxe we have stored. + var/obj/item/pickaxe/stored + +/obj/item/mod/module/drill/on_use() + . = ..() + if(!.) + return + if(!stored) + var/obj/item/pickaxe/holding = mod.wearer.get_active_held_item() + if(!holding) + balloon_alert(mod.wearer, "nothing to store!") + return + if(!istype(holding)) + balloon_alert(mod.wearer, "it doesn't fit!") + return + if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE)) + stored = holding + balloon_alert(mod.wearer, "mining instrument stored") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else if(mod.wearer.put_in_active_hand(stored, forced = FALSE, ignore_animation = TRUE)) + balloon_alert(mod.wearer, "mining instrument retrieved") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else + balloon_alert(mod.wearer, "mining instrument storage full!") + +/obj/item/mod/module/drill/on_uninstall(deleting = FALSE) + if(stored) + stored.forceMove(drop_location()) + +/obj/item/mod/module/drill/Exited(atom/movable/gone, direction) + . = ..() + if(gone == stored) + stored = null + +/obj/item/mod/module/drill/Destroy() + QDEL_NULL(stored) + return ..() + +/obj/item/mod/module/orebag // TODO + name = "MOD mining satchel storage module" + desc = "Provides a convenient storage department for a mining satchel." + icon_state = "ore" + module_type = MODULE_USABLE + complexity = 1 + use_power_cost = DEFAULT_CHARGE_DRAIN * 0.2 + incompatible_modules = list(/obj/item/mod/module/orebag) + cooldown_time = 0.5 SECONDS + allowed_inactive = TRUE + /// Pickaxe we have stored. + var/obj/item/storage/bag/ore/stored + +/obj/item/mod/module/orebag/on_use() + . = ..() + if(!.) + return + if(!stored) + var/obj/item/storage/bag/ore/holding = mod.wearer.get_active_held_item() + if(!holding) + balloon_alert(mod.wearer, "nothing to store!") + return + if(!istype(holding)) + balloon_alert(mod.wearer, "it doesn't fit!") + return + if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE)) + stored = holding + balloon_alert(mod.wearer, "mining satchel stored") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) + else if(mod.wearer.put_in_active_hand(stored, forced = FALSE, ignore_animation = TRUE)) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + balloon_alert(mod.wearer, "mining satchel retrieved") + playsound(src, 'sound/weapons/revolverempty.ogg', 100, TRUE) + else + balloon_alert(mod.wearer, "mining satchel storage full!") + +/obj/item/mod/module/orebag/on_uninstall(deleting = FALSE) + if(stored) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + stored.forceMove(drop_location()) + +/obj/item/mod/module/orebag/on_equip() + if(stored) + RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) + +/obj/item/mod/module/orebag/on_unequip() + if(stored) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + +/obj/item/mod/module/orebag/Exited(atom/movable/gone, direction) + . = ..() + if(gone == stored) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + stored = null + +/obj/item/mod/module/orebag/Destroy() + if(stored) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + QDEL_NULL(stored) + return ..() + +/obj/item/mod/module/orebag/proc/Pickup_ores() + if(stored) + stored.Pickup_ores(mod.wearer) + +// Ash accretion looks cool, but can't be arsed to implement +// Same with sphere transformation diff --git a/code/modules/mod/modules/modules_visor.dm b/code/modules/mod/modules/modules_visor.dm new file mode 100644 index 0000000000..0e44900a46 --- /dev/null +++ b/code/modules/mod/modules/modules_visor.dm @@ -0,0 +1,91 @@ +//Visor modules for MODsuits + +///Base Visor - Adds a specific HUD and traits to you. +/obj/item/mod/module/visor + name = "MOD visor module" + desc = "A heads-up display installed into the visor of the suit. They say these also let you see behind you." + module_type = MODULE_TOGGLE + complexity = 2 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 + incompatible_modules = list(/obj/item/mod/module/visor) + cooldown_time = 0.5 SECONDS + /// The HUD type given by the visor. + var/hud_type + /// The traits given by the visor. + var/list/visor_traits = list() + +/obj/item/mod/module/visor/on_activation() + . = ..() + if(!.) + return + if(hud_type) + var/datum/atom_hud/hud = GLOB.huds[hud_type] + hud.add_hud_to(mod.wearer) + for(var/trait in visor_traits) + ADD_TRAIT(mod.wearer, trait, MOD_TRAIT) + mod.wearer.update_sight() + +/obj/item/mod/module/visor/on_deactivation(display_message = TRUE, deleting = FALSE) + . = ..() + if(!.) + return + if(hud_type) + var/datum/atom_hud/hud = GLOB.huds[hud_type] + hud.remove_hud_from(mod.wearer) + for(var/trait in visor_traits) + REMOVE_TRAIT(mod.wearer, trait, MOD_TRAIT) + mod.wearer.update_sight() + +//Medical Visor - Gives you a medical HUD. +/obj/item/mod/module/visor/medhud + name = "MOD medical visor module" + desc = "A heads-up display installed into the visor of the suit. This cross-references suit sensor data with a modern \ + biological scanning suite, allowing the user to visualize the current health of organic lifeforms, as well as \ + access data such as patient files in a convenient readout. They say these also let you see behind you." + icon_state = "medhud_visor" + hud_type = DATA_HUD_MEDICAL_ADVANCED + +//Diagnostic Visor - Gives you a diagnostic HUD. +/obj/item/mod/module/visor/diaghud + name = "MOD diagnostic visor module" + desc = "A heads-up display installed into the visor of the suit. This uses a series of advanced sensors to access data \ + from advanced machinery, exosuits, and other devices, allowing the user to visualize current power levels \ + and integrity of such. They say these also let you see behind you." + icon_state = "diaghud_visor" + hud_type = DATA_HUD_DIAGNOSTIC_ADVANCED + +//Security Visor - Gives you a security HUD. +/obj/item/mod/module/visor/sechud + name = "MOD security visor module" + desc = "A heads-up display installed into the visor of the suit. This module is a heavily-retrofitted targeting system, \ + plugged into various criminal databases to be able to view arrest records, command simple security-oriented robots, \ + and generally know who to shoot. They say these also let you see behind you." + icon_state = "sechud_visor" + hud_type = DATA_HUD_SECURITY_ADVANCED + +//Meson Visor - Gives you meson vision. +/obj/item/mod/module/visor/meson + name = "MOD meson visor module" + desc = "A heads-up display installed into the visor of the suit. This module is based off well-loved meson scanner \ + technology, used by construction workers and miners across the galaxy to see basic structural and terrain layouts \ + through walls, regardless of lighting conditions. They say these also let you see behind you." + icon_state = "meson_visor" + visor_traits = list(TRAIT_MESON_VISION) + +//Thermal Visor - Gives you thermal vision. +/obj/item/mod/module/visor/thermal + name = "MOD thermal visor module" + desc = "A heads-up display installed into the visor of the suit. This uses a small IR scanner to detect and identify \ + the thermal radiation output of objects near the user. While it can detect the heat output of even something as \ + small as a rodent, it still produces irritating red overlay. They say these also let you see behind you." + icon_state = "thermal_visor" + visor_traits = list(TRAIT_THERMAL_VISION) + +//Night Visor - Gives you night vision. +/obj/item/mod/module/visor/night + name = "MOD night visor module" + desc = "A heads-up display installed into the visor of the suit. Typical for both civilian and military applications, \ + this allows the user to perceive their surroundings while in complete darkness, enhancing the view by tenfold; \ + yet brightening everything into a spooky green glow. They say these also let you see behind you." + icon_state = "night_visor" + visor_traits = list(TRAIT_TRUE_NIGHT_VISION) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 6c6733627f..0aee51bb84 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -69,6 +69,8 @@ var/projectile_piercing = NONE /// number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc! var/pierces = 0 + /// If objects are below this layer, we pass through them + var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER /// "leftover" pixels for Range() calculation as pixel_move() was moved to simulated semi-pixel movement and Range() is in tiles. var/pixels_range_leftover = 0 /// "leftover" tick pixels and stuff yeah, so we don't round off things and introducing tracing inaccuracy. @@ -519,7 +521,7 @@ if(!isliving(target)) if(isturf(target)) // non dense turfs return FALSE - if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER) + if(target.layer < hit_threshhold) return FALSE else if(!direct_target) // non dense objects do not get hit unless specifically clicked return FALSE diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index d233866f80..77d713047a 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -890,6 +890,7 @@ R.reaction_turf(A, R.volume * volume_modifier, show_message, from_gas) if("OBJ") R.reaction_obj(A, R.volume * volume_modifier, show_message) + SEND_SIGNAL(A, COMSIG_ATOM_EXPOSE_REAGENTS, cached_reagents, src, method, volume_modifier, show_message, from_gas) /datum/reagents/proc/holder_full() if(total_volume >= maximum_volume) diff --git a/code/modules/research/designs/mod_designs.dm b/code/modules/research/designs/mod_designs.dm new file mode 100644 index 0000000000..8ceadeb8fb --- /dev/null +++ b/code/modules/research/designs/mod_designs.dm @@ -0,0 +1,372 @@ +//MODsuit construction + +/datum/design/mod_shell + name = "MOD Shell" + desc = "A 'Nakamura Engineering' designed shell for a Modular Suit." + id = "mod_shell" + build_type = MECHFAB + materials = list(/datum/material/iron = 10000, /datum/material/plasma = 5000) + construction_time = 25 SECONDS + build_path = /obj/item/mod/construction/shell + category = list("MODsuit Chassis") + +/datum/design/mod_helmet + name = "MOD Helmet" + desc = "A 'Nakamura Engineering' designed helmet for a Modular Suit." + id = "mod_helmet" + build_type = MECHFAB + materials = list(/datum/material/iron = 5000) + construction_time = 10 SECONDS + build_path = /obj/item/mod/construction/helmet + category = list("MODsuit Chassis") + +/datum/design/mod_chestplate + name = "MOD Chestplate" + desc = "A 'Nakamura Engineering' designed chestplate for a Modular Suit." + id = "mod_chestplate" + build_type = MECHFAB + materials = list(/datum/material/iron = 5000) + construction_time = 10 SECONDS + build_path = /obj/item/mod/construction/chestplate + category = list("MODsuit Chassis") + +/datum/design/mod_gauntlets + name = "MOD Gauntlets" + desc = "'Nakamura Engineering' designed gauntlets for a Modular Suit." + id = "mod_gauntlets" + build_type = MECHFAB + materials = list(/datum/material/iron = 5000) + construction_time = 10 SECONDS + build_path = /obj/item/mod/construction/gauntlets + category = list("MODsuit Chassis") + +/datum/design/mod_boots + name = "MOD Boots" + desc = "'Nakamura Engineering' designed boots for a Modular Suit." + id = "mod_boots" + build_type = MECHFAB + materials = list(/datum/material/iron = 5000) + construction_time = 10 SECONDS + build_path = /obj/item/mod/construction/boots + category = list("MODsuit Chassis") + +/datum/design/mod_plating + name = "MOD External Plating" + desc = "External plating for a MODsuit." + id = "mod_plating_standard" + build_type = PROTOLATHE | MECHFAB + materials = list(/datum/material/iron = 6000, /datum/material/glass = 3000, /datum/material/plasma = 1000) + construction_time = 15 SECONDS + build_path = /obj/item/mod/construction/armor + category = list("MODsuit Chassis", "MODsuit Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + research_icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + research_icon_state = "standard-plating" + +/datum/design/mod_plating/New() + . = ..() + var/obj/item/mod/construction/armor/armor_type = build_path + var/datum/mod_theme/theme = GLOB.mod_themes[initial(armor_type.theme)] + desc = "External plating for a MODsuit. [theme.desc]" + +/datum/design/mod_plating/engineering + name = "MOD Engineering Plating" + id = "mod_plating_engineering" + build_path = /obj/item/mod/construction/armor/engineering + materials = list(/datum/material/iron = 6000, /datum/material/gold = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + research_icon_state = "engineering-plating" + +/datum/design/mod_plating/atmospheric + name = "MOD Atmospheric Plating" + id = "mod_plating_atmospheric" + build_path = /obj/item/mod/construction/armor/atmospheric + materials = list(/datum/material/iron = 6000, /datum/material/titanium = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + research_icon_state = "atmospheric-plating" + +/datum/design/mod_plating/mining + name = "MOD Mining Plating" + id = "mod_plating_mining" + build_path = /obj/item/mod/construction/armor/mining + materials = list(/datum/material/iron = 6000, /datum/material/titanium = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_CARGO + research_icon_state = "atmospheric-mining" + +/datum/design/mod_plating/medical + name = "MOD Medical Plating" + id = "mod_plating_medical" + build_path = /obj/item/mod/construction/armor/medical + materials = list(/datum/material/iron = 6000, /datum/material/silver = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + research_icon_state = "medical-plating" + +/datum/design/mod_plating/security + name = "MOD Security Plating" + id = "mod_plating_security" + build_path = /obj/item/mod/construction/armor/security + materials = list(/datum/material/iron = 6000, /datum/material/uranium = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + research_icon_state = "security-plating" + +/datum/design/mod_plating/cosmohonk + name = "MOD Cosmohonk Plating" + id = "mod_plating_cosmohonk" + build_path = /obj/item/mod/construction/armor/cosmohonk + materials = list(/datum/material/iron = 6000, /datum/material/bananium = 2000, /datum/material/glass = 1000, /datum/material/plasma = 1000) + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + research_icon_state = "cosmohonk-plating" + +/datum/design/mod_paint_kit + name = "MOD Paint Kit" + desc = "A paint kit for Modular Suits." + id = "mod_paint_kit" + build_type = MECHFAB + materials = list(/datum/material/iron = 1000, /datum/material/plastic = 500) + construction_time = 5 SECONDS + build_path = /obj/item/mod/paint + category = list("MODsuit Modules", "MODsuit Designs") + +//MODsuit modules + +/datum/design/module + name = "MOD Module" + build_type = PROTOLATHE | MECHFAB + construction_time = 1 SECONDS + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000) + build_path = /obj/item/mod/module + category = list("MODsuit Modules", "MODsuit Designs") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/datum/design/module/New() + . = ..() + var/obj/item/mod/module/module = build_path + desc = "[initial(module.desc)] It uses [initial(module.complexity)] complexity." + +/datum/design/module/mod_storage + name = "Storage Module" + id = "mod_storage" + materials = list(/datum/material/iron = 2500, /datum/material/glass = 500) + build_path = /obj/item/mod/module/storage + +/datum/design/module/mod_visor_medhud + name = "Medical Visor Module" + id = "mod_visor_medhud" + materials = list(/datum/material/silver = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/visor/medhud + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/module/mod_visor_diaghud + name = "Diagnostic Visor Module" + id = "mod_visor_diaghud" + materials = list(/datum/material/gold = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/visor/diaghud + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/module/mod_visor_sechud + name = "Security Visor Module" + id = "mod_visor_sechud" + materials = list(/datum/material/titanium = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/visor/sechud + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/module/mod_visor_meson + name = "Meson Visor Module" + id = "mod_visor_meson" + materials = list(/datum/material/uranium = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/visor/meson + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_visor_welding + name = "Welding Protection Module" + id = "mod_welding" + materials = list(/datum/material/iron = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/welding + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_t_ray + name = "T-Ray Scanner Module" + id = "mod_t_ray" + materials = list(/datum/material/iron = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/t_ray + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_health_analyzer + name = "Health Analyzer Module" + id = "mod_health_analyzer" + materials = list(/datum/material/iron = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/health_analyzer + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/module/mod_stealth + name = "Cloak Module" + id = "mod_stealth" + materials = list(/datum/material/iron = 1000, /datum/material/bluespace = 500) + build_path = /obj/item/mod/module/stealth + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/module/mod_jetpack + name = "Ion Jetpack Module" + id = "mod_jetpack" + materials = list(/datum/material/iron = 1500, /datum/material/plasma = 1000) + build_path = /obj/item/mod/module/jetpack + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_magboot + name = "Magnetic Stabilizator Module" + id = "mod_magboot" + materials = list(/datum/material/iron = 1000, /datum/material/gold = 500) + build_path = /obj/item/mod/module/magboot + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_mag_harness + name = "Magnetic Harness Module" + id = "mod_mag_harness" + materials = list(/datum/material/iron = 1500, /datum/material/silver = 500) + build_path = /obj/item/mod/module/magnetic_harness + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/module/mod_tether + name = "Emergency Tether Module" + id = "mod_tether" + materials = list(/datum/material/iron = 1000, /datum/material/silver = 500) + build_path = /obj/item/mod/module/tether + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_mouthhole + name = "Eating Apparatus Module" + id = "mod_mouthhole" + materials = list(/datum/material/iron = 1500) + build_path = /obj/item/mod/module/mouthhole + +/datum/design/module/mod_rad_protection + name = "Radiation Protection Module" + id = "mod_rad_protection" + materials = list(/datum/material/iron = 1000, /datum/material/uranium = 1000) + build_path = /obj/item/mod/module/rad_protection + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_emp_shield + name = "EMP Shield Module" + id = "mod_emp_shield" + materials = list(/datum/material/iron = 1000, /datum/material/plasma = 1000) + build_path = /obj/item/mod/module/emp_shield + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/module/mod_flashlight + name = "Flashlight Module" + id = "mod_flashlight" + materials = list(/datum/material/iron = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/flashlight + +/datum/design/module/mod_reagent_scanner + name = "Reagent Scanner Module" + id = "mod_reagent_scanner" + materials = list(/datum/material/glass = 1000) + build_path = /obj/item/mod/module/reagent_scanner + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/module/mod_gps + name = "Internal GPS Module" + id = "mod_gps" + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/mod/module/gps + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO + +/datum/design/module/mod_constructor + name = "Constructor Module" + id = "mod_constructor" + materials = list(/datum/material/iron = 1000, /datum/material/titanium = 500) + build_path = /obj/item/mod/module/constructor + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_quick_carry + name = "Quick Carry Module" + id = "mod_quick_carry" + materials = list(/datum/material/iron = 1000, /datum/material/titanium = 500) + build_path = /obj/item/mod/module/quick_carry + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/module/mod_bikehorn + name = "Bike Horn Module" + id = "mod_bikehorn" + materials = list(/datum/material/plastic = 500, /datum/material/iron = 500) + build_path = /obj/item/mod/module/bikehorn + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/module/mod_microwave_beam + name = "Microwave Beam Module" + id = "mod_microwave_beam" + materials = list(/datum/material/iron = 1000, /datum/material/uranium = 500) + build_path = /obj/item/mod/module/microwave_beam + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/module/mod_clamp + name = "Crate Clamp Module" + id = "mod_clamp" + materials = list(/datum/material/iron = 2000) + build_path = /obj/item/mod/module/clamp + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/module/mod_drill + name = "Drill Module" + id = "mod_drill" + materials = list(/datum/material/silver = 1000, /datum/material/iron = 2000) + build_path = /obj/item/mod/module/drill + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/module/mod_orebag + name = "Ore Bag Module" + id = "mod_orebag" + materials = list(/datum/material/iron = 1500) + build_path = /obj/item/mod/module/orebag + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/module/mod_dna_lock + name = "DNA Lock Module" + id = "mod_dna_lock" + materials = list(/datum/material/diamond = 500, /datum/material/glass = 1000) + build_path = /obj/item/mod/module/dna_lock + +/datum/design/module/mister_atmos + name = "Resin Mister Module" + id = "mod_mister_atmos" + materials = list(/datum/material/glass = 1000, /datum/material/titanium = 1500) + build_path = /obj/item/mod/module/mister/atmos + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/module/mod_holster + name = "Holster Module" + id = "mod_holster" + materials = list(/datum/material/iron = 1500, /datum/material/glass = 500) + build_path = /obj/item/mod/module/holster + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/module/surgicalprocessor + name = "Surgical Processor Module" + id = "mod_surgicalprocessor" + materials = list(/datum/material/titanium = 250, /datum/material/glass = 1000, /datum/material/silver = 1500) + build_path = /obj/item/mod/module/surgical_processor + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +/datum/design/module/defibrillator + name = "Defibrillator Module" + id = "mod_defib" + materials = list(/datum/material/titanium = 250, /datum/material/diamond = 1000, /datum/material/silver = 1500) + build_path = /obj/item/mod/module/defibrillator + departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + +//MODsuit anomalock modules +/datum/design/module/mod_antigrav + name = "Anti-Gravity Module" + id = "mod_antigrav" + materials = list(/datum/material/iron = 2500, /datum/material/glass = 2000, /datum/material/uranium = 2000) + build_path = /obj/item/mod/module/anomaly_locked/antigrav + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/module/mod_teleporter + name = "Teleporter Module" + id = "mod_teleporter" + materials = list(/datum/material/iron = 2500, /datum/material/glass = 2000, /datum/material/bluespace = 2000) + build_path = /obj/item/mod/module/anomaly_locked/teleporter + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index 684f27ccad..4057503e09 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -15,7 +15,8 @@ "Weapons", "Ammo", "Firing Pins", - "Computer Parts" + "Computer Parts", + "MODsuit Designs" ) production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index f93560ed10..110b6c037c 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -26,7 +26,8 @@ "Subspace Telecomms", "Research Machinery", "Misc. Machinery", - "Computer Parts" + "Computer Parts", + "MODsuit Designs" ) console_link = FALSE production_animation = "protolathe_n" diff --git a/code/modules/research/techweb/nodes/mod_nodes.dm b/code/modules/research/techweb/nodes/mod_nodes.dm new file mode 100644 index 0000000000..db453bb0d6 --- /dev/null +++ b/code/modules/research/techweb/nodes/mod_nodes.dm @@ -0,0 +1,125 @@ +/datum/techweb_node/mod_basic + id = "mod" + starting_node = TRUE + display_name = "Basic Modular Suits" + description = "Specialized back mounted power suits with various different modules." + design_ids = list( + "mod_shell", + "mod_helmet", + "mod_chestplate", + "mod_gauntlets", + "mod_boots", + "mod_plating_standard", + "mod_paint_kit", + "mod_storage", + "mod_welding", + "mod_mouthhole", + "mod_flashlight", + ) + +/datum/techweb_node/mod_advanced + id = "mod_advanced" + display_name = "Advanced Modular Suits" + description = "More advanced modules, to improve modular suits." + prereq_ids = list("mod", "robotics") + design_ids = list( + "mod_plating_mining", + "mod_visor_diaghud", + "mod_gps", + "mod_reagent_scanner", + "mod_clamp", + "mod_drill", + "mod_orebag", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/mod_engineering + id = "mod_engineering" + display_name = "Engineering Modular Suits" + description = "Engineering suits, for powered engineers." + prereq_ids = list("mod_advanced", "engineering") + design_ids = list( + "mod_plating_engineering", + "mod_visor_meson", + "mod_t_ray", + "mod_magboot", + "mod_tether", + "mod_constructor", + "mod_mister_atmos", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/mod_advanced_engineering + id = "mod_advanced_engineering" + display_name = "Advanced Engineering Modular Suits" + description = "Advanced Engineering suits, for advanced powered engineers." + prereq_ids = list("mod_engineering", "adv_engi") + design_ids = list( + "mod_plating_atmospheric", + "mod_jetpack", + "mod_rad_protection", + "mod_emp_shield", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + +/datum/techweb_node/mod_medical + id = "mod_medical" + display_name = "Medical Modular Suits" + description = "Medical suits for quick rescue purposes." + prereq_ids = list("mod_advanced", "biotech") + design_ids = list( + "mod_plating_medical", + "mod_visor_medhud", + "mod_health_analyzer", + "mod_quick_carry", + "mod_dna_lock", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/mod_advanced_medical + id = "mod_advanced_medical" + display_name = "Advanced Medical Modular Suits" + description = "Advanced medical suits for quicker rescue purposes." + prereq_ids = list("mod_medical", "adv_biotech") + design_ids = list( + "mod_defib", + "mod_surgicalprocessor", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + +/datum/techweb_node/mod_security + id = "mod_security" + display_name = "Security Modular Suits" + description = "Security suits for space crime handling." + prereq_ids = list("mod_advanced", "sec_basic") + design_ids = list( + "mod_plating_security", + "mod_visor_sechud", + "mod_stealth", + "mod_mag_harness", + "mod_holster", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/mod_entertainment + id = "mod_entertainment" + display_name = "Entertainment Modular Suits" + description = "Powered suits for protection against low-humor environments." + prereq_ids = list("mod_advanced", "clown") + design_ids = list( + "mod_plating_cosmohonk", + "mod_bikehorn", + "mod_microwave_beam", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/mod_anomaly + id = "mod_anomaly" + display_name = "Anomalock Modular Suits" + description = "Modules for modular suits that require anomaly cores to function." + prereq_ids = list("mod_advanced", "anomaly_research") + design_ids = list( + "mod_antigrav", + "mod_teleporter", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index a4eafc4b01..9e39ec58e8 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -68,6 +68,15 @@ var/obj/item/surgical_processor/SP = locate() in R.module.modules if(SP) advanced_surgeries |= SP.advanced_surgeries + else + var/obj/item/surgical_processor/SP + for(var/obj/item/surgical_processor/processor in user.held_items) + SP = processor + break + if(!SP) + SP = locate(/obj/item/surgical_processor) in get_turf(user) + if(SP) + advanced_surgeries |= SP.advanced_surgeries var/turf/T = get_turf(patient) var/obj/structure/table/optable/table = locate(/obj/structure/table/optable, T) diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index de5628dc05..f4ffda302e 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -817,7 +817,7 @@ mecha_flags &= ~SILICON_PILOT AI.forceMove(card) card.AI = AI - AI.controlled_mech = null + AI.controlled_equipment = null AI.remote_control = null to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") @@ -856,7 +856,7 @@ mecha_flags |= SILICON_PILOT moved_inside(AI) AI.cancel_camera() - AI.controlled_mech = src + AI.controlled_equipment = src AI.remote_control = src AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. if(interaction == AI_MECH_HACK) @@ -1083,7 +1083,7 @@ AI.linked_core = null return to_chat(AI, "Returning to core...") - AI.controlled_mech = null + AI.controlled_equipment = null AI.remote_control = null mob_container = AI newloc = get_turf(AI.linked_core) diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm index 9bcf539bc8..71f0d8537e 100644 --- a/code/modules/vehicles/mecha/mech_fabricator.dm +++ b/code/modules/vehicles/mecha/mech_fabricator.dm @@ -58,6 +58,8 @@ "Exosuit Equipment", "Exosuit Ammunition", "Cyborg Upgrade Modules", + "MODsuit Chassis", + "MODsuit Modules", "Cybernetics", "Implants", "Control Interfaces", diff --git a/code/modules/vehicles/mecha/mecha_wreckage.dm b/code/modules/vehicles/mecha/mecha_wreckage.dm index b9f299731a..70c543fbde 100644 --- a/code/modules/vehicles/mecha/mecha_wreckage.dm +++ b/code/modules/vehicles/mecha/mecha_wreckage.dm @@ -35,7 +35,7 @@ AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. AI.forceMove(src) //Put the dead AI inside the wreckage for recovery add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon - AI.controlled_mech = null + AI.controlled_equipment = null AI.remote_control = null /obj/structure/mecha_wreckage/Destroy() diff --git a/html/changelogs/archive/2023-05.yml b/html/changelogs/archive/2023-05.yml index 99a602cd82..2b0d37de3e 100644 --- a/html/changelogs/archive/2023-05.yml +++ b/html/changelogs/archive/2023-05.yml @@ -5,3 +5,5 @@ BongaTheProto: - bugfix: fixed the runner jackets' icons - rscadd: adds a new snout + SandPoot and Dexxiol: + - imageadd: Replaced commando alt-skin sprites with some better ones. diff --git a/icons/mob/actions/actions_mod.dmi b/icons/mob/actions/actions_mod.dmi new file mode 100644 index 0000000000..84fcbc00db Binary files /dev/null and b/icons/mob/actions/actions_mod.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_clothing.dmi b/icons/mob/clothing/modsuit/mod_clothing.dmi new file mode 100644 index 0000000000..f8dba0a006 Binary files /dev/null and b/icons/mob/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_clothing_anthro.dmi b/icons/mob/clothing/modsuit/mod_clothing_anthro.dmi new file mode 100644 index 0000000000..a18800875c Binary files /dev/null and b/icons/mob/clothing/modsuit/mod_clothing_anthro.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_modules.dmi b/icons/mob/clothing/modsuit/mod_modules.dmi new file mode 100644 index 0000000000..1001ae77d2 Binary files /dev/null and b/icons/mob/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_clothing.dmi b/icons/obj/clothing/modsuit/mod_clothing.dmi new file mode 100644 index 0000000000..41ebc1a2ba Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_construction.dmi b/icons/obj/clothing/modsuit/mod_construction.dmi new file mode 100644 index 0000000000..5140af8cb6 Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_construction.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi new file mode 100644 index 0000000000..037e7fe696 Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/obj/defibrillators.dmi b/icons/obj/defibrillators.dmi index a4dc9e65e3..8871d5b049 100644 Binary files a/icons/obj/defibrillators.dmi and b/icons/obj/defibrillators.dmi differ diff --git a/modular_sand/icons/mob/clothing/head.dmi b/modular_sand/icons/mob/clothing/head.dmi index b0b9a34673..91671c1423 100644 Binary files a/modular_sand/icons/mob/clothing/head.dmi and b/modular_sand/icons/mob/clothing/head.dmi differ diff --git a/modular_sand/icons/mob/clothing/head_muzzled.dmi b/modular_sand/icons/mob/clothing/head_muzzled.dmi index 19eb0eaf85..04df6e4161 100644 Binary files a/modular_sand/icons/mob/clothing/head_muzzled.dmi and b/modular_sand/icons/mob/clothing/head_muzzled.dmi differ diff --git a/modular_sand/icons/mob/clothing/suit.dmi b/modular_sand/icons/mob/clothing/suit.dmi index 0cfaf655f8..15ef52c83c 100644 Binary files a/modular_sand/icons/mob/clothing/suit.dmi and b/modular_sand/icons/mob/clothing/suit.dmi differ diff --git a/modular_sand/icons/mob/clothing/suit_digi.dmi b/modular_sand/icons/mob/clothing/suit_digi.dmi index 70bfbe3245..3daa324e77 100644 Binary files a/modular_sand/icons/mob/clothing/suit_digi.dmi and b/modular_sand/icons/mob/clothing/suit_digi.dmi differ diff --git a/modular_sand/icons/obj/clothing/hats.dmi b/modular_sand/icons/obj/clothing/hats.dmi index b269f8d166..dbc7ff2a2a 100644 Binary files a/modular_sand/icons/obj/clothing/hats.dmi and b/modular_sand/icons/obj/clothing/hats.dmi differ diff --git a/modular_sand/icons/obj/clothing/suits.dmi b/modular_sand/icons/obj/clothing/suits.dmi index 05ba6f3f04..bcd9b3a898 100644 Binary files a/modular_sand/icons/obj/clothing/suits.dmi and b/modular_sand/icons/obj/clothing/suits.dmi differ diff --git a/sound/items/modsuit/atrocinator_step.ogg b/sound/items/modsuit/atrocinator_step.ogg new file mode 100644 index 0000000000..deda85ac35 Binary files /dev/null and b/sound/items/modsuit/atrocinator_step.ogg differ diff --git a/sound/items/modsuit/ballin.ogg b/sound/items/modsuit/ballin.ogg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sound/items/modsuit/ballout.ogg b/sound/items/modsuit/ballout.ogg new file mode 100644 index 0000000000..f911f1a6a6 Binary files /dev/null and b/sound/items/modsuit/ballout.ogg differ diff --git a/sound/items/modsuit/flamethrower.ogg b/sound/items/modsuit/flamethrower.ogg new file mode 100644 index 0000000000..447245d50b Binary files /dev/null and b/sound/items/modsuit/flamethrower.ogg differ diff --git a/sound/items/modsuit/inflate_bloon.ogg b/sound/items/modsuit/inflate_bloon.ogg new file mode 100644 index 0000000000..9b030d66ce Binary files /dev/null and b/sound/items/modsuit/inflate_bloon.ogg differ diff --git a/sound/items/modsuit/loader_charge.ogg b/sound/items/modsuit/loader_charge.ogg new file mode 100644 index 0000000000..61d5531f72 Binary files /dev/null and b/sound/items/modsuit/loader_charge.ogg differ diff --git a/sound/items/modsuit/loader_launch.ogg b/sound/items/modsuit/loader_launch.ogg new file mode 100644 index 0000000000..513118f3c6 Binary files /dev/null and b/sound/items/modsuit/loader_launch.ogg differ diff --git a/sound/items/modsuit/magnetic_harness.ogg b/sound/items/modsuit/magnetic_harness.ogg new file mode 100644 index 0000000000..3d19fccc56 Binary files /dev/null and b/sound/items/modsuit/magnetic_harness.ogg differ diff --git a/sound/items/modsuit/rewinder.ogg b/sound/items/modsuit/rewinder.ogg new file mode 100644 index 0000000000..2587562dc1 Binary files /dev/null and b/sound/items/modsuit/rewinder.ogg differ diff --git a/sound/items/modsuit/springlock.ogg b/sound/items/modsuit/springlock.ogg new file mode 100644 index 0000000000..8d0013d263 Binary files /dev/null and b/sound/items/modsuit/springlock.ogg differ diff --git a/sound/items/modsuit/tem_shot.ogg b/sound/items/modsuit/tem_shot.ogg new file mode 100644 index 0000000000..50905b95f1 Binary files /dev/null and b/sound/items/modsuit/tem_shot.ogg differ diff --git a/sound/items/modsuit/time_anchor_set.ogg b/sound/items/modsuit/time_anchor_set.ogg new file mode 100644 index 0000000000..457f8e6dba Binary files /dev/null and b/sound/items/modsuit/time_anchor_set.ogg differ diff --git a/sound/mecha/hydraulic.ogg b/sound/mecha/hydraulic.ogg new file mode 100644 index 0000000000..3281ed2dc0 Binary files /dev/null and b/sound/mecha/hydraulic.ogg differ diff --git a/sound/weapons/revolverempty.ogg b/sound/weapons/revolverempty.ogg new file mode 100644 index 0000000000..81ddb1e2e0 Binary files /dev/null and b/sound/weapons/revolverempty.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 7ef2accd98..0165558c72 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -80,6 +80,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" @@ -157,8 +158,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_medical.dm" +#include "code\__DEFINES\dcs\signals\signals_mod.dm" #include "code\__DEFINES\dcs\signals\signals_movable.dm" #include "code\__DEFINES\dcs\signals\signals_painting.dm" +#include "code\__DEFINES\dcs\signals\signals_reagent.dm" #include "code\__DEFINES\dcs\signals\signals_screentips.dm" #include "code\__DEFINES\dcs\signals\signals_subsystem.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm" @@ -870,6 +874,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" @@ -3014,6 +3019,27 @@ #include "code\modules\mob\living\simple_animal\slime\slime.dm" #include "code\modules\mob\living\simple_animal\slime\slime_mobility.dm" #include "code\modules\mob\living\simple_animal\slime\subtypes.dm" +#include "code\modules\mod\mod_actions.dm" +#include "code\modules\mod\mod_activation.dm" +#include "code\modules\mod\mod_ai.dm" +#include "code\modules\mod\mod_clothes.dm" +#include "code\modules\mod\mod_construction.dm" +#include "code\modules\mod\mod_control.dm" +#include "code\modules\mod\mod_paint.dm" +#include "code\modules\mod\mod_theme.dm" +#include "code\modules\mod\mod_types.dm" +#include "code\modules\mod\mod_ui.dm" +#include "code\modules\mod\modules\_module.dm" +#include "code\modules\mod\modules\modules.dm" +#include "code\modules\mod\modules\modules_engineering.dm" +#include "code\modules\mod\modules\modules_general.dm" +#include "code\modules\mod\modules\modules_maint.dm" +#include "code\modules\mod\modules\modules_medical.dm" +#include "code\modules\mod\modules\modules_science.dm" +#include "code\modules\mod\modules\modules_security.dm" +#include "code\modules\mod\modules\modules_service.dm" +#include "code\modules\mod\modules\modules_supply.dm" +#include "code\modules\mod\modules\modules_visor.dm" #include "code\modules\modular_computers\laptop_vendor.dm" #include "code\modules\modular_computers\computers\_modular_computer_shared.dm" #include "code\modules\modular_computers\computers\item\computer.dm" @@ -3427,6 +3453,7 @@ #include "code\modules\research\designs\medical_designs.dm" #include "code\modules\research\designs\mining_designs.dm" #include "code\modules\research\designs\misc_designs.dm" +#include "code\modules\research\designs\mod_designs.dm" #include "code\modules\research\designs\nanite_designs.dm" #include "code\modules\research\designs\power_designs.dm" #include "code\modules\research\designs\smelting_designs.dm" @@ -3496,6 +3523,7 @@ #include "code\modules\research\techweb\nodes\mecha_nodes.dm" #include "code\modules\research\techweb\nodes\medical_nodes.dm" #include "code\modules\research\techweb\nodes\misc_nodes.dm" +#include "code\modules\research\techweb\nodes\mod_nodes.dm" #include "code\modules\research\techweb\nodes\nanites_nodes.dm" #include "code\modules\research\techweb\nodes\robotics_nodes.dm" #include "code\modules\research\techweb\nodes\syndicate_nodes.dm" diff --git a/tgui/packages/tgui/interfaces/MODpaint.js b/tgui/packages/tgui/interfaces/MODpaint.js new file mode 100644 index 0000000000..9a329688eb --- /dev/null +++ b/tgui/packages/tgui/interfaces/MODpaint.js @@ -0,0 +1,151 @@ +import { useBackend } from '../backend'; +import { Box, Stack, Section, ByondUi, Slider, Flex, Button } from '../components'; +import { Window } from '../layouts'; +import { capitalize } from 'common/string'; + +const colorToMatrix = (param) => { + switch (param) { + case 'red': + return [ + 1, 0, 0, 0, 0.25, 0.5, 0, 0, 0.25, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, + ]; + case 'yellow': + return [ + 0.5, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0.25, 0.25, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, + 0, + ]; + case 'green': + return [ + 0.5, 0.25, 0, 0, 0, 1, 0, 0, 0, 0.25, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, + ]; + case 'teal': + return [ + 0.25, 0.25, 0.25, 0, 0, 0.5, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 0, 1, 0, 0, + 0, 0, + ]; + case 'blue': + return [ + 0.25, 0, 0.25, 0, 0, 0.5, 0.25, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, + ]; + case 'purple': + return [ + 0.5, 0, 0.5, 0, 0.25, 0.5, 0.25, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, + 0, + ]; + } +}; + +const displayText = (param) => { + switch (param) { + case 'r': + return 'Red'; + case 'g': + return 'Green'; + case 'b': + return 'Blue'; + } +}; + +export const MODpaint = (props, context) => { + const { act, data } = useBackend(context); + const { mapRef, currentColor } = data; + const [ + [rr, rg, rb, ra], + [gr, gg, gb, ga], + [br, bg, bb, ba], + [ar, ag, ab, aa], + [cr, cg, cb, ca], + ] = currentColor; + const presets = ['red', 'yellow', 'green', 'teal', 'blue', 'purple']; + const prefixes = ['r', 'g', 'b']; + return ( + + + + + {[0, 1, 2].map((row) => ( +
+ {[0, 1, 2].map((col) => ( + + + + {`${displayText(prefixes[col])}:`} + + + + `${value}%`} + onDrag={(e, value) => { + let retColor = currentColor; + retColor[row * 4 + col] = value / 100; + act('transition_color', { color: retColor }); + }} + /> + + + ))} +
+ ))} +
+ +
+ + {presets.map((preset) => ( +
+
+
+
+ +
+ +
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/MODsuit.js b/tgui/packages/tgui/interfaces/MODsuit.js new file mode 100644 index 0000000000..7975facad6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MODsuit.js @@ -0,0 +1,754 @@ +import { useBackend, useLocalState } from '../backend'; +import { Button, ColorBox, LabeledList, ProgressBar, Section, Collapsible, Box, Icon, Stack, Table, Dimmer, NumberInput, Flex, AnimatedNumber, Dropdown } from '../components'; +import { Window } from '../layouts'; + +const ConfigureNumberEntry = (props, context) => { + const { name, value, module_ref } = props; + const { act } = useBackend(context); + return ( + + act('configure', { + 'key': name, + 'value': value, + 'ref': module_ref, + })} + /> + ); +}; + +const ConfigureBoolEntry = (props, context) => { + const { name, value, module_ref } = props; + const { act } = useBackend(context); + return ( + + act('configure', { + 'key': name, + 'value': !value, + 'ref': module_ref, + })} + /> + ); +}; + +const ConfigureColorEntry = (props, context) => { + const { name, value, module_ref } = props; + const { act } = useBackend(context); + return ( + <> + + + + + + ); +}; + +const displayText = (param) => { + switch (param) { + case 1: + return 'Use'; + case 2: + return 'Toggle'; + case 3: + return 'Select'; + } +}; + +const ParametersSection = (props, context) => { + const { act, data } = useBackend(context); + const { + active, + malfunctioning, + open, + selected_module, + complexity, + complexity_max, + wearer_name, + wearer_job, + AI, + is_pAI, + is_user_AI, + } = data; + const status = malfunctioning + ? 'Malfunctioning' + : active + ? 'Active' + : 'Inactive'; + return ( +
+ + act('activate')} + /> + }> + {status} + + + {open ? 'Open' : 'Closed'} + + + {selected_module || 'None'} + + + {complexity} ({complexity_max}) + + + {wearer_name}, {wearer_job} + + act('remove_pai')} + /> + ) : (<> ) + }> + {AI || 'None'} + + +
+ ); +}; + +const HardwareSection = (props, context) => { + const { act, data } = useBackend(context); + const { + active, + control, + helmet, + chestplate, + gauntlets, + boots, + cell, + charge, + } = data; + return ( +
+ + + {control} + {helmet || 'None'} + + {chestplate || 'None'} + + + {gauntlets || 'None'} + + {boots || 'None'} + + + + {(cell && ( + + {cell} + + + + + )) || ( + + No Cell Detected + + )} + +
+ ); +}; + +const InfoSection = (props, context) => { + const { act, data } = useBackend(context); + const { active, modules } = data; + const info_modules = modules.filter((module) => !!module.id); + + return ( +
+ + {(info_modules.length !== 0 + && info_modules.map((module) => { + const Module = ID2MODULE[module.id]; + return ( + + {!active && } + + + ); + })) || No Info Modules Detected} + +
+ ); +}; + +const ModuleSection = (props, context) => { + const { act, data } = useBackend(context); + const { complexity_max, modules } = data; + const [configureState, setConfigureState] = useLocalState( + context, + 'module_configuration', + null + ); + return ( +
+ + {(modules.length !== 0 + && modules.map((module) => { + return ( + + +
+ {configureState === module.ref && ( + setConfigureState(null)} + /> + )} + + + +
+ {module.description} +
+
+
+ ); + })) || ( + + No Modules Detected + + )} +
+
+ ); +}; + +export const MODsuit = (props, context) => { + const { act, data } = useBackend(context); + const { ui_theme, interface_break } = data; + return ( + + + {(!!interface_break && ) || ( + + + + + + + + + + + + + + + )} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/SuitStorageUnit.js b/tgui/packages/tgui/interfaces/SuitStorageUnit.js index 0ac3c61932..70bec672f7 100644 --- a/tgui/packages/tgui/interfaces/SuitStorageUnit.js +++ b/tgui/packages/tgui/interfaces/SuitStorageUnit.js @@ -15,6 +15,7 @@ export const SuitStorageUnit = (props, context) => { helmet, mask, shoes, + mod, storage, } = data; return ( @@ -99,6 +100,15 @@ export const SuitStorageUnit = (props, context) => { item: 'shoes', })} /> + +