From b538ad721b3fd5f71fdc39b336934aca45720d1e Mon Sep 17 00:00:00 2001 From: PeriodicChaos Date: Sat, 20 Aug 2022 12:55:37 -0400 Subject: [PATCH] modsuits part 2 --- code/datums/ai/objects/mod.dm | 48 - code/modules/client/preferences/mod_select.dm | 23 + code/modules/mob/living/carbon/carbon.dm | 17 +- .../mob/living/carbon/human/inventory.dm | 10 +- .../mob/living/carbon/human/species.dm | 2 +- code/modules/mob/living/living.dm | 5 +- code/modules/mob/living/silicon/ai/ai.dm | 22 +- code/modules/mob/mob.dm | 2 +- code/modules/mob/mob_helpers.dm | 2 +- code/modules/mod/mod_actions.dm | 71 ++ code/modules/mod/mod_activation.dm | 217 +++++ code/modules/mod/mod_ai.dm | 88 ++ code/modules/mod/mod_clothes.dm | 123 +++ code/modules/mod/mod_construction.dm | 278 ++++++ code/modules/mod/mod_control.dm | 559 ++++++++++++ code/modules/mod/mod_theme.dm | 854 ++++++++++++++++++ code/modules/mod/mod_types.dm | 157 ++++ code/modules/mod/mod_ui.dm | 80 ++ code/modules/mod/modules/_module.dm | 252 ++++++ code/modules/mod/modules/modules.dm | 352 ++++++++ tgstation.dme | 14 +- 21 files changed, 3109 insertions(+), 67 deletions(-) delete mode 100644 code/datums/ai/objects/mod.dm create mode 100644 code/modules/client/preferences/mod_select.dm create mode 100644 code/modules/mod/mod_actions.dm create mode 100644 code/modules/mod/mod_activation.dm create mode 100644 code/modules/mod/mod_ai.dm create mode 100644 code/modules/mod/mod_clothes.dm create mode 100644 code/modules/mod/mod_construction.dm create mode 100644 code/modules/mod/mod_control.dm create mode 100644 code/modules/mod/mod_theme.dm create mode 100644 code/modules/mod/mod_types.dm create mode 100644 code/modules/mod/mod_ui.dm create mode 100644 code/modules/mod/modules/_module.dm create mode 100644 code/modules/mod/modules/modules.dm diff --git a/code/datums/ai/objects/mod.dm b/code/datums/ai/objects/mod.dm deleted file mode 100644 index ced2ffceb6..0000000000 --- a/code/datums/ai/objects/mod.dm +++ /dev/null @@ -1,48 +0,0 @@ -/// An AI controller for the MODsuit pathfinder module. It's activated by implant and attaches itself to the user. -/datum/ai_controller/mod - blackboard = list( - BB_MOD_TARGET, - BB_MOD_IMPLANT, - ) - max_target_distance = MOD_AI_RANGE //a little spicy but its one specific item that summons it, and it doesnt run otherwise - ai_movement = /datum/ai_movement/jps - ///ID card generated from the suit's required access. Used for pathing. - var/obj/item/card/id/advanced/id_card - -/datum/ai_controller/mod/TryPossessPawn(atom/new_pawn) - if(!istype(new_pawn, /obj/item/mod/control)) - return AI_CONTROLLER_INCOMPATIBLE - var/obj/item/mod/control/mod = new_pawn - id_card = new /obj/item/card/id/advanced/simple_bot() - if(length(mod.req_access)) - id_card.set_access(mod.req_access) - return ..() //Run parent at end - -/datum/ai_controller/mod/UnpossessPawn(destroy) - QDEL_NULL(id_card) - return ..() //Run parent at end - -/datum/ai_controller/mod/SelectBehaviors(delta_time) - current_behaviors = list() - if(blackboard[BB_MOD_TARGET] && blackboard[BB_MOD_IMPLANT]) - queue_behavior(/datum/ai_behavior/mod_attach) - -/datum/ai_controller/mod/get_access() - return id_card - -/datum/ai_behavior/mod_attach - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/mod_attach/perform(delta_time, datum/ai_controller/controller) - . = ..() - if(!controller.pawn.Adjacent(controller.blackboard[BB_MOD_TARGET])) - return - var/obj/item/implant/mod/implant = controller.blackboard[BB_MOD_IMPLANT] - implant.module.attach(controller.blackboard[BB_MOD_TARGET]) - finish_action(controller, TRUE) - -/datum/ai_behavior/mod_attach/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.blackboard[BB_MOD_TARGET] = null - var/obj/item/implant/mod/implant = controller.blackboard[BB_MOD_IMPLANT] - implant.end_recall(succeeded) diff --git a/code/modules/client/preferences/mod_select.dm b/code/modules/client/preferences/mod_select.dm new file mode 100644 index 0000000000..c80c8b4c69 --- /dev/null +++ b/code/modules/client/preferences/mod_select.dm @@ -0,0 +1,23 @@ +/// Switches between mouse buttons for MODsuit active modules +/datum/preference/choiced/mod_select + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "mod_select" + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/choiced/mod_select/init_possible_values() + return list(MIDDLE_CLICK, ALT_CLICK) + +/datum/preference/choiced/mod_select/create_default_value() + return MIDDLE_CLICK + +/datum/preference/choiced/mod_select/apply_to_client_updated(client/client, value) + if(!ishuman(client.mob)) + return + var/mob/living/carbon/human/client_owner = client.mob + if(!istype(client_owner.back, /obj/item/mod/control)) + return + var/obj/item/mod/control/mod = client_owner.back + if(!mod.selected_module) + return + UnregisterSignal(mod.wearer, mod.selected_module.used_signal) + mod.selected_module.update_signal(value) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index e12a28d01a..daedb7a2b3 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -27,8 +27,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) @@ -624,12 +624,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/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 4c30a1d742..2f3c1357da 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -304,7 +304,8 @@ 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(!storage) if(!thing) equipped_back.attack_hand(src) else @@ -314,10 +315,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/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index afceabf2a6..68a9f8bd0f 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1303,7 +1303,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(ITEM_SLOT_FEET) if(H.shoes) return FALSE - if( !(I.slot_flags & ITEM_SLOT_FEET) ) + if((DIGITIGRADE in species_traits) && !(I.item_flags & IGNORE_DIGITIGRADE)) return FALSE if(num_legs < 2) return FALSE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index be6508dd96..e6aac9cde9 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -47,6 +47,8 @@ return ..() /mob/living/proc/ZImpactDamage(turf/T, levels) + if(SEND_SIGNAL(src, COMSIG_LIVING_Z_IMPACT, levels, T) & NO_Z_IMPACT_DAMAGE) + return visible_message("[src] crashes into [T] with a sickening noise!", \ "You crash into [T] with a sickening noise!") adjustBruteLoss((levels * 5) ** 1.5) @@ -753,7 +755,8 @@ return pick("trails_1", "trails_2") /mob/living/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0) - if(buckled) + playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE) + if(buckled || mob_negates_gravity()) return if(client && client.move_delay >= world.time + world.tick_lag*2) pressure_resistance_prob_delta -= 30 diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index deaf50973d..3cda68c015 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 @@ -173,10 +173,20 @@ 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) + 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 +420,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/mob.dm b/code/modules/mob/mob.dm index 507a94b613..baa4d87075 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -722,7 +722,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 90a7aae95e..cdad94cf3f 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -585,7 +585,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..2c27879e79 --- /dev/null +++ b/code/modules/mod/mod_actions.dm @@ -0,0 +1,71 @@ +/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 + +/datum/action/item_action/mod/New(Target) + ..() + mod = Target + +/datum/action/item_action/mod/Grant(mob/M) + if(owner) + Share(M) + return + ..() + +/datum/action/item_action/mod/Remove(mob/M) + var/mob_to_grant + for(var/datum/weakref/reference as anything in sharers) + var/mob/freeloader = reference.resolve() + if(!freeloader) + continue + mob_to_grant = freeloader + break + ..() + if(mob_to_grant) + Grant(mob_to_grant) + +/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/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/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/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 diff --git a/code/modules/mod/mod_activation.dm b/code/modules/mod/mod_activation.dm new file mode 100644 index 0000000000..f7869dc227 --- /dev/null +++ b/code/modules/mod/mod_activation.dm @@ -0,0 +1,217 @@ +#define MOD_ACTIVATION_STEP_TIME 2 SECONDS +#define MOD_ACTIVATION_STEP_FLAGS IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED + +/// 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 || 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) + 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(locked && !active && !allowed(user) && !force_deactivate) + balloon_alert(user, "access insufficient!") + 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) + continue + module.on_deactivation() + activating = TRUE + to_chat(wearer, span_notice("MODsuit [active ? "shutting down" : "starting up"].")) + if(do_after(wearer, MOD_ACTIVATION_STEP_TIME, wearer, MOD_ACTIVATION_STEP_FLAGS)) + 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) + if(do_after(wearer, MOD_ACTIVATION_STEP_TIME, wearer, MOD_ACTIVATION_STEP_FLAGS)) + 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) + if(do_after(wearer, MOD_ACTIVATION_STEP_TIME, wearer, MOD_ACTIVATION_STEP_FLAGS)) + 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) + if(do_after(wearer, MOD_ACTIVATION_STEP_TIME, wearer, MOD_ACTIVATION_STEP_FLAGS)) + 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) + if(do_after(wearer, MOD_ACTIVATION_STEP_TIME, wearer, MOD_ACTIVATION_STEP_FLAGS)) + 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) + activating = FALSE + return TRUE + +///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 + if(part == boots) + boots.icon_state = "[skin]-boots[seal ? "-sealed" : ""]" + wearer.update_inv_shoes() + if(part == gauntlets) + gauntlets.icon_state = "[skin]-gauntlets[seal ? "-sealed" : ""]" + wearer.update_inv_gloves() + if(part == chestplate) + chestplate.icon_state = "[skin]-chestplate[seal ? "-sealed" : ""]" + wearer.update_inv_wear_suit() + wearer.update_inv_w_uniform() + if(part == helmet) + helmet.icon_state = "[skin]-helmet[seal ? "-sealed" : ""]" + 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) + +#undef MOD_ACTIVATION_STEP_TIME +#undef MOD_ACTIVATION_STEP_FLAGS diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm new file mode 100644 index 0000000000..f470fb8046 --- /dev/null +++ b/code/modules/mod/mod_ai.dm @@ -0,0 +1,88 @@ +/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 + balloon_alert(user, "transferring to card...") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert(user, "interrupted!") + 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) + if(action.owner == intAI) + action.Remove(intAI) + else + action.Unshare(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(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 + 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(new_ai) + +#define MOVE_DELAY 2 +#define WEARER_DELAY 1 +#define LONE_DELAY 5 +#define CELL_PER_STEP DEFAULT_CELL_DRAIN * 2.5 + +/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) + COOLDOWN_START(src, cooldown_mod_move, movedelay * timemodifier + slowdown) + playsound(src, 'sound/mecha/mechmove01.ogg', 25, TRUE) + cell.charge = max(0, cell.charge - CELL_PER_STEP) + if(ismovable(wearer?.loc)) + return wearer.loc.relaymove(wearer, direction) + if(wearer && !wearer.Process_Spacemove(direction)) + return FALSE + var/atom/movable/mover = wearer || src + return step(mover, direction) + +#undef MOVE_DELAY +#undef WEARER_DELAY +#undef LONE_DELAY +#undef CELL_PER_STEP diff --git a/code/modules/mod/mod_clothes.dm b/code/modules/mod/mod_clothes.dm new file mode 100644 index 0000000000..37045015d6 --- /dev/null +++ b/code/modules/mod/mod_clothes.dm @@ -0,0 +1,123 @@ +/obj/item/clothing/head/helmet/space/mod + name = "MOD helmet" + desc = "A helmet for a MODsuit." + icon = 'icons/obj/mod.dmi' + icon_state = "helmet" + worn_icon = 'icons/mob/mod.dmi' + 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 = FLASH_PROTECTION_NONE + clothing_flags = SNUG_FIT + flags_inv = HIDEFACIALHAIR + flags_cover = NONE + visor_flags = THICKMATERIAL|STOPSPRESSUREDAMAGE + visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT + visor_flags_cover = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF + var/alternate_layer = NECK_LAYER + var/obj/item/mod/control/mod + +/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/armor/mod + name = "MOD chestplate" + desc = "A chestplate for a MODsuit." + icon = 'icons/obj/mod.dmi' + icon_state = "chestplate" + worn_icon = 'icons/mob/mod.dmi' + 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 + 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 + +/obj/item/clothing/suit/armor/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/mod.dmi' + icon_state = "gauntlets" + worn_icon = 'icons/mob/mod.dmi' + 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 + +/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, silent = TRUE) + overslot = null + +/obj/item/clothing/shoes/mod + name = "MOD boots" + desc = "A pair of boots for a MODsuit." + icon = 'icons/obj/mod.dmi' + icon_state = "boots" + worn_icon = 'icons/mob/mod.dmi' + 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 = IGNORE_DIGITIGRADE + var/obj/item/mod/control/mod + var/obj/item/clothing/overslot + +/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, silent = TRUE) + overslot = null diff --git a/code/modules/mod/mod_construction.dm b/code/modules/mod/mod_construction.dm new file mode 100644 index 0000000000..4916f8b6bc --- /dev/null +++ b/code/modules/mod/mod_construction.dm @@ -0,0 +1,278 @@ +/obj/item/mod/construction + desc = "A part used in MOD construction." + inhand_icon_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" + 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-broken" + 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-armor" + 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]-armor" + +/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/mining + theme = /datum/mod_theme/mining + +/obj/item/mod/construction/armor/medical + theme = /datum/mod_theme/medical + +/obj/item/mod/construction/armor/security + theme = /datum/mod_theme/security + +/obj/item/mod/construction/armor/cosmohonk + theme = /datum/mod_theme/cosmohonk + +/obj/item/mod/paint + name = "MOD paint kit" + desc = "This kit will repaint your MODsuit to something unique." + icon = 'icons/obj/mod.dmi' + icon_state = "paintkit" + +#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..8ca7dc8573 --- /dev/null +++ b/code/modules/mod/mod_control.dm @@ -0,0 +1,559 @@ +/// 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/mod.dmi' + icon_state = "standard-control" + worn_icon = 'icons/mob/mod.dmi' + +/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" + inhand_icon_state = "mod_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) + 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) + 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 ID locked. + var/locked = 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_CELL_DRAIN + /// Slowdown of the MOD when not active. + var/slowdown_inactive = 2 + /// Slowdown of the MOD when active. + var/slowdown_active = 1 + /// 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/armor/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 mob inhabiting the MOD. + var/mob/living/silicon/ai/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(length(req_access)) + locked = TRUE + 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/armor/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)]" + 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(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 + +/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/screwdriver_act(mob/living/user, obj/item/screwdriver) + if(..()) + return TRUE + if(active || activating || ai_controller) + 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/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(istype(attacking_item, /obj/item/mod/paint)) + if(active || activating) + balloon_alert(user, "suit is active!") + else if(paint(user, attacking_item)) + balloon_alert(user, "suit painted") + else + balloon_alert(user, "not painted!") + 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/GetAccess() + if(ai_controller) + return req_access.Copy() + else + return ..() + +/obj/item/mod/control/emag_act(mob/user) + locked = !locked + balloon_alert(user, "[locked ? "locked" : "unlocked"]") + +/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(mutable_appearance/standing, 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(standing) + 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/paint(mob/user, obj/item/paint) + if(length(theme.skins) <= 1) + return FALSE + var/list/skins = list() + for(var/mod_skin in theme.skins) + skins[mod_skin] = image(icon = icon, icon_state = "[mod_skin]-control") + var/pick = show_radial_menu(user, src, skins, custom_check = FALSE, require_near = TRUE) + if(!pick || !user.is_holding(paint)) + return FALSE + skin = pick + 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() + return TRUE + +/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_theme.dm b/code/modules/mod/mod_theme.dm new file mode 100644 index 0000000000..174cc1bab3 --- /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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 100, FIRE = 25, ACID = 25, WOUND = 10) + /// 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_CELL_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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 100, ACID = 25, WOUND = 10) + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 100, ACID = 75, WOUND = 10) + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, FIRE = 100, ACID = 90, WOUND = 10) + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, FIRE = 100, ACID = 75, WOUND = 15) + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cell_drain = DEFAULT_CELL_DRAIN * 2 + complexity_max = DEFAULT_MAX_COMPLEXITY + 5 + skins = list( + "mining" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 60, ACID = 75, WOUND = 10) + cell_drain = DEFAULT_CELL_DRAIN * 1.5 + slowdown_inactive = 1 + slowdown_active = 0.5 + skins = list( + "medical" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 100, ACID = 100, WOUND = 10) + resistance_flags = FIRE_PROOF|ACID_PROOF + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cell_drain = DEFAULT_CELL_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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100, WOUND = 15) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 75, ACID = 75, WOUND = 20) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 25, BIO = 100, FIRE = 100, ACID = 95, WOUND = 25) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, FIRE = 100, ACID = 100, WOUND = 20) + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 100, FIRE = 60, ACID = 30, WOUND = 5) + cell_drain = DEFAULT_CELL_DRAIN * 0.25 + slowdown_inactive = 1.75 + slowdown_active = 1.25 + skins = list( + "cosmohonk" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR, + SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 35, BIO = 100, FIRE = 50, ACID = 90, WOUND = 25) + 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(/obj/item/mod/module/armor_booster) + skins = list( + "syndicate" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 55, BIO = 100, FIRE = 100, ACID = 100, WOUND = 25) + 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(/obj/item/mod/module/armor_booster/elite) + skins = list( + "elite" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 40, ENERGY = 50, BOMB = 35, BIO = 100, FIRE = 100, ACID = 100, WOUND = 30) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, FIRE = 100, ACID = 75, WOUND = 5) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 35, BULLET = 30, LASER = 30, ENERGY = 40, BOMB = 50, BIO = 100, FIRE = 100, ACID = 90, WOUND = 15) + 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 = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR, + SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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 = 35, BULLET = 40, LASER = 40, ENERGY = 50, BOMB = 50, BIO = 100, FIRE = 100, ACID = 100, WOUND = 15) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + 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) + 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 = SNUG_FIT|THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH, + SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF, + ), + 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) + 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_CELL_DRAIN * 0 + slowdown_inactive = 0 + slowdown_active = 0 + skins = list( + "debug" = list( + HELMET_LAYER = null, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE, + UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCKS_SHOVE_KNOCKDOWN, + 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..af482e50f4 --- /dev/null +++ b/code/modules/mod/mod_types.dm @@ -0,0 +1,157 @@ +/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/large_capacity, /obj/item/mod/module/welding, /obj/item/mod/module/rad_protection, /obj/item/mod/module/jetpack, /obj/item/mod/module/flashlight) + +/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/large_capacity, /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/large_capacity, /obj/item/mod/module/flashlight, /obj/item/mod/module/health_analyzer, /obj/item/mod/module/injector) + +/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/large_capacity, /obj/item/mod/module/welding, /obj/item/mod/module/flashlight, /obj/item/mod/module/circuit, /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/large_capacity, /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/large_capacity, /obj/item/mod/module/welding, /obj/item/mod/module/holster, /obj/item/mod/module/pathfinder) + +/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/syndicate, /obj/item/mod/module/welding, /obj/item/mod/module/tether, /obj/item/mod/module/pathfinder, /obj/item/mod/module/flashlight, /obj/item/mod/module/dna_lock) + +/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/syndicate, /obj/item/mod/module/welding, /obj/item/mod/module/jetpack, /obj/item/mod/module/visor/thermal, /obj/item/mod/module/flashlight, /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/syndicate, /obj/item/mod/module/welding, /obj/item/mod/module/emp_shield, /obj/item/mod/module/jetpack, /obj/item/mod/module/visor/thermal, /obj/item/mod/module/flashlight, /obj/item/mod/module/holster) + +/obj/item/mod/control/pre_equipped/enchanted + theme = /datum/mod_theme/enchanted + cell = /obj/item/stock_parts/cell/crystal_cell/wizard + initial_modules = list(/obj/item/mod/module/storage/large_capacity, /obj/item/mod/module/energy_shield/wizard, /obj/item/mod/module/emp_shield) + +/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/large_capacity, /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/large_capacity, /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/bluespace, /obj/item/mod/module/welding, /obj/item/mod/module/emp_shield, /obj/item/mod/module/jetpack, /obj/item/mod/module/holster) + +/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/bluespace, /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/bluespace, /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, /obj/item/mod/module/injector) //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/bluespace, /obj/item/mod/module/welding, /obj/item/mod/module/stealth/ninja, /obj/item/mod/module/quick_carry/advanced, /obj/item/mod/module/magboot/advanced, /obj/item/mod/module/jetpack) + +//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..1e719e93eb --- /dev/null +++ b/code/modules/mod/mod_ui.dm @@ -0,0 +1,80 @@ +/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() + var/data = list() + data["interface_break"] = interface_break + data["malfunctioning"] = malfunctioning + data["open"] = open + data["active"] = active + data["locked"] = locked + 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["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(!allowed(usr) && locked) + balloon_alert(usr, "insufficient access!") + playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return + if(malfunctioning && prob(75)) + balloon_alert(usr, "button malfunctions!") + return + switch(action) + if("lock") + locked = !locked + balloon_alert(usr, "[locked ? "locked" : "unlocked"]!") + 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"]) + return TRUE diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm new file mode 100644 index 0000000000..a310f95537 --- /dev/null +++ b/code/modules/mod/modules/_module.dm @@ -0,0 +1,252 @@ +/obj/item/mod/module + name = "MOD module" + 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_CELL_DRAIN * 0 + /// Power use when active + var/active_power_cost = DEFAULT_CELL_DRAIN * 0 + /// Power use when used, we call it manually + var/use_power_cost = DEFAULT_CELL_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 + /// 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 + /// 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(HAS_TRAIT(user, TRAIT_DIAGNOSTIC_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 || module_type == MODULE_PASSIVE) + return + if(module_type != MODULE_USABLE) + if(active) + on_deactivation() + else + on_activation() + else + on_use() + SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED) + +/// 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(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 + var/used_button = mod.wearer.client?.prefs.read_preference(/datum/preference/choiced/mod_select) || MIDDLE_CLICK + update_signal(used_button) + balloon_alert(mod.wearer, "[src] activated, [used_button]-click to use") + active = TRUE + COOLDOWN_START(src, cooldown_timer, cooldown_time) + 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 + 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) + 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(mutable_appearance/standing) + . = list() + 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('icons/mob/mod.dmi', used_overlay, layer = standing.layer + 0.1) + . += module_icon + +/// Updates the signal used by active modules to be activated +/obj/item/mod/module/proc/update_signal(value) + switch(value) + if(MIDDLE_CLICK) + mod.selected_module.used_signal = COMSIG_MOB_MIDDLECLICKON + if(ALT_CLICK) + mod.selected_module.used_signal = COMSIG_MOB_ALTCLICKON + RegisterSignal(mod.wearer, mod.selected_module.used_signal, /obj/item/mod/module.proc/on_special_click) diff --git a/code/modules/mod/modules/modules.dm b/code/modules/mod/modules/modules.dm new file mode 100644 index 0000000000..b5fc9a41e9 --- /dev/null +++ b/code/modules/mod/modules/modules.dm @@ -0,0 +1,352 @@ +/obj/item/mod/module + name = "MOD module" + 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_CELL_DRAIN * 0 + /// Power use when active + var/active_power_cost = DEFAULT_CELL_DRAIN * 0 + /// Power use when used, we call it manually + var/use_power_cost = DEFAULT_CELL_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 + /// 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 + /// 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(HAS_TRAIT(user, TRAIT_DIAGNOSTIC_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 || module_type == MODULE_PASSIVE) + return + if(module_type != MODULE_USABLE) + if(active) + on_deactivation() + else + on_activation() + else + on_use() + SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED) + +/// 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(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 + var/used_button = mod.wearer.client?.prefs.read_preference(/datum/preference/choiced/mod_select) || MIDDLE_CLICK + update_signal(used_button) + balloon_alert(mod.wearer, "[src] activated, [used_button]-click to use") + active = TRUE + COOLDOWN_START(src, cooldown_timer, cooldown_time) + 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 + 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) + 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(mutable_appearance/standing) + . = list() + 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('icons/mob/mod.dmi', used_overlay, layer = standing.layer + 0.1) + . += module_icon + +/// Updates the signal used by active modules to be activated +/obj/item/mod/module/proc/update_signal(value) + switch(value) + if(MIDDLE_CLICK) + mod.selected_module.used_signal = COMSIG_MOB_MIDDLECLICKON + if(ALT_CLICK) + mod.selected_module.used_signal = COMSIG_MOB_ALTCLICKON + RegisterSignal(mod.wearer, mod.selected_module.used_signal, /obj/item/mod/module.proc/on_special_click) + icon_state = "magic_nullifier" + removable = FALSE + incompatible_modules = list(/obj/item/mod/module/anti_magic) + +/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_CELL_DRAIN*0.75 +// use_power_cost = DEFAULT_CELL_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(mutable_appearance/standing) + 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/tgstation.dme b/tgstation.dme index 1ced56efb1..5efc223582 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -506,7 +506,6 @@ #include "code\datums\achievements\misc_scores.dm" #include "code\datums\achievements\skill_achievements.dm" #include "code\datums\actions\beam_rifle.dm" -#include "code\datums\ai\objects\mod.dm" #include "code\datums\announcers\_announcer.dm" #include "code\datums\announcers\default_announcer.dm" #include "code\datums\announcers\intern_announcer.dm" @@ -2023,6 +2022,7 @@ #include "code\modules\client\preferences_savefile.dm" #include "code\modules\client\preferences_toggles.dm" #include "code\modules\client\preferences_vr.dm" +#include "code\modules\client\preferences\mod_select.dm" #include "code\modules\client\verbs\aooc.dm" #include "code\modules\client\verbs\autobunker.dm" #include "code\modules\client\verbs\etips.dm" @@ -2958,6 +2958,18 @@ #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_construction.dm" +#include "code\modules\mod\mod_control.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\modular_computers\laptop_vendor.dm" #include "code\modules\modular_computers\computers\_modular_computer_shared.dm" #include "code\modules\modular_computers\computers\item\computer.dm"