mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-20 15:46:32 +01:00
358 lines
9.6 KiB
Plaintext
358 lines
9.6 KiB
Plaintext
/**
|
|
* ability system
|
|
*
|
|
* - single, mob owner
|
|
* - supports trigger / toggle presets
|
|
* - supports custom tgui
|
|
*
|
|
* used for intrinsic species / antagonist / body abiltiies
|
|
*/
|
|
/datum/ability
|
|
//? basics
|
|
/// name
|
|
var/name = "Unnamed ability"
|
|
/// desc
|
|
var/desc = "Some sort of ability."
|
|
/// category - used for tgui
|
|
var/category = "Misc"
|
|
|
|
//? mob
|
|
/// owning mob - can be null if we aren't bound / granted to anyone.
|
|
var/mob/owner
|
|
|
|
//? binding
|
|
/// action datum
|
|
var/datum/action/action
|
|
/// action button icon
|
|
var/action_icon = 'icons/screen/actions/actions.dmi'
|
|
/// action button icon state
|
|
var/action_state = "default"
|
|
/// action button background icon
|
|
var/background_icon = 'icons/screen/actions/backgrounds.dmi'
|
|
/// action button background icon state - the _on overlay will be added while active, automatically.
|
|
var/background_state = "default"
|
|
/// currently hotbound?
|
|
var/bound = FALSE
|
|
/// automatically hotbound?
|
|
var/always_bind = FALSE
|
|
|
|
//? interaction
|
|
/// default interaction mode
|
|
var/interact_type = ABILITY_INTERACT_NONE
|
|
/// currently hidden?
|
|
var/hidden = FALSE
|
|
/// targeted?
|
|
/// targeted abilities only work with ABILITY_INTERACT_TOGGLE
|
|
var/targeted = FALSE
|
|
/// target type? Can be anything (atoms), mobs or turfs
|
|
var/targeting_type = ABILITY_TARGET_ALL
|
|
/// range?
|
|
var/range
|
|
|
|
//? ui
|
|
/// tgui id
|
|
var/tgui_id = "TGUIAbility"
|
|
|
|
//? checks
|
|
/// check flags - see [code/__DEFINES/ability.dm]
|
|
var/ability_check_flags = NONE
|
|
/// mobility check flags
|
|
var/mobility_check_flags = NONE
|
|
|
|
//? state
|
|
/// cooldown delay, if we have a cooldown
|
|
var/cooldown = 0
|
|
/// for toggled abilities, turning off incurs the cooldown - otherwise, cooldown begins on toggling off.
|
|
var/cooldown_for_deactivation = TRUE
|
|
/// windup delay, if we have a windup
|
|
var/windup = 0
|
|
/// windup requires standing still
|
|
var/windup_requires_still = TRUE
|
|
/// last use world.time; null if we haven't been used yet
|
|
var/last_used
|
|
/// timerid for cooldown finish action button update
|
|
var/cooldown_visual_timerid
|
|
/// for toggle interacts: are we enabled?
|
|
var/enabled = FALSE
|
|
|
|
/datum/ability/Destroy()
|
|
if(!isnull(owner))
|
|
disassociate(owner)
|
|
if(!isnull(action))
|
|
QDEL_NULL(action)
|
|
return ..()
|
|
|
|
/**
|
|
* generates our action button if it doesn't exist
|
|
*
|
|
* @return our action button.
|
|
*/
|
|
/datum/ability/proc/generate_action()
|
|
if(!isnull(action))
|
|
return action
|
|
rebuild_action()
|
|
return action
|
|
|
|
/**
|
|
* full action button update including icon regeneration
|
|
* creates the action if it did not already exist.
|
|
*/
|
|
/datum/ability/proc/rebuild_action()
|
|
if(isnull(action))
|
|
action = new(src)
|
|
action.name = hotbind_name()
|
|
action.desc = hotbind_desc()
|
|
action.button_icon = action_icon
|
|
action.button_icon_state = action_state
|
|
action.background_icon = background_icon
|
|
action.background_icon_state = background_state
|
|
update_action()
|
|
|
|
/**
|
|
* updates action button for availability / toggle status
|
|
*/
|
|
/datum/ability/proc/update_action()
|
|
var/availability = 1
|
|
if(cooldown && !isnull(last_used))
|
|
availability = clamp((world.time - last_used) / cooldown, 0, 1)
|
|
action?.background_additional_overlay = (interact_type) == ABILITY_INTERACT_TOGGLE && enabled ? "[background_state]_on" : null
|
|
action?.push_button_availability(availability, FALSE)
|
|
action?.update_buttons()
|
|
recheck_queued_action_update()
|
|
|
|
/datum/ability/proc/recheck_queued_action_update()
|
|
if(cooldown_visual_timerid)
|
|
deltimer(cooldown_visual_timerid)
|
|
cooldown_visual_timerid = null
|
|
var/next_available = 0
|
|
if(cooldown && !isnull(last_used) && (world.time < last_used + cooldown))
|
|
next_available = max(next_available, (last_used + cooldown) - world.time)
|
|
if(next_available > 0)
|
|
addtimer(CALLBACK(src, PROC_REF(update_action)), next_available, TIMER_STOPPABLE)
|
|
|
|
/datum/ability/ui_action_click(datum/action/action, datum/event_args/actor/actor)
|
|
. = ..()
|
|
action_trigger(actor.performer)
|
|
|
|
/datum/ability/proc/action_trigger(mob/user)
|
|
attempt_trigger(user)
|
|
|
|
/datum/ability/proc/hotbind_name()
|
|
return "[category] - [name]"
|
|
|
|
/datum/ability/proc/hotbind_desc()
|
|
return desc
|
|
|
|
/**
|
|
* called to try to trigger.
|
|
*
|
|
* @params
|
|
* * user - triggering user. this is usually owner, but sometimes isn't.
|
|
* * toggling - null if not toggled ability / not toggling, TRUE / FALSE for on / off.
|
|
*/
|
|
/datum/ability/proc/attempt_trigger(mob/user, toggling)
|
|
if(interact_type == ABILITY_INTERACT_TOGGLE)
|
|
if(!isnull(toggling) && toggling == enabled)
|
|
return
|
|
if(isnull(toggling))
|
|
toggling = !enabled
|
|
if(targeted)
|
|
if(ishuman(user) && toggling == TRUE)
|
|
var/mob/living/carbon/human/H = user
|
|
H.ab_handler?.process_ability(src)
|
|
if(!check_trigger(user, toggling, TRUE))
|
|
return
|
|
if(windup)
|
|
if(!do_after(user, windup, flags = (windup_requires_still? NONE : DO_AFTER_IGNORE_MOVEMENT), mobility_flags = mobility_check_flags))
|
|
return
|
|
if(!check_trigger(user, toggling, TRUE))
|
|
return
|
|
on_trigger(user, toggling)
|
|
|
|
/**
|
|
* called to check a trigger.
|
|
*
|
|
* @params
|
|
* * user - triggering user. this is usually owner, but sometimes isn't.
|
|
* * toggling - null if not toggled ability / not toggling, TRUE / FALSE for on / off.
|
|
* * feedback - output feedback messages
|
|
*/
|
|
/datum/ability/proc/check_trigger(mob/user, toggling, feedback)
|
|
if(!isnull(last_used) && (isnull(toggling) || toggling || (!toggling && cooldown_for_deactivation)) && (cooldown + last_used > world.time))
|
|
to_chat(user, SPAN_WARNING("[src] is still on cooldown! ([round((world.time - last_used) * 0.1, 0.1)] / [round(cooldown * 0.1, 0.1)])"))
|
|
return FALSE
|
|
if(!available_check())
|
|
to_chat(user, SPAN_WARNING(unavailable_reason()))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* called on trigger
|
|
*
|
|
* @params
|
|
* * user - triggering user. this is usually owner, but sometimes isn't.
|
|
* * toggling - null if not toggled ability / not toggling, TRUE / FALSE for on / off.
|
|
*/
|
|
/datum/ability/proc/on_trigger(mob/user, toggling)
|
|
last_used = world.time
|
|
if(interact_type != ABILITY_INTERACT_TOGGLE)
|
|
update_action()
|
|
return
|
|
if(!isnull(toggling) && toggling == enabled)
|
|
return
|
|
if(isnull(toggling))
|
|
toggling = !enabled
|
|
if(enabled && !toggling)
|
|
disable()
|
|
else if(!enabled && toggling)
|
|
enable()
|
|
|
|
/datum/ability/proc/enable(update_action)
|
|
enabled = TRUE
|
|
if(update_action)
|
|
update_action()
|
|
action?.background_icon_state += "_on"
|
|
action?.update_buttons()
|
|
on_enable()
|
|
|
|
/datum/ability/proc/disable(update_action)
|
|
enabled = FALSE
|
|
if(update_action)
|
|
update_action()
|
|
action?.background_icon_state = background_state
|
|
action?.update_buttons()
|
|
on_disable()
|
|
|
|
/datum/ability/proc/on_enable()
|
|
return
|
|
|
|
/datum/ability/proc/on_disable()
|
|
return
|
|
|
|
/datum/ability/proc/quickbind()
|
|
if(bound)
|
|
return
|
|
bound = TRUE
|
|
generate_action()
|
|
if(!isnull(owner))
|
|
action?.grant(owner.actions_innate)
|
|
|
|
/datum/ability/proc/unbind()
|
|
bound = FALSE
|
|
if(!isnull(owner))
|
|
action?.revoke(owner.actions_innate)
|
|
|
|
/datum/ability/proc/associate(mob/M)
|
|
if(owner == M)
|
|
return
|
|
ASSERT(isnull(owner))
|
|
owner = M
|
|
owner.register_ability(src)
|
|
if(bound)
|
|
action?.grant(owner.actions_innate)
|
|
update_action()
|
|
else if(always_bind && !hidden)
|
|
quickbind()
|
|
|
|
/datum/ability/proc/disassociate(mob/M)
|
|
ASSERT(owner == M)
|
|
if(bound)
|
|
action?.revoke(owner.actions_innate)
|
|
owner.unregister_ability(src)
|
|
owner = null
|
|
|
|
/**
|
|
* checks if we can be used
|
|
*/
|
|
/datum/ability/proc/available_check()
|
|
if(isnull(owner))
|
|
return FALSE
|
|
if(ability_check_flags == NONE)
|
|
return TRUE
|
|
if((ability_check_flags & ABILITY_CHECK_CONSCIOUS) && !IS_CONSCIOUS(owner))
|
|
return FALSE
|
|
if((ability_check_flags & ABILITY_CHECK_STANDING) && IS_PRONE(owner))
|
|
return FALSE
|
|
if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(!owner.are_usable_hands_full()))
|
|
return FALSE
|
|
if((ability_check_flags & ABILITY_CHECK_RESTING) && !IS_PRONE(owner))
|
|
return FALSE
|
|
if(!CHECK_MOBILITY(owner, mobility_check_flags))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* checks if we can be used, returning reason on failure or null for success.
|
|
*/
|
|
/datum/ability/proc/unavailable_reason()
|
|
if(isnull(owner))
|
|
return "!ERROR - NO OWNER!"
|
|
if(ability_check_flags == NONE)
|
|
return
|
|
if((ability_check_flags & ABILITY_CHECK_CONSCIOUS) && !IS_CONSCIOUS(owner))
|
|
return "You cannot do that while unconscious."
|
|
if((ability_check_flags & ABILITY_CHECK_STANDING) && owner.lying)
|
|
return "You cannot do that while on the ground."
|
|
if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(!owner.are_usable_hands_full()))
|
|
return "You cannot do that without a free hand."
|
|
if((ability_check_flags & ABILITY_CHECK_RESTING) && !owner.lying)
|
|
return "You must be lying down to do that."
|
|
if(!CHECK_MOBILITY(owner, mobility_check_flags))
|
|
return "You cannot do that while incapacitated."
|
|
|
|
/**
|
|
* sets us to hidden - no binding and not seen on panel
|
|
*/
|
|
/datum/ability/proc/hide()
|
|
hidden = TRUE
|
|
unbind()
|
|
|
|
/**
|
|
* sets us to not hidden - bind if needed, seen on panel.
|
|
*/
|
|
/datum/ability/proc/unhide()
|
|
hidden = FALSE
|
|
if(always_bind)
|
|
quickbind()
|
|
|
|
|
|
/**
|
|
* checks if the target is within range and the ability is triggerable
|
|
*/
|
|
/datum/ability/proc/target_check(mob/user, atom/target)
|
|
if(range)
|
|
if(get_dist(get_turf(user),get_turf(target)) > range)
|
|
return FALSE
|
|
if(check_trigger(user))
|
|
target_trigger(user,target)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/**
|
|
* target-affecting proc
|
|
*/
|
|
/datum/ability/proc/target_trigger(mob/user, atom/target)
|
|
//target-related code goes here
|
|
disable()
|
|
|
|
/**
|
|
* static data for tgui panel
|
|
*/
|
|
/datum/ability/ui_static_data(mob/user, datum/tgui/ui)
|
|
return list(
|
|
"$tgui" = tgui_id,
|
|
"$src" = REF(src),
|
|
"interact_type" = interact_type,
|
|
"can_bind" = !always_bind && (interact_type != ABILITY_INTERACT_NONE),
|
|
"bound" = bound,
|
|
"name" = name,
|
|
"desc" = desc,
|
|
)
|
|
|
|
/**
|
|
* action datums for abilities
|
|
*/
|
|
/datum/action/ability
|
|
abstract_type = /datum/action/ability
|
|
target_type = /datum/ability
|