Files
Bubberstation/code/modules/mod/mod_actions.dm
Stonetear 4756a44141 MODSuit procs now pass who clicked the UI button + misc code cleanup (#92424)
## About The Pull Request

Most of these changes are centered around removing `mod.wearer`
references in module function bubble alerts. However, I cleaned up a few
other things that I thought were easy fixes. ~~This PR should be
testmerged~~ nah send it. I think I did a pretty good job testing, but
there might be a bug or two I missed. (debug modsuit should allow
conflicting modules and have unlimited complexity btw)

### Track who clicks the activate button
* Adds `mob/activator` to `on_select()`, `activate()`, `deactivate()`,
`used()`, `on_activation()`, `on_deactivation()` and `on_use()`
* `mod_ui` now passes `ui.user`, which is who actually clicked the
button in the UI.1
* module action proc now passes the person clicking.
* **Alert bubbles:** Modifies many module code bubbles to pass the
activation bubble text to `mob/activator` instead of `mob.wearer` so
that pAIs get feedback on why clicking the button isn't working.

### Cargo clamp
* **Clamp code cleanup:** The cargo clamp now has a variable for the max
creature weight it can support, and the logic is changed around a bit to
support this.
* The cargo clamp uses an `accepted_items` typecache.

### Code cleanup
* **Button malfunction chance** is controlled by a
`MOD_MALFUNCTION_PROB` define.
* **Pathfinder runtime:** `mod_control`'s `GetAccess()` now checks if
there is an access before returning it. (This previously caused runtimes
when using the pathfinder module if you didn't swipe your ID)
* **Pathfinder code tweaks:** Reworks the code for the pathfinder module
a bit. Activation logic is now stored in the module instead of the
implant. The suit is prevented from being recalled by pAIs, which is
controlled by a variable.
* Adds `MODULE_ALLOW_UNWORN`, which lets you activate modules in suits
that aren't currently being worn. Module activation code now smoothly
supports modules being activated while not worn.
* Chameleon module now works when unworn.

This will probably be a Part 1, with a Part 2 to follow. Actions are
kinda funky and could probably be cleaned up a little better. Plus, I
want to make selectable modules theoretically usable by the AI, even if
I leave it disabled.

## Why It's Good For The Game

This PR doesn't contain any balance changes, and I manually disabled any
new serious functionality that pAIs might gain. (Such as being able to
activate the pathfinder implant) They *can* use the chameleon module
with no wearer- but I'm going to consider this a bug that they couldn't
before.

Paves the way for more pAI modsuit nonsense I'm doing downsteam.

## Changelog
🆑 Stonetear
refactor: MODsuit module code now knows who clicked the activation
button.
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2025-09-07 10:27:38 +00:00

214 lines
6.3 KiB
Plaintext

/datum/action/item_action/mod
background_icon_state = "bg_mod"
overlay_icon_state = "bg_mod_border"
button_icon = 'icons/mob/actions/actions_mod.dmi'
check_flags = AB_CHECK_CONSCIOUS
/// Whether this action is intended for the AI. Stuff breaks a lot if this is done differently.
var/ai_action = FALSE
/datum/action/item_action/mod/New(Target)
..()
if(!istype(Target, /obj/item/mod/control))
qdel(src)
return
if(ai_action)
background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
/datum/action/item_action/mod/Grant(mob/user)
var/obj/item/mod/control/mod = target
if(ai_action && user != mod.ai_assistant)
return
else if(!ai_action && user == mod.ai_assistant)
return
return ..()
/datum/action/item_action/mod/Remove(mob/user)
var/obj/item/mod/control/mod = target
if(ai_action && user != mod.ai_assistant)
return
else if(!ai_action && user == mod.ai_assistant)
return
return ..()
/datum/action/item_action/mod/do_effect(trigger_flags)
var/obj/item/mod/control/mod = target
if(mod.malfunctioning && prob(MOD_MALFUNCTION_PROB))
mod.balloon_alert(usr, "button malfunctions!")
return FALSE
return TRUE
/datum/action/item_action/mod/deploy
name = "Deploy MODsuit"
desc = "LMB: Deploy/Undeploy part. RMB: Deploy/Undeploy full suit."
button_icon_state = "deploy"
/datum/action/item_action/mod/deploy/do_effect(trigger_flags)
. = ..()
if(!.)
return
var/obj/item/mod/control/mod = target
if(trigger_flags & TRIGGER_SECONDARY_ACTION)
mod.quick_deploy(usr)
else
mod.choose_deploy(usr)
/datum/action/item_action/mod/deploy/ai
ai_action = TRUE
/datum/action/item_action/mod/activate
name = "Activate MODsuit"
desc = "LMB: Activate/Deactivate suit with prompt. RMB: Activate/Deactivate suit skipping prompt."
button_icon_state = "activate"
/// First time clicking this will set it to TRUE, second time will activate it.
var/ready = FALSE
/datum/action/item_action/mod/activate/do_effect(trigger_flags)
. = ..()
if(!.)
return
if(!(trigger_flags & TRIGGER_SECONDARY_ACTION) && !ready)
ready = TRUE
button_icon_state = "activate-ready"
build_all_button_icons()
addtimer(CALLBACK(src, PROC_REF(reset_ready)), 3 SECONDS)
return
var/obj/item/mod/control/mod = target
reset_ready()
mod.toggle_activate(usr)
/// Resets the state requiring to be doubleclicked again.
/datum/action/item_action/mod/activate/proc/reset_ready()
ready = FALSE
button_icon_state = initial(button_icon_state)
build_all_button_icons()
/datum/action/item_action/mod/activate/ai
ai_action = TRUE
/datum/action/item_action/mod/module
name = "Toggle Module"
desc = "Toggle a MODsuit module."
button_icon_state = "module"
/datum/action/item_action/mod/module/do_effect(trigger_flags)
. = ..()
if(!.)
return
var/obj/item/mod/control/mod = target
mod.quick_module(usr)
/datum/action/item_action/mod/module/ai
ai_action = TRUE
/datum/action/item_action/mod/panel
name = "MODsuit Panel"
desc = "Open the MODsuit's panel."
button_icon_state = "panel"
/datum/action/item_action/mod/panel/do_effect(trigger_flags)
. = ..()
if(!.)
return
var/obj/item/mod/control/mod = target
mod.ui_interact(usr)
/datum/action/item_action/mod/panel/ai
ai_action = TRUE
/datum/action/item_action/mod/pinnable
/// A reference to the mob we are pinned to.
var/mob/pinner
/datum/action/item_action/mod/pinnable/New(Target, mob/user)
. = ..()
var/obj/item/mod/control/mod = Target
if(user == mod.ai_assistant)
ai_action = TRUE
pinner = user
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(pinner_deleted))
/datum/action/item_action/mod/pinnable/Grant(mob/user)
if(pinner != user)
return
return ..()
/// If the guy whose UI we are pinned to got deleted
/datum/action/item_action/mod/pinnable/proc/pinner_deleted()
SIGNAL_HANDLER
pinner = null
qdel(src)
/datum/action/item_action/mod/pinnable/module
desc = "Activate the module."
/// Overrides the icon applications.
var/override = FALSE
/// Module we are linked to.
var/obj/item/mod/module/module
/// Timer until we remove our cooldown overlay
var/cooldown_timer
/datum/action/item_action/mod/pinnable/module/New(Target, mob/user, obj/item/mod/module/linked_module)
button_icon = linked_module.icon
button_icon_state = linked_module.icon_state
. = ..()
module = linked_module
module.pinned_to[REF(user)] = src
if(linked_module.allow_flags & MODULE_ALLOW_INCAPACITATED)
// clears check hands and check conscious
check_flags = NONE
name = "Activate [capitalize(linked_module.name)]"
desc = "Quickly activate [linked_module]."
RegisterSignals(linked_module, list(
COMSIG_MODULE_ACTIVATED,
COMSIG_MODULE_DEACTIVATED,
COMSIG_MODULE_USED,
), PROC_REF(module_interacted_with))
RegisterSignal(linked_module, COMSIG_MODULE_COOLDOWN_STARTED, PROC_REF(cooldown_started))
/datum/action/item_action/mod/pinnable/module/Destroy()
deltimer(cooldown_timer)
UnregisterSignal(module, list(
COMSIG_MODULE_ACTIVATED,
COMSIG_MODULE_DEACTIVATED,
COMSIG_MODULE_COOLDOWN_STARTED,
COMSIG_MODULE_USED,
))
module.pinned_to -= REF(pinner)
module = null
pinner = null
return ..()
/datum/action/item_action/mod/pinnable/module/do_effect(trigger_flags)
. = ..()
if(!.)
return
module.on_select(owner)
/datum/action/item_action/mod/pinnable/module/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force)
current_button.cut_overlays()
if(override)
return ..()
var/obj/item/mod/control/mod = target
if(module == mod.selected_module)
current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_selected", layer = FLOAT_LAYER-0.1))
else if(module.active)
current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_active", layer = FLOAT_LAYER-0.1))
if(!COOLDOWN_FINISHED(module, cooldown_timer))
current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown"))
return ..()
/datum/action/item_action/mod/pinnable/module/proc/module_interacted_with(datum/source)
SIGNAL_HANDLER
build_all_button_icons(UPDATE_BUTTON_OVERLAY|UPDATE_BUTTON_STATUS)
/datum/action/item_action/mod/pinnable/module/proc/cooldown_started(datum/source, cooldown_time)
SIGNAL_HANDLER
deltimer(cooldown_timer)
build_all_button_icons(UPDATE_BUTTON_OVERLAY)
if (cooldown_time == 0)
return
cooldown_timer = addtimer(CALLBACK(src, PROC_REF(build_all_button_icons), UPDATE_BUTTON_OVERLAY), cooldown_time + 1, TIMER_STOPPABLE)