Files
FenodyreeAvandGitHub 1a3caa5deb Fixes RIGs with no boots suffering slowdown for not deploying boots (#22890)
Some light rigs don't even have boots to deploy, so they shouldn't get
slowdown because of it.
Decided just to make all light RIGs have no slowdown, as none of them
had footstep sounds anyway.

changes:
- bugfix: "Fixes light rigs getting slowdown from having undeployed
boots."
  - bugfix: "Fixes light rigs having no energy armour."
2026-07-21 00:33:11 +00:00

1348 lines
44 KiB
Plaintext

/*
* Defines the behavior of hardsuits/rigs/power armor.
*/
/obj/item/rig
name = "hardsuit control module"
desc = "A back-mounted hardsuit deployment and control mechanism."
icon = 'icons/obj/rig_modules.dmi'
contained_sprite = TRUE
slot_flags = SLOT_BACK
req_one_access = list()
req_access = list()
w_class = WEIGHT_CLASS_BULKY
// These values are passed on to all component pieces.
armor = list(
MELEE = ARMOR_MELEE_RESISTANT,
BULLET = ARMOR_BALLISTIC_MINOR,
LASER = ARMOR_LASER_SMALL,
ENERGY = ARMOR_ENERGY_MINOR,
BOMB = ARMOR_BOMB_PADDED,
BIO = ARMOR_BIO_SHIELDED,
RAD = ARMOR_RAD_MINOR
)
min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
max_pressure_protection = RIG_MAX_PRESSURE
min_pressure_protection = 0
siemens_coefficient = 0.35
permeability_coefficient = 0.1
unacidable = 1
slowdown = 0.5 // All rigs by default should have slowdown.
action_button_name = "Open Hardsuit Interface"
var/has_sealed_state = FALSE
var/has_hidden_jumpsuit = FALSE
/// Used for AI moving.
var/wearer_move_delay
var/ai_controlled_move_delay = 10
/// When did a mounted pAI or AI use a module? used to prevent admin msg spam
var/last_remote_message
/// Keeps track of what this rig should spawn with.
var/suit_type = "hardsuit"
var/list/initial_modules
var/chest_type = /obj/item/clothing/suit/space/rig
var/helm_type = /obj/item/clothing/head/helmet/space/rig
var/boot_type = /obj/item/clothing/shoes/magboots/rig
var/glove_type = /obj/item/clothing/gloves/rig
var/cell_type = /obj/item/cell/high
var/air_type = /obj/item/tank/oxygen
//Component/device holders.
/// Air tank, if any.
var/obj/item/tank/air_supply
/// Deployable boots, if any.
var/obj/item/clothing/shoes/boots = null
/// Deployable chestpiece, if any.
var/obj/item/clothing/suit/space/rig/chest
/// Deployable helmet, if any.
var/obj/item/clothing/head/helmet/space/rig/helmet = null
/// Deployable gauntlets, if any.
var/obj/item/clothing/gloves/rig/gloves = null
/// Power supply, if any.
var/obj/item/cell/cell
/// Primary system (used with middle-click)
var/obj/item/rig_module/selected_module = null
/// Kinda shitty to have a var for a module, but saves time.
var/obj/item/rig_module/vision/visor
/// As above.
var/obj/item/rig_module/voice/speech
/// The person currently wearing the rig.
var/mob/living/carbon/human/wearer
/// Holder for on-mob icon.
var/image/mob_icon
/// Power consumption/use bookkeeping.
var/list/installed_modules = list()
// Rig status vars.
/// Access panel status.
var/open = FALSE
/// Lock status.
var/locked = TRUE
/// To whom do we belong?
var/dnaLock
/// Are we crushing the occupant to death?
var/crushing = FALSE
var/subverted = 0
/// Interfaces get locked when the corresponding rig wire is pulsed.
var/interface_locked = FALSE
/// Whether or not the AI is currently overriding control of the suit.
var/control_overridden = FALSE
/// Whether or not the AI is capable of overriding control of the suit.
var/ai_override_enabled = FALSE
/// Access req status.
var/security_check_enabled = TRUE
var/malfunctioning = 0
var/malfunction_delay = 0
var/electrified = 0
/// Ensures that you can't queue up multiple piece toggle commands at once and bug anything out.
var/piece_being_deployed = FALSE
/// Rate at which the suit cell will passively use power when online.
var/cell_draw_rate = CHARGE_DRAIN_DEFAULT
var/seal_delay = SEAL_DELAY
/// Keeps track of seal status independantly of canremove.
var/sealing
/// Should we be applying suit maluses? Note that this is tristate: 0 = online; 1 = offline, modules still active; 2 = offline, inc. modules
/// Generally speaking, a suit is 0 when on, 1 when just turned off, and 2 after it has run process(), which deactivates all modules if offline == 1
var/offline = 0
/// If the suit is deployed and unpowered, it sets slowdown to this.
var/offline_slowdown = 1.5
var/vision_restriction = TINT_NONE
var/offline_vision_restriction = TINT_HEAVY
///If set, will adjust the ITEM_FLAG_AIRTIGHT flag on components. Otherwise it should leave them untouched.
var/airtight = TRUE
var/emp_protection = 0
// Wiring! How exciting.
var/datum/wires/rig/wires
var/datum/effect_system/sparks/spark_system
/// All rigs by default should have access to general
var/allowed_module_types = MODULE_GENERAL
var/list/species_restricted = list(BODYTYPE_HUMAN,BODYTYPE_TAJARA,BODYTYPE_UNATHI, BODYTYPE_SKRELL, BODYTYPE_IPC, BODYTYPE_IPC_BISHOP, BODYTYPE_IPC_ZENGHU)
///If TRUE, this rig will protect against anomalies. Currently only used for AMI hardsuit.
var/anomaly_protection = FALSE
var/ui_configure_ref
/obj/item/rig/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
. = ..()
if(wearer)
for(var/obj/item/piece in list(helmet,gloves,chest,boots))
if(!piece || piece.loc != wearer)
continue
. += "[icon2html(piece, usr)] \The [piece] [piece.gender == PLURAL ? "are" : "is"] deployed."
if(src.loc == usr)
. += "The maintenance panel is [open ? "open" : "closed"]."
. += "Hardsuit systems are [offline ? SPAN_WARNING("offline") : SPAN_GOOD("<span class='good'>online")]."
/obj/item/rig/Initialize()
. = ..()
item_state = icon_state
wires = new(src)
if(!LAZYLEN(req_access) && !LAZYLEN(req_one_access))
locked = FALSE
spark_system = bind_spark(src, 5)
last_remote_message = world.time
if(initial_modules && initial_modules.len)
for(var/path in initial_modules)
var/obj/item/rig_module/module = new path(src)
installed_modules += module
module.installed(src)
// Create and initialize our various segments.
if(cell_type)
cell = new cell_type(src)
if(air_type)
air_supply = new air_type(src)
if(glove_type)
gloves = new glove_type(src)
verbs |= /obj/item/rig/proc/toggle_gauntlets
if(helm_type)
helmet = new helm_type(src)
verbs |= /obj/item/rig/proc/toggle_helmet
if(boot_type)
boots = new boot_type(src)
verbs |= /obj/item/rig/proc/toggle_boots
if(chest_type)
chest = new chest_type(src)
if(allowed)
chest.allowed = allowed
verbs |= /obj/item/rig/proc/toggle_chest
for(var/obj/item/clothing/piece in list(gloves,helmet,boots,chest))
if(!istype(piece))
continue
piece.canremove = FALSE
piece.name = "[suit_type] [initial(piece.name)]"
piece.desc = "It seems to be part of a [src.name]."
piece.icon = icon
piece.icon_state = "[initial(icon_state)]_[piece.clothing_class()]"
piece.item_state = "[initial(icon_state)]"
piece.contained_sprite = TRUE
if(length(icon_supported_species_tags))
set_piece_adaptation(piece)
piece.min_cold_protection_temperature = min_cold_protection_temperature
piece.max_heat_protection_temperature = max_heat_protection_temperature
if(piece.siemens_coefficient > siemens_coefficient) //So that insulated gloves keep their insulation.
piece.siemens_coefficient = siemens_coefficient
piece.permeability_coefficient = permeability_coefficient
piece.unacidable = unacidable
piece.species_restricted = species_restricted
if(islist(armor))
var/datum/component/armor/armor_component = piece.GetComponent(/datum/component/armor)
if(istype(armor_component))
qdel(armor_component)
piece.AddComponent(/datum/component/armor, armor, ARMOR_TYPE_STANDARD|ARMOR_TYPE_RIG)
if(chest.flags_inv & HIDEJUMPSUIT)
has_hidden_jumpsuit = TRUE
chest.flags_inv &= ~HIDEJUMPSUIT
set_vision(!offline)
update_icon(1)
/// Sets the icon adaptation and species supported tags equal to parent, can be overriden for custom functionality
/obj/item/rig/proc/set_piece_adaptation(var/obj/item/clothing/piece)
piece.icon_auto_adapt = TRUE
piece.icon_supported_species_tags = icon_supported_species_tags
/obj/item/rig/Destroy()
STOP_PROCESSING(SSprocessing, src)
QDEL_NULL(air_supply)
QDEL_NULL(boots)
QDEL_NULL(chest)
QDEL_NULL(helmet)
QDEL_NULL(gloves)
QDEL_NULL(cell)
selected_module = null
visor = null
speech = null
wearer?.wearing_rig = null
wearer = null
mob_icon = null
QDEL_LIST(installed_modules)
QDEL_NULL(wires)
QDEL_NULL(spark_system)
return ..()
/obj/item/rig/proc/set_vision(var/active)
if(helmet)
helmet.tint = (active? vision_restriction : offline_vision_restriction)
/**
* Updates the hardsuit's slowdown if it changes.
* This gives a heavy slowdown penalty if the suit is deployed but the boots are not.
* Otherwise, it is an easy exploit to make any hardsuit stealthy by wearing everything EXCEPT the boots.
* Now you can creep in your hardsuit, but you have to go slowly to do it.
*/
/obj/item/rig/proc/update_slowdown()
var/new_slowdown = initial(slowdown)
if(offline)
new_slowdown = offline_slowdown
else if(chest && wearer?.wear_suit == chest && (!boots || wearer.shoes != boots)) //If the chest is deployed, but the boots are not.
new_slowdown = offline_slowdown + 2 //The boots aren't taking the weight of the suit, so the wearer isn't benefiting from the hardsuit's powered servos.
if(slowdown != new_slowdown)
slowdown = new_slowdown
wearer?.update_equipment_speed_mods() //This should only proc if worn, but just in case we check.
if(slowdown == offline_slowdown + 2)
to_chat(wearer, SPAN_WARNING("Without the boots deployed, the hardsuit's servos aren't supporting the weight of the suit."))
/obj/item/rig/proc/suit_is_deployed()
if(!istype(wearer) || src.loc != wearer || wearer.back != src)
return 0
if(helm_type && !(helmet && wearer.head == helmet))
return 0
if(glove_type && !(gloves && wearer.gloves == gloves))
return 0
if(boot_type && !(boots && wearer.shoes == boots))
return 0
if(chest_type && !(chest && wearer.wear_suit == chest))
return 0
return 1
/// Not currently used.
/obj/item/rig/proc/reset()
offline = 2
canremove = 1
crushing = FALSE
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
if(!piece) continue
piece.icon_state = "[initial(icon_state)]"
if(airtight)
piece.max_pressure_protection = initial(piece.max_pressure_protection)
piece.min_pressure_protection = initial(piece.min_pressure_protection)
piece.item_flags &= ~ITEM_FLAG_AIRTIGHT
update_icon(1)
/obj/item/rig/proc/toggle_seals(var/mob/initiator,var/instant)
if(sealing) return
// Seal toggling can be initiated by the suit AI, too
if(!wearer)
to_chat(initiator, SPAN_DANGER("Cannot toggle suit: The suit is currently not being worn by anyone."))
return 0
if(!check_power_cost(wearer))
return 0
deploy(wearer,instant)
var/seal_target = !canremove
var/failed_to_seal
canremove = 0 // No removing the suit while unsealing.
sealing = 1
if(!seal_target && !suit_is_deployed())
wearer.visible_message(
SPAN_DANGER("[wearer]'s suit flashes an error light."),
SPAN_DANGER("Your suit flashes an error light. It can't function properly without being fully deployed.")
)
playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE)
failed_to_seal = 1
var/is_in_cycler = istype(initiator.loc, /obj/structure/machinery/suit_cycler)
seal_delay = is_in_cycler ? 1 : initial(seal_delay)
var/jumpsuit_was_hidden = FALSE
if(suit_is_deployed() && (wearer.wear_suit.flags_inv & HIDEJUMPSUIT) && !instant && has_hidden_jumpsuit)
/// Don't hide the jumpsuit while removing the rig.
wearer.wear_suit.flags_inv &= ~HIDEJUMPSUIT
wearer.update_inv_wear_suit()
jumpsuit_was_hidden = TRUE
if(!failed_to_seal)
if(!instant)
wearer.visible_message(SPAN_NOTICE("[wearer]'s suit emits a quiet hum as it begins to adjust its seals."),
SPAN_NOTICE("With a quiet hum, the suit begins running checks and adjusting components."))
if(seal_delay && !do_after(wearer, seal_delay, src, do_flags = DO_DEFAULT & ~DO_USER_SAME_HAND))
if(wearer) to_chat(wearer, SPAN_WARNING("You must remain still while the suit is adjusting the components."))
playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE)
failed_to_seal = 1
if(!wearer)
failed_to_seal = 1
else
for(var/list/piece_data in list(
list(wearer.shoes, boots, "boots", boot_type),
list(wearer.gloves, gloves, "gloves", glove_type),
list(wearer.head, helmet, "helmet", helm_type),
list(wearer.wear_suit, chest, "chest", chest_type)))
var/obj/item/clothing/piece = piece_data[1]
var/obj/item/compare_piece = piece_data[2]
var/msg_type = piece_data[3]
var/piece_type = piece_data[4]
if(!piece || !piece_type)
continue
if(!istype(wearer) || !istype(piece) || !istype(compare_piece) || !msg_type)
if(wearer) to_chat(wearer, SPAN_WARNING("You must remain still while the suit is adjusting the components."))
playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE)
failed_to_seal = 1
break
if(!failed_to_seal && wearer.back == src && piece == compare_piece)
if(seal_delay && !instant && !do_after(wearer, seal_delay, src, do_flags = DO_DEFAULT & ~DO_USER_SAME_HAND))
failed_to_seal = 1
update_sealed_piece_icon(piece, seal_target)
switch(msg_type)
if("boots")
to_chat(wearer, SPAN_NOTICE("\The [piece] [!seal_target ? "seal around your feet" : "relax their grip on your legs"]."))
wearer.update_inv_shoes()
if("gloves")
to_chat(wearer, SPAN_NOTICE("\The [piece] [!seal_target ? "tighten around your fingers and wrists" : "become loose around your fingers"]."))
wearer.update_inv_gloves()
if("chest")
to_chat(wearer, SPAN_NOTICE("\The [piece] [!seal_target ? "cinches your chest tightly" : "releases your chest"]."))
if(has_hidden_jumpsuit)
if(!seal_target)
piece.flags_inv |= HIDEJUMPSUIT
else
piece.flags_inv &= ~HIDEJUMPSUIT
wearer.update_inv_wear_suit()
if("helmet")
to_chat(wearer, SPAN_NOTICE("\The [piece] hisses [!seal_target ? "closed" : "open"]."))
wearer.update_inv_head()
if(helmet)
helmet.update_light(wearer)
//sealed pieces become airtight, protecting against diseases
var/datum/component/armor/armor_component = piece.GetComponent(/datum/component/armor)
if(istype(armor_component))
armor_component.sealed = !seal_target
playsound(src, "[!seal_target ? 'sound/machines/rig/rig_deploy.ogg' : 'sound/machines/rig/rig_retract.ogg']", 30, FALSE)
else
failed_to_seal = 1
if((wearer && !(istype(wearer) && wearer.back == src)) || (!seal_target && !suit_is_deployed()))
failed_to_seal = 1
sealing = null
if(failed_to_seal)
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
if(!piece)
continue
piece.icon_state = "[initial(icon_state)][!seal_target ? "" : "_sealed"]"
canremove = !seal_target
if(airtight)
update_component_sealed()
if(jumpsuit_was_hidden && wearer.wear_suit == chest && has_hidden_jumpsuit)
wearer.wear_suit.flags_inv |= HIDEJUMPSUIT
update_icon(1)
return 0
// Success!
canremove = seal_target
to_chat(wearer, SPAN_NOTICE("<b>Your entire suit [canremove ? "loosens as the components relax" : "tightens around you as the components lock into place"].</b>"))
playsound(src, 'sound/items/rped.ogg', 30, FALSE)
if (has_sealed_state)
icon_state = canremove ? initial(icon_state) : "[initial(icon_state)]_sealed"
if(dnaLock && !offline)
if(dnaLock != wearer.dna)
visible_message("[icon2html(src, viewers(get_turf(src)))] <b>[src]</b> announces, <span class='notice'>\"DNA mismatch. Unauthorized access detected.\"</span>")
crushing = TRUE
if(wearer != initiator)
to_chat(initiator, SPAN_NOTICE("Suit adjustment complete. Suit is now [canremove ? "unsealed" : "sealed"]."))
if(canremove)
for(var/obj/item/rig_module/module in installed_modules)
module.deactivate(initiator)
if(airtight)
update_component_sealed()
update_icon(1)
if(is_in_cycler)
initiator.loc.update_icon()
SSstatpanels.set_action_tabs(initiator.client, initiator)
/// Sets the piece's icon and item state based on the seal target, can be overriden for custom functionality
/obj/item/rig/proc/update_sealed_piece_icon(var/obj/item/clothing/piece, var/seal_target)
piece.icon_state = "[initial(icon_state)][!seal_target ? "_sealed" : ""]_[piece.clothing_class()]"
piece.item_state = "[initial(icon_state)][!seal_target ? "_sealed" : ""]"
/obj/item/rig/proc/update_component_sealed()
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
if(canremove)
piece.max_pressure_protection = initial(piece.max_pressure_protection)
piece.min_pressure_protection = initial(piece.min_pressure_protection)
piece.item_flags &= ~ITEM_FLAG_AIRTIGHT
else
piece.max_pressure_protection = max_pressure_protection
piece.min_pressure_protection = min_pressure_protection
piece.item_flags |= ITEM_FLAG_AIRTIGHT
update_icon(1)
/obj/item/rig/process(seconds_per_tick)
// If we've lost any parts, grab them back.
var/mob/living/M
for(var/obj/item/piece in list(gloves,boots,helmet,chest))
if(piece.loc != src && !(wearer && piece.loc == wearer))
if(istype(piece.loc, /mob/living))
M = piece.loc
M.drop_from_inventory(piece,src)
else
piece.forceMove(src)
var/previous_offline_status = offline
// Consume energy per tick while active. If you don't have a cell... Weird, but we'll deal with that shortly.
var/power_usage_this_tick = ((!offline && cell) ? (cell_draw_rate * seconds_per_tick) : 0.0)
if(!istype(wearer) || loc != wearer || wearer.back != src || canremove || !cell || cell.charge <= 0)
if(!cell || cell.charge <= 0)
if(electrified > 0)
electrified = 0
if(!offline)
if(istype(wearer))
playsound(src, 'sound/machines/rig/rig_shutdown.ogg', 35, FALSE)
if(!canremove)
if(offline_slowdown < 3)
to_chat(wearer, SPAN_DANGER("Your suit beeps stridently, and suddenly goes dead."))
else
to_chat(wearer, SPAN_DANGER("Your suit beeps stridently, and suddenly you're wearing a leaden mass of metal and plastic composites instead of a powered suit."))
if(offline_vision_restriction == 1)
to_chat(wearer, SPAN_DANGER("The suit optics flicker and die, leaving you with restricted vision."))
else if(offline_vision_restriction == 2)
to_chat(wearer, SPAN_DANGER("The suit optics drop out completely, drowning you in darkness."))
if(!offline)
offline = 1
else
if(offline)
offline = 0
if(istype(wearer) && !wearer.wearing_rig)
wearer.wearing_rig = src
update_slowdown()
if(offline != previous_offline_status)
update_icon(TRUE)
set_vision(!offline)
if(cell && cell.charge > 0 && electrified > 0)
electrified -= seconds_per_tick
if(crushing)
wearer.apply_damage(10 * seconds_per_tick) // Applies 10 brute damage per second to a random extremity
if(wearer.stat == DEAD)
crushing = FALSE
visible_message(SPAN_DANGER("A squelching sound comes from within the sealed hardsuit..")) // this denotes that the user inside has died.
if(offline)
crushing = FALSE
if(offline == 1)
for(var/obj/item/rig_module/module in installed_modules)
module.deactivate()
offline = 2
update_slowdown()
return
if(malfunction_delay > 0)
malfunction_delay--
else if(malfunctioning)
malfunctioning--
malfunction()
for(var/obj/item/rig_module/module in installed_modules)
power_usage_this_tick += module.process()
if(power_usage_this_tick)
cell.use(power_usage_this_tick)
if(!malfunctioning && !electrified && !open && offline && !wearer) //If nothing is wrong with it and it is not in use, it should stop processing.
return PROCESS_KILL
/obj/item/rig/proc/check_power_cost(var/mob/living/user, var/cost, var/use_unconcious, var/obj/item/rig_module/mod, var/user_is_ai)
if(!istype(user))
return FALSE
var/fail_msg
if(!user_is_ai)
var/mob/living/carbon/human/H = user
if(istype(H) && H.back != src)
fail_msg = SPAN_WARNING("You must be wearing \the [src] to do this.")
else if(user.incorporeal_move)
fail_msg = SPAN_WARNING("You must be solid to do this.")
if(sealing)
fail_msg = SPAN_WARNING("The hardsuit is in the process of adjusting seals and cannot be activated.")
else if(!fail_msg && ((use_unconcious && user.stat > 1) || (!use_unconcious && user.stat)))
fail_msg = SPAN_WARNING("You are in no fit state to do that.")
else if(!cell)
fail_msg = SPAN_WARNING("There is no cell installed in the suit.")
else if(cost && cell?.charge < cost) //TODO: Cellrate?
fail_msg = SPAN_WARNING("Not enough stored power.")
if(fail_msg)
// If a module has been passed to this proc, then the module will handle its own error messages.
if(!mod)
to_chat(user, "[fail_msg]")
playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE)
return FALSE
// This is largely for cancelling stealth and whatever.
if(mod && mod.disruptive)
for(var/obj/item/rig_module/module in (installed_modules - mod))
if(module.active && module.disruptable)
module.deactivate()
cell.use(cost)
return TRUE
/obj/item/rig/update_icon(var/update_mob_icon)
//TODO: Maybe consider a cache for this (use mob_icon as blank canvas, use suit icon overlay).
ClearOverlays()
if(!mob_icon || update_mob_icon)
var/species_icon = 'icons/mob/rig_back.dmi'
// Since setting mob_icon will override the species checks in
// update_inv_wear_suit(), handle species checks here.
if(wearer && sprite_sheets && sprite_sheets[wearer.species.get_bodytype()])
species_icon = sprite_sheets[wearer.species.get_bodytype()]
mob_icon = image("icon" = species_icon, "icon_state" = "[icon_state]")
if(length(installed_modules))
for(var/obj/item/rig_module/module in installed_modules)
if(module.suit_overlay)
chest.AddOverlays(image("icon" = 'icons/mob/rig_modules.dmi', "icon_state" = "[module.suit_overlay]", "dir" = SOUTH))
if(wearer)
wearer.update_inv_shoes()
wearer.update_inv_gloves()
wearer.update_inv_head()
wearer.update_inv_wear_suit()
wearer.update_inv_back()
return
/obj/item/rig/get_mob_overlay(var/mob/living/carbon/human/H, var/mob_icon, var/mob_state, var/slot)
var/image/I = ..()
if(length(installed_modules))
if((chest_type && (chest && wearer?.wear_suit == chest)))
for(var/obj/item/rig_module/module in installed_modules)
if(module.suit_overlay)
I.AddOverlays(image('icons/mob/rig_modules.dmi', null, module.suit_overlay))
return I
/obj/item/rig/get_cell()
if(cell)
return cell
return ..()
/obj/item/rig/proc/is_integrated_rig_ai(var/mob/living/user)
if(!user)
return FALSE
for(var/obj/item/rig_module/ai_container/module in installed_modules)
if(module.integrated_ai == user)
return TRUE
return FALSE
/obj/item/rig/proc/check_suit_access(var/mob/living/user)
if(!security_check_enabled || !locked)
return TRUE
if(is_integrated_rig_ai(user))
return TRUE
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(malfunction_check(H))
return FALSE
if(H.back != src)
return FALSE
else if(!src.allowed(H))
sound_to(H, 'sound/machines/terminal/terminal_error.ogg')
balloon_alert(H, "access denied!")
return FALSE
else if(!ai_override_enabled)
to_chat(user, SPAN_DANGER("Synthetic access disabled. Please consult hardware provider."))
return FALSE
return TRUE
/obj/item/rig/ui_interact(mob/user, datum/tgui/ui)
if(!user)
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Hardsuit", src, 480, 550)
ui.open()
ui.set_autoupdate(TRUE)
/obj/item/rig/ui_data(mob/user)
var/list/data = list()
if(selected_module)
data["primarysystem"] = selected_module.interface_name
data["primarysystem_ref"] = REF(selected_module)
// Needs to be explicitly nulled when de-selected, otherwise the old data persists in the UI.
else
data["primarysystem"] = null
data["primarysystem_ref"] = null
data["ai"] = (src.loc != user)
data["seals"] = canremove
data["sealing"] = sealing
data["helmet"] = helmet ? helmet.name : "None."
data["gauntlets"] = gloves ? gloves.name : "None."
data["boots"] = boots ? boots.name : "None."
data["chest"] = chest ? chest.name : "None."
var/data_charge = cell ? round(cell.charge, 1) : 0
var/data_maxcharge = cell ? cell.maxcharge : 1
data["charge"] = data_charge
data["maxcharge"] = data_maxcharge
data["chargedisplay"] = "[power_joules_readable(data_charge)] / [power_joules_readable(data_maxcharge)]"
data["chargestatus"] = cell ? FLOOR((cell.charge / cell.maxcharge) * 50, 1) : 0
data["emagged"] = subverted
data["coverlock"] = locked
data["interfacelock"] = interface_locked
data["aicontrol"] = control_overridden
data["aioverride"] = ai_override_enabled
data["id_lock"] = security_check_enabled
data["malf"] = malfunction_delay
var/list/module_list = list()
var/i = 1
for(var/obj/item/rig_module/module in installed_modules)
var/list/module_data = list(
"index" = i,
"module_name" = module.interface_name,
"desc" = module.interface_desc,
"module_type" = module.module_type,
"module_active" = module.active,
"module_selected" = selected_module == module ? TRUE : FALSE,
"engagecost" = module.use_power_cost,
"activecost" = module.active_power_cost,
"passivecost" = module.passive_power_cost,
"engagestring" = module.engage_string,
"activatestring" = module.activate_string,
"deactivatestring" = module.deactivate_string,
"damage" = module.damage,
"ref" = REF(module),
"configuration_data" = module.get_configuration(user)
)
if(module.charges && module.charges.len)
module_data["charges"] = list()
var/datum/rig_charge/selected = module.charges[module.charge_selected]
module_data["charge_selected"] = "[module.charge_selected]"
module_data["chargetype"] = selected ? selected.display_name : "none"
for(var/chargetype in module.charges)
var/datum/rig_charge/charge = module.charges[chargetype]
module_data["charges"] += list(list(
"chargetype" = "[chargetype]",
"charges" = "[charge.charges]",
"index" = "[chargetype]"
))
module_list += list(module_data)
i++
if(module_list.len)
data["modules"] = module_list
return data
/obj/item/rig/ui_act(action, params)
. = ..()
if(.)
return TRUE
var/mob/user = usr
if(!user)
return TRUE
// Just handles the UI's examine buttons. Doesn't need to check for access.
switch(action)
// Returns the Armor Values UI.
if("examine_armor")
var/list/armor_details = list()
for(var/armor_type in armor)
armor_details[armor_type] = armor[armor_type]
var/datum/tgui_module/armor_values/AV = new /datum/tgui_module/armor_values(user, capitalize_first_letters(name), armor_details)
AV.ui_interact(user)
return TRUE
if("examine_fluff")
examine(user, show_extended = TRUE)
return TRUE
if(!check_suit_access(user))
return FALSE
// Suit access-gated actions.
switch(action)
// Toggles a 'clothing' piece on or off.
if("toggle_piece")
var/piece = "[params["piece"]]"
if(!piece)
return TRUE
if(ishuman(user) && (user.stat || user.stunned || user.lying))
return TRUE
toggle_piece(piece, user)
// Deploys all pieces.
if("toggle_seals")
toggle_seals(user)
// Module interactions.
if("interact_module")
var/module_index = text2num(params["index"])
if(!module_index)
return TRUE
if(module_index > 0 && module_index <= installed_modules.len)
var/obj/item/rig_module/module = installed_modules[module_index]
var/mode = "[params["mode"]]"
switch(mode)
if("activate")
module.activate(user)
sound_to(usr, module?.sound_activate)
if("deactivate")
module.deactivate(user)
sound_to(usr, module?.sound_deactivate)
if("engage")
module.do_engage(null, user)
if("select")
if(selected_module != module)
// Clear the currently selected module's overlay.
if(selected_module)
selected_module.suit_overlay = null
selected_module = module
// Set the new selected module's suit overlay.
if(selected_module.suit_overlay_active)
selected_module.suit_overlay = selected_module.suit_overlay_active
sound_to(usr, module?.sound_activate)
balloon_alert_to_viewers("deploys \the [module.interface_name]", "deploys \the [module.interface_name]")
else
sound_to(usr, selected_module?.sound_deactivate)
selected_module.suit_overlay = null
selected_module = null
balloon_alert_to_viewers("retracts \the [module.interface_name]", "retracts \the [module.interface_name]")
update_icon(TRUE)
playsound(src.loc, 'sound/items/rfd_dispense.ogg', 25, FALSE)
if("select_charge_type")
module.charge_selected = "[params["charge_type"]]"
sound_to(usr, module.sound_config)
if("toggle_ai_control")
ai_override_enabled = !ai_override_enabled
notify_ai("Synthetic suit control has been [ai_override_enabled ? "enabled" : "disabled"].")
sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg')
// Cover panel locked or unlocked (by access ID).
if("toggle_suit_lock")
locked = !locked
sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg')
// Toggles whether access locks are enabled.
if("toggle_id_lock")
// Only those people who have access rights in the first place should be able to toggle access on or off.
if(!src.allowed(user))
sound_to(user, 'sound/machines/terminal/terminal_error.ogg')
balloon_alert(user, "access denied!")
else
security_check_enabled = !security_check_enabled
sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg')
if("configure")
var/obj/item/rig_module/module = locate(params["ref"]) in installed_modules
if(!module)
return
module.configure_edit(params["key"], params["value"], user)
sound_to(usr, module.sound_config)
user.set_machine(src)
src.add_fingerprint(user)
return TRUE
// Makes sure the UI is only available if you're wearing it, unless you're an AI.
/obj/item/rig/ui_state(mob/user)
if(!issilicon(user))
return GLOB.inventory_state
/obj/item/rig/proc/notify_ai(var/message)
for(var/obj/item/rig_module/ai_container/module in installed_modules)
if(module.integrated_ai && module.integrated_ai.client && !module.integrated_ai.stat)
to_chat(module.integrated_ai, "[message]")
. = 1
/obj/item/rig/check_equipped(mob/living/carbon/human/M, slot, assisted_equip = FALSE)
if(istype(M) && slot == slot_back)
if(!assisted_equip && seal_delay > 0)
M.visible_message(SPAN_NOTICE("[M] starts putting on \the [src]..."), SPAN_NOTICE("You start putting on \the [src]..."))
if(!do_after(M, seal_delay))
return FALSE
if(wearer && wearer != M)
unregister_wearer_signals(wearer)
M.visible_message(SPAN_NOTICE("<b>[M] struggles into \the [src].</b>"), SPAN_NOTICE("<b>You struggle into \the [src].</b>"))
wearer = M
wearer.wearing_rig = src
register_wearer_signals(wearer)
update_icon(TRUE)
return TRUE
return TRUE
/obj/item/rig/proc/toggle_piece(var/piece, var/mob/initiator, var/deploy_mode)
if(piece_being_deployed)
return
if(!usr || sealing || !cell || !cell.charge)
return
if(!istype(wearer) || !wearer.back == src)
return
if(initiator == wearer && (usr.stat||usr.paralysis||usr.stunned)) // If the initiator isn't wearing the suit it's probably an AI.
return
var/obj/item/check_slot
var/equip_to
var/obj/item/use_obj
if(!wearer)
return
switch(piece)
if("helmet")
equip_to = slot_head
use_obj = helmet
check_slot = wearer.head
if("gauntlets")
equip_to = slot_gloves
use_obj = gloves
check_slot = wearer.gloves
if("boots")
equip_to = slot_shoes
use_obj = boots
check_slot = wearer.shoes
if("chest")
equip_to = slot_wear_suit
use_obj = chest
check_slot = wearer.wear_suit
piece_being_deployed = TRUE
if(use_obj)
// Handle retracting the piece, if possible.
if(check_slot == use_obj && deploy_mode != ONLY_DEPLOY)
var/mob/living/carbon/human/holder
holder = use_obj.loc
if(istype(holder))
if(check_slot == use_obj)
to_chat(wearer, "<font color='blue'><b>Your [use_obj.name] [use_obj.gender == PLURAL ? "retract" : "retracts"] swiftly.</b></font>")
playsound(src, 'sound/machines/rig/rig_retract.ogg', 30, FALSE)
use_obj.canremove = 1
holder.drop_from_inventory(use_obj,get_turf(src)) //TODO: TEST THIS CODE!
use_obj.dropped(wearer)
use_obj.canremove = 0
use_obj.forceMove(src)
// Handle deploying the piece, if possible.
else if (deploy_mode != ONLY_RETRACT)
if(check_slot && check_slot == use_obj)
piece_being_deployed = FALSE
return TRUE
if(!do_after(wearer, 8))
if(wearer)
to_chat(wearer, SPAN_WARNING("You must remain still while the suit deploys its parts."))
piece_being_deployed = FALSE
return FALSE
if(!wearer || wearer.back != src) ///Prevents an edge case where a suit with a storage module can be removed while deploying, causing a runtime.
piece_being_deployed = FALSE
return FALSE
use_obj.forceMove(wearer)
if(src.color)
use_obj.color = src.color
if(!wearer.equip_to_slot_if_possible(use_obj, equip_to, 0, 1))
use_obj.forceMove(src)
if(check_slot)
to_chat(initiator, SPAN_DANGER("You are unable to deploy \the [piece] as \the [check_slot] [check_slot.gender == PLURAL ? "are" : "is"] in the way."))
playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE)
piece_being_deployed = FALSE
return FALSE
else
to_chat(wearer, SPAN_NOTICE("Your [use_obj.name] [use_obj.gender == PLURAL ? "deploy" : "deploys"] swiftly."))
playsound(src, 'sound/machines/rig/rig_deploy.ogg', 30, FALSE)
if(piece == "helmet" && helmet)
helmet.update_light(wearer)
update_icon(TRUE)
update_slowdown()
piece_being_deployed = FALSE
return TRUE
/obj/item/rig/proc/deploy(mob/M,var/sealed)
var/mob/living/carbon/human/H = M
if(!H || !istype(H)) return
if(H.back != src)
return
if(sealed)
if(H.head)
var/obj/item/garbage = H.head
H.head = null
qdel(garbage)
if(H.gloves)
var/obj/item/garbage = H.gloves
H.gloves = null
qdel(garbage)
if(H.shoes)
var/obj/item/garbage = H.shoes
H.shoes = null
qdel(garbage)
if(H.wear_suit)
var/obj/item/garbage = H.wear_suit
H.wear_suit = null
qdel(garbage)
for(var/piece in list("helmet","gauntlets","boots",BP_CHEST))
toggle_piece(piece, H, ONLY_DEPLOY)
/obj/item/rig/proc/retract(mob/M,var/sealed)
var/mob/living/carbon/human/H = M
if(!H || !istype(H)) return
if(H.back != src)
return
if(sealed)
if(H.head)
var/obj/item/garbage = H.head
H.head = null
qdel(garbage)
if(H.gloves)
var/obj/item/garbage = H.gloves
H.gloves = null
qdel(garbage)
if(H.shoes)
var/obj/item/garbage = H.shoes
H.shoes = null
qdel(garbage)
if(H.wear_suit)
var/obj/item/garbage = H.wear_suit
H.wear_suit = null
qdel(garbage)
for(var/piece in list("helmet","gauntlets",BP_CHEST,"boots"))
toggle_piece(piece, H, ONLY_RETRACT)
/obj/item/rig/proc/null_wearer(var/mob/user)
unregister_wearer_signals(wearer)
for(var/piece in list("helmet","gauntlets",BP_CHEST,"boots"))
toggle_piece(piece, user, ONLY_RETRACT)
if(wearer)
wearer.wearing_rig = null
wearer = null
/obj/item/rig/proc/register_wearer_signals(mob/living/carbon/human/new_wearer)
if(!new_wearer)
return
RegisterSignal(new_wearer, COMSIG_MOB_CLICKON, PROC_REF(handle_wearer_click))
/obj/item/rig/proc/unregister_wearer_signals(mob/living/carbon/human/old_wearer)
if(!old_wearer)
return
UnregisterSignal(old_wearer, COMSIG_MOB_CLICKON)
/obj/item/rig/proc/handle_wearer_click(mob/user, atom/target, modifiers)
SIGNAL_HANDLER
if(offline == 2)
return
if(LAZYACCESS(modifiers, SHIFT_CLICK))
return
if(LAZYACCESS(modifiers, ALT_CLICK))
return
if(LAZYACCESS(modifiers, RIGHT_CLICK))
return
if(user.in_throw_mode && (isturf(target) || isturf(target.loc)) && user.throw_item(target)) //Prevents throwing items while remaining cloaked.
attack_disrupt_check()
if(ismob(target)) //This doesn't prevent guns firing at turfs, that is handled in /obj/item/gun/proc/handle_post_fire
if(target != user)
attack_disrupt_check()
/obj/item/rig/on_slotmove(var/mob/user)
..()
null_wearer(user)
/obj/item/rig/dropped(mob/user)
..()
if(user.client)
SSstatpanels.set_action_tabs_RIG(user.client, user)
null_wearer(user)
STOP_PROCESSING(SSprocessing, src)
//Todo
/obj/item/rig/proc/malfunction()
return 0
/obj/item/rig/emp_act(severity)
. = ..()
var/obj/item/rig_module/storage/storage = locate() in installed_modules
if(storage)
storage.pockets.emp_act(severity)
//set malfunctioning
if(emp_protection < 30) //for ninjas, really.
malfunctioning += 10
if(malfunction_delay <= 0)
malfunction_delay = max(malfunction_delay, round(30/severity))
//drain some charge
if(cell) cell.powerdrain((severity + 15)*1+(0.1*emp_protection))
//possibly damage some modules
take_hit((100/severity), "electrical pulse", 1)
/// Handles running electrocute_mob() on the passed user. If the user is wearing the suit, it picks between head, chest, and groin for contact_zone.
/// If not wearing the suit (i.e. tampering with the wires), contact_zone is assumed to be the passed user's hands.
/obj/item/rig/proc/shock(mob/user)
var/touchy = pick(BP_CHEST,BP_HEAD,BP_GROIN)
if(!wearer)
touchy = "hand"
if(electrocute_mob(user, cell, src, contact_zone = touchy)) //electrocute_mob() handles removing charge from the cell, no need to do that here.
spark_system.queue()
if(user.stunned)
return TRUE
return FALSE
/obj/item/rig/proc/take_hit(damage, source, is_emp=0)
if(!installed_modules.len)
return
var/chance
if(!is_emp)
var/damage_resistance = 0
if(istype(chest, /obj/item/clothing/suit/space))
damage_resistance = chest.breach_threshold
chance = 2*max(0, damage - damage_resistance)
else
//Want this to be roughly independant of the number of modules, meaning that X emp hits will disable Y% of the suit's modules on average.
//that way people designing hardsuits don't have to worry (as much) about how adding that extra module will affect emp resiliance by 'soaking' hits for other modules
chance = 2*max(0, damage - emp_protection)*min(installed_modules.len/15, 1)
if(!prob(chance))
return
//deal addition damage to already damaged module first.
//This way the chances of a module being disabled aren't so remote.
var/list/valid_modules = list()
var/list/damaged_modules = list()
for(var/obj/item/rig_module/module in installed_modules)
if(module.damage < 2)
valid_modules |= module
if(module.damage > 0)
damaged_modules |= module
var/obj/item/rig_module/dam_module = null
if(damaged_modules.len)
dam_module = pick(damaged_modules)
else if(valid_modules.len)
dam_module = pick(valid_modules)
if(!dam_module) return
dam_module.damage++
if(!source)
source = "hit"
if(wearer)
if(dam_module.damage >= 2)
to_chat(wearer, SPAN_DANGER("The [source] has disabled your [dam_module.interface_name]!"))
else
to_chat(wearer, SPAN_WARNING("The [source] has damaged your [dam_module.interface_name]!"))
dam_module.deactivate()
/obj/item/rig/proc/malfunction_check(var/mob/living/user)
if(malfunction_delay)
if(offline)
to_chat(user, SPAN_DANGER("The suit is completely unresponsive."))
else
to_chat(user, SPAN_DANGER("ERROR: Hardware fault. Rebooting interface..."))
return 1
return 0
/obj/item/rig/proc/ai_can_move_suit(var/mob/user, var/check_user_module = 0, var/check_for_ai = 0)
if(check_for_ai)
if(!(locate(/obj/item/rig_module/ai_container) in contents))
return 0
var/found_ai
for(var/obj/item/rig_module/ai_container/module in contents)
if(module.damage >= 2)
continue
if(module.integrated_ai && module.integrated_ai.client && !module.integrated_ai.stat)
found_ai = 1
break
if(!found_ai)
return 0
if(check_user_module)
if(!user || !user.loc || !user.loc.loc)
return 0
var/obj/item/rig_module/ai_container/module = user.loc.loc
if(!istype(module) || module.damage >= 2)
to_chat(user, SPAN_WARNING("Your host module is unable to interface with the suit."))
return 0
if(offline || !cell || !cell.charge)
if(user) to_chat(user, SPAN_WARNING("Your host rig is unpowered and unresponsive."))
return 0
if(!wearer || wearer.back != src)
if(user) to_chat(user, SPAN_WARNING("Your host rig is not being worn."))
return 0
if(!wearer.stat && !control_overridden && !ai_override_enabled)
if(user) to_chat(user, SPAN_WARNING("You are locked out of the suit servo controller."))
return 0
return 1
/obj/item/rig/proc/force_rest(var/mob/user)
if(!ai_can_move_suit(user, check_user_module = 1))
return
wearer.lay_down()
to_chat(user, SPAN_NOTICE("\The [wearer] is now [wearer.resting ? "resting" : "getting up"]."))
/obj/item/rig/proc/forced_move(var/direction, var/mob/user)
// Why is all this shit in client/Move()? Who knows?
if(world.time < wearer_move_delay)
return
if(!wearer || !wearer.loc || !ai_can_move_suit(user, check_user_module = 1))
return
if(!wearer.stat && !wearer.paralysis) // don't force move if our wearer is awake and capable of moving
return
//This is sota the goto stop mobs from moving var
if(wearer.transforming || !wearer.canmove)
return
if(!wearer.lastarea)
wearer.lastarea = get_area(wearer.loc)
if(!wearer.check_solid_ground())
var/allowmove = wearer.Allow_Spacemove(0)
if(!allowmove)
return 0
else if(allowmove == -1 && wearer.handle_spaceslipping()) //Check to see if we slipped
return 0
else
wearer.inertia_dir = 0 //If not then we can reset inertia and move
if(malfunctioning)
direction = pick(GLOB.cardinals)
// Inside an object, tell it we moved.
if(isobj(wearer.loc) || ismob(wearer.loc))
var/atom/O = wearer.loc
return O.relaymove(wearer, direction)
if(isturf(wearer.loc))
if(wearer.restrained())//Why being pulled while cuffed prevents you from moving
for(var/mob/M in range(wearer, 1))
if(M.pulling == wearer)
if(!M.restrained() && M.stat == 0 && M.canmove && wearer.Adjacent(M))
to_chat(user, SPAN_NOTICE("Your host is restrained! They can't move!"))
return 0
else
M.stop_pulling()
if(wearer.pinned.len)
to_chat(src, "<span class='notice'>Your host is pinned to a wall by [wearer.pinned[1]]</span>!")
return 0
// AIs are a bit slower than regular and ignore move intent.
wearer_move_delay = world.time + ai_controlled_move_delay
if(istype(wearer.buckled_to, /obj/vehicle))
//manually set move_delay for vehicles so we don't inherit any mob movement penalties
//specific vehicle move delays are set in code\modules\vehicles\vehicle.dm
wearer_move_delay = world.time
return wearer.buckled_to.relaymove(wearer, direction)
if(istype(wearer.machine, /obj/structure/machinery))
if(wearer.machine.relaymove(wearer, direction))
return
if(wearer.pulledby || wearer.buckled_to) // Wheelchair driving!
if(istype(wearer.loc, /turf/space))
return // No wheelchair driving in space
if(istype(wearer.pulledby, /obj/structure/bed/stool/chair/office/wheelchair))
return wearer.pulledby.relaymove(wearer, direction)
else if(istype(wearer.buckled_to, /obj/structure/bed/stool/chair/office/wheelchair))
if(ishuman(wearer.buckled_to))
var/obj/item/organ/external/l_hand = wearer.get_organ(BP_L_HAND)
var/obj/item/organ/external/r_hand = wearer.get_organ(BP_R_HAND)
if((!l_hand || (l_hand.status & ORGAN_DESTROYED)) && (!r_hand || (r_hand.status & ORGAN_DESTROYED)))
return // No hands to drive your chair? Tough luck!
wearer_move_delay += 2
return wearer.buckled_to.relaymove(wearer,direction)
cell.use(10)
wearer.Move(get_step(get_turf(wearer),direction),direction)
// This returns the rig if you are contained inside one, but not if you are wearing it
/atom/proc/get_rig()
if(loc)
return loc.get_rig()
return null
/obj/item/rig/get_rig()
return src
/mob/living/carbon/human/get_rig()
return back
//Used in random rig spawning for cargo
//Randomly deletes modules
/obj/item/rig/proc/lose_modules(var/probability)
for(var/obj/item/rig_module/module in installed_modules)
if (probability)
qdel(module)
//Fiddles with some wires to possibly make the suit malfunction a little.
/obj/item/rig/proc/misconfigure(var/probability)
if (prob(probability))
wires.emp_pulse()
if (prob(probability))
wires.emp_pulse()
//Drains, rigs or removes the cell
/obj/item/rig/proc/sabotage_cell()
if (!cell)
return
if (prob(50))
cell.charge = rand(0, cell.charge*0.5)
else if (prob(15))
cell.rigged = 1
else
cell = null
//Depletes or removes the airtank
/obj/item/rig/proc/sabotage_tank()
if (!air_supply)
return
if (prob(70))
air_supply.remove_air(air_supply.air_contents.total_moles)
else
air_supply = null
/obj/item/rig/equipped()
. = ..()
START_PROCESSING(SSprocessing, src)