Files
Bubberstation/code/datums/components/tippable.dm
SkyratBot 7415526265 [MIRROR] You can tip flashed borgs [MDB IGNORE] (#21339)
* You can tip flashed borgs (#75539)

## About The Pull Request

Fixes tippable not working on flashed mobs by letting it work on those
who are forced standing even if they aren't conscious, then gives that
trait to borgs.
I thought this would be the best fix for it because borgs technically
are just forced standing anyways, and I didn't want to just add an
issilicon check.

## Why It's Good For The Game

Fixes an old bug that I should've fixed a long time ago, makes tipping
something that can realistically happen in-game.

## Changelog

🆑
fix: Borgs can be tipped over while flashed.
/🆑

* You can tip flashed borgs

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
2023-05-21 22:49:08 -07:00

260 lines
8.7 KiB
Plaintext

/**
* Tippable component. For making mobs able to be tipped, like cows and medibots.
*/
/datum/component/tippable
/// Time it takes to tip the mob. Can be 0, for instant tipping.
var/tip_time = 3 SECONDS
/// Time it takes to untip the mob. Can also be 0, for instant untip.
var/untip_time = 1 SECONDS
/// Time it takes for the mob to right itself. Can be 0 for instant self-righting, or null, to never self-right.
var/self_right_time = 60 SECONDS
/// Whether the mob is currently tipped.
var/is_tipped = FALSE
/// Callback to additional behavior before being tipped (on try_tip). Return anything from this callback to cancel the tip.
var/datum/callback/pre_tipped_callback
/// Callback to additional behavior after successfully tipping the mob.
var/datum/callback/post_tipped_callback
/// Callback to additional behavior after being untipped.
var/datum/callback/post_untipped_callback
/// Callback to any extra roleplay behaviour
var/datum/callback/roleplay_callback
///The timer given until they untip themselves
var/self_untip_timer
///Should we accept roleplay?
var/roleplay_friendly
///Have we roleplayed?
var/roleplayed = FALSE
///List of emotes that will half their untip time
var/list/roleplay_emotes
/datum/component/tippable/Initialize(
tip_time = 3 SECONDS,
untip_time = 1 SECONDS,
self_right_time = 60 SECONDS,
datum/callback/pre_tipped_callback,
datum/callback/post_tipped_callback,
datum/callback/post_untipped_callback,
roleplay_friendly = FALSE,
roleplay_emotes,
datum/callback/roleplay_callback,
)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.tip_time = tip_time
src.untip_time = untip_time
src.self_right_time = self_right_time
src.pre_tipped_callback = pre_tipped_callback
src.post_tipped_callback = post_tipped_callback
src.post_untipped_callback = post_untipped_callback
src.roleplay_friendly = roleplay_friendly
src.roleplay_emotes = roleplay_emotes
src.roleplay_callback = roleplay_callback
/datum/component/tippable/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND_SECONDARY, PROC_REF(interact_with_tippable))
if (roleplay_friendly)
RegisterSignal(parent, COMSIG_MOB_EMOTE, PROC_REF(accept_roleplay))
/datum/component/tippable/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND_SECONDARY)
/datum/component/tippable/Destroy()
if(pre_tipped_callback)
QDEL_NULL(pre_tipped_callback)
if(post_tipped_callback)
QDEL_NULL(post_tipped_callback)
if(post_untipped_callback)
QDEL_NULL(post_untipped_callback)
if(roleplay_callback)
QDEL_NULL(roleplay_callback)
return ..()
/**
* Attempt to interact with [source], either tipping it or helping it up.
*
* source - the mob being tipped over
* user - the mob interacting with source
*/
/datum/component/tippable/proc/interact_with_tippable(mob/living/source, mob/user)
SIGNAL_HANDLER
var/mob/living/living_user = user
if(DOING_INTERACTION_WITH_TARGET(user, source))
return
if(istype(living_user) && !living_user.combat_mode)
return
if(is_tipped)
INVOKE_ASYNC(src, PROC_REF(try_untip), source, user)
else
INVOKE_ASYNC(src, PROC_REF(try_tip), source, user)
return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
/**
* Try to tip over [tipped_mob].
* If the mob is dead, or optional callback returns a value, or our do-after fails, we don't tip the mob.
* Otherwise, upon completing of the do_after, tip over the mob.
*
* tipped_mob - the mob being tipped over
* tipper - the mob tipping the tipped_mob
*/
/datum/component/tippable/proc/try_tip(mob/living/tipped_mob, mob/tipper)
if(tipped_mob.stat != CONSCIOUS && !HAS_TRAIT(tipped_mob, TRAIT_FORCED_STANDING))
return
if(pre_tipped_callback?.Invoke(tipper))
return
if(tip_time > 0)
to_chat(tipper, span_warning("You begin tipping over [tipped_mob]..."))
tipped_mob.visible_message(
span_warning("[tipper] begins tipping over [tipped_mob]."),
span_userdanger("[tipper] begins tipping you over!"),
ignored_mobs = tipper
)
if(!do_after(tipper, tip_time, target = tipped_mob))
if(!isnull(tipped_mob.client))
tipped_mob.log_message("was attempted to tip over by [key_name(tipper)]", LOG_VICTIM, log_globally = FALSE)
tipper.log_message("failed to tip over [key_name(tipped_mob)]", LOG_ATTACK)
to_chat(tipper, span_danger("You fail to tip over [tipped_mob]."))
return
do_tip(tipped_mob, tipper)
/**
* Actually tip over the mob, setting it to tipped.
* Also invoking any callbacks we have, with the tipper as the argument,
* and set a timer to right our self-right our tipped mob if we can.
*
* tipped_mob - the mob who was tipped
* tipper - the mob who tipped the tipped_mob
*/
/datum/component/tippable/proc/do_tip(mob/living/tipped_mob, mob/tipper)
if(QDELETED(tipped_mob))
CRASH("Tippable component: do_tip() called with QDELETED tipped_mob!")
to_chat(tipper, span_warning("You tip over [tipped_mob]."))
if (!isnull(tipped_mob.client))
tipped_mob.log_message("has been tipped over by [key_name(tipper)].", LOG_ATTACK)
tipper.log_message("has tipped over [key_name(tipped_mob)].", LOG_ATTACK)
tipped_mob.visible_message(
span_warning("[tipper] tips over [tipped_mob]."),
span_userdanger("You are tipped over by [tipper]!"),
ignored_mobs = tipper
)
set_tipped_status(tipped_mob, TRUE)
post_tipped_callback?.Invoke(tipper)
if(isnull(self_right_time))
return
else if(self_right_time <= 0)
right_self(tipped_mob)
else
self_untip_timer = addtimer(CALLBACK(src, PROC_REF(right_self), tipped_mob), self_right_time, TIMER_UNIQUE | TIMER_STOPPABLE)
/**
* Try to untip a mob that has been tipped.
* After a do-after is completed, we untip the mob.
*
* tipped_mob - the mob who is tipped
* untipper - the mob who is untipping the tipped_mob
*/
/datum/component/tippable/proc/try_untip(mob/living/tipped_mob, mob/untipper)
if(untip_time > 0)
to_chat(untipper, span_notice("You begin righting [tipped_mob]..."))
tipped_mob.visible_message(
span_notice("[untipper] begins righting [tipped_mob]."),
span_notice("[untipper] begins righting you."),
ignored_mobs = untipper
)
if(!do_after(untipper, untip_time, target = tipped_mob))
to_chat(untipper, span_warning("You fail to right [tipped_mob]."))
return
do_untip(tipped_mob, untipper)
/**
* Actually untip over the mob, setting it to untipped.
* Also invoke any untip callbacks we have, with the untipper as the argument.
*
* tipped_mob - the mob who was tipped
* tipper - the mob who tipped the tipped_mob
*/
/datum/component/tippable/proc/do_untip(mob/living/tipped_mob, mob/untipper)
if(QDELETED(tipped_mob))
return
to_chat(untipper, span_notice("You right [tipped_mob]."))
tipped_mob.visible_message(
span_notice("[untipper] rights [tipped_mob]."),
span_notice("You are righted by [untipper]!"),
ignored_mobs = untipper
)
if(self_untip_timer)
deltimer(self_untip_timer)
set_tipped_status(tipped_mob, FALSE)
post_untipped_callback?.Invoke(untipper)
/**
* Proc called after a timer to have a tipped mob un-tip itself after a certain length of time.
* Sets our mob to untipped and invokes the untipped callback without any arguments if we have one.
*
* tipped_mob - the mob who was tipped, and is freeing itself
*/
/datum/component/tippable/proc/right_self(mob/living/tipped_mob)
if(!is_tipped || QDELETED(tipped_mob))
return
set_tipped_status(tipped_mob, FALSE)
post_untipped_callback?.Invoke()
tipped_mob.visible_message(
span_notice("[tipped_mob] rights itself."),
span_notice("You right yourself.")
)
/**
* Toggles our tipped status between tipped or untipped (TRUE or FALSE)
* also handles rotating our mob and adding immobilization traits
*
* tipped_mob - the mob we're setting to tipped or untipped
* new_status - the tipped status we're setting the mob to - TRUE for tipped, FALSE for untipped
*/
/datum/component/tippable/proc/set_tipped_status(mob/living/tipped_mob, new_status = FALSE)
is_tipped = new_status
if(is_tipped)
tipped_mob.transform = turn(tipped_mob.transform, 180)
ADD_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER)
else
tipped_mob.transform = turn(tipped_mob.transform, -180)
REMOVE_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER)
/**
* Accepts "roleplay" in the form of emotes, which removes a quarter of the remaining time left to untip ourself.
*
* Arguments:
* * mob/living/user - The tipped mob
* * datum/emote/emote - The emote used by the mob
*/
/datum/component/tippable/proc/accept_roleplay(mob/living/user, datum/emote/emote)
SIGNAL_HANDLER
if (!is_tipped)
return
if (roleplayed)
return
if (!is_type_in_list(emote, roleplay_emotes))
return
var/time_left = timeleft(self_untip_timer)
deltimer(self_untip_timer)
self_untip_timer = addtimer(CALLBACK(src, PROC_REF(right_self), user), time_left * 0.75, TIMER_UNIQUE | TIMER_STOPPABLE)
roleplayed = TRUE
roleplay_callback?.Invoke(user)