mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 13:38:41 +01:00
Given the existence of basic mobs with hand slots, it feels like throwing and giving items shouldn't be something exclusive to carbon mobs, so I've pulled things around to make this happen. The only basic mobs with hands at time of writing are gorillas and dextrous holoparasites, but the inability to throw things when you're a gorilla just doesn't seem right to me. Some more details about what I've done here: - Made the dextrous component optionally enable throwing for the mob it's added to. - Moved offer/give item functionality to /mob/living (I can't see any reason why only carbon mobs should have this option) - Moved throwing and give item hotkeys from carbon to "human" (where all the other /mob/living hotkeys go) and, as a result, removed carbon hotkeys (nothing is left in them). - Moved throwing code and item offering code to its own file because living.dm is 3000+ lines long and should probably be broken up some day (I'm not brave enough for that) - Cleaned up an unused global signal that hasn't been used since dogs got moved to basic mobs. - Other miscellaneous cleanup where I noticed it. - In terms of testing: Tested using gorillas (only checked the dextrous holoparasite to confirm the button and hotkeys worked). Things that were working: - Can throw items if the mob is set up to allow it. - Can give items as a gorilla to a human, as a human to a gorilla, and as a human to a human. - Can give a high five to a gorilla (and the gorilla can receive it). Gorillas can't give a high five back, though (they don't have the emote), this already ballooned in scope, someone else can make that happen. - There are an alarmingly high amount of niche emote-into-item-into-giving behaviours I suspect half the playerbase or more aren't even aware of (does anyone offer their hand to someone to get them up off of the ground?) and I don't know if I broke any of them with this, but the fact high fives work gives me some hope they're probably still fine. Lets gorillas and dextrous holoparasites throw things and give things, but most importantly sets up more framework for any future dextrous basic mobs to also be able to do this. There's no real reason to keep this functionality confined to carbon mobs when dextrous basic mobs are a thing. 🆑 add: Gorillas can now throw things and offer items to players. refactor: Moved throwing and offering item code to be based on living mobs, not just carbon mobs. /🆑 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
217 lines
8.0 KiB
Plaintext
217 lines
8.0 KiB
Plaintext
#define MIN_DISARM_CHANCE 25
|
|
#define MAX_DISARM_CHANCE 75
|
|
|
|
/obj/item/toy/basketball
|
|
name = "basketball"
|
|
icon = 'icons/obj/toys/balls.dmi'
|
|
icon_state = "basketball"
|
|
inhand_icon_state = "basketball"
|
|
desc = "Here's your chance, do your dance at the Space Jam."
|
|
w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets
|
|
/// The person dribbling the basketball
|
|
var/mob/living/wielder
|
|
/// So the basketball doesn't make sound every step
|
|
var/steps = 0
|
|
var/step_delay = 2
|
|
/// So they can't spam dribbling (at least not too much)
|
|
var/last_use = 0
|
|
var/use_delay = 0.2 SECONDS
|
|
/// List of player ckeys who aren't allowed to pickup the ball (after scoring)
|
|
/// This resets after someone else picks up the ball or a certain amount of time has passed
|
|
var/pickup_restriction_ckeys = list()
|
|
/// Pickup restriction cooldown
|
|
COOLDOWN_DECLARE(pickup_cooldown)
|
|
|
|
/obj/item/toy/basketball/Initialize(mapload)
|
|
. = ..()
|
|
|
|
RegisterSignal(src, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
|
|
RegisterSignal(src, COMSIG_ITEM_DROPPED, PROC_REF(remove_ball_effects))
|
|
RegisterSignal(src, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(after_throw_reset))
|
|
RegisterSignal(src, COMSIG_MOVABLE_IMPACT, PROC_REF(after_throw_reset))
|
|
|
|
/obj/item/toy/basketball/proc/reset_pickup_restriction()
|
|
pickup_restriction_ckeys = list()
|
|
COOLDOWN_RESET(src, pickup_cooldown)
|
|
|
|
/obj/item/toy/basketball/proc/on_equip(obj/item/source, mob/living/user, slot)
|
|
SIGNAL_HANDLER
|
|
|
|
wielder = user
|
|
|
|
// the equip signal is sent AFTER the object is put in hands
|
|
// so we need to manually check if the held object is different
|
|
for(var/obj/item/toy/basketball/ball in user.held_items)
|
|
if(ball != src)
|
|
return // multiple balls in different hands so no need to setup signals again
|
|
|
|
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(movement_effect))
|
|
RegisterSignal(user, COMSIG_MOB_EMOTED("spin"), PROC_REF(on_spin))
|
|
RegisterSignal(user, COMSIG_LIVING_DISARM_HIT, PROC_REF(on_equipped_mob_disarm))
|
|
RegisterSignal(user, COMSIG_LIVING_STATUS_KNOCKDOWN, PROC_REF(on_equipped_mob_knockdown))
|
|
|
|
/obj/item/toy/basketball/proc/remove_ball_effects()
|
|
SIGNAL_HANDLER
|
|
|
|
// unlike on_equip, this signal is triggered after the ball is removed from hands
|
|
// so we can just use is_holding_item_of_type() proc to check for multiple balls
|
|
if(!wielder.is_holding_item_of_type(/obj/item/toy/basketball))
|
|
UnregisterSignal(wielder, list(COMSIG_MOVABLE_MOVED, COMSIG_MOB_EMOTED("spin"), COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN))
|
|
|
|
wielder = null
|
|
|
|
/**
|
|
* After a ball is thrown we need to reset the pass_flags since shooting lets you shoot through mobs
|
|
* * source: Datum src from original signal call
|
|
**/
|
|
/obj/item/toy/basketball/proc/after_throw_reset() // don't need the args for the signal
|
|
SIGNAL_HANDLER
|
|
|
|
pass_flags = initial(pass_flags)
|
|
|
|
/obj/item/toy/basketball/attack_hand(mob/living/user, list/modifiers)
|
|
if(!user.can_perform_action(src, NEED_HANDS))
|
|
return
|
|
|
|
if((user.ckey in pickup_restriction_ckeys) && !COOLDOWN_FINISHED(src, pickup_cooldown))
|
|
user.balloon_alert(user, "cant pickup for [COOLDOWN_TIMELEFT(src, pickup_cooldown) *0.1] seconds!")
|
|
return
|
|
|
|
reset_pickup_restriction()
|
|
return ..()
|
|
|
|
/obj/item/toy/basketball/proc/movement_effect(atom/movable/source, atom/old_loc, dir, forced)
|
|
SIGNAL_HANDLER
|
|
|
|
if(steps > step_delay)
|
|
playsound(src, 'sound/items/basketball_bounce.ogg', 75, FALSE)
|
|
steps = 0
|
|
else
|
|
steps++
|
|
|
|
/obj/item/toy/basketball/proc/on_spin(mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
for(var/i in 1 to 6)
|
|
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), src, 'sound/items/basketball_bounce.ogg', 75, FALSE), 0.25 SECONDS * i)
|
|
addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living/carbon/, adjustStaminaLoss), STAMINA_COST_SPINNING), 1.5 SECONDS)
|
|
|
|
/// Used to calculate our disarm chance based on stamina, direction, and spinning
|
|
/// Note - monkeys use attack_paw() and never trigger this signal (so they always have 100% disarm)
|
|
/obj/item/toy/basketball/proc/on_equipped_mob_disarm(mob/living/baller, mob/living/stealer, zone, obj/item/weapon)
|
|
SIGNAL_HANDLER
|
|
|
|
// spinning gives you a lower disarm chance but it drains stamina
|
|
var/disarm_chance = HAS_TRAIT(baller, TRAIT_SPINNING) ? 35 : 50
|
|
// ballers stamina results in lower disarm, stealer stamina results in higher disarm
|
|
disarm_chance += (baller.getStaminaLoss() - stealer.getStaminaLoss()) / 2
|
|
// the lowest chance for disarm is 25% and the highest is 75%
|
|
disarm_chance = clamp(disarm_chance, MIN_DISARM_CHANCE, MAX_DISARM_CHANCE)
|
|
|
|
// getting disarmed or shoved while holding the ball drains stamina
|
|
baller.adjustStaminaLoss(STAMINA_COST_DISARMING)
|
|
|
|
if(!prob(disarm_chance))
|
|
return // the disarm failed
|
|
|
|
playsound(src, 'sound/items/basketball_bounce.ogg', 75, FALSE)
|
|
var/blocking_dir_bonus = check_target_facings(stealer, baller)
|
|
|
|
switch(blocking_dir_bonus)
|
|
if(FACING_EACHOTHER)
|
|
stealer.balloon_alert_to_viewers("steals the ball")
|
|
INVOKE_ASYNC(stealer, TYPE_PROC_REF(/mob, put_in_hands), src)
|
|
if(FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR)
|
|
if(prob(50))
|
|
if(!baller.dropItemToGround(src))
|
|
return
|
|
stealer.balloon_alert_to_viewers("bats the ball")
|
|
else
|
|
stealer.balloon_alert_to_viewers("steals the ball")
|
|
INVOKE_ASYNC(stealer, TYPE_PROC_REF(/mob, put_in_hands), src)
|
|
if(FACING_SAME_DIR)
|
|
if(!baller.dropItemToGround(src))
|
|
return
|
|
stealer.balloon_alert_to_viewers("bats the ball")
|
|
|
|
/obj/item/toy/basketball/proc/on_equipped_mob_knockdown(mob/living/user, amount)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!istype(user))
|
|
return
|
|
|
|
// Healing knockdown or setting knockdown to zero or something? No fumble
|
|
if(amount <= 0)
|
|
return
|
|
|
|
if(!user.dropItemToGround(src))
|
|
return
|
|
|
|
user.balloon_alert_to_viewers("fumbles the ball")
|
|
|
|
/obj/item/toy/basketball/attack(mob/living/carbon/target, mob/living/user, list/modifiers, list/attack_modifiers)
|
|
if(!iscarbon(target) || user.combat_mode)
|
|
return ..()
|
|
|
|
playsound(src, 'sound/items/basketball_bounce.ogg', 75, FALSE)
|
|
target.put_in_hands(src)
|
|
|
|
/obj/item/toy/basketball/attack_self(mob/living/user)
|
|
if(!user.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH))
|
|
return
|
|
|
|
// no spamming
|
|
if(last_use + use_delay > world.time)
|
|
return
|
|
|
|
// need a free hand and can't be spinning
|
|
if(!user.put_in_inactive_hand(src) || HAS_TRAIT(user, TRAIT_SPINNING))
|
|
return
|
|
|
|
last_use = world.time
|
|
user.swap_hand(user.get_held_index_of_item(src))
|
|
playsound(src, 'sound/items/basketball_bounce.ogg', 75, FALSE)
|
|
|
|
/obj/item/toy/basketball/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
return interact_with_atom(interacting_with, user, modifiers)
|
|
|
|
/obj/item/toy/basketball/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
|
if(user.combat_mode)
|
|
user.throw_item(interacting_with)
|
|
return ITEM_INTERACT_SUCCESS
|
|
return NONE
|
|
|
|
/obj/item/toy/basketball/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
|
|
return interact_with_atom_secondary(interacting_with, user, modifiers)
|
|
|
|
/obj/item/toy/basketball/interact_with_atom_secondary(atom/interacting_with, mob/living/baller, list/modifiers)
|
|
if(istype(interacting_with, /obj/structure/hoop) && baller.Adjacent(interacting_with))
|
|
return NONE // Do hoop stuff
|
|
|
|
baller.adjustStaminaLoss(STAMINA_COST_SHOOTING)
|
|
|
|
var/dunk_dir = get_dir(baller, interacting_with)
|
|
var/dunk_pixel_y = dunk_dir & SOUTH ? -16 : 16
|
|
var/dunk_pixel_x = dunk_dir & EAST && 16 || dunk_dir & WEST && -16 || 0
|
|
|
|
animate(baller, pixel_x = dunk_pixel_x, pixel_y = dunk_pixel_y, time = 5, easing = BOUNCE_EASING|EASE_IN|EASE_OUT)
|
|
if(do_after(baller, 0.5 SECONDS))
|
|
pass_flags |= PASSMOB
|
|
baller.throw_item(interacting_with)
|
|
animate(baller, pixel_x = 0, pixel_y = 0, time = 3)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
animate(baller, pixel_x = 0, pixel_y = 0, time = 3)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
/obj/item/toy/basketball/throw_impact(mob/living/carbon/target, datum/thrownthing/throwingdatum)
|
|
playsound(src, 'sound/items/basketball_bounce.ogg', 75, FALSE)
|
|
|
|
if(!istype(target))
|
|
return ..()
|
|
|
|
target.put_in_hands(src)
|
|
|
|
#undef MAX_DISARM_CHANCE
|
|
#undef MIN_DISARM_CHANCE
|