mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-21 12:08:55 +01:00
## LTS Document Check this document before making any significant future changes to blood worms, please. https://hackmd.io/@RikuTheKiller/H1AHQSKNZx ## About The Pull Request THIS PR SHOULD ABSOLUTELY BE TM'D FIRST Blood worms are a new progression antag. When the event runs, 2 candidates are picked from ghosts and spawned in as blood worm hatchlings, which then have to grow up, do a couple objectives and take over the station. Hatchlings are weak outside of a host, while juveniles can stand their own reasonably well. Adults have high offensive power and can only be dealt with using the right gear or a lot of luck and robustness. They're meant to be a moment of glory for achieving maximum progression and they can bootstrap the next hatchlings by gathering corpses before cocooning. Each growth stage requires 30 seconds in a cocoon, which can only be created after consuming a lot of blood. There's a falloff curve on a per-blood-type basis, meaning you can't drain the same person over and over again to reach adulthood. The medbay freezer is a priority target for the blood worms and can get one of them to the juvenile stage if fully ransacked. It takes 500 blood to mature from hatchling to juvenile, and 1500 blood to mature from juvenile to adult. You can only get up to 1000 blood from synthetic sources like monkeys, and consuming synthetic blood is 30% less efficient. Blood worms can also examine living targets to see how much blood a target has, and how much growth the blood worm would gain for consuming that blood. Blood worms spawn in vents and have night vision for maneuvering in maintenance. Hatchlings can ventcrawl, while juveniles can move around by breaking things. Optionally, you can take over a host with a lot of access like the Captain to go basically anywhere, especially if nobody knows you killed the captain. Behind the scenes, host-taking kicks the host's original mind to a backseat mob. This needs the most testing in practice, but it's confirmed that it returns the host's mind back to their body, at least in testing. All mob, ability and action sprites are made by INFRARED_BARON. Legal rights were transferred to me after I paid for the commission. Note, I've been working on this massive PR for quite a while, so documenting every small change is really hard! Apologies for anything I've missed. There's a lot. Final note, admins can spawn these by either: A. Trigger the midround event via the dynamic-panel verb, under the Rulesets tab. B. Giving someone the Blood Worm antag datum via the Traitor Panel in the Player Panel for the target player. This will transform their mob into a valid Blood Worm, with all of the associated objectives and such. ### Active Abilities 1. Leech Blood (No Host) - Lets the blood worm drain blood from living targets and reagent containers. Uses an aggressive grab to restrain living targets until leeching is over, which takes around a second to initiate. Causes oxyloss during the leeching. NPC monkeys can't escape from this and it floors targets as well. 2. Spit Blood (Both) - Multi-function ability, lets the blood worm fire ranged corrosive blood spit at targets, melt restraints on their hosts by right-clicking, and as an adult, shoot a burst of blood spit at a target by right-clicking. Note of the right-click abilities, shooting bursts can't be done while in a host. (to avoid unfair stealth kills) Shooting a burst has a much longer cooldown than shooting normally. All spit types cost blood to use. 3. Invade Corpse (No Host) - Lets the blood worm take a host for themselves, consuming all of the host's blood and in essence, "becoming" the host. Any bloodloss inflicted on the host is taken as damage to the blood worm, and the blood worm retains its weakness to fire even in this state. Burn damage itself no longer has any extra damage, though. 4. Leave Host (Host) - Title, literally just leaves the host after a delay. Notably works even while the host is moving, dead, incapacitated or otherwise fucked up in any way, shape or form. 5. Inject Blood (Host) - Lets the blood worm heal its host. The potency of this increases as the worm grows up, but so does the cooldown and blood consumption. This works on organ damage, injuries, etc. 6. Mature (No Host) - Makes the blood worm enter a cocoon for 30 seconds, emerging as the next growth stage. Requires an increasing amount of consumed blood / growth as the blood worm uses it. 7. Reproduce (No Host, Adult Only) - Makes the blood worm enter a cocoon for 30 seconds, with 4 hatchlings emerging out of it, including the original blood worm, now reverted back into a hatchling as well. 8. Revive Host (Host) - If the host is in a viable state to be revived, revives them after an animation sequence plays out. ### Passive Abilities 1. Space Immunity - Blood worms are immune to the cold, low pressures and a lack of oxygen. Only the immunity to a lack of oxygen carries on to hosts from this. 2. Organ Insertion - Blood worms can insert organs into their hosts by right-clicking on them with the organ in-hand. This mainly exists to deal with hosts that lack organs, and avoids the gotcha where an adult blood worm ends up gutting their host by hitting them too hard, as they can simply fix it on the spot. 3. Life Support - Blood worm hosts don't need a heart, lungs or a liver to survive. Lungs are useful for speaking, and a liver is necessary to process reagents. 4. Regeneration - Blood worms slowly heal over time. This is nowhere near enough to overcome bleeding or heat damage, since it's 0.3 hp/s for a hatchling, 0.4 hp/s for a juvenile and 0.5 hp/s for an adult. 5. Night Vision - Blood worms can see in the dark. This doesn't extend to hosts. 6. Ventcrawling - Hatchling blood worms can ventcrawl. 7. Doorcrawling - Hatchling and juvenile blood worms can slide under doors. Doing so takes 3 seconds for a hatchling and 5 seconds for a juvenile. 8. HUD - Blood worms can tell how much blood targets have at a glance, via a blood HUD bar exclusive to them. They can also tell apart other blood worm hosts from normal people via an antag HUD. There's also an examine message they can use on living targets for even more info. ### Weaknesses 1. Heat and Fire - Blood worms quickly die to heat, their bodies are flammable and their blood will burn up if their host's core temperature is too high. The main counter to this is getting a host with flame-resistant gear. 2. Bleeding - While in a host, bleeding wounds will directly damage the blood worm itself. How much a host needs to bleed before the worm dies depends on their growth stage. Blood worm hosts keep bleeding even while dead, so just keep hitting them and they'll die. Blood worms automatically leave their hosts when they hit 10% health or lower, and their hosts bleed 50% faster than normal people. 3. Stuns - Blood worms have no way of dealing with a stunned host other than getting out. They can deal with any restraints by melting them, though. 4. Testing - Security can order a blood worm testing crate from cargo, either for a 20 minute cooldown via the security cargo interface console, or for 10000 credits via the supply console. It contains 4 single-use testers that hurt a bit when applied, but are instant to use and 100% accurate. The stopgap is that they're really fucking expensive and only work once per item. ### Screenshot <img width="280" height="132" alt="image" src="https://github.com/user-attachments/assets/00d22361-997e-4347-a0bf-aa240de40727" /> ## Why It's Good For The Game Antagonist variety, mainly. This is basically Cortical Borers 2: Electric Boogaloo. Currently, we lack any antagonists with mind control abilities. That really sucks! I've also gotten a lot of positive feedback about the antagonist while working on it. This antagonist also has great potential for roleplay, as they can take over hosts, surprise attack people by getting out of a dead corpse, talk to each other using Wormspeak, etc. I think we're also itching for variety on "pest" antagonists. Right now we just have spiders and xenos. Everybody knows these two, so why not mix it up a bit? And as for balance? Blood worms are relatively easy to dispatch when you know their weaknesses, which are extremely clear. Bleeding for hosts, fire for either one, lasers for the worms themselves. As long as you get the host in crit and keep hitting, you've pretty much won, and they can't keep spamming Inject Blood forever since they'll quickly run out of blood to use. ## Changelog 🆑 add: Added a new heavy roundstart/midround antagonist, the Blood Worm. Credit to INFRARED_BARON for the sprites! fix: Removing traits based on a source no longer causes issues with trait signals. fix: High-priority effects no longer double-trigger due to subsystem issues. fix: Weighted averaging in reagent merging code has been band-aid fixed. It's not the best, but it works. /🆑
460 lines
17 KiB
Plaintext
460 lines
17 KiB
Plaintext
/**
|
|
* # Action system
|
|
*
|
|
* A simple base for an modular behavior attached to atom or datum.
|
|
*/
|
|
/datum/action
|
|
/// The name of the action
|
|
var/name = "Generic Action"
|
|
/// The description of what the action does, shown in button tooltips
|
|
var/desc
|
|
/// The target the action is attached to. If the target datum is deleted, the action is as well.
|
|
/// Set in New() via the proc link_to(). PLEASE set a target if you're making an action
|
|
var/datum/target
|
|
/// Where any buttons we create should be by default. Accepts screen_loc and location defines
|
|
var/default_button_position = SCRN_OBJ_IN_LIST
|
|
/// This is who currently owns the action, and most often, this is who is using the action if it is triggered
|
|
/// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove()
|
|
var/mob/owner
|
|
/// If False, the owner of this action does not get a hud and cannot activate it on their own
|
|
var/owner_has_control = TRUE
|
|
/// Flags that will determine of the owner / user of the action can... use the action
|
|
var/check_flags = NONE
|
|
/// Whether the button becomes transparent when it can't be used, or just reddened
|
|
var/transparent_when_unavailable = TRUE
|
|
///List of all mobs that are viewing our action button -> A unique movable for them to view.
|
|
var/list/viewers = list()
|
|
/// If TRUE, this action button will be shown to observers / other mobs who view from this action's owner's eyes.
|
|
/// Used in [/mob/proc/show_other_mob_action_buttons]
|
|
var/show_to_observers = TRUE
|
|
/// If observers can click this action at any time, regardless of the owner
|
|
var/allow_observer_click = FALSE
|
|
|
|
/// The style the button's tooltips appear to be
|
|
var/buttontooltipstyle = ""
|
|
|
|
/// This is the file for the BACKGROUND underlay icon of the button
|
|
var/background_icon = 'icons/mob/actions/backgrounds.dmi'
|
|
/// This is the icon state state for the BACKGROUND underlay icon of the button
|
|
/// (If set to ACTION_BUTTON_DEFAULT_BACKGROUND, uses the hud's default background)
|
|
var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
|
|
|
|
/// This is the file for the icon that appears on the button
|
|
var/button_icon = 'icons/hud/actions.dmi'
|
|
/// This is the icon state for the icon that appears on the button
|
|
var/button_icon_state = "default"
|
|
|
|
/// This is the file for any FOREGROUND overlay icons on the button (such as borders)
|
|
var/overlay_icon = 'icons/mob/actions/backgrounds.dmi'
|
|
/// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
|
|
var/overlay_icon_state
|
|
|
|
/// full key we are bound to
|
|
var/full_key
|
|
|
|
/// Toggles whether this action is usable or not
|
|
var/action_disabled = FALSE
|
|
/// Can this action be shared with our rider?
|
|
var/can_be_shared = TRUE
|
|
|
|
/datum/action/New(Target)
|
|
link_to(Target)
|
|
|
|
/// Links the passed target to our action, registering any relevant signals
|
|
/datum/action/proc/link_to(Target)
|
|
target = Target
|
|
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
|
|
|
|
if(isatom(target))
|
|
RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_target_icon_update))
|
|
|
|
if(istype(target, /datum/mind))
|
|
RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_target_mind_swapped))
|
|
|
|
/datum/action/Destroy()
|
|
if(owner)
|
|
Remove(owner)
|
|
target = null
|
|
QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS**
|
|
return ..()
|
|
|
|
/// Signal proc that clears any references based on the owner or target deleting
|
|
/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete
|
|
/datum/action/proc/clear_ref(datum/ref)
|
|
SIGNAL_HANDLER
|
|
if(ref == owner)
|
|
Remove(owner)
|
|
if(ref == target)
|
|
qdel(src)
|
|
|
|
/// Grants the action to the passed mob, making it the owner
|
|
/datum/action/proc/Grant(mob/grant_to)
|
|
if(isnull(grant_to))
|
|
Remove(owner)
|
|
return
|
|
if(grant_to == owner)
|
|
return // We already have it
|
|
var/mob/previous_owner = owner
|
|
owner = grant_to
|
|
if(!isnull(previous_owner))
|
|
Remove(previous_owner)
|
|
SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner)
|
|
SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src)
|
|
RegisterSignal(owner, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
|
|
|
|
// Register some signals based on our check_flags
|
|
// so that our button icon updates when relevant
|
|
if(check_flags & AB_CHECK_CONSCIOUS)
|
|
RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_INCAPACITATED)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), SIGNAL_REMOVETRAIT(TRAIT_INCAPACITATED)), PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_IMMOBILE)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED)), PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_HANDS_BLOCKED)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), SIGNAL_REMOVETRAIT(TRAIT_HANDS_BLOCKED)), PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_LYING)
|
|
RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_PHASED)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_PHASED), SIGNAL_REMOVETRAIT(TRAIT_MAGICALLY_PHASED)), PROC_REF(update_status_on_signal))
|
|
if(check_flags & AB_CHECK_OPEN_TURF)
|
|
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal))
|
|
if(owner_has_control)
|
|
RegisterSignal(grant_to, COMSIG_MOB_KEYDOWN, PROC_REF(keydown), override = TRUE)
|
|
GiveAction(grant_to)
|
|
|
|
/// Remove the passed mob from being owner of our action
|
|
/datum/action/proc/Remove(mob/remove_from)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
for(var/datum/hud/hud in viewers)
|
|
if(!hud.mymob)
|
|
continue
|
|
HideFrom(hud.mymob)
|
|
LAZYREMOVE(remove_from?.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
|
|
viewers = list()
|
|
UnregisterSignal(remove_from, COMSIG_MOB_KEYDOWN)
|
|
|
|
if(isnull(owner))
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
|
|
SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
|
|
UnregisterSignal(owner, COMSIG_QDELETING)
|
|
|
|
// Clean up our check_flag signals
|
|
UnregisterSignal(owner, list(
|
|
COMSIG_LIVING_SET_BODY_POSITION,
|
|
COMSIG_MOB_STATCHANGE,
|
|
COMSIG_MOVABLE_MOVED,
|
|
SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED),
|
|
SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED),
|
|
SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED),
|
|
SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_PHASED),
|
|
SIGNAL_REMOVETRAIT(TRAIT_HANDS_BLOCKED),
|
|
SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED),
|
|
SIGNAL_REMOVETRAIT(TRAIT_INCAPACITATED),
|
|
SIGNAL_REMOVETRAIT(TRAIT_MAGICALLY_PHASED),
|
|
))
|
|
|
|
if(target == owner)
|
|
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref))
|
|
if (owner == remove_from)
|
|
owner = null
|
|
|
|
/// Actually triggers the effects of the action.
|
|
/// Called when the on-screen button is clicked, for example.
|
|
/datum/action/proc/Trigger(mob/clicker, trigger_flags)
|
|
if(!(trigger_flags & TRIGGER_FORCE_AVAILABLE) && !IsAvailable(feedback = TRUE))
|
|
return FALSE
|
|
if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* Whether our action is currently available to use or not
|
|
* * feedback - If true this is being called to check if we have any messages to show to the owner
|
|
*/
|
|
/datum/action/proc/IsAvailable(feedback = FALSE)
|
|
if(!owner)
|
|
return FALSE
|
|
if(action_disabled)
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS)
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "[owner.stat == DEAD ? "dead" : "unconscious"]!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "hands blocked!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "can't move!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_INCAPACITATED) && HAS_TRAIT(owner, TRAIT_INCAPACITATED))
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "incapacitated!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_LYING) && isliving(owner))
|
|
var/mob/living/action_owner = owner
|
|
if(action_owner.body_position == LYING_DOWN)
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "must stand up!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_PHASED) && HAS_TRAIT(owner, TRAIT_MAGICALLY_PHASED))
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "incorporeal!")
|
|
return FALSE
|
|
if((check_flags & AB_CHECK_OPEN_TURF) && !isopenturf(owner.loc))
|
|
if (feedback)
|
|
owner.balloon_alert(owner, "not enough space!")
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Builds / updates all buttons we have shared or given out
|
|
/datum/action/proc/build_all_button_icons(update_flags = ALL, force)
|
|
for(var/datum/hud/hud as anything in viewers)
|
|
build_button_icon(viewers[hud], update_flags, force)
|
|
|
|
/**
|
|
* Builds the icon of the button.
|
|
*
|
|
* Concept:
|
|
* - Underlay (Background icon)
|
|
* - Icon (button icon)
|
|
* - Maptext
|
|
* - Overlay (Background border)
|
|
*
|
|
* button - which button we are modifying the icon of
|
|
* force - whether we're forcing a full update
|
|
*/
|
|
/datum/action/proc/build_button_icon(atom/movable/screen/movable/action_button/button, update_flags = ALL, force = FALSE)
|
|
if(!button)
|
|
return
|
|
|
|
button.actiontooltipstyle = buttontooltipstyle
|
|
|
|
if(update_flags & UPDATE_BUTTON_NAME)
|
|
update_button_name(button, force)
|
|
|
|
if(update_flags & UPDATE_BUTTON_BACKGROUND)
|
|
apply_button_background(button, force)
|
|
|
|
if(update_flags & UPDATE_BUTTON_ICON)
|
|
apply_button_icon(button, force)
|
|
|
|
if(update_flags & UPDATE_BUTTON_OVERLAY)
|
|
apply_button_overlay(button, force)
|
|
|
|
if(update_flags & UPDATE_BUTTON_STATUS)
|
|
update_button_status(button, force)
|
|
|
|
/**
|
|
* Updates the name and description of the button to match our action name and discription.
|
|
*
|
|
* current_button - what button are we editing?
|
|
* force - whether an update is forced regardless of existing status
|
|
*/
|
|
/datum/action/proc/update_button_name(atom/movable/screen/movable/action_button/button, force = FALSE)
|
|
button.name = name
|
|
if(desc)
|
|
button.desc = desc
|
|
|
|
/**
|
|
* Creates the background underlay for the button
|
|
*
|
|
* current_button - what button are we editing?
|
|
* force - whether an update is forced regardless of existing status
|
|
*/
|
|
/datum/action/proc/apply_button_background(atom/movable/screen/movable/action_button/current_button, force = FALSE)
|
|
if(!background_icon || !background_icon_state || (current_button.active_underlay_icon_state == background_icon_state && !force))
|
|
return
|
|
|
|
// What icons we use for our background
|
|
var/list/icon_settings = list(
|
|
// The icon file
|
|
"bg_icon" = background_icon,
|
|
// The icon state, if is_action_active() returns FALSE
|
|
"bg_state" = background_icon_state,
|
|
// The icon state, if is_action_active() returns TRUE
|
|
"bg_state_active" = background_icon_state,
|
|
)
|
|
|
|
// If background_icon_state is ACTION_BUTTON_DEFAULT_BACKGROUND instead use our hud's action button scheme
|
|
if(background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND && owner?.hud_used)
|
|
icon_settings = owner.hud_used.get_action_buttons_icons()
|
|
|
|
// Determine which icon to use
|
|
var/used_icon_key = is_action_active(current_button) ? "bg_state_active" : "bg_state"
|
|
|
|
// Make the underlay
|
|
current_button.underlays.Cut()
|
|
current_button.underlays += image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key])
|
|
current_button.active_underlay_icon_state = icon_settings[used_icon_key]
|
|
|
|
/**
|
|
* Applies our button icon and icon state to the button
|
|
*
|
|
* current_button - what button are we editing?
|
|
* force - whether an update is forced regardless of existing status
|
|
*/
|
|
/datum/action/proc/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
|
|
if(!button_icon || !button_icon_state || (current_button.icon_state == button_icon_state && !force))
|
|
return
|
|
|
|
current_button.icon = button_icon
|
|
current_button.icon_state = button_icon_state
|
|
|
|
/**
|
|
* Applies any overlays to our button
|
|
*
|
|
* current_button - what button are we editing?
|
|
* force - whether an update is forced regardless of existing status
|
|
*/
|
|
/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force = FALSE)
|
|
|
|
SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, current_button, force)
|
|
|
|
if(!overlay_icon || !overlay_icon_state || (current_button.active_overlay_icon_state == overlay_icon_state && !force))
|
|
return
|
|
|
|
current_button.cut_overlay(current_button.button_overlay)
|
|
current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state)
|
|
current_button.add_overlay(current_button.button_overlay)
|
|
current_button.active_overlay_icon_state = overlay_icon_state
|
|
|
|
/**
|
|
* Any other miscellaneous "status" updates within the action button is handled here,
|
|
* such as redding out when unavailable or modifying maptext.
|
|
*
|
|
* current_button - what button are we editing?
|
|
* force - whether an update is forced regardless of existing status
|
|
*/
|
|
/datum/action/proc/update_button_status(atom/movable/screen/movable/action_button/current_button, force = FALSE)
|
|
current_button.update_keybind_maptext(full_key)
|
|
if(IsAvailable())
|
|
current_button.color = rgb(255,255,255,255)
|
|
else
|
|
current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
|
|
|
|
/// Gives our action to the passed viewer.
|
|
/// Puts our action in their actions list and shows them the button.
|
|
/datum/action/proc/GiveAction(mob/viewer)
|
|
var/datum/hud/our_hud = viewer.hud_used
|
|
if(viewers[our_hud]) // Already have a copy of us? go away
|
|
return
|
|
|
|
LAZYOR(viewer.actions, src) // Move this in
|
|
ShowTo(viewer)
|
|
|
|
/// Adds our action button to the screen of the passed viewer.
|
|
/datum/action/proc/ShowTo(mob/viewer)
|
|
var/datum/hud/our_hud = viewer.hud_used
|
|
if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place
|
|
return
|
|
|
|
var/atom/movable/screen/movable/action_button/button = create_button()
|
|
SetId(button, viewer)
|
|
|
|
button.our_hud = our_hud
|
|
viewers[our_hud] = button
|
|
if(viewer.client)
|
|
viewer.client.screen += button
|
|
|
|
button.load_position(viewer)
|
|
viewer.update_action_buttons()
|
|
|
|
/// Removes our action from the passed viewer.
|
|
/datum/action/proc/HideFrom(mob/viewer)
|
|
var/datum/hud/our_hud = viewer.hud_used
|
|
var/atom/movable/screen/movable/action_button/button = viewers[our_hud]
|
|
LAZYREMOVE(viewer.actions, src)
|
|
if(button)
|
|
qdel(button)
|
|
|
|
/// Creates an action button movable for the passed mob, and returns it.
|
|
/datum/action/proc/create_button()
|
|
var/atom/movable/screen/movable/action_button/button = new()
|
|
button.linked_action = src
|
|
button.allow_observer_click = allow_observer_click
|
|
build_button_icon(button, ALL, TRUE)
|
|
return button
|
|
|
|
/datum/action/proc/SetId(atom/movable/screen/movable/action_button/our_button, mob/owner)
|
|
//button id generation
|
|
var/bitfield = 0
|
|
for(var/datum/action/action in owner.actions)
|
|
if(action == src) // This could be us, which is dumb
|
|
continue
|
|
var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used]
|
|
if(action.name == name && button.id)
|
|
bitfield |= button.id
|
|
|
|
bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one
|
|
for(var/i in 0 to 23) // We get 24 possible bitflags in dm
|
|
var/bitflag = 1 << i // Shift us over one
|
|
if(bitfield & bitflag)
|
|
our_button.id = bitflag
|
|
return
|
|
|
|
/// Updates our buttons if our target's icon was updated
|
|
/datum/action/proc/on_target_icon_update(datum/source, updates, updated)
|
|
SIGNAL_HANDLER
|
|
|
|
var/update_flag = NONE
|
|
var/forced = FALSE
|
|
if(updates & UPDATE_ICON_STATE)
|
|
update_flag |= UPDATE_BUTTON_ICON
|
|
forced = TRUE
|
|
if(updates & UPDATE_OVERLAYS)
|
|
update_flag |= UPDATE_BUTTON_OVERLAY
|
|
forced = TRUE
|
|
if(updates & (UPDATE_NAME|UPDATE_DESC))
|
|
update_flag |= UPDATE_BUTTON_NAME
|
|
// Status is not relevant, and background is not relevant. Neither will change
|
|
|
|
// Force the update if an icon state or overlay change was done
|
|
build_all_button_icons(update_flag, forced)
|
|
|
|
/// A general use signal proc that reacts to an event and updates JUST our button's status
|
|
/datum/action/proc/update_status_on_signal(datum/source, new_stat, old_stat)
|
|
SIGNAL_HANDLER
|
|
|
|
build_all_button_icons(UPDATE_BUTTON_STATUS)
|
|
|
|
/// Signal proc for COMSIG_MIND_TRANSFERRED - for minds, transfers our action to our new mob on mind transfer
|
|
/datum/action/proc/on_target_mind_swapped(datum/mind/source, mob/old_current)
|
|
SIGNAL_HANDLER
|
|
|
|
// Grant() calls Remove() from the existing owner so we're covered on that
|
|
Grant(source.current)
|
|
|
|
/// Checks if our action is actively selected. Used for selecting icons primarily.
|
|
/datum/action/proc/is_action_active(atom/movable/screen/movable/action_button/current_button)
|
|
return FALSE
|
|
|
|
/datum/action/proc/begin_creating_bind(atom/movable/screen/movable/action_button/current_button, mob/user)
|
|
if(!current_button || user != owner)
|
|
return
|
|
if(!isnull(full_key))
|
|
full_key = null
|
|
update_button_status(current_button)
|
|
return
|
|
full_key = tgui_input_keycombo(user, "Please bind a key for this action.")
|
|
update_button_status(current_button)
|
|
|
|
/datum/action/proc/keydown(mob/source, key, client/client, full_key)
|
|
SIGNAL_HANDLER
|
|
if(isnull(full_key) || full_key != src.full_key)
|
|
return
|
|
if(istype(source))
|
|
if(source.next_click > world.time)
|
|
return
|
|
else
|
|
source.next_click = world.time + CLICK_CD_ACTIVATE_ABILITY
|
|
INVOKE_ASYNC(src, PROC_REF(Trigger))
|
|
|
|
/// Used for setting the keybind via external sources.
|
|
/datum/action/proc/set_key(new_full_key)
|
|
full_key = new_full_key
|
|
build_all_button_icons(UPDATE_BUTTON_STATUS)
|