From 333bef46fec2049df3e81530c23bc01966fd723f Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:52:08 -0400 Subject: [PATCH] Improves jousting and gives it to more weapons (#76321) ## About The Pull Request I was reading over components and saw one called jousting, I vividly remember it being mentioned every so often but I've never seen it in-game. Turns out the SINGLE case for it is when you are using a spear on a borg. The code itself was also very over the place, making it a little confusing to figure out what it did. I tried cleaning the file up as much as I could, and since I wanted to see this in-game more often, I made some player-facing changes too: - You can now joust from any vehicle, not just borgs (Secway, ATV, scooter, Charger holoparasite) - Added jousting to the broom, pitchfork, captain's sabre, and energy sword while active (ONLY esword, NOT desword). - Added examine text to indicate this feature exists. Extra notes: Esword gains half the damage increase and half the knockdown chance than other ways of jousting Broom only gets 25% damage increase from jousting, since the broom is already pretty strong I thought it would be better off as something used mostly to knockdown. Spears have to travel a longer distance than other weapons to get their jousting benefits (since it's supposed to be a ghetto weapon) Jousting now takes the minimum distance needed into account when handling knockdown chance & damage dealt, so travelling 5 tiles will no longer be 100% chance of knockdown if you need a minimum distance of 3 tiles to joust (it will instead be 40%, since you've only traveled 2 tiles in 'jousting' mode). ## Why It's Good For The Game This is an underused component and I thought it would bring some pretty cool interactions, especially for Holoparasite & Janitors, as a new way to use vehicles to your advantage when it's otherwise seen as just a slowdown. ## Changelog :cl: balance: Jousting now works on anything you're buckled to, not just Cyborgs. balance: Brooms, Pitchforks, the Captain's Sabre, and Energy swords can now be used for jousting. balance: Spears need to travel a longer distance to joust now. balance: Jousting's knockdown and damage now only gets stronger after you've traveled the minimum tiles needed to joust. /:cl: --- code/datums/components/jousting.dm | 153 +++++++++++++++++------- code/game/objects/items/broom.dm | 9 +- code/game/objects/items/melee/energy.dm | 1 + code/game/objects/items/melee/misc.dm | 7 +- code/game/objects/items/pitchfork.dm | 1 + code/game/objects/items/spear.dm | 5 +- 6 files changed, 126 insertions(+), 50 deletions(-) diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index abde9cefaa6..7fa5c8d9f41 100644 --- a/code/datums/components/jousting.dm +++ b/code/datums/components/jousting.dm @@ -1,27 +1,77 @@ -/datum/component/jousting - var/current_direction = NONE - var/max_tile_charge = 5 - var/min_tile_charge = 2 //tiles before this code gets into effect. - var/current_tile_charge = 0 - var/movement_reset_tolerance = 3 //deciseconds - var/unmounted_damage_boost_per_tile = 0 - var/unmounted_knockdown_chance_per_tile = 0 - var/unmounted_knockdown_time = 0 - var/mounted_damage_boost_per_tile = 2 - var/mounted_knockdown_chance_per_tile = 20 - var/mounted_knockdown_time = 20 - var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. - var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. - var/mob/current_holder - var/current_timerid +///Amount of time each timer has, used to reset the jousting back, indicating that the person has stopped moving. +#define MOVEMENT_RESET_COOLDOWN_TIME (0.3 SECONDS) -/datum/component/jousting/Initialize() +/** + * ##jousting + * + * Given to items, it allows you to charge into people with additional damage and potential knockdown + * by being buckled onto something. If the other person is also jousting, can knock eachother down. + */ +/datum/component/jousting + ///The current person holding parent. + var/mob/living/current_holder + ///The current direction of the jousting. + var/current_direction = NONE + ///How many tiles we've charged up thus far + var/current_tile_charge = 0 + + ///How much of an increase in damage is achieved every tile moved during jousting. + var/damage_boost_per_tile + ///The boosted chances of a knockdown occuring while jousting. + var/knockdown_chance_per_tile + ///How much of an increase in knockdown is achieved every tile moved during jousting, if it knocks down. + var/knockdown_time + ///The max amount of tiles before you can joust someone. + var/max_tile_charge + ///The min amount of tiles before you can joust someone. + var/min_tile_charge + +/datum/component/jousting/Initialize( + damage_boost_per_tile = 2, + knockdown_chance_per_tile = 20, + knockdown_time = 2 SECONDS, + max_tile_charge = 5, + min_tile_charge = 2, +) if(!isitem(parent)) return COMPONENT_INCOMPATIBLE + src.damage_boost_per_tile = damage_boost_per_tile + src.knockdown_chance_per_tile = knockdown_chance_per_tile + src.knockdown_time = knockdown_time + src.max_tile_charge = max_tile_charge + src.min_tile_charge = min_tile_charge + + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(on_attack)) + RegisterSignal(parent, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) +/datum/component/jousting/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list( + COMSIG_ATOM_EXAMINE, + COMSIG_ITEM_EQUIPPED, + COMSIG_ITEM_DROPPED, + COMSIG_ITEM_ATTACK, + COMSIG_TRANSFORMING_ON_TRANSFORM, + )) + +/datum/component/jousting/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("It can be used on a vehicle for jousting, dealing potential knockdowns and additional damage.") + +/datum/component/jousting/proc/on_transform(obj/item/source, mob/user, active) + SIGNAL_HANDLER + if(!user) + return + + if(active) + INVOKE_ASYNC(src, PROC_REF(on_equip), user) + else + INVOKE_ASYNC(src, PROC_REF(on_drop), user) + +///Called when a mob equips the spear, registers them as the holder and checks their signals for moving. /datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) SIGNAL_HANDLER @@ -31,52 +81,65 @@ /datum/component/jousting/proc/on_drop(datum/source, mob/user) SIGNAL_HANDLER + reset_charge() UnregisterSignal(user, COMSIG_MOVABLE_MOVED) current_holder = null - current_direction = NONE - current_tile_charge = 0 +/** + * Performs the actual attack, handling damage/knockdown depending on how far you've jousted. + * We deduct the minimum tile charge from the current tile charge to get what will actually be buffed + * So your charge will only get benefits from each extra tile after the minimum (and before the maximum). + */ /datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) SIGNAL_HANDLER + if(user != current_holder || !user.buckled) + return + var/usable_charge = (current_tile_charge - min_tile_charge) + if(!current_direction || (usable_charge <= 0)) + return - if(user != current_holder) - return - var/current = current_tile_charge - var/obj/item/I = parent - var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. - if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) - return var/turf/target_turf = get_step(user, current_direction) if(target in range(1, target_turf)) - var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current - var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) - var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current - var/sharp = I.get_sharpness() - var/msg - if(damage) - msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" - target.apply_damage(damage, BRUTE, user.zone_selected, 0) - if(prob(knockdown_chance)) - msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" - if(target_buckled) + var/obj/item/parent_item = parent + var/sharp = parent_item.get_sharpness() + var/msg = "[user] [sharp ? "impales" : "slams into"] [target] [sharp ? "on" : "with"] their [parent]" + target.apply_damage((damage_boost_per_tile * usable_charge), BRUTE, user.zone_selected, 0) + if(prob(knockdown_chance_per_tile * usable_charge)) + msg += " and knocks [target] [target.buckled ? "off of [target.buckled]" : "down"]" + if(target.buckled) target.buckled.unbuckle_mob(target) target.Paralyze(knockdown_time) - if(length(msg)) - user.visible_message(span_danger("[msg]!")) + user.visible_message(span_danger("[msg]!")) +/** + * Called when a mob moves. + * Handles checking their direction, changing it if they turned, + * and increments how many tiles they've been charging for. + * Lastly, refreshes their charge reset timer, giving them a new one instead. + */ /datum/component/jousting/proc/mob_move(datum/source, newloc, dir) SIGNAL_HANDLER - if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) + if(!current_holder) + CRASH("[src] called mob_move despite supposedly not having a mob registed to joust as.") + if(!current_holder.buckled) return + if(dir != current_direction) - current_tile_charge = 0 + current_tile_charge = initial(current_tile_charge) current_direction = dir if(current_tile_charge < max_tile_charge) current_tile_charge++ - if(current_timerid) - deltimer(current_timerid) - current_timerid = addtimer(CALLBACK(src, PROC_REF(reset_charge)), movement_reset_tolerance, TIMER_STOPPABLE) + addtimer(CALLBACK(src, PROC_REF(reset_charge)), MOVEMENT_RESET_COOLDOWN_TIME, TIMER_UNIQUE | TIMER_OVERRIDE) +/** + * reset charge + * + * Resets their direction and tile charge back to their initial values. + * This is used when someone is no longer jousting and it should cleanup. + */ /datum/component/jousting/proc/reset_charge() - current_tile_charge = 0 + current_direction = initial(current_direction) + current_tile_charge = initial(current_tile_charge) + +#undef MOVEMENT_RESET_COOLDOWN_TIME diff --git a/code/game/objects/items/broom.dm b/code/game/objects/items/broom.dm index ec08eec60d3..724328cb0b3 100644 --- a/code/game/objects/items/broom.dm +++ b/code/game/objects/items/broom.dm @@ -20,7 +20,14 @@ /obj/item/pushbroom/Initialize(mapload) . = ..() - AddComponent(/datum/component/two_handed, force_unwielded=8, force_wielded=12, icon_wielded="[base_icon_state]1", wield_callback = CALLBACK(src, PROC_REF(on_wield)), unwield_callback = CALLBACK(src, PROC_REF(on_unwield))) + AddComponent(/datum/component/jousting, damage_boost_per_tile = 1) + AddComponent(/datum/component/two_handed, \ + force_unwielded = 8, \ + force_wielded = 12, \ + icon_wielded = "[base_icon_state]1", \ + wield_callback = CALLBACK(src, PROC_REF(on_wield)), \ + unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \ + ) /obj/item/pushbroom/update_icon_state() icon_state = "[base_icon_state]0" diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index b87885f3cfa..b8bb4d194d7 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -250,6 +250,7 @@ /obj/item/melee/energy/sword/saber/Initialize(mapload) . = ..() + AddComponent(/datum/component/jousting, damage_boost_per_tile = 1, knockdown_chance_per_tile = 10) if(!sword_color_icon && LAZYLEN(possible_sword_colors)) sword_color_icon = pick(possible_sword_colors) diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 11575b48a14..a5278f4b5c4 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -86,11 +86,12 @@ /obj/item/melee/sabre/Initialize(mapload) . = ..() + AddComponent(/datum/component/jousting) //fast and effective, but as a sword, it might damage the results. AddComponent(/datum/component/butchering, \ - speed = 3 SECONDS, \ - effectiveness = 95, \ - bonus_modifier = 5, \ + speed = 3 SECONDS, \ + effectiveness = 95, \ + bonus_modifier = 5, \ ) // The weight of authority comes down on the tider's crimes. AddElement(/datum/element/bane, target_type = /mob/living/carbon/human, damage_multiplier = 0.35) diff --git a/code/game/objects/items/pitchfork.dm b/code/game/objects/items/pitchfork.dm index 7538c1123bd..63c4db34b39 100644 --- a/code/game/objects/items/pitchfork.dm +++ b/code/game/objects/items/pitchfork.dm @@ -29,6 +29,7 @@ /obj/item/pitchfork/Initialize(mapload) . = ..() + AddComponent(/datum/component/jousting) AddComponent(/datum/component/two_handed, force_unwielded=7, force_wielded=15, icon_wielded="[base_icon_state]1") /obj/item/pitchfork/update_icon_state() diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index f6d628ad4f9..319537f2478 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -40,7 +40,10 @@ . = ..() force = force_unwielded //decent in a pinch, but pretty bad. - AddComponent(/datum/component/jousting) + AddComponent(/datum/component/jousting, \ + max_tile_charge = 9, \ + min_tile_charge = 6, \ + ) AddComponent(/datum/component/butchering, \ speed = 10 SECONDS, \