diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index a3bfd5614792..273ceaeeded4 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -147,6 +147,11 @@
#define COMSIG_ITEM_HIT_REACT "item_hit_react"
#define COMPONENT_HIT_REACTION_BLOCK (1<<0)
+/// from /datum/component/cleave_attack/perform_sweep(): (atom/target, obj/item/item, mob/living/user, params)
+#define COMSIG_ATOM_CLEAVE_ATTACK "atom_cleave_attack"
+ // allows cleave attack to hit things it normally wouldn't
+ #define ATOM_ALLOW_CLEAVE_ATTACK (1<<0)
+
/// Called before an item is embedded (mob/living/carbon/target = carbon that it is getting embedded into)
#define COMSIG_ITEM_EMBEDDED "mob_carbon_embedded"
// Prevents the embed
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index bc2cea177ca6..65475f68432e 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -670,6 +670,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_APC_SHOCKING "apc_shocking"
/// Properly wielded two handed item
#define TRAIT_WIELDED "wielded"
+/// This item is currently performing a cleaving attack
+#define TRAIT_CLEAVING "cleaving"
/// A transforming item that is actively extended / transformed
#define TRAIT_TRANSFORM_ACTIVE "active_transform"
/// Buckling yourself to objects with this trait won't immobilize you
diff --git a/code/datums/components/cleave_attack.dm b/code/datums/components/cleave_attack.dm
new file mode 100644
index 000000000000..bd7911d30625
--- /dev/null
+++ b/code/datums/components/cleave_attack.dm
@@ -0,0 +1,146 @@
+/datum/component/cleave_attack
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// Size of the attack arc in degrees
+ var/arc_size
+ /// Make this TRUE for two-handed weapons like axes
+ var/requires_wielded
+ /// How much slower is it to swing
+ var/swing_speed_mod
+ /// Which effect should this use
+ var/cleave_effect
+ /// Whether this item is disallowed from hitting more than one target
+ var/no_multi_hit
+ /// Callback when the cleave attack is finished
+ var/datum/callback/cleave_end_callback
+
+/datum/component/cleave_attack/Initialize(
+ arc_size=90,
+ swing_speed_mod=1.25,
+ requires_wielded=FALSE,
+ no_multi_hit=FALSE,
+ datum/callback/cleave_end_callback,
+ cleave_effect,
+ ...
+ )
+
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.arc_size = arc_size
+ src.swing_speed_mod = swing_speed_mod
+ src.requires_wielded = requires_wielded
+ src.no_multi_hit = no_multi_hit
+ src.cleave_end_callback = cleave_end_callback
+ set_cleave_effect(cleave_effect) // set it based on arc size if an effect wasn't specified
+
+/datum/component/cleave_attack/InheritComponent(
+ datum/component/C,
+ i_am_original,
+ arc_size,
+ swing_speed_mod,
+ requires_wielded,
+ datum/callback/cleave_end_callback,
+ cleave_effect
+ )
+
+ if(!i_am_original)
+ return
+ if(arc_size)
+ src.arc_size = arc_size
+ if(swing_speed_mod)
+ src.swing_speed_mod = swing_speed_mod
+ if(requires_wielded)
+ src.requires_wielded = requires_wielded
+ if(no_multi_hit)
+ src.no_multi_hit = no_multi_hit
+ if(cleave_end_callback)
+ src.cleave_end_callback = cleave_end_callback
+ set_cleave_effect(cleave_effect)
+
+/// Sets the cleave effect to the specified effect, or based on arc size if one wasn't specified.
+/datum/component/cleave_attack/proc/set_cleave_effect(new_effect)
+ if(new_effect)
+ cleave_effect = new_effect
+ return
+ switch(arc_size)
+ if(0 to 120)
+ cleave_effect = /obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack
+ if(120 to 240)
+ cleave_effect = /obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack/semicircle
+ else
+ cleave_effect = /obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack/full_circle
+
+/datum/component/cleave_attack/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_afterattack))
+
+/datum/component/cleave_attack/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ITEM_AFTERATTACK))
+
+/datum/component/cleave_attack/proc/on_examine(atom/examined_item, mob/user, list/examine_list)
+ var/arc_desc
+ switch(arc_size)
+ if(0 to 90)
+ arc_desc = "narrow arc"
+ if(90 to 180)
+ arc_desc = "wide arc"
+ if(180 to 270)
+ arc_desc = "very wide arc"
+ if(270 to INFINITY)
+ arc_desc = "full circle"
+ examine_list += "It can swing in a [arc_desc]."
+
+/datum/component/cleave_attack/proc/on_afterattack(obj/item/item, atom/target, mob/user, proximity_flag, click_parameters)
+ if(proximity_flag || user.a_intent != INTENT_HARM)
+ return // don't sweep on precise hits or non-harmful intents
+ perform_sweep(item, target, user, click_parameters)
+
+/datum/component/cleave_attack/proc/perform_sweep(obj/item/item, atom/target, mob/living/user, params)
+ if(user.next_move > world.time)
+ return // don't spam it
+ if(requires_wielded && !HAS_TRAIT(item, TRAIT_WIELDED))
+ return // if it needs to be wielded, check to make sure it is
+
+ // some information we're going to need later
+ var/turf/user_turf = get_turf(user)
+ var/turf/center_turf = get_turf_in_angle(get_angle(user, target), user_turf)
+ var/facing_dir = get_dir(user, center_turf)
+ var/swing_direction = (user.active_hand_index % 2) ? -1 : 1
+
+ // make a list of turfs to swing across
+ var/list/turf_list = list()
+ var/turfs_count = round(arc_size / 90, 1)
+ for(var/i in -min(turfs_count, 3) to min(turfs_count, 4)) // do NOT hit the same tile more than once
+ turf_list.Add(get_step(user_turf, turn(facing_dir, i * 45 * swing_direction)))
+
+ // do some effects so everyone knows you're swinging a weapon
+ playsound(item, 'sound/weapons/punchmiss.ogg', 50, TRUE)
+ new cleave_effect(user_turf, facing_dir)
+
+ // now swing across those turfs
+ ADD_TRAIT(item, TRAIT_CLEAVING, REF(src))
+ for(var/turf/T as anything in turf_list)
+ if(hit_atoms_on_turf(item, target, user, T, params))
+ break
+ REMOVE_TRAIT(item, TRAIT_CLEAVING, REF(src))
+
+ // do these last so they don't get overridden during the attack loop
+ cleave_end_callback?.Invoke(item, user)
+ user.do_attack_animation(center_turf, no_effect=TRUE)
+ user.changeNext_move(CLICK_CD_MELEE * item.weapon_stats[SWING_SPEED] * swing_speed_mod)
+ user.weapon_slow(item)
+
+/// Hits all possible atoms on a turf, returns TRUE if the swing should end early
+/datum/component/cleave_attack/proc/hit_atoms_on_turf(obj/item/item, atom/target, mob/living/user, turf/hit_turf, params)
+ for(var/atom/movable/hit_atom in hit_turf)
+ if(hit_atom == user || hit_atom == target)
+ continue // why are you hitting yourself
+ if(!(SEND_SIGNAL(hit_atom, COMSIG_ATOM_CLEAVE_ATTACK, item, user) & ATOM_ALLOW_CLEAVE_ATTACK))
+ if(hit_atom.pass_flags & LETPASSTHROW)
+ continue // if you can throw something over it, you can swing over it too
+ if(!hit_atom.density && hit_atom.uses_integrity)
+ continue
+ item.melee_attack_chain(user, hit_atom, params)
+ if(no_multi_hit && isliving(hit_atom))
+ return TRUE
+ return FALSE
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index 4f2530061adb..a4b161fb1961 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -186,6 +186,7 @@
AddComponent(/datum/component/two_handed, \
force_wielded = 14, \
)
+ AddComponent(/datum/component/cleave_attack, arc_size=180, requires_wielded=TRUE)
/obj/item/melee/bostaff/update_icon_state()
. = ..()
diff --git a/code/game/mecha/equipment/weapons/melee_weapons.dm b/code/game/mecha/equipment/weapons/melee_weapons.dm
index dc85d046db5a..8af238c3c807 100644
--- a/code/game/mecha/equipment/weapons/melee_weapons.dm
+++ b/code/game/mecha/equipment/weapons/melee_weapons.dm
@@ -45,7 +45,7 @@
/// Effect on hitting something
var/hit_effect = ATTACK_EFFECT_SLASH
/// Effect of the cleave attack
- var/cleave_effect = /obj/effect/temp_visual/dir_setting/firing_effect/mecha_swipe
+ var/cleave_effect = /obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack
/obj/item/mecha_parts/mecha_equipment/melee_weapon/can_attach(obj/mecha/M)
if(!..())
@@ -210,8 +210,7 @@
O.visible_message(span_danger("[chassis.name] strikes [O] with a wide swing of [src]!")) //Don't really need to make a message for EVERY object, just important ones
playsound(O,'sound/weapons/smash.ogg', 50) //metallic bonk noise
- var/turf/cleave_effect_loc = get_step(get_turf(src), SOUTHWEST)
- new cleave_effect(cleave_effect_loc, chassis.dir)
+ new cleave_effect(get_turf(src), chassis.dir)
/obj/item/mecha_parts/mecha_equipment/melee_weapon/sword/precise_attack(atom/target)
special_hit(target)
@@ -649,8 +648,7 @@
playsound(chassis, attack_sound, 50, 1)
for(var/turf/T in list(get_turf(chassis), get_step(chassis, chassis.dir), get_step(chassis, turn(chassis.dir, -45)), get_step(chassis, turn(chassis.dir, 45))))
do_mop(chassis, T, 3) // mop the floor with them!
- var/turf/cleave_effect_loc = get_step(get_turf(src), SOUTHWEST)
- new cleave_effect(cleave_effect_loc, chassis.dir)
+ new cleave_effect(get_turf(src), chassis.dir)
/obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter
name = "comically large flyswatter"
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 124ba550dfcf..3c7795a7f01c 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -20,31 +20,31 @@
. = ..()
if(set_color)
color = set_color
- var/target_pixel_x = 0
- var/target_pixel_y = 0
+ var/target_pixel_x = pixel_x
+ var/target_pixel_y = pixel_y
switch(set_dir)
if(NORTH)
- target_pixel_y = 16
+ target_pixel_y += 16
if(SOUTH)
- target_pixel_y = -16
+ target_pixel_y += -16
layer = ABOVE_MOB_LAYER
if(EAST)
- target_pixel_x = 16
+ target_pixel_x += 16
if(WEST)
- target_pixel_x = -16
+ target_pixel_x += -16
if(NORTHEAST)
- target_pixel_x = 16
- target_pixel_y = 16
+ target_pixel_x += 16
+ target_pixel_y += 16
if(NORTHWEST)
- target_pixel_x = -16
- target_pixel_y = 16
+ target_pixel_x += -16
+ target_pixel_y += 16
if(SOUTHEAST)
- target_pixel_x = 16
- target_pixel_y = -16
+ target_pixel_x += 16
+ target_pixel_y += -16
layer = ABOVE_MOB_LAYER
if(SOUTHWEST)
- target_pixel_x = -16
- target_pixel_y = -16
+ target_pixel_x += -16
+ target_pixel_y += -16
layer = ABOVE_MOB_LAYER
animate(src, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = duration)
@@ -71,14 +71,14 @@
switch(newdir)
if(NORTH)
layer = BELOW_MOB_LAYER
- pixel_x = rand(-3,3)
- pixel_y = rand(4,6)
+ pixel_x += rand(-3,3)
+ pixel_y += rand(4,6)
if(SOUTH)
- pixel_x = rand(-3,3)
- pixel_y = rand(-1,1)
+ pixel_x += rand(-3,3)
+ pixel_y += rand(-1,1)
else
- pixel_x = rand(-1,1)
- pixel_y = rand(-1,1)
+ pixel_x += rand(-1,1)
+ pixel_y += rand(-1,1)
..()
/obj/effect/temp_visual/dir_setting/firing_effect/energy
@@ -89,11 +89,20 @@
icon_state = "shieldsparkles"
duration = 0.3 SECONDS
-/obj/effect/temp_visual/dir_setting/firing_effect/mecha_swipe
+/obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack
icon = 'icons/effects/96x96.dmi'
icon_state = "big_slash"
+ pixel_x = -32
+ pixel_y = -32
duration = 0.3 SECONDS
+/obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack/semicircle
+ icon_state = "big_slash_180"
+
+/obj/effect/temp_visual/dir_setting/firing_effect/sweep_attack/full_circle
+ icon_state = "big_slash_360"
+ duration = 0.4 SECONDS
+
/obj/effect/temp_visual/dir_setting/ninja
name = "ninja shadow"
icon = 'icons/mob/mob.dmi'
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 8e01632a70d4..3cf8adbfd8f2 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -147,6 +147,10 @@
menutab = MENU_WEAPON
additional_desc = "An exceptionally large sword, capable of occasionally deflecting blows."
+/obj/item/nullrod/claymore/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack)
+
/obj/item/nullrod/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(attack_type == PROJECTILE_ATTACK)
final_block_chance = 0 //Don't bring a sword to a gunfight
@@ -359,6 +363,10 @@
menutab = MENU_WEAPON
additional_desc = "The weapon of choice for a devout monk. Block incoming blows while striking weak points until your opponent is too exhausted to continue."
+/obj/item/nullrod/bostaff/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack, arc_size=180)
+
/obj/item/nullrod/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(attack_type == PROJECTILE_ATTACK)
final_block_chance = 0 //Don't bring a stick to a gunfight
@@ -610,6 +618,7 @@
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
AddComponent(/datum/component/butchering, 30, 100, 0, hitsound)
+ AddComponent(/datum/component/cleave_attack)
/obj/item/nullrod/armblade
name = "dark blessing"
@@ -633,6 +642,7 @@
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
AddComponent(/datum/component/butchering, 80, 70)
+ AddComponent(/datum/component/cleave_attack)
/obj/item/nullrod/armblade/tentacle
name = "unholy blessing"
@@ -1040,6 +1050,10 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
menutab = MENU_MISC
additional_desc = "You feel an unwoken presence in this one."
+/obj/item/nullrod/talking/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack)
+
/obj/item/nullrod/talking/relaymove(mob/user)
return //stops buckled message spam for the ghost.
@@ -1340,6 +1354,10 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
menutab = MENU_MISC //banish it from being associated with proper weapons
additional_desc = "Hey, God here. Asking you to pick literally anything else as your implement of justice."
+/obj/item/nullrod/sord/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack) // i guess???
+
//NOT CHAPLAIN SPAWNABLE
/obj/item/nullrod/talking/chainsword
name = "possessed chainsaw sword"
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index bf0e97233bb1..62f568306497 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -105,6 +105,10 @@
block_chance = 50
saber_color = "green"
+/obj/item/melee/transforming/energy/sword/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack) // very cool
+
/obj/item/melee/transforming/energy/sword/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/melee/transforming/energy/sword))
if(HAS_TRAIT(I, TRAIT_NODROP) || HAS_TRAIT(src, TRAIT_NODROP))
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index f6e5c5423699..02fb769b7ddf 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -80,8 +80,13 @@
hitsound = 'sound/weapons/rapierhit.ogg'
materials = list(/datum/material/iron = 1000)
+/obj/item/melee/cutlass/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack)
+
/obj/item/melee/sabre/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack) // YES
AddComponent(/datum/component/butchering, 30, 95, 5) //fast and effective, but as a sword, it might damage the results.
/obj/item/melee/sabre/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm
index d5d4ba24e585..a4f2fd4ae875 100644
--- a/code/game/objects/items/singularityhammer.dm
+++ b/code/game/objects/items/singularityhammer.dm
@@ -100,6 +100,7 @@
force_wielded = 20, \
icon_wielded = "[base_icon_state]1", \
)
+ AddComponent(/datum/component/cleave_attack, arc_size=180, requires_wielded=TRUE)
/obj/item/mjolnir/update_icon_state()
. = ..()
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index 027d16615440..83429cb1cdb2 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -62,6 +62,10 @@
item_state = "crowbar"
toolspeed = 0.7
+/obj/item/crowbar/large/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack, no_multi_hit=TRUE) // it's big
+
/obj/item/crowbar/cyborg
name = "hydraulic crowbar"
desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs."
diff --git a/code/game/objects/items/two_handed/baseball_bat.dm b/code/game/objects/items/two_handed/baseball_bat.dm
index 2f6dbb3153c1..400a7bbcd90a 100644
--- a/code/game/objects/items/two_handed/baseball_bat.dm
+++ b/code/game/objects/items/two_handed/baseball_bat.dm
@@ -21,6 +21,7 @@
/obj/item/melee/baseball_bat/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, require_twohands = TRUE)
+ AddComponent(/datum/component/cleave_attack, arc_size=90, requires_wielded=TRUE, no_multi_hit=TRUE)
/obj/item/melee/baseball_bat/homerun
name = "home run bat"
diff --git a/code/game/objects/items/two_handed/chainsaw.dm b/code/game/objects/items/two_handed/chainsaw.dm
index 9aa4aa9affe9..aa801f98c3eb 100644
--- a/code/game/objects/items/two_handed/chainsaw.dm
+++ b/code/game/objects/items/two_handed/chainsaw.dm
@@ -26,6 +26,7 @@
/obj/item/melee/chainsaw/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack)
AddComponent(/datum/component/two_handed, require_twohands = TRUE)
AddComponent(/datum/component/butchering, 30, 100, 0, 'sound/weapons/chainsawhit.ogg', TRUE)
diff --git a/code/game/objects/items/two_handed/dualsaber.dm b/code/game/objects/items/two_handed/dualsaber.dm
index e369a884d41c..a91dedaf6052 100644
--- a/code/game/objects/items/two_handed/dualsaber.dm
+++ b/code/game/objects/items/two_handed/dualsaber.dm
@@ -57,6 +57,7 @@
wield_callback = CALLBACK(src, PROC_REF(on_wield)), \
unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \
)
+ AddComponent(/datum/component/cleave_attack, arc_size=360, swing_speed_mod=1.5, requires_wielded=TRUE) // lol, lmao even
/obj/item/melee/dualsaber/Destroy()
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/items/two_handed/fireaxe.dm b/code/game/objects/items/two_handed/fireaxe.dm
index 16b1bc27484d..0d726252caac 100644
--- a/code/game/objects/items/two_handed/fireaxe.dm
+++ b/code/game/objects/items/two_handed/fireaxe.dm
@@ -29,6 +29,7 @@
force_wielded = force_wielded, \
icon_wielded = "[base_icon_state]1", \
)
+ AddComponent(/datum/component/cleave_attack, arc_size=180, requires_wielded=TRUE) // YEAHHHHH
AddComponent(/datum/component/butchering, 100, 80, 0 , hitsound) //axes are not known for being precision butchering tools
/obj/item/fireaxe/update_icon_state()
@@ -45,7 +46,7 @@
return
if(QDELETED(A))
return
- if(HAS_TRAIT(src, TRAIT_WIELDED)) //destroys shit faster, generally in 1-2 hits.
+ if(HAS_TRAIT(src, TRAIT_WIELDED) && !HAS_TRAIT(src, TRAIT_CLEAVING)) //destroys shit faster, generally in 1-2 hits.
if(istype(A, /obj/structure/window))
var/obj/structure/window/W = A
W.take_damage(W.max_integrity*2, BRUTE, MELEE, FALSE, null, armour_penetration)
diff --git a/code/game/objects/items/two_handed/highfrequencyblade.dm b/code/game/objects/items/two_handed/highfrequencyblade.dm
index 40cd8a0c1a18..c1341fcd7ae0 100644
--- a/code/game/objects/items/two_handed/highfrequencyblade.dm
+++ b/code/game/objects/items/two_handed/highfrequencyblade.dm
@@ -23,6 +23,7 @@
force_wielded = 20, \
icon_wielded = "[base_icon_state]1", \
)
+ AddComponent(/datum/component/cleave_attack, requires_wielded=TRUE)
AddComponent(/datum/component/butchering, 20, 105)
/obj/item/vibro_weapon/update_icon_state()
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index cefc64e51f50..82681c1a609e 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -74,6 +74,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack, arc_size=90)
AddComponent(/datum/component/butchering, 40, 105)
/obj/item/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
@@ -259,6 +260,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50)
resistance_flags = FIRE_PROOF
+/obj/item/katana/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack)
+
/obj/item/katana/basalt
name = "basalt katana"
desc = "A katana made of hardened basalt. Particularly damaging to lavaland fauna.
(Activate this item in hand to dodge roll in the direction you're facing)"
@@ -584,6 +589,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/mounted_chainsaw/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack)
ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
/obj/item/mounted_chainsaw/Destroy()
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index bca62264764c..17757fdf3a85 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -158,6 +158,9 @@
. = . || (mover.pass_flags & PASSGRILLE)
/obj/structure/grille/attackby(obj/item/W, mob/user, params)
+ var/obj/structure/window/window = locate() in loc
+ if(window && window.fulltile && window.anchored)
+ return TRUE // don't attack grilles through windows, that's weird and causes too many problems
user.changeNext_move(CLICK_CD_MELEE)
add_fingerprint(user)
if(W.tool_behaviour == TOOL_WIRECUTTER)
diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm
index 1b8e79ee65fd..1a4a880c6d91 100644
--- a/code/modules/antagonists/blob/structures/_blob.dm
+++ b/code/modules/antagonists/blob/structures/_blob.dm
@@ -4,7 +4,7 @@
icon = 'icons/mob/blob.dmi'
light_range = 2
desc = "A thick wall of writhing tendrils."
- density = FALSE //this being false causes two bugs, being able to attack blob tiles behind other blobs and being unable to move on blob tiles in no gravity, but turning it to 1 causes the blob mobs to be unable to path through blobs, which is probably worse.
+ density = TRUE
opacity = FALSE
anchored = TRUE
layer = BELOW_MOB_LAYER
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/battlehammer.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/battlehammer.dm
index 7c654e2a3fcf..b95d491e07da 100644
--- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/battlehammer.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/battlehammer.dm
@@ -12,6 +12,7 @@
/obj/item/clockwork/weapon/brass_battlehammer/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, require_twohands = TRUE)
+ AddComponent(/datum/component/cleave_attack, arc_size=180, requires_wielded=TRUE, no_multi_hit=TRUE) // big hammer
/obj/item/clockwork/weapon/brass_battlehammer/attack(mob/living/target, mob/living/carbon/human/user)
. = ..()
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/longsword.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/longsword.dm
index 601c6219a627..0bd2c2bb853d 100644
--- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/longsword.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/longsword.dm
@@ -12,6 +12,10 @@
var/emp_cooldown = 0
var/cooldown_duration = 10 SECONDS
+/obj/item/clockwork/weapon/brass_sword/Initialize(mapload, new_action)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack) // slice and dice in the name of ratvar
+
/obj/item/clockwork/weapon/brass_sword/attack(mob/living/target, mob/living/carbon/human/user)
. = ..()
if(world.time > emp_cooldown && !is_servant_of_ratvar(target))
diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm
index 439ba4ffddfd..9cce3d6b51fd 100644
--- a/code/modules/events/spacevine.dm
+++ b/code/modules/events/spacevine.dm
@@ -288,6 +288,10 @@
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
+ RegisterSignal(src, COMSIG_ATOM_CLEAVE_ATTACK, PROC_REF(on_cleave_attack))
+
+/obj/structure/spacevine/proc/on_cleave_attack()
+ return ATOM_ALLOW_CLEAVE_ATTACK // vines don't have density but should still be cleavable
/obj/structure/spacevine/examine(mob/user)
. = ..()
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index 97b78a364f38..c8eab1654462 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -142,6 +142,7 @@
/obj/item/scythe/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack, arc_size=180)
AddComponent(/datum/component/butchering, 90, 105)
/obj/item/scythe/suicide_act(mob/user)
@@ -154,20 +155,6 @@
playsound(src,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1)
return (BRUTELOSS)
-/obj/item/scythe/pre_attack(atom/A, mob/living/user, params)
- if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user))
- return ..()
- else
- var/turf/user_turf = get_turf(user)
- var/dir_to_target = get_dir(user_turf, get_turf(A))
- swiping = TRUE
- var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90)
- for(var/i in scythe_slash_angles)
- var/turf/T = get_step(user_turf, turn(dir_to_target, i))
- for(var/obj/structure/spacevine/V in T)
- if(user.Adjacent(V))
- melee_attack_chain(user, V)
- swiping = FALSE
// *************************************
// Nutrient defines for hydroponics
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 7856adbc45d0..1826ff0baaeb 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -93,7 +93,7 @@
if(dodging && target && in_melee && isturf(loc) && isturf(target.loc))
var/datum/cb = CALLBACK(src, PROC_REF(sidestep))
if(sidestep_per_cycle > 1) //For more than one just spread them equally - this could changed to some sensible distribution later
- var/sidestep_delay = SSnpcpool.wait / sidestep_per_cycle
+ var/sidestep_delay = round(SSnpcpool.wait / sidestep_per_cycle)
for(var/i in 1 to sidestep_per_cycle)
addtimer(cb, (i - 1)*sidestep_delay)
else //Otherwise randomize it to make the players guessing.
@@ -261,7 +261,7 @@
/mob/living/simple_animal/hostile/proc/MeleeAction(patience = TRUE)
if(rapid_melee > 1)
var/datum/callback/cb = CALLBACK(src, PROC_REF(CheckAndAttack))
- var/delay = SSnpcpool.wait / rapid_melee
+ var/delay = round(SSnpcpool.wait / rapid_melee)
for(var/i in 1 to rapid_melee)
addtimer(cb, (i - 1)*delay)
else
@@ -454,7 +454,7 @@
if(CanSmashTurfs(T))
T.attack_animal(src)
for(var/obj/O in T)
- if(O.density && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !O.IsObscured())
+ if(O.density && !O.CanAllowThrough(src) && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !O.IsObscured())
O.attack_animal(src)
return
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 27c5a86f39bc..81ff251a72b6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -412,7 +412,7 @@ Difficulty: Hard
/obj/effect/decal/cleanable/blood/gibs/bubblegum/can_bloodcrawl_in()
return TRUE
-/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!charging)
..()
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 985e0397a275..211f195938d6 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -241,6 +241,7 @@
/obj/item/circular_saw/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/cleave_attack)
AddComponent(/datum/component/butchering, 40 * toolspeed, 100, 5, 'sound/weapons/circsawhit.ogg') //saws are very accurate and fast at butchering
/obj/item/circular_saw/attack(mob/living/M, mob/user)
@@ -406,6 +407,9 @@
demolition_mod = 1.5 // lasers are good at cutting metal
sharpness = SHARP_EDGED
+/obj/item/scalpel/advanced/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/cleave_attack) // woe, angry medbay be upon ye
/obj/item/scalpel/advanced/attack_self(mob/user)
playsound(get_turf(user), 'sound/machines/click.ogg', 50, TRUE)
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index d7e81d7c86c2..8e4bd9e87d9a 100644
Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ
diff --git a/yogstation.dme b/yogstation.dme
index d0b5a90de459..732c65ffb3bc 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -579,6 +579,7 @@
#include "code\datums\components\butchering.dm"
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
+#include "code\datums\components\cleave_attack.dm"
#include "code\datums\components\connect_containers.dm"
#include "code\datums\components\connect_loc_behalf.dm"
#include "code\datums\components\connect_mob_behalf.dm"
diff --git a/yogstation/code/game/objects/items/wielded/big_spoon.dm b/yogstation/code/game/objects/items/wielded/big_spoon.dm
index 107bce062e96..d6f0ad40c6ca 100644
--- a/yogstation/code/game/objects/items/wielded/big_spoon.dm
+++ b/yogstation/code/game/objects/items/wielded/big_spoon.dm
@@ -28,6 +28,7 @@
wield_callback = CALLBACK(src, PROC_REF(on_wield)), \
unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \
)
+ AddComponent(/datum/component/cleave_attack, requires_wielded=TRUE, no_multi_hit=TRUE)
/obj/item/bigspoon/proc/on_wield(atom/source, mob/living/user)
hitsound = 'yogstation/sound/weapons/bat_hit.ogg'
diff --git a/yogstation/code/game/objects/items/wielded/sledgehammer.dm b/yogstation/code/game/objects/items/wielded/sledgehammer.dm
index 1822bd0a0198..750a91ccf359 100644
--- a/yogstation/code/game/objects/items/wielded/sledgehammer.dm
+++ b/yogstation/code/game/objects/items/wielded/sledgehammer.dm
@@ -33,6 +33,11 @@
require_twohands = TRUE, \
wielded_stats = list(SWING_SPEED = 1.5, ENCUMBRANCE = 0.5, ENCUMBRANCE_TIME = 1 SECONDS, REACH = 1, DAMAGE_LOW = 0, DAMAGE_HIGH = 0), \
)
+ AddComponent(/datum/component/cleave_attack, \
+ arc_size=180, \
+ requires_wielded=TRUE, \
+ no_multi_hit=TRUE, \
+ ) // big and heavy hammer makes wide arc
/obj/item/melee/sledgehammer/proc/on_wield(atom/source, mob/living/user)
hitsound = "swing_hit"
diff --git a/yogstation/code/game/objects/items/wielded/vxtvulhammer.dm b/yogstation/code/game/objects/items/wielded/vxtvulhammer.dm
index 2aa35b57a80f..dcdf2f921549 100644
--- a/yogstation/code/game/objects/items/wielded/vxtvulhammer.dm
+++ b/yogstation/code/game/objects/items/wielded/vxtvulhammer.dm
@@ -46,6 +46,11 @@
force_wielded = force_wielded, \
unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \
)
+ AddComponent(/datum/component/cleave_attack, \
+ arc_size=180, \
+ requires_wielded=TRUE, \
+ cleave_end_callback=CALLBACK(src, PROC_REF(end_swing)), \
+ )
/obj/item/melee/vxtvulhammer/Destroy() //Even though the hammer won't probably be destroyed, Everâ„¢
QDEL_NULL(spark_system)
@@ -70,6 +75,10 @@
user.visible_message(span_warning("[user] flicks the hammer off!"))
charging = FALSE
+/obj/item/melee/vxtvulhammer/proc/end_swing(obj/item/weapon, mob/user)
+ if(supercharged)
+ supercharge()
+
/obj/item/melee/vxtvulhammer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(attack_type == PROJECTILE_ATTACK || !HAS_TRAIT(src, TRAIT_WIELDED)) //Doesn't work against ranged or if it's not wielded
final_block_chance = 0 //Please show me how you can block a bullet with an industrial hammer I would LOVE to see it
@@ -143,7 +152,8 @@
K.color = color
playsound(loc, 'sound/effects/powerhammerhit.ogg', 80, FALSE) //Mainly this sound
playsound(loc, 'sound/effects/explosion3.ogg', 20, TRUE) //Bit of a reverb
- supercharge() //At start so it doesn't give an unintentional message if you hit yourself
+ if(!HAS_TRAIT(src, TRAIT_CLEAVING)) // wait for the swing to end
+ supercharge() //At start so it doesn't give an unintentional message if you hit yourself
if(ismecha(target) && !toy)
user.visible_message(span_danger("The hammer thunders against [target], caving in part of its outer plating!"))