Converts tippable behavior from cows and medibots to a component (tip over anything with adminbus) (#59705)

This PR converts cow-tipping and medibot-tipping into a component, /datum/component/tippable. Cows and medibots now use the tippable component to control their tipping behaviors.

This PR also goes through a few atom_attack_hand signals and makes them all send the same arguments.
This commit is contained in:
MrMelbert
2021-06-22 22:39:22 -05:00
committed by GitHub
parent 6fcbce39cd
commit 8dfd1fb627
9 changed files with 314 additions and 88 deletions
+1 -1
View File
@@ -1223,7 +1223,7 @@
#define COMPONENT_SKIP_ATTACK (1<<1)
///from base of atom/attack_ghost(): (mob/dead/observer/ghost)
#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost"
///from base of atom/attack_hand(): (mob/user)
///from base of atom/attack_hand(): (mob/user, list/modifiers)
#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand"
///from base of atom/attack_paw(): (mob/user)
#define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw"
+1 -1
View File
@@ -28,7 +28,7 @@
#define BOT_WAIT_FOR_NAV 16 // waiting for nav computation
#define BOT_NO_ROUTE 17 // no destination beacon found (or no route)
#define BOT_SHOWERSTANCE 18 // cleaning unhygienic humans
#define BOT_TIPPED 19 // someone tipped a medibot over ;_;
#define BOT_TIPPED 19 // someone tipped a bot over ;_;
//Bot types
#define SEC_BOT (1<<0) // Secutritrons (Beepsky) and ED-209s
+2 -2
View File
@@ -603,8 +603,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define SOULSTONE_TRAIT "soulstone"
/// Trait applied to slimes by low temperature
#define SLIME_COLD "slime-cold"
/// Trait applied to bots by being tipped over
#define BOT_TIPPED_OVER "bot-tipped-over"
/// Trait applied to mobs by being tipped over
#define TIPPED_OVER "tipped-over"
/// Trait applied to PAIs by being folded
#define PAI_FOLDED "pai-folded"
/// Trait applied to brain mobs when they lack external aid for locomotion, such as being inside a mech.
+207
View File
@@ -0,0 +1,207 @@
/*
* 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
/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)
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
/datum/component/tippable/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/interact_with_tippable)
/datum/component/tippable/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND)
/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)
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
* modifiers - list of on click modifiers (we only tip mobs over using right click!)
*/
/datum/component/tippable/proc/interact_with_tippable(mob/living/source, mob/user, modifiers)
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/try_untip, source, user)
else if(LAZYACCESS(modifiers, RIGHT_CLICK))
INVOKE_ASYNC(src, .proc/try_tip, source, user)
return COMPONENT_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)
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))
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]."))
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
addtimer(CALLBACK(src, .proc/right_self, tipped_mob), self_right_time)
/*
* 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
)
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)
@@ -146,7 +146,7 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/mob/living/carbon/attack_hand(mob/living/carbon/human/user, list/modifiers)
if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN)
if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
. = TRUE
for(var/thing in diseases)
var/datum/disease/D = thing
@@ -62,7 +62,7 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/mob/living/silicon/attack_hand(mob/living/carbon/human/user, list/modifiers)
. = FALSE
if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN)
if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
. = TRUE
if(has_buckled_mobs() && !user.combat_mode)
user_unbuckle_mob(buckled_mobs[1], user)
@@ -33,43 +33,43 @@
window_name = "Automatic Medical Unit v1.1"
data_hud_type = DATA_HUD_MEDICAL_ADVANCED
path_image_color = "#DDDDFF"
/// drop determining variable
/// drop determining variable
var/healthanalyzer = /obj/item/healthanalyzer
/// drop determining variable
/// drop determining variable
var/firstaid = /obj/item/storage/firstaid
///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox"
///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox"
var/skin
var/mob/living/carbon/patient
var/mob/living/carbon/oldpatient
var/oldloc
var/last_found = 0
/// Don't spam the "HEY I'M COMING" messages
/// Don't spam the "HEY I'M COMING" messages
var/last_newpatient_speak = 0
/// How much healing do we do at a time?
/// How much healing do we do at a time?
var/heal_amount = 2.5
/// Start healing when they have this much damage in a category
/// Start healing when they have this much damage in a category
var/heal_threshold = 10
/// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types
/// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types
var/damagetype_healer = BRUTE
/// If active, the bot will transmit a critical patient alert to MedHUD users.
/// If active, the bot will transmit a critical patient alert to MedHUD users.
var/declare_crit = TRUE
/// Prevents spam of critical patient alerts.
/// Prevents spam of critical patient alerts.
var/declare_cooldown = FALSE
/// If enabled, the Medibot will not move automatically.
/// If enabled, the Medibot will not move automatically.
var/stationary_mode = FALSE
/// silences the medbot if TRUE
/// silences the medbot if TRUE
var/shut_up = FALSE
/// techweb linked to the medbot
/// techweb linked to the medbot
var/datum/techweb/linked_techweb
///Is the medbot currently tending wounds
///Is the medbot currently tending wounds
var/tending = FALSE
///How panicked we are about being tipped over (why would you do this?)
///How panicked we are about being tipped over (why would you do this?)
var/tipped_status = MEDBOT_PANIC_NONE
///The name we got when we were tipped
///The name we got when we were tipped
var/tipper_name
///The last time we were tipped/righted and said a voice line, to avoid spam
var/last_tipping_action_voice = 0
///Cooldown to track last time we were tipped/righted and said a voice line, to avoid spam
COOLDOWN_DECLARE(last_tipping_action_voice)
/mob/living/simple_animal/bot/medbot/mysterious
name = "\improper Mysterious Medibot"
@@ -119,6 +119,13 @@
if(damagetype_healer == "all")
return
AddComponent(/datum/component/tippable, \
tip_time = 3 SECONDS, \
untip_time = 3 SECONDS, \
self_right_time = 3.5 MINUTES, \
pre_tipped_callback = CALLBACK(src, .proc/pre_tip_over), \
post_tipped_callback = CALLBACK(src, .proc/after_tip_over), \
post_untipped_callback = CALLBACK(src, .proc/after_righted))
/mob/living/simple_animal/bot/medbot/bot_reset()
..()
@@ -244,37 +251,60 @@
else
return
/mob/living/simple_animal/bot/medbot/proc/tip_over(mob/user)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, BOT_TIPPED_OVER)
playsound(src, 'sound/machines/warning-buzzer.ogg', 50)
user.visible_message(span_danger("[user] tips over [src]!"), span_danger("You tip [src] over!"))
/*
* Proc used in a callback for before this medibot is tipped by the tippable component.
*
* user - the mob who is tipping us over
*/
/mob/living/simple_animal/bot/medbot/proc/pre_tip_over(mob/user)
if(!COOLDOWN_FINISHED(src, last_tipping_action_voice))
return
COOLDOWN_START(src, last_tipping_action_voice, 15 SECONDS) // message for tipping happens when we start interacting, message for righting comes after finishing
var/static/list/messagevoice = list(
"Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg',
"Please don't..." = 'sound/voice/medbot/please_dont.ogg',
"I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg',
"Nooo..." = 'sound/voice/medbot/nooo.ogg',
"Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg',
)
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70, FALSE)
/*
* Proc used in a callback for after this medibot is tipped by the tippable component.
*
* user - the mob who tipped us over
*/
/mob/living/simple_animal/bot/medbot/proc/after_tip_over(mob/user)
mode = BOT_TIPPED
var/matrix/mat = transform
transform = mat.Turn(180)
tipper_name = user.name
playsound(src, 'sound/machines/warning-buzzer.ogg', 50)
/mob/living/simple_animal/bot/medbot/proc/set_right(mob/user)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, BOT_TIPPED_OVER)
/*
* Proc used in a callback for after this medibot is righted, either by themselves or by a mob, by the tippable component.
*
* user - the mob who righted us. Can be null.
*/
/mob/living/simple_animal/bot/medbot/proc/after_righted(mob/user)
var/list/messagevoice
if(user)
user.visible_message(span_notice("[user] sets [src] right-side up!"), span_green("You set [src] right-side up!"))
if(user.name == tipper_name)
messagevoice = list("I forgive you." = 'sound/voice/medbot/forgive.ogg')
else
messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg')
else
visible_message(span_notice("[src] manages to wriggle enough to right itself."))
messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg')
tipper_name = null
if(world.time > last_tipping_action_voice + 15 SECONDS)
last_tipping_action_voice = world.time
if(COOLDOWN_FINISHED(src, last_tipping_action_voice))
COOLDOWN_START(src, last_tipping_action_voice, 15 SECONDS)
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70)
tipped_status = MEDBOT_PANIC_NONE
mode = BOT_IDLE
transform = matrix()
/// if someone tipped us over, check whether we should ask for help or just right ourselves eventually
/mob/living/simple_animal/bot/medbot/proc/handle_panic()
@@ -294,7 +324,6 @@
messagevoice = list("Is this the end?" = 'sound/voice/medbot/is_this_the_end.ogg', "Nooo!" = 'sound/voice/medbot/nooo.ogg')
if(MEDBOT_PANIC_END)
speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel)
set_right() // strong independent medbot
if(prob(tipped_status))
do_jitter_animation(tipped_status * 0.1)
@@ -452,31 +481,6 @@
if(damagetype_healer == "all" && treat_me_for.len)
return TRUE
/mob/living/simple_animal/bot/medbot/attack_hand(mob/living/carbon/human/user, list/modifiers)
if(DOING_INTERACTION_WITH_TARGET(user, src))
to_chat(user, span_warning("You're already interacting with [src]."))
return
if(LAZYACCESS(modifiers, RIGHT_CLICK) && mode != BOT_TIPPED)
user.visible_message(span_danger("[user] begins tipping over [src]."), span_warning("You begin tipping over [src]..."))
if(world.time > last_tipping_action_voice + 15 SECONDS)
last_tipping_action_voice = world.time // message for tipping happens when we start interacting, message for righting comes after finishing
var/list/messagevoice = list("Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg',"Please don't..." = 'sound/voice/medbot/please_dont.ogg',"I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', "Nooo..." = 'sound/voice/medbot/nooo.ogg', "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg')
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70, FALSE)
if(do_after(user, 3 SECONDS, target=src))
tip_over(user)
else if(!user.combat_mode && mode == BOT_TIPPED)
user.visible_message(span_notice("[user] begins righting [src]."), span_notice("You begin righting [src]..."))
if(do_after(user, 3 SECONDS, target=src))
set_right(user)
else
..()
/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A, proximity_flag, list/modifiers)
if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
return
@@ -133,6 +133,11 @@
/mob/living/simple_animal/cow/Initialize()
AddComponent(/datum/component/udder)
AddComponent(/datum/component/tippable, \
tip_time = 0.5 SECONDS, \
untip_time = 0.5 SECONDS, \
self_right_time = rand(25 SECONDS, 50 SECONDS), \
post_tipped_callback = CALLBACK(src, .proc/after_cow_tipped))
AddElement(/datum/element/pet_bonus, "moos happily!")
add_cell_sample()
make_tameable()
@@ -150,34 +155,43 @@
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/cow)
/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/user, list/modifiers)
if(!stat && LAZYACCESS(modifiers, RIGHT_CLICK) && icon_state != icon_dead)
user.visible_message(span_warning("[user] tips over [src]."),
span_notice("You tip over [src]."))
to_chat(src, span_userdanger("You are tipped over by [user]!"))
Paralyze(60, ignore_canstun = TRUE)
icon_state = icon_dead
addtimer(CALLBACK(src, .proc/cow_tipped, user), rand(20,50))
/*
* Proc called via callback after the cow is tipped by the tippable component.
* Begins a timer for us pleading for help.
*
* tipper - the mob who tipped us
*/
/mob/living/simple_animal/cow/proc/after_cow_tipped(mob/living/carbon/tipper)
addtimer(CALLBACK(src, .proc/look_for_help, tipper), rand(10 SECONDS, 20 SECONDS))
else
..()
/*
* Find a mob in a short radius around us (prioritizing the person who originally tipped us)
* and either look at them for help, or give up. No actual mechanical difference between the two.
*
* tipper - the mob who originally tipped us
*/
/mob/living/simple_animal/cow/proc/look_for_help(mob/living/carbon/tipper)
// visible part of the visible message
var/seen_message = ""
// self part of the visible message
var/self_message = ""
// the mob we're looking to for aid
var/mob/living/carbon/savior
// look for someone in a radius around us for help. If our original tipper is in range, prioritize them
for(var/mob/living/carbon/potential_aid in oview(3, get_turf(src)))
if(potential_aid == tipper)
savior = tipper
break
savior = potential_aid
/mob/living/simple_animal/cow/proc/cow_tipped(mob/living/carbon/M)
if(QDELETED(M) || stat)
return
icon_state = icon_living
var/external
var/internal
if(prob(75))
var/text = pick("imploringly.", "pleadingly.",
"with a resigned expression.")
external = "[src] looks at [M] [text]"
internal = "You look at [M] [text]"
if(prob(75) && savior)
var/text = pick("imploringly", "pleadingly", "with a resigned expression")
seen_message = "[src] looks at [savior] [text]."
self_message = "You look at [savior] [text]."
else
external = "[src] seems resigned to its fate."
internal = "You resign yourself to your fate."
visible_message(span_notice("[external]"),
span_revennotice("[internal]"))
seen_message = "[src] seems resigned to its fate."
self_message = "You resign yourself to your fate."
visible_message(span_notice("[seen_message]"), span_notice("[self_message]"))
///Wisdom cow, gives XP to a random skill and speaks wisdoms
/mob/living/simple_animal/cow/wisdom
+1
View File
@@ -578,6 +578,7 @@
#include "code\datums\components\technoshy.dm"
#include "code\datums\components\tether.dm"
#include "code\datums\components\thermite.dm"
#include "code\datums\components\tippable.dm"
#include "code\datums\components\trapdoor.dm"
#include "code\datums\components\twohanded.dm"
#include "code\datums\components\udder.dm"