Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions modular_doppler/swing_combat/code/helpers.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Returns a list of turfs, from left to right (by default), that are in the direction from the base atom
*
* For example, a the dir of north would give you the tile to the top left, top, and top right from the base
* And a dir of northwest would give you the tile directly left, top left, and directly top
*
* * base - The atom to get the turfs from
* * general_dir - The direction to get the turfs in
* * reversed - Whether or not to reverse the order of the turfs, from left to right to right to left instead
*/
/proc/get_turfs_and_adjacent_in_direction(atom/base, general_dir, reversed = FALSE)
var/list/result_list = list()
var/turf/left_turf = get_step(base, turn(general_dir, -45))
var/turf/middle_turf = get_step(base, general_dir)
var/turf/right_turf = get_step(base, turn(general_dir, 45))
if(istype(left_turf))
result_list += left_turf
if(istype(middle_turf))
result_list += middle_turf
if(istype(right_turf))
result_list += right_turf
if(reversed)
reverse_range(result_list)
return result_list

/proc/get_turfs_in_straight_line_toward(atom/base, atom/target, attack_range = 2)
var/list/result_list = list()
var/reach_iterator = 1
var/turf/last_turf
while(reach_iterator <= attack_range)
if(!last_turf)
last_turf = get_step_towards(base, target)
else
last_turf = get_step_towards(last_turf, target)
result_list += last_turf
reach_iterator++
return result_list

/// Gets a dir towards a target, so that 90% of attacks aren't diagonal
/proc/get_vague_dir(atom/source, atom/target)
return angle2dir(get_angle(source, target))

/// Animate attack but specifically for swing combat, lets you choose which direction a swing arc takes rather than random
/obj/item/proc/animate_attack_swing_combat(atom/movable/attacker, atom/attacked_atom, animation_type, swing_reversed)
var/list/image_override = list()
var/list/animation_override = list()
var/used_icon_angle = icon_angle
var/list/angle_override = list()
SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_ANIMATION, attacker, attacked_atom, animation_type, image_override, animation_override, angle_override)
var/image/attack_image = null
if (!length(image_override))
attack_image = isnull(attack_icon) ? image(icon = src) : image(icon = attack_icon, icon_state = attack_icon_state)
else
attack_image = image_override[1]

if (length(animation_override))
animation_type = animation_override[1]
else if (!animation_type)
switch (get_sharpness())
if (SHARP_EDGED)
animation_type = ATTACK_ANIMATION_SLASH
if (SHARP_POINTY)
animation_type = ATTACK_ANIMATION_PIERCE
else
animation_type = ATTACK_ANIMATION_BLUNT

if (length(angle_override))
used_icon_angle = angle_override[1]

attack_image.plane = attacked_atom.plane + 1
attack_image.pixel_w = attacker.base_pixel_x + attacker.base_pixel_w - attacked_atom.base_pixel_x - attacked_atom.base_pixel_w
attack_image.pixel_z = attacker.base_pixel_y + attacker.base_pixel_z - attacked_atom.base_pixel_y - attacked_atom.base_pixel_z
// Scale the icon.
attack_image.transform *= 0.5
// The icon should not rotate.
attack_image.appearance_flags = APPEARANCE_UI

var/atom/movable/flick_visual/attack = attacked_atom.flick_overlay_view(attack_image, 1 SECONDS)
var/matrix/copy_transform = new(attacker.transform)
var/x_sign = 0
var/y_sign = 0
var/direction = get_dir(attacker, attacked_atom)
if (direction & NORTH)
y_sign = -1
else if (direction & SOUTH)
y_sign = 1

if (direction & EAST)
x_sign = -1
else if (direction & WEST)
x_sign = 1

// Attacking self, or something on the same turf as us
if (!direction)
y_sign = 1
// Not a fan of this, but its the "cleanest" way to animate this
x_sign = 0.25 * (prob(50) ? 1 : -1)
// For piercing attacks
direction = SOUTH

// And animate the attack!
switch (animation_type)
if (ATTACK_ANIMATION_BLUNT)
attack.pixel_x = 14 * x_sign
attack.pixel_y = 12 * y_sign
animate(attack, alpha = 175, transform = copy_transform.Scale(0.75), pixel_x = 4 * x_sign, pixel_y = 3 * y_sign, time = 0.2 SECONDS)
animate(time = 0.1 SECONDS)
animate(alpha = 0, time = 0.1 SECONDS, easing = CIRCULAR_EASING|EASE_OUT)

if (ATTACK_ANIMATION_PIERCE)
var/attack_angle = dir2angle(direction) + rand(-7, 7)
// Deducting 90 because we're assuming that icon_angle of 0 means an east-facing sprite
var/anim_angle = attack_angle - 90 - used_icon_angle
var/angle_mult = 1
if (x_sign && y_sign)
angle_mult = 1.4
attack.pixel_x = 22 * x_sign * angle_mult
attack.pixel_y = 18 * y_sign * angle_mult
attack.transform = attack.transform.Turn(anim_angle)
copy_transform = copy_transform.Turn(anim_angle)
animate(
attack,
pixel_x = (22 * x_sign - 12 * sin(attack_angle)) * angle_mult,
pixel_y = (18 * y_sign - 8 * cos(attack_angle)) * angle_mult,
time = 0.1 SECONDS,
easing = CUBIC_EASING|EASE_IN,
)
animate(
attack,
alpha = 175,
transform = copy_transform.Scale(0.75),
pixel_x = (22 * x_sign + 26 * sin(attack_angle)) * angle_mult,
pixel_y = (18 * y_sign + 22 * cos(attack_angle)) * angle_mult,
time = 0.3 SECONDS,
easing = CUBIC_EASING|EASE_OUT,
)
animate(
alpha = 0,
pixel_x = -3 * -(x_sign + sin(attack_angle)),
pixel_y = -2 * -(y_sign + cos(attack_angle)),
time = 0.1 SECONDS,
easing = CIRCULAR_EASING|EASE_OUT
)

if (ATTACK_ANIMATION_SLASH)
attack.pixel_x = 18 * x_sign
attack.pixel_y = 14 * y_sign
var/x_rot_sign = 0
var/y_rot_sign = 0
var/attack_dir = (swing_reversed ? -1 : 1)
var/anim_angle = dir2angle(direction) - 90 - used_icon_angle

if (x_sign)
y_rot_sign = attack_dir
if (y_sign)
x_rot_sign = attack_dir

// Animations are flipped, so flip us too!
if (x_sign > 0 || y_sign < 0)
attack_dir *= -1

// We're swinging diagonally, use separate logic
var/anim_dir = attack_dir
if (x_sign && y_sign)
if (attack_dir < 0)
x_rot_sign = -x_sign * 1.4
y_rot_sign = 0
else
x_rot_sign = 0
y_rot_sign = -y_sign * 1.4

// Flip us if we've been flipped *unless* we're flipped due to both axis
if ((x_sign < 0 && y_sign > 0) || (x_sign > 0 && y_sign < 0))
anim_dir *= -1

attack.pixel_x += 10 * x_rot_sign
attack.pixel_y += 8 * y_rot_sign
attack.transform = attack.transform.Turn(anim_angle - 45 * anim_dir)
copy_transform = copy_transform.Scale(0.75)
animate(attack, alpha = 175, time = 0.3 SECONDS, flags = ANIMATION_PARALLEL)
animate(time = 0.1 SECONDS)
animate(alpha = 0, time = 0.1 SECONDS, easing = CIRCULAR_EASING|EASE_OUT)

animate(attack, transform = copy_transform.Turn(anim_angle + 45 * anim_dir), time = 0.3 SECONDS, flags = ANIMATION_PARALLEL)

var/x_return = 10 * -x_rot_sign
var/y_return = 8 * -y_rot_sign

if (!x_rot_sign)
x_return = 18 * x_sign
if (!y_rot_sign)
y_return = 14 * y_sign

var/angle_mult = 1
if (x_sign && y_sign)
angle_mult = 1.4
if (attack_dir > 0)
x_return = 8 * x_sign
y_return = 14 * y_sign
else
x_return = 18 * x_sign
y_return = 6 * y_sign

animate(attack, pixel_x = 4 * x_sign * angle_mult, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_IN, flags = ANIMATION_PARALLEL)
animate(pixel_x = x_return, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_OUT)

animate(attack, pixel_y = 3 * y_sign * angle_mult, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_IN, flags = ANIMATION_PARALLEL)
animate(pixel_y = y_return, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_OUT)
121 changes: 121 additions & 0 deletions modular_doppler/swing_combat/code/melee.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/obj/item/melee
/// The attack speed nextmove if doing a swing type attack
var/swing_attack_speed
/// The attack speed nextmove if doing a swing attack secondary
var/secondary_swing_attack_speed
/// If the weapon can hit multiple living targets in swing attacks
var/swing_multihits = FALSE

/obj/item/melee/Initialize(mapload)
. = ..()
if(!swing_attack_speed)
swing_attack_speed = attack_speed
if(!secondary_swing_attack_speed)
if(secondary_attack_speed)
secondary_swing_attack_speed = secondary_attack_speed
else
secondary_swing_attack_speed = swing_attack_speed

/// To be overwritten by subtypes, determines the animation for each attack
/obj/item/melee/proc/get_attack_anim_type(secondary)
return ATTACK_ANIMATION_SLASH

/// Checks if a swing attack is valid before running the giant proc below, also handles attack cooldowns
/obj/item/melee/proc/start_swing_attack(atom/target, mob/living/attacker, backwards, secondary)
if(!attacker.combat_mode)
return ITEM_INTERACT_SUCCESS
if(attacker.next_move > world.time)
return ITEM_INTERACT_SUCCESS
var/attack_dir = get_vague_dir(attacker, target)
var/attack_type = get_attack_anim_type(secondary)
run_swing_attack(attack_dir, attacker, backwards, swing_multihits, secondary, attack_type, target)
attacker.changeNext_move(secondary ? secondary_swing_attack_speed : swing_attack_speed)
return ITEM_INTERACT_SUCCESS

/// Handles how the weapon gets it's target turfs when swinging primary
/obj/item/melee/proc/get_targets(mob/living/attacker, direction, backwards, atom/target)
return // Overwrite with whatever the tile getting proc should be

/// Handles how the weapon gets it's target turfs when swinging secondary
/obj/item/melee/proc/get_targets_secondary(mob/living/attacker, direction, backwards, atom/target)
return get_targets(attacker, direction, backwards, target)

/// Handles swing attack targeting, includes handling for hitting walls and whatnot
/obj/item/melee/proc/run_swing_attack(direction, mob/living/attacker, backwards, multihit, secondary, attack_type, atom/target)
var/list/target_turfs = list()
if(secondary)
target_turfs = get_targets_secondary(attacker, direction, backwards, target)
else
target_turfs = get_targets(attacker, direction, backwards, target)
var/turf_index = 1
var/halfway_point
var/turf_list_length = length(target_turfs)
if(turf_list_length <= 1)
halfway_point = 1 // Futureproofing for one tile swings
else if(turf_list_length == 2)
halfway_point = 2
else
halfway_point = round(turf_list_length / 2)
for(var/turf/target_turf in target_turfs)
// The animation is only played if we don't hit anything by half way through the swing
if(turf_index == halfway_point)
animate_attack_swing_combat(attacker, get_step(attacker, direction), attack_type, backwards)
attacker.do_attack_animation(target_turf, no_effect = TRUE)
playsound(attacker, 'sound/items/weapons/fwoosh.ogg', 50, TRUE)
turf_index++
if(target_turf.is_blocked_turf(exclude_mobs = TRUE))
if(target_turf.density)
animate_attack_swing_combat(attacker, target_turf, ATTACK_ANIMATION_PIERCE)
attacker.Shake(1, 1, 0.5 SECONDS)
do_sparks(2, FALSE, target_turf)
playsound(attacker, 'sound/items/weapons/parry.ogg', 50, TRUE)
return
// This part stops grilles getting hit under windows and stuff
var/list/real_order_turf_contents = target_turf.contents
reverse_range(real_order_turf_contents)
for(var/atom/movable/potentially_blocking_thing as anything in real_order_turf_contents)
if(ismob(potentially_blocking_thing))
continue
if(!potentially_blocking_thing.density)
continue
melee_attack_chain(attacker, potentially_blocking_thing)
return // If we hit something solid that's not a mob then we stop
for(var/mob/living/new_victim in target_turf.contents)
if((new_victim.body_position == LYING_DOWN) && (HAS_TRAIT(new_victim, TRAIT_INCAPACITATED)))
continue // Swings miss you if you're incapacitated and floored
melee_attack_chain(attacker, new_victim)
if(!multihit)
return

// For testing
/obj/item/melee/tizirian_sword/get_attack_anim_type(secondary)
return ATTACK_ANIMATION_SLASH

/obj/item/melee/tizirian_sword/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return start_swing_attack(interacting_with, user)

/obj/item/melee/tizirian_sword/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
return start_swing_attack(interacting_with, user, TRUE, TRUE)

/obj/item/melee/tizirian_sword/get_targets(mob/living/attacker, direction, backwards)
return get_turfs_and_adjacent_in_direction(attacker, direction, backwards)

// image(icon = 'icons/effects/effects.dmi', icon_state = "slash")

// Acts like a spear
/obj/item/melee/tizirian_sword/acts_like_spear
name = "spear swing combat tester"

/obj/item/melee/tizirian_sword/acts_like_spear/get_attack_anim_type(secondary)
return ATTACK_ANIMATION_PIERCE

/obj/item/melee/tizirian_sword/acts_like_spear/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return start_swing_attack(interacting_with, user)

/obj/item/melee/tizirian_sword/acts_like_spear/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
return start_swing_attack(interacting_with, user)

/obj/item/melee/tizirian_sword/acts_like_spear/get_targets(mob/living/attacker, direction, backwards, target)
return get_turfs_in_straight_line_toward(attacker, target, 2)

// image(icon = 'icons/effects/effects.dmi', icon_state = "shove")
2 changes: 2 additions & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -7914,6 +7914,8 @@
#include "modular_doppler\suuuper_trustworthy_item_orders\code\categories\order_hardware.dm"
#include "modular_doppler\suuuper_trustworthy_item_orders\code\categories\order_software.dm"
#include "modular_doppler\suuuper_trustworthy_item_orders\code\categories\order_tooling.dm"
#include "modular_doppler\swing_combat\code\helpers.dm"
#include "modular_doppler\swing_combat\code\melee.dm"
#include "modular_doppler\taurs\code\bodyparts.dm"
#include "modular_doppler\taurs\code\clothing.dm"
#include "modular_doppler\taurs\code\taur_body.dm"
Expand Down
Loading