Files
Bubberstation/code/datums/components/scope.dm
EnterTheJake 0d0270b3dc Blade Heretic/Side knowledges improvements/fixes. (#87167)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Blade Heretic has received a few changes.

The cost of crafting a Dark blade has been reduced in exchange for a
lower blade capacity, The Dark blade itself has received a new sprite.

![Updated Dark
Blade](https://github.com/user-attachments/assets/f5dde8a3-6827-4d4f-a530-0a1a01965a63)

Realignment pulls you out stuns a bit faster and grants baton resistance
while active.

You may now infuse your blades with a (weaker) mansus grasp upon
unlocking the ability to dual wield, they also gain increased demolition
modifier.

Mawed Crucible now slowly refills and requires fewer organs to brew a
potion; you may now use a charge to refill your eldritch flask.

The potion themselves have also received changes more on that below.

The cooldown on the cursed curio shield has been reduced.

Lionhunter's rifle no longer does increased damage on scoped targets,
instead it marks them with Mansus grasp and teleports the heretic to
them.

Lastly Blade ascension has been fixed, you once again get the Ring of
Blades.

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

Oh boy, here we go.

# **Blade Heretic changes**

Blade Heretic sits in a pretty decent spot, I wouldn't call the path
weak by any stretch of imagination, but there are few aspects that could
be reasonably improved without changing the overall strength of the path
significantly.

**Sundered Blade**
I think these are too expensive to craft, especially compared to the
other blades which require very basic materials.

It's not uncommon to run into situations where you just cannot afford to
make more than a set of blades, and i'd argue it's not fun for the crew
to have their titanium or silver deposit drained every time a blade
heretic rolls around.

As a solution, i'm halving the cost in exchange of lowering the cap from
5 to 4 blades.

**Realingment**

This spell is lowkey awful; 25 stamina regen per second really doesn't
make much of a difference when you are getting chain batoned, I have
footage of blade heretics dying to a single shove stun while this abilty
was active.

The stamina regen and reduce immobility timer has been buffed on top of
granting baton resist so long as it stays active, so you can properly
get in fighting position without getting constantly knocked down.

Mind you, It's still no CNS rebooter, so stuns will still yield a few
seconds of vulnerability.

**Swift Blades reworked into Empowered Blades**

You may now use your Mansus grasp to infuse your Dark blades.

It comes with the tradeoff of losing the knockdown and the stamina
damage, you still retain the backstab.

Video Demonstration: https://www.youtube.com/watch?v=9cO9BOD8Zz4

Dark Blades also gain increased demolition modifier.

Dual wielding puts the heretic in the annoying position of having to
switch between the second Blade and an empty hand to use Mansus grasp.

Blade is supposed to be a master of melee combat, but they are still a
dark mage, so why shouldn't they be able to infuse their blades?

It still comes with a tradeoff, I'd reckon super sweaty players will
still want to hotswap, but hey, the option is there.

The added demolition modifier is to provide Blade with some way of
breaking in and out of places, given the path has no jaunts or utility
whatsoever, this seems reasonable to me.

Lastly Malestrom of Silver finally works now; you once again get the
blade aura upon ascending.


# **Side Knowledge changes**

**Mawed Crucible**

The crucible now passively refills, and has a special interaction to
refill the Eldritch Flask, the potion themselves have received changes.

- Xray Potion: duration bumped from 60 to 90 seconds.

- Wall phasing potion: Duration bumped from 15 to 40 seconds, you may
now recall to your original location at will.

- Potion of the Wounded soldier: Upon expiring, it heals your wounds and
regrows missing limbs.

**Reasoning**: Let's be honest here, noone ever makes this thing, the
cost of making 1 potion is exorbitant and the potion themselves are not
even that good to begin with.

I'm not gonna explain every change in detail, but considering the
crucible is one of the OG side knoweldges and you hardly hear anyone
talk about it, we can safely give it a few buffs.

**Unfathomable Curio**

Cooldown on the shield has been halved.

**Reasoning**: discussed it with Rex (the guy who created it), 60
seconds for 1 block is a bit excessive , 30 seconds seems reasonable
enough.

**Lionhunter's Rifle**

Made a bit easier to craft and maintain, it can now be stored in the
vest slot of the Eldritch Robes.

The homing projectile now fully penetrates armor instead of having bonus
damage; it also marks the victim with Mansus grasp and teleports the
Heretic directly to them, the homing on the projectile itself has been
improved.

**Reasoning**: another side knowledge that sadly barely sees any play.

Frankly this gun just doesn't have a purpose to exist, long range
weaponry don't really mix with Heretic toolkit all that well, as you
want to get close to your target to drag em to the spook dimension, not
snipe 'em from a distance

Lionhunter now works as an initiation tool, upon marking the target, the
Heretic transforms into the fired bullet until it connects, applying
mansus grasp on the victim.

Keep in mind you still need xray or thermals to use the rifle to its
full potential, either from the Crucible or the ashen medallion.

Video Demonstration: https://www.youtube.com/watch?v=AXmidKrx-Fg

As a trade off, the damage has been halved from 60 to 30.

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and its effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
balance: Sundered Blades now require 1 Titanium or Silver bar to craft
and their capacity has been reduced to 4.
balance: Realignment pulls you out of stuns a bit faster and grants
baton resist while active.
balance: Blade Heretic dual wielding now let's you infuse Your Dark
Blades with a weaker mansus grasp and grants an increase in demolition
modifier.
fix: Malestrom of Silver grants the ring of protective blades once
again.
balance: Mawed Crucible requires 3 organs to brew one potion, passively
refills overtime and can be used to refill the Eldritch Flask
balance: Brew of Crucible soul effect bumped to 40 seconds and can be
ended early.
balance: Brew Of Dusk and Dawn effect bumped to 3 minutes.
balance: Brew of the wounded soldier now offers a very minor passive
heal and fully heals your wounds and limbs upon expiring.
balance: Cursed Curio shield now recharges faster.
balance: Lionhunter's rifle has been reworked, it now fits on the
eldritch robes vest slots, it's cheaper to craft it and its ammunition
and works as an initiation tool.
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

---------

Co-authored-by: Xander3359 <66163761+Xander3359@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-10-16 22:02:08 +02:00

274 lines
9.5 KiB
Plaintext

///Used to allow reaching the maximum offset range without exiting the boundaries of the game screen.
#define MOUSE_POINTER_OFFSET_MULT 1.1
///A component that allows players to use the item to zoom out. Mainly intended for firearms, but now works with other items too.
/datum/component/scope
/// How far we can extend, with modifier of 1, up to our vision edge, higher numbers multiply.
var/range_modifier = 1
/// Fullscreen object we use for tracking.
var/atom/movable/screen/fullscreen/cursor_catcher/scope/tracker
/// The owner of the tracker's ckey. For comparing with the current owner mob, in case the client has left it (e.g. ghosted).
var/tracker_owner_ckey
/// The method which we zoom in and out
var/zoom_method = ZOOM_METHOD_RIGHT_CLICK
/// if not null, an item action will be added. Redundant if the mode is ZOOM_METHOD_RIGHT_CLICK or ZOOM_METHOD_WIELD.
var/item_action_type
/datum/component/scope/Initialize(range_modifier = 1, zoom_method = ZOOM_METHOD_RIGHT_CLICK, item_action_type)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
src.range_modifier = range_modifier
src.zoom_method = zoom_method
src.item_action_type = item_action_type
/datum/component/scope/Destroy(force)
if(tracker)
stop_zooming(tracker.owner)
return ..()
/datum/component/scope/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
switch(zoom_method)
if(ZOOM_METHOD_RIGHT_CLICK)
RegisterSignal(parent, COMSIG_RANGED_ITEM_INTERACTING_WITH_ATOM_SECONDARY, PROC_REF(do_secondary_zoom))
if(ZOOM_METHOD_WIELD)
RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_WIELDED), PROC_REF(on_wielded))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_WIELDED), PROC_REF(on_unwielded))
if(item_action_type)
var/obj/item/parent_item = parent
var/datum/action/item_action/scope = parent_item.add_item_action(item_action_type)
RegisterSignal(scope, COMSIG_ACTION_TRIGGER, PROC_REF(on_action_trigger))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
if(isgun(parent))
RegisterSignal(parent, COMSIG_GUN_TRY_FIRE, PROC_REF(on_gun_fire))
/datum/component/scope/UnregisterFromParent()
if(item_action_type)
var/obj/item/parent_item = parent
var/datum/action/item_action/scope = locate(item_action_type) in parent_item.actions
parent_item.remove_item_action(scope)
UnregisterSignal(parent, list(
COMSIG_MOVABLE_MOVED,
COMSIG_RANGED_ITEM_INTERACTING_WITH_ATOM_SECONDARY,
SIGNAL_ADDTRAIT(TRAIT_WIELDED),
SIGNAL_REMOVETRAIT(TRAIT_WIELDED),
COMSIG_GUN_TRY_FIRE,
COMSIG_ATOM_EXAMINE,
))
/datum/component/scope/process(seconds_per_tick)
var/mob/user_mob = tracker.owner
var/client/user_client = user_mob.client
if(!user_client)
stop_zooming(user_mob)
return
tracker.calculate_params()
if(!user_client.intended_direction)
user_mob.face_atom(tracker.given_turf)
animate(user_client, world.tick_lag, pixel_x = tracker.given_x, pixel_y = tracker.given_y)
/datum/component/scope/proc/on_move(atom/movable/source, atom/oldloc, dir, forced)
SIGNAL_HANDLER
if(!tracker)
return
stop_zooming(tracker.owner)
/datum/component/scope/proc/do_secondary_zoom(datum/source, mob/user, atom/target, click_parameters)
SIGNAL_HANDLER
if(tracker)
stop_zooming(user)
else
zoom(user)
return ITEM_INTERACT_BLOCKING
/datum/component/scope/proc/on_action_trigger(datum/action/source)
SIGNAL_HANDLER
var/obj/item/item = source.target
var/mob/living/user = item.loc
if(tracker)
stop_zooming(user)
else
zoom(user)
/datum/component/scope/proc/on_wielded(obj/item/source, trait)
SIGNAL_HANDLER
var/mob/living/user = source.loc
zoom(user)
/datum/component/scope/proc/on_unwielded(obj/item/source, trait)
SIGNAL_HANDLER
var/mob/living/user = source.loc
stop_zooming(user)
/datum/component/scope/proc/on_gun_fire(obj/item/gun/source, mob/living/user, atom/target, flag, params)
SIGNAL_HANDLER
if(!tracker?.given_turf || target == get_target(tracker.given_turf))
return NONE
INVOKE_ASYNC(source, TYPE_PROC_REF(/obj/item/gun, fire_gun), get_target(tracker.given_turf), user)
return COMPONENT_CANCEL_GUN_FIRE
/datum/component/scope/proc/on_examine(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
var/scope = isgun(parent) ? "scope in" : "zoom out"
switch(zoom_method)
if(ZOOM_METHOD_RIGHT_CLICK)
examine_list += span_notice("You can [scope] with <b>right-click</b>.")
if(ZOOM_METHOD_WIELD)
examine_list += span_notice("You can [scope] by wielding it with both hands.")
/**
* We find and return the best target to hit on a given turf.
*
* Arguments:
* * target_turf: The turf we are looking for targets on.
*/
/datum/component/scope/proc/get_target(turf/target_turf)
var/list/object_targets = list()
var/list/non_dense_targets = list()
for(var/atom/movable/possible_target in target_turf)
if(possible_target.layer <= PROJECTILE_HIT_THRESHHOLD_LAYER)
continue
if(possible_target.invisibility > tracker.owner.see_invisible)
continue
if(!possible_target.mouse_opacity)
continue
if(iseffect(possible_target))
continue
if(ismob(possible_target))
if(possible_target == tracker.owner)
continue
return possible_target
if(!possible_target.density)
non_dense_targets += possible_target
continue
object_targets += possible_target
for(var/obj/important_object as anything in object_targets)
return important_object
for(var/obj/unimportant_object as anything in non_dense_targets)
return unimportant_object
return target_turf
/**
* We start zooming by adding our tracker overlay and starting our processing.
*
* Arguments:
* * user: The mob we are starting zooming on.
*/
/datum/component/scope/proc/zoom(mob/user)
if(isnull(user.client))
return
if(HAS_TRAIT(user, TRAIT_USER_SCOPED))
user.balloon_alert(user, "already zoomed!")
return
user.playsound_local(parent, 'sound/items/weapons/scope.ogg', 75, TRUE)
tracker = user.overlay_fullscreen("scope", /atom/movable/screen/fullscreen/cursor_catcher/scope, isgun(parent))
tracker.assign_to_mob(user, range_modifier)
tracker_owner_ckey = user.ckey
if(user.is_holding(parent))
RegisterSignals(user, list(COMSIG_MOB_SWAP_HANDS, COMSIG_QDELETING), PROC_REF(stop_zooming))
RegisterSignal(user, COMSIG_ATOM_ENTERING, PROC_REF(on_enter_new_loc))
else // The item is likely worn (eg. mothic cap)
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(stop_zooming))
RegisterSignal(user, COMSIG_ATOM_ENTERING, PROC_REF(on_enter_new_loc))
var/static/list/capacity_signals = list(
COMSIG_LIVING_STATUS_KNOCKDOWN,
COMSIG_LIVING_STATUS_PARALYZE,
COMSIG_LIVING_STATUS_STUN,
)
RegisterSignals(user, capacity_signals, PROC_REF(on_incapacitated))
START_PROCESSING(SSprojectiles, src)
ADD_TRAIT(user, TRAIT_USER_SCOPED, REF(src))
return TRUE
///Stop scoping if the `newloc` we move to is not a turf
/datum/component/scope/proc/on_enter_new_loc(datum/source, atom/newloc, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(!isturf(newloc))
stop_zooming(tracker.owner)
/datum/component/scope/proc/on_incapacitated(mob/living/source, amount = 0, ignore_canstun = FALSE)
SIGNAL_HANDLER
if(amount > 0)
stop_zooming(source)
/**
* We stop zooming, canceling processing, resetting stuff back to normal and deleting our tracker.
*
* Arguments:
* * user: The mob we are canceling zooming on.
*/
/datum/component/scope/proc/stop_zooming(mob/user)
SIGNAL_HANDLER
if(!HAS_TRAIT(user, TRAIT_USER_SCOPED))
return
STOP_PROCESSING(SSprojectiles, src)
UnregisterSignal(user, list(
COMSIG_LIVING_STATUS_KNOCKDOWN,
COMSIG_LIVING_STATUS_PARALYZE,
COMSIG_LIVING_STATUS_STUN,
COMSIG_MOB_SWAP_HANDS,
COMSIG_QDELETING,
COMSIG_ATOM_ENTERING,
))
REMOVE_TRAIT(user, TRAIT_USER_SCOPED, REF(src))
user.playsound_local(parent, 'sound/items/weapons/scope.ogg', 75, TRUE, frequency = -1)
user.clear_fullscreen("scope")
// if the client has ended up in another mob, find that mob so we can fix their cursor
var/mob/true_user
if(user.ckey != tracker_owner_ckey)
true_user = get_mob_by_ckey(tracker_owner_ckey)
if(!isnull(true_user))
user = true_user
if(user.client)
animate(user.client, 0.2 SECONDS, pixel_x = 0, pixel_y = 0)
tracker = null
tracker_owner_ckey = null
/atom/movable/screen/fullscreen/cursor_catcher/scope
icon_state = "scope"
/// Multiplier for given_X an given_y.
var/range_modifier = 1
/atom/movable/screen/fullscreen/cursor_catcher/scope/assign_to_mob(mob/new_owner, range_modifier)
src.range_modifier = range_modifier
return ..()
/atom/movable/screen/fullscreen/cursor_catcher/scope/Click(location, control, params)
if(usr == owner)
calculate_params()
return ..()
/atom/movable/screen/fullscreen/cursor_catcher/scope/calculate_params()
var/list/modifiers = params2list(mouse_params)
var/icon_x = text2num(LAZYACCESS(modifiers, VIS_X))
if(isnull(icon_x))
icon_x = text2num(LAZYACCESS(modifiers, ICON_X))
if(isnull(icon_x))
icon_x = view_list[1]*ICON_SIZE_X/2
var/icon_y = text2num(LAZYACCESS(modifiers, VIS_Y))
if(isnull(icon_y))
icon_y = text2num(LAZYACCESS(modifiers, ICON_Y))
if(isnull(icon_y))
icon_y = view_list[2]*ICON_SIZE_Y/2
var/x_cap = range_modifier * view_list[1]*ICON_SIZE_X / 2
var/y_cap = range_modifier * view_list[2]*ICON_SIZE_Y / 2
var/uncapped_x = round(range_modifier * (icon_x - view_list[1]*ICON_SIZE_X/2) * MOUSE_POINTER_OFFSET_MULT)
var/uncapped_y = round(range_modifier * (icon_y - view_list[2]*ICON_SIZE_Y/2) * MOUSE_POINTER_OFFSET_MULT)
given_x = clamp(uncapped_x, -x_cap, x_cap)
given_y = clamp(uncapped_y, -y_cap, y_cap)
given_turf = locate(owner.x+round(given_x/ICON_SIZE_X, 1),owner.y+round(given_y/ICON_SIZE_Y, 1),owner.z)
#undef MOUSE_POINTER_OFFSET_MULT