mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-16 04:34:21 +00:00
## About The Pull Request This PR makes several changes to how circuit action buttons work: - The MOD action and BCI action components have been merged into a single component. - MOD circuit actions can be pinned from the configuration menu. This works the same way as pinning individual modules, and can be done both by the wearer and a suit AI. - Action components have an output pin for the user of the action. This allows MOD module circuits to distinguish between the wearer and an AI. - Creates a supertype for `/datum/action/item_action/mod/pinned_module` named `/datum/action/item_action/mod/pinnable`, which implements common functionality for pinned modules and pinned circuit module actions. ## Why It's Good For The Game The prior functionality of circuit MOD actions was somewhat unintuitive, requiring the user to select an action from a radial menu *after* activating the module, whether from a pinned action or from the module radial. Providing similar pinning functionality to modules themselves makes MOD actions more readily usable. Merging the two different types of circuit components into one was made with the idea that adding new types of shells with equipment actions would inflate the number of subtypes of `/obj/item/circuit_component/equipment_action` without adding much meaningful functionality. ## Changelog 🆑 qol: MOD wearers and internal AIs can pin the individual actions in a MOD circuit module in a similar way to how they can pin modules. Circuit module actions can be pinned from the configuration menu of the circuit refactor: The MOD action and BCI action components have been merged into one component - the Equipment Action component. /🆑
770 lines
27 KiB
Plaintext
770 lines
27 KiB
Plaintext
/// MODsuits, trade-off between armor and utility
|
|
/obj/item/mod
|
|
name = "Base MOD"
|
|
desc = "You should not see this, yell at a coder!"
|
|
icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
|
|
worn_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
|
|
|
|
/obj/item/mod/control
|
|
name = "MOD control unit"
|
|
desc = "The control unit of a Modular Outerwear Device, a powered suit that protects against various environments."
|
|
icon_state = "standard-control"
|
|
inhand_icon_state = "mod_control"
|
|
base_icon_state = "control"
|
|
w_class = WEIGHT_CLASS_BULKY
|
|
slot_flags = ITEM_SLOT_BACK
|
|
strip_delay = 10 SECONDS
|
|
armor_type = /datum/armor/none
|
|
actions_types = list(
|
|
/datum/action/item_action/mod/deploy,
|
|
/datum/action/item_action/mod/activate,
|
|
/datum/action/item_action/mod/panel,
|
|
/datum/action/item_action/mod/module,
|
|
/datum/action/item_action/mod/deploy/ai,
|
|
/datum/action/item_action/mod/activate/ai,
|
|
/datum/action/item_action/mod/panel/ai,
|
|
/datum/action/item_action/mod/module/ai,
|
|
)
|
|
resistance_flags = NONE
|
|
max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT
|
|
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
|
|
siemens_coefficient = 0.5
|
|
alternate_worn_layer = HANDS_LAYER+0.1 //we want it to go above generally everything, but not hands
|
|
/// 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/charge_drain = DEFAULT_CHARGE_DRAIN
|
|
/// Slowdown of the MOD when not active.
|
|
var/slowdown_inactive = 1.25
|
|
/// Slowdown of the MOD when active.
|
|
var/slowdown_active = 0.75
|
|
/// How long this MOD takes each part to seal.
|
|
var/activation_step_time = MOD_ACTIVATION_STEP_TIME
|
|
/// Extended description of the theme.
|
|
var/extended_desc
|
|
/// MOD helmet.
|
|
var/obj/item/clothing/head/mod/helmet
|
|
/// MOD chestplate.
|
|
var/obj/item/clothing/suit/mod/chestplate
|
|
/// MOD gauntlets.
|
|
var/obj/item/clothing/gloves/mod/gauntlets
|
|
/// MOD boots.
|
|
var/obj/item/clothing/shoes/mod/boots
|
|
/// MOD core.
|
|
var/obj/item/mod/core/core
|
|
/// Associated list of parts (helmet, chestplate, gauntlets, boots) to their unsealed worn layer.
|
|
var/list/mod_parts = list()
|
|
/// Associated list of parts that can overslot to their overslot (overslot means the part can cover another layer of clothing).
|
|
var/list/overslotting_parts = list()
|
|
/// Modules the MOD currently possesses.
|
|
var/list/modules = list()
|
|
/// Currently used module.
|
|
var/obj/item/mod/module/selected_module
|
|
/// AI or pAI mob inhabiting the MOD.
|
|
var/mob/living/silicon/ai_assistant
|
|
/// The MODlink datum, letting us call people from the suit.
|
|
var/datum/mod_link/mod_link
|
|
/// The starting MODlink frequency, overridden on subtypes that want it to be something.
|
|
var/starting_frequency = null
|
|
/// Delay between moves as AI.
|
|
var/static/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, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core)
|
|
. = ..()
|
|
if(!movedelay)
|
|
movedelay = CONFIG_GET(number/movedelay/run_delay)
|
|
if(new_theme)
|
|
theme = new_theme
|
|
theme = GLOB.mod_themes[theme]
|
|
slot_flags = theme.slot_flags
|
|
extended_desc = theme.extended_desc
|
|
slowdown_inactive = theme.slowdown_inactive
|
|
slowdown_active = theme.slowdown_active
|
|
activation_step_time = theme.activation_step_time
|
|
complexity_max = theme.complexity_max
|
|
ui_theme = theme.ui_theme
|
|
charge_drain = theme.charge_drain
|
|
set_wires(new /datum/wires/mod(src))
|
|
if(length(req_access))
|
|
locked = TRUE
|
|
new_core?.install(src)
|
|
helmet = new /obj/item/clothing/head/mod(src)
|
|
mod_parts += helmet
|
|
chestplate = new /obj/item/clothing/suit/mod(src)
|
|
chestplate.allowed += theme.allowed_suit_storage
|
|
mod_parts += chestplate
|
|
gauntlets = new /obj/item/clothing/gloves/mod(src)
|
|
mod_parts += gauntlets
|
|
boots = new /obj/item/clothing/shoes/mod(src)
|
|
mod_parts += boots
|
|
var/list/all_parts = mod_parts + src
|
|
for(var/obj/item/part as anything in all_parts)
|
|
part.name = "[theme.name] [part.name]"
|
|
part.desc = "[part.desc] [theme.desc]"
|
|
part.set_armor(theme.armor_type)
|
|
part.resistance_flags = theme.resistance_flags
|
|
part.flags_1 |= theme.atom_flags //flags like initialization or admin spawning are here, so we cant set, have to add
|
|
part.heat_protection = NONE
|
|
part.cold_protection = NONE
|
|
part.max_heat_protection_temperature = theme.max_heat_protection_temperature
|
|
part.min_cold_protection_temperature = theme.min_cold_protection_temperature
|
|
part.siemens_coefficient = theme.siemens_coefficient
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
RegisterSignal(part, COMSIG_ATOM_DESTRUCTION, PROC_REF(on_part_destruction))
|
|
RegisterSignal(part, COMSIG_QDELETING, PROC_REF(on_part_deletion))
|
|
set_mod_skin(new_skin || theme.default_skin)
|
|
update_speed()
|
|
RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
|
|
RegisterSignal(src, COMSIG_SPEED_POTION_APPLIED, PROC_REF(on_potion))
|
|
for(var/obj/item/mod/module/module as anything in theme.inbuilt_modules)
|
|
module = new module(src)
|
|
install(module)
|
|
START_PROCESSING(SSobj, src)
|
|
|
|
/obj/item/mod/control/Destroy()
|
|
STOP_PROCESSING(SSobj, src)
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
uninstall(module, deleting = TRUE)
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
overslotting_parts -= part
|
|
var/atom/deleting_atom
|
|
if(!QDELETED(helmet))
|
|
deleting_atom = helmet
|
|
helmet = null
|
|
mod_parts -= deleting_atom
|
|
qdel(deleting_atom)
|
|
if(!QDELETED(chestplate))
|
|
deleting_atom = chestplate
|
|
chestplate = null
|
|
mod_parts -= deleting_atom
|
|
qdel(deleting_atom)
|
|
if(!QDELETED(gauntlets))
|
|
deleting_atom = gauntlets
|
|
gauntlets = null
|
|
mod_parts -= deleting_atom
|
|
qdel(deleting_atom)
|
|
if(!QDELETED(boots))
|
|
deleting_atom = boots
|
|
boots = null
|
|
mod_parts -= deleting_atom
|
|
qdel(deleting_atom)
|
|
if(core)
|
|
QDEL_NULL(core)
|
|
QDEL_NULL(wires)
|
|
QDEL_NULL(mod_link)
|
|
return ..()
|
|
|
|
/obj/item/mod/control/atom_destruction(damage_flag)
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
uninstall(module)
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(!overslotting_parts[part])
|
|
continue
|
|
var/obj/item/overslot = overslotting_parts[part]
|
|
overslot.forceMove(drop_location())
|
|
overslotting_parts[part] = null
|
|
if(ai_assistant)
|
|
if(ispAI(ai_assistant))
|
|
INVOKE_ASYNC(src, PROC_REF(remove_pai), /* user = */ null, /* forced = */ TRUE) // async to appease spaceman DMM because the branch we don't run has a do_after
|
|
else
|
|
for(var/datum/action/action as anything in actions)
|
|
if(action.owner == ai_assistant)
|
|
action.Remove(ai_assistant)
|
|
new /obj/item/mod/ai_minicard(drop_location(), ai_assistant)
|
|
return ..()
|
|
|
|
/obj/item/mod/control/examine(mob/user)
|
|
. = ..()
|
|
if(active)
|
|
. += span_notice("Charge: [core ? "[get_charge_percent()]%" : "No core"].")
|
|
. += span_notice("Selected module: [selected_module || "None"].")
|
|
if(!open && !active)
|
|
if(!wearer)
|
|
. += span_notice("You could equip it to turn it on.")
|
|
. += span_notice("You could open the cover with a <b>screwdriver</b>.")
|
|
else if(open)
|
|
. += span_notice("You could close the cover with a <b>screwdriver</b>.")
|
|
. += span_notice("You could use <b>modules</b> on it to install them.")
|
|
. += span_notice("You could remove modules with a <b>crowbar</b>.")
|
|
. += span_notice("You could update the access lock with an <b>ID</b>.")
|
|
. += span_notice("You could access the wire panel with a <b>wire tool</b>.")
|
|
if(core)
|
|
. += span_notice("You could remove [core] with a <b>wrench</b>.")
|
|
else
|
|
. += span_notice("You could use a <b>MOD core</b> on it to install one.")
|
|
if(isnull(ai_assistant))
|
|
. += span_notice("You could install an AI or pAI using their <b>storage card</b>.")
|
|
else if(isAI(ai_assistant))
|
|
. += span_notice("You could remove [ai_assistant] with an <b>intellicard</b>.")
|
|
. += span_notice("You could copy/set link frequency with a <b>multitool</b>.")
|
|
. += span_notice("<i>You could examine it more thoroughly...</i>")
|
|
|
|
/obj/item/mod/control/examine_more(mob/user)
|
|
. = ..()
|
|
. += "<i>[extended_desc]</i>"
|
|
|
|
/obj/item/mod/control/process(seconds_per_tick)
|
|
if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
|
|
seconds_electrified--
|
|
if(mod_link.link_call)
|
|
subtract_charge((DEFAULT_CHARGE_DRAIN * 0.25) * seconds_per_tick)
|
|
if(!active)
|
|
return
|
|
if(!get_charge() && active && !activating)
|
|
power_off()
|
|
return
|
|
var/malfunctioning_charge_drain = 0
|
|
if(malfunctioning)
|
|
malfunctioning_charge_drain = rand(1,20)
|
|
subtract_charge((charge_drain + malfunctioning_charge_drain) * seconds_per_tick)
|
|
update_charge_alert()
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
if(malfunctioning && module.active && SPT_PROB(5, seconds_per_tick))
|
|
module.on_deactivation(display_message = TRUE)
|
|
module.on_process(seconds_per_tick)
|
|
|
|
/obj/item/mod/control/equipped(mob/user, slot)
|
|
..()
|
|
if(slot & slot_flags)
|
|
set_wearer(user)
|
|
else if(wearer)
|
|
unset_wearer()
|
|
|
|
/obj/item/mod/control/dropped(mob/user)
|
|
. = ..()
|
|
if(!wearer)
|
|
return
|
|
clean_up()
|
|
|
|
/obj/item/mod/control/item_action_slot_check(slot)
|
|
if(slot & slot_flags)
|
|
return TRUE
|
|
|
|
// Grant pinned actions to pin owners, gives AI pinned actions to the AI and not the wearer
|
|
/obj/item/mod/control/grant_action_to_bearer(datum/action/action)
|
|
if (!istype(action, /datum/action/item_action/mod/pinnable))
|
|
return ..()
|
|
var/datum/action/item_action/mod/pinnable/pinned = action
|
|
give_item_action(action, pinned.pinner, slot_flags)
|
|
|
|
/obj/item/mod/control/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
|
. = ..()
|
|
if(!wearer || old_loc != wearer || loc == wearer)
|
|
return
|
|
clean_up()
|
|
|
|
/obj/item/mod/control/allow_attack_hand_drop(mob/user)
|
|
if(user != wearer)
|
|
return ..()
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(part.loc != src)
|
|
balloon_alert(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(usr != wearer || !istype(over_object, /atom/movable/screen/inventory/hand))
|
|
return ..()
|
|
for(var/obj/item/part as anything 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/wrench_act(mob/living/user, obj/item/wrench)
|
|
if(..())
|
|
return TRUE
|
|
if(seconds_electrified && get_charge() && shock(user))
|
|
return TRUE
|
|
if(open)
|
|
if(!core)
|
|
balloon_alert(user, "no core!")
|
|
return TRUE
|
|
balloon_alert(user, "removing core...")
|
|
wrench.play_tool_sound(src, 100)
|
|
if(!wrench.use_tool(src, user, 3 SECONDS) || !open)
|
|
balloon_alert(user, "interrupted!")
|
|
return TRUE
|
|
wrench.play_tool_sound(src, 100)
|
|
balloon_alert(user, "core removed")
|
|
core.forceMove(drop_location())
|
|
update_charge_alert()
|
|
return TRUE
|
|
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"] cover...")
|
|
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, "cover [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 cover 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(SEND_SIGNAL(src, COMSIG_MOD_MODULE_REMOVAL, user) & MOD_CANCEL_REMOVAL)
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
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)
|
|
SEND_SIGNAL(src, COMSIG_MOD_MODULE_REMOVED, user)
|
|
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/pai_card))
|
|
if(!open)
|
|
balloon_alert(user, "open the cover first!")
|
|
return FALSE
|
|
insert_pai(user, attacking_item)
|
|
return TRUE
|
|
if(istype(attacking_item, /obj/item/mod/module))
|
|
if(!open)
|
|
balloon_alert(user, "open the cover first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
install(attacking_item, user)
|
|
SEND_SIGNAL(src, COMSIG_MOD_MODULE_ADDED, user)
|
|
return TRUE
|
|
else if(istype(attacking_item, /obj/item/mod/core))
|
|
if(!open)
|
|
balloon_alert(user, "open the cover first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(core)
|
|
balloon_alert(user, "core already installed!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
var/obj/item/mod/core/attacking_core = attacking_item
|
|
attacking_core.install(src)
|
|
balloon_alert(user, "core installed")
|
|
playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
update_charge_alert()
|
|
return TRUE
|
|
else if(is_wire_tool(attacking_item) && open)
|
|
wires.interact(user)
|
|
return TRUE
|
|
else if(open && attacking_item.GetID())
|
|
update_access(user, attacking_item.GetID())
|
|
return TRUE
|
|
return ..()
|
|
|
|
/obj/item/mod/control/get_cell()
|
|
if(!open)
|
|
return
|
|
var/obj/item/stock_parts/cell/cell = get_charge_source()
|
|
if(!istype(cell))
|
|
return
|
|
return cell
|
|
|
|
/obj/item/mod/control/GetAccess()
|
|
if(ai_controller)
|
|
return req_access.Copy()
|
|
else
|
|
return ..()
|
|
|
|
/obj/item/mod/control/emag_act(mob/user, obj/item/card/emag/emag_card)
|
|
locked = !locked
|
|
balloon_alert(user, "suit access [locked ? "locked" : "unlocked"]")
|
|
return TRUE
|
|
|
|
/obj/item/mod/control/emp_act(severity)
|
|
. = ..()
|
|
if(!active || !wearer)
|
|
return
|
|
to_chat(wearer, span_notice("[severity > 1 ? "Light" : "Strong"] electromagnetic pulse detected!"))
|
|
if(. & EMP_PROTECT_CONTENTS)
|
|
return
|
|
selected_module?.on_deactivation(display_message = TRUE)
|
|
wearer.apply_damage(5 / 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/visual_equipped(mob/user, slot, initial = FALSE)
|
|
if(slot & slot_flags)
|
|
set_wearer(user)
|
|
|
|
/obj/item/mod/control/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
|
|
. = ..()
|
|
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 as anything in mod_parts)
|
|
if(part.loc == src)
|
|
continue
|
|
retract(null, part)
|
|
return ..()
|
|
|
|
/obj/item/mod/control/update_icon_state()
|
|
icon_state = "[skin]-[base_icon_state][active ? "-sealed" : ""]"
|
|
return ..()
|
|
|
|
/obj/item/mod/control/proc/set_wearer(mob/living/carbon/human/user)
|
|
if (wearer == user)
|
|
// This should also not happen.
|
|
// This path is hit when equipping an outfit with visualsOnly, but only sometimes, and this eventually gets called twice.
|
|
// I'm not sure this proc should ever be being called by visualsOnly, but it is,
|
|
// and this was an emergency patch.
|
|
return
|
|
else if (!isnull(wearer))
|
|
stack_trace("set_wearer() was called with a new wearer without unset_wearer() being called")
|
|
|
|
wearer = user
|
|
SEND_SIGNAL(src, COMSIG_MOD_WEARER_SET, wearer)
|
|
RegisterSignal(wearer, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
|
|
RegisterSignal(wearer, COMSIG_SPECIES_GAIN, PROC_REF(on_species_gain))
|
|
update_charge_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_SPECIES_GAIN))
|
|
wearer.clear_alert(ALERT_MODSUIT_CHARGE)
|
|
SEND_SIGNAL(src, COMSIG_MOD_WEARER_UNSET, wearer)
|
|
wearer = null
|
|
|
|
/obj/item/mod/control/proc/clean_up()
|
|
if(active || activating)
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
if(!module.active)
|
|
continue
|
|
module.on_deactivation(display_message = FALSE)
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
seal_part(part, seal = FALSE)
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
retract(null, part)
|
|
if(active)
|
|
finish_activation(on = FALSE)
|
|
mod_link?.end_call()
|
|
var/mob/old_wearer = wearer
|
|
unset_wearer()
|
|
old_wearer.temporarilyRemoveItemFromInventory(src)
|
|
|
|
/obj/item/mod/control/proc/on_species_gain(datum/source, datum/species/new_species, datum/species/old_species)
|
|
SIGNAL_HANDLER
|
|
|
|
var/list/all_parts = mod_parts + src
|
|
for(var/obj/item/part in all_parts)
|
|
if(!(new_species.no_equip_flags & part.slot_flags) || is_type_in_list(new_species, part.species_exception))
|
|
continue
|
|
forceMove(drop_location())
|
|
return
|
|
|
|
/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)
|
|
if(module == selected_module)
|
|
module_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_selected")
|
|
else if(module.active)
|
|
module_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_active")
|
|
if(!COOLDOWN_FINISHED(module, cooldown_timer))
|
|
module_image.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown"))
|
|
items += list(module.name = module_image)
|
|
if(!length(items))
|
|
return
|
|
var/radial_anchor = src
|
|
if(istype(user.loc, /obj/effect/dummy/phased_mob))
|
|
radial_anchor = get_turf(user.loc) //they're phased out via some module, anchor the radial on the turf so it may still display
|
|
var/pick = show_radial_menu(user, radial_anchor, items, custom_check = FALSE, require_near = TRUE, tooltips = TRUE)
|
|
if(!pick)
|
|
return
|
|
var/module_reference = display_names[pick]
|
|
var/obj/item/mod/module/picked_module = locate(module_reference) in modules
|
|
if(!istype(picked_module))
|
|
return
|
|
picked_module.on_select()
|
|
|
|
/obj/item/mod/control/proc/shock(mob/living/user)
|
|
if(!istype(user) || get_charge() < 1)
|
|
return FALSE
|
|
do_sparks(5, TRUE, src)
|
|
var/check_range = TRUE
|
|
return electrocute_mob(user, get_charge_source(), src, 0.7, check_range)
|
|
|
|
/obj/item/mod/control/proc/install(obj/item/mod/module/new_module, mob/user)
|
|
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
|
|
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.RegisterSignal(src, COMSIG_ITEM_GET_WORN_OVERLAYS, TYPE_PROC_REF(/obj/item/mod/module, add_module_overlay))
|
|
new_module.on_install()
|
|
if(wearer)
|
|
new_module.on_equip()
|
|
if(active)
|
|
new_module.on_suit_activation()
|
|
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(obj/item/mod/module/old_module, deleting = FALSE)
|
|
modules -= old_module
|
|
complexity -= old_module.complexity
|
|
if(wearer)
|
|
old_module.on_unequip()
|
|
if(active)
|
|
old_module.on_suit_deactivation(deleting = deleting)
|
|
if(old_module.active)
|
|
old_module.on_deactivation(display_message = !deleting, deleting = deleting)
|
|
old_module.UnregisterSignal(src, COMSIG_ITEM_GET_WORN_OVERLAYS)
|
|
old_module.on_uninstall(deleting = deleting)
|
|
QDEL_LIST_ASSOC_VAL(old_module.pinned_to)
|
|
old_module.mod = null
|
|
|
|
/// Intended for callbacks, don't use normally, just get wearer by itself.
|
|
/obj/item/mod/control/proc/get_wearer()
|
|
return wearer
|
|
|
|
/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/get_charge_source()
|
|
return core?.charge_source()
|
|
|
|
/obj/item/mod/control/proc/get_charge()
|
|
return core?.charge_amount() || 0
|
|
|
|
/obj/item/mod/control/proc/get_max_charge()
|
|
return core?.max_charge_amount() || 1 //avoid dividing by 0
|
|
|
|
/obj/item/mod/control/proc/get_charge_percent()
|
|
return ROUND_UP((get_charge() / get_max_charge()) * 100)
|
|
|
|
/obj/item/mod/control/proc/add_charge(amount)
|
|
return core?.add_charge(amount) || FALSE
|
|
|
|
/obj/item/mod/control/proc/subtract_charge(amount)
|
|
return core?.subtract_charge(amount) || FALSE
|
|
|
|
/obj/item/mod/control/proc/check_charge(amount)
|
|
return core?.check_charge(amount) || FALSE
|
|
|
|
/obj/item/mod/control/proc/update_charge_alert()
|
|
if(!wearer)
|
|
return
|
|
if(!core)
|
|
wearer.throw_alert(ALERT_MODSUIT_CHARGE, /atom/movable/screen/alert/nocore)
|
|
return
|
|
core.update_charge_alert()
|
|
|
|
/obj/item/mod/control/proc/update_speed()
|
|
var/list/all_parts = mod_parts + src
|
|
for(var/obj/item/part as anything in all_parts)
|
|
part.slowdown = (active ? slowdown_active : slowdown_inactive) / length(all_parts)
|
|
wearer?.update_equipment_speed_mods()
|
|
|
|
/obj/item/mod/control/proc/power_off()
|
|
balloon_alert(wearer, "no power!")
|
|
toggle_activate(wearer, force_deactivate = TRUE)
|
|
|
|
/obj/item/mod/control/proc/set_mod_color(new_color)
|
|
var/list/all_parts = mod_parts + src
|
|
for(var/obj/item/part as anything in all_parts)
|
|
part.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
|
|
part.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
|
|
wearer?.regenerate_icons()
|
|
|
|
/obj/item/mod/control/proc/set_mod_skin(new_skin)
|
|
if(active)
|
|
CRASH("[src] tried to set skin while active!")
|
|
skin = new_skin
|
|
var/list/used_skin = theme.skins[new_skin]
|
|
if(used_skin[CONTROL_LAYER])
|
|
alternate_worn_layer = used_skin[CONTROL_LAYER]
|
|
var/list/skin_updating = mod_parts + src
|
|
for(var/obj/item/part as anything in skin_updating)
|
|
part.icon = used_skin[MOD_ICON_OVERRIDE] || 'icons/obj/clothing/modsuit/mod_clothing.dmi'
|
|
part.worn_icon = used_skin[MOD_WORN_ICON_OVERRIDE] || 'icons/mob/clothing/modsuit/mod_clothing.dmi'
|
|
part.icon_state = "[skin]-[part.base_icon_state]"
|
|
for(var/obj/item/clothing/part as anything in mod_parts)
|
|
var/used_category
|
|
if(part == helmet)
|
|
used_category = HELMET_FLAGS
|
|
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
|
|
part.alternate_worn_layer = category[UNSEALED_LAYER]
|
|
mod_parts[part] = part.alternate_worn_layer
|
|
if(!category[CAN_OVERSLOT])
|
|
if(overslotting_parts[part])
|
|
var/obj/item/overslot = overslotting_parts[part]
|
|
overslot.forceMove(drop_location())
|
|
overslotting_parts -= part
|
|
continue
|
|
overslotting_parts |= part
|
|
wearer?.regenerate_icons()
|
|
|
|
/obj/item/mod/control/proc/on_exit(datum/source, atom/movable/part, direction)
|
|
SIGNAL_HANDLER
|
|
|
|
if(part.loc == src)
|
|
return
|
|
if(part == core)
|
|
core.uninstall()
|
|
update_charge_alert()
|
|
return
|
|
if(part.loc == wearer)
|
|
return
|
|
if(part in modules)
|
|
uninstall(part)
|
|
return
|
|
if(part in mod_parts)
|
|
if(!wearer)
|
|
part.forceMove(src)
|
|
return
|
|
retract(wearer, part)
|
|
if(active)
|
|
INVOKE_ASYNC(src, PROC_REF(toggle_activate), wearer, TRUE)
|
|
|
|
/obj/item/mod/control/proc/on_part_destruction(obj/item/part, damage_flag)
|
|
SIGNAL_HANDLER
|
|
|
|
if(overslotting_parts[part])
|
|
var/obj/item/overslot = overslotting_parts[part]
|
|
overslot.forceMove(drop_location())
|
|
overslotting_parts[part] = null
|
|
if(QDELETED(src))
|
|
return
|
|
atom_destruction(damage_flag)
|
|
|
|
/obj/item/mod/control/proc/on_part_deletion(obj/item/part) //the part doesnt count as being qdeleted, so our destroying does an infinite loop, fix later
|
|
SIGNAL_HANDLER
|
|
|
|
if(QDELETED(src))
|
|
return
|
|
qdel(src)
|
|
|
|
/obj/item/mod/control/proc/on_overslot_exit(datum/source, atom/movable/overslot, direction)
|
|
SIGNAL_HANDLER
|
|
|
|
if(overslot != overslotting_parts[source])
|
|
return
|
|
overslotting_parts[source] = null
|
|
|
|
/obj/item/mod/control/proc/on_potion(atom/movable/source, obj/item/slimepotion/speed/speed_potion, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
if(slowdown_inactive <= 0)
|
|
to_chat(user, span_warning("[src] has already been coated with red, that's as fast as it'll go!"))
|
|
return SPEED_POTION_STOP
|
|
if(active)
|
|
to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's active!"))
|
|
return SPEED_POTION_STOP
|
|
to_chat(user, span_notice("You slather the red gunk over [src], making it faster."))
|
|
set_mod_color("#FF0000")
|
|
slowdown_inactive = 0
|
|
slowdown_active = 0
|
|
update_speed()
|
|
qdel(speed_potion)
|
|
return SPEED_POTION_STOP
|
|
|
|
/// Disables the mod link frequency attached to this unit.
|
|
/obj/item/mod/control/proc/disable_modlink()
|
|
if(isnull(mod_link))
|
|
return
|
|
|
|
mod_link.end_call()
|
|
mod_link.frequency = null
|