Files
Bubberstation/code/modules/mod/modules/module_pathfinder.dm
MrMelbert 329921639a Rewrites how action buttons icons are generated, makes them layer nicer. Allows observers to see a mob's action buttons. (#71339)
## About The Pull Request

- Rewrites how action button icons are generated.
- Prior, generated an action button icon was fairly simplistic and
didn't allow for many changes. Someone recently added the option for
overlays to be generated over action buttons, but the framework was very
weak.
- Now, action button icon generation is split across multiple procs,
like atom icon updates.
      - The background of action buttons are underlays
- The actual icon of the action button is the icon and icon state of the
action button movable
- The rim / border of the button is an overlay, layered overtop the
button.

- Allows observers to see what action buttons a mob has. They even
update in real time! And no, the observers cannot click on them.

## Why It's Good For The Game

- Runechat text of action buttons are no longer hidden behind the actual
icon. This was very ugly with cooldown actions, as the cooldown text was
hidden behind a lot of spell icons.
- Cuts down on a lot of icon duplication. 
- Gives much finer control over action button icons
- Saves a bit of processing from generating full action button icons
when not necessary. Not implemented in many places, but is in some.


![image](https://user-images.githubusercontent.com/51863163/202816617-342e87e6-2cc6-488e-9af2-4b2053dc3dc6.png)


![image](https://user-images.githubusercontent.com/51863163/202816604-da8d4821-0e2b-45af-b289-7442367f98ce.png)

## Changelog

🆑 Melbert
add: Observers can now see what action buttons an observed mob has. No,
you can't click them. And no it doesn't show EVERY action.
refactor: Refactored how action button icons are generated. Some actions
will now use a colored border when active instead of just turning green.
Cooldown text will also appear on the top layer of actions too. If you
see any funky lookin' icons (namely their borders), let me know.
refactor: Bluespace Golem's teleport action is now a cooldown action.
fix: Construct actions go to the middle of the screen like expected. 
/🆑
2022-12-03 19:01:08 -08:00

171 lines
6.1 KiB
Plaintext

///Pathfinder - Can fly the suit from a long distance to an implant installed in someone.
/obj/item/mod/module/pathfinder
name = "MOD pathfinder module"
desc = "This module, brought to you by Nakamura Engineering, has two components. \
The first component is a series of thrusters and a computerized location subroutine installed into the \
very control unit of the suit, allowing it flight at highway speeds using the suit's access locks \
to navigate through the station, and to be able to locate the second part of the system; \
a pathfinding implant installed into the base of the user's spine, \
broadcasting their location to the suit and allowing them to recall it to their person at any time. \
The implant is stored in the module and needs to be injected in a human to function. \
Nakamura Engineering swears up and down there's airbrakes."
icon_state = "pathfinder"
complexity = 2
use_power_cost = DEFAULT_CHARGE_DRAIN * 10
incompatible_modules = list(/obj/item/mod/module/pathfinder)
/// The pathfinding implant.
var/obj/item/implant/mod/implant
/obj/item/mod/module/pathfinder/Initialize(mapload)
. = ..()
implant = new(src)
/obj/item/mod/module/pathfinder/Destroy()
implant = null
return ..()
/obj/item/mod/module/pathfinder/examine(mob/user)
. = ..()
if(implant)
. += span_notice("Use it on a human to implant them.")
else
. += span_warning("The implant is missing.")
/obj/item/mod/module/pathfinder/attack(mob/living/target, mob/living/user, params)
if(!ishuman(target) || !implant)
return
if(!do_after(user, 1.5 SECONDS, target = target))
balloon_alert(user, "interrupted!")
return
if(!implant.implant(target, user))
balloon_alert(user, "can't implant!")
return
if(target == user)
to_chat(user, span_notice("You implant yourself with [implant]."))
else
target.visible_message(span_notice("[user] implants [target]."), span_notice("[user] implants you with [implant]."))
playsound(src, 'sound/effects/spray.ogg', 30, TRUE, -6)
icon_state = "pathfinder_empty"
implant = null
/obj/item/mod/module/pathfinder/proc/attach(mob/living/user)
if(!ishuman(user))
return
var/mob/living/carbon/human/human_user = user
if(human_user.get_item_by_slot(mod.slot_flags) && !human_user.dropItemToGround(human_user.get_item_by_slot(mod.slot_flags)))
return
if(!human_user.equip_to_slot_if_possible(mod, mod.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
return
mod.quick_deploy(user)
human_user.update_action_buttons(TRUE)
balloon_alert(human_user, "[mod] attached")
playsound(mod, 'sound/machines/ping.ogg', 50, TRUE)
drain_power(use_power_cost)
/obj/item/implant/mod
name = "MOD pathfinder implant"
desc = "Lets you recall a MODsuit to you at any time."
actions_types = list(/datum/action/item_action/mod_recall)
/// The pathfinder module we are linked to.
var/obj/item/mod/module/pathfinder/module
/// The jet icon we apply to the MOD.
var/image/jet_icon
/obj/item/implant/mod/Initialize(mapload)
. = ..()
if(!istype(loc, /obj/item/mod/module/pathfinder))
return INITIALIZE_HINT_QDEL
module = loc
jet_icon = image(icon = 'icons/obj/clothing/modsuit/mod_modules.dmi', icon_state = "mod_jet", layer = LOW_ITEM_LAYER)
/obj/item/implant/mod/Destroy()
if(module?.mod?.ai_controller)
end_recall(successful = FALSE)
module = null
jet_icon = null
return ..()
/obj/item/implant/mod/get_data()
var/dat = {"<b>Implant Specifications:</b><BR>
<b>Name:</b> Nakamura Engineering Pathfinder Implant<BR>
<b>Implant Details:</b> Allows for the recall of a Modular Outerwear Device by the implant owner at any time.<BR>"}
return dat
/obj/item/implant/mod/proc/recall()
if(!module?.mod)
balloon_alert(imp_in, "no connected suit!")
return FALSE
if(module.mod.open)
balloon_alert(imp_in, "suit is open!")
return FALSE
if(module.mod.ai_controller)
balloon_alert(imp_in, "already in transit!")
return FALSE
if(ismob(get_atom_on_turf(module.mod)))
balloon_alert(imp_in, "already on someone!")
return FALSE
if(module.z != z || get_dist(imp_in, module.mod) > MOD_AI_RANGE)
balloon_alert(imp_in, "too far away!")
return FALSE
var/datum/ai_controller/mod_ai = new /datum/ai_controller/mod(module.mod)
module.mod.ai_controller = mod_ai
mod_ai.set_movement_target(imp_in)
mod_ai.blackboard[BB_MOD_TARGET] = imp_in
mod_ai.blackboard[BB_MOD_IMPLANT] = src
module.mod.interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP
module.mod.AddElement(/datum/element/movetype_handler)
ADD_TRAIT(module.mod, TRAIT_MOVE_FLYING, MOD_TRAIT)
animate(module.mod, 0.2 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y)
module.mod.add_overlay(jet_icon)
RegisterSignal(module.mod, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
balloon_alert(imp_in, "suit recalled")
return TRUE
/obj/item/implant/mod/proc/end_recall(successful = TRUE)
if(!module?.mod)
return
QDEL_NULL(module.mod.ai_controller)
module.mod.interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP
REMOVE_TRAIT(module.mod, TRAIT_MOVE_FLYING, MOD_TRAIT)
module.mod.RemoveElement(/datum/element/movetype_handler)
module.mod.cut_overlay(jet_icon)
module.mod.transform = matrix()
UnregisterSignal(module.mod, COMSIG_MOVABLE_MOVED)
if(!successful)
balloon_alert(imp_in, "suit lost connection!")
/obj/item/implant/mod/proc/on_move(atom/movable/source, atom/old_loc, dir, forced)
SIGNAL_HANDLER
var/matrix/mod_matrix = matrix()
mod_matrix.Turn(get_angle(source, imp_in))
source.transform = mod_matrix
/datum/action/item_action/mod_recall
name = "Recall MOD"
desc = "Recall a MODsuit anyplace, anytime."
check_flags = AB_CHECK_CONSCIOUS
background_icon_state = "bg_mod"
overlay_icon_state = "bg_mod_border"
button_icon = 'icons/mob/actions/actions_mod.dmi'
button_icon_state = "recall"
/// The cooldown for the recall.
COOLDOWN_DECLARE(recall_cooldown)
/datum/action/item_action/mod_recall/New(Target)
..()
if(!istype(Target, /obj/item/implant/mod))
qdel(src)
return
/datum/action/item_action/mod_recall/Trigger(trigger_flags)
. = ..()
if(!.)
return
var/obj/item/implant/mod/implant = target
if(!COOLDOWN_FINISHED(src, recall_cooldown))
implant.balloon_alert(implant.imp_in, "on cooldown!")
return
if(implant.recall())
COOLDOWN_START(src, recall_cooldown, 15 SECONDS)