Ports TG attack animations. (#22319)

https://github.com/user-attachments/assets/0af58b65-552e-48c2-ab97-56ebf0794127

To-do:

- [x] Simple mobs
- [ ] Icon angles for most icons
- [x] Attack effects for kicking/biting/etc

---------

Co-authored-by: Matt Atlas <liermattia@gmail.com>
This commit is contained in:
Matt Atlas
2026-06-05 10:12:43 +00:00
committed by GitHub
co-authored by Matt Atlas
parent 5c74b702a8
commit f759aebad3
66 changed files with 403 additions and 150 deletions
+11 -97
View File
@@ -119,39 +119,23 @@ note dizziness decrements automatically in the mob's Life() proc.
//reset the pixel offsets to defaults
is_floating = FALSE
/atom/movable/proc/do_attack_animation(atom/A, atom/movable/weapon, var/image/attack_image, var/initial_pixel_x = 0, var/initial_pixel_y = 0)
/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE, item_animation_override = null)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item, animation_type = item_animation_override)
if(attacked_atom == src)
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
var/turn_dir = 1
var/direction = get_dir(src, A)
switch(direction)
if(NORTH)
pixel_y_diff = 8
if(SOUTH)
pixel_y_diff = -8
if(EAST)
pixel_x_diff = 8
if(WEST)
pixel_x_diff = -8
if(NORTHEAST)
pixel_x_diff = 8
pixel_y_diff = 8
if(NORTHWEST)
pixel_x_diff = -8
pixel_y_diff = 8
if(SOUTHEAST)
pixel_x_diff = 8
pixel_y_diff = -8
if(SOUTHWEST)
pixel_x_diff = -8
pixel_y_diff = -8
var/direction = get_dir(src, attacked_atom)
if(direction & NORTH)
pixel_y_diff = 8
turn_dir = rand(50) ? -1 : 1
turn_dir = prob(50) ? -1 : 1
else if(direction & SOUTH)
pixel_y_diff = -8
turn_dir = rand(50) ? -1 : 1
turn_dir = prob(50) ? -1 : 1
if(direction & EAST)
pixel_x_diff = 8
@@ -159,80 +143,10 @@ note dizziness decrements automatically in the mob's Life() proc.
pixel_x_diff = -8
turn_dir = -1
if(!initial_pixel_x)
initial_pixel_x = initial(pixel_x)
if(!initial_pixel_y)
initial_pixel_y = initial(pixel_y)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2)
animate(pixel_x = initial_pixel_x, pixel_y = initial_pixel_y, time = 2)
var/matrix/initial_transform = matrix(transform)
var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform = rotated_transform, time = 2, easing = BACK_EASING | EASE_IN)
animate(pixel_x = initial_pixel_x, pixel_y = initial_pixel_y, transform = initial_transform, time = 2, easing = SINE_EASING)
// either attack_item OR attack_image should be used. if both are used, attack_image will be the one chosen
/mob/do_attack_animation(atom/A, var/atom/attack_item, var/image/attack_image)
set waitfor = FALSE
var/initial_pixel_x = get_standard_pixel_x()
var/initial_pixel_y = get_standard_pixel_y()
..(A, attack_item, attack_image, initial_pixel_x, initial_pixel_y)
if(is_floating)
addtimer(CALLBACK(src, PROC_REF(start_floating)), 4)
if(attack_item == FIST_ATTACK_ANIMATION) // only play the physical movement
return
// What icon do we use for the attack?
var/image/I
if(attack_image)
I = attack_image
else if(attack_item)
I = image(attack_item.icon, A, attack_item.icon_state, A.layer + 1)
else
if(hand && l_hand) // Attacked with item in left hand.
I = image(l_hand.icon, A, l_hand.icon_state, A.layer + 1)
else if (!hand && r_hand) // Attacked with item in right hand.
I = image(r_hand.icon, A, r_hand.icon_state, A.layer + 1)
else // Attacked with a fist?
return
// Who can see the attack?
var/list/viewing = list()
for (var/mob/M in viewers(A))
if (M.client)
viewing |= M.client
flick_overlay(I, viewing, 5) // 5 ticks/half a second
// Scale the icon.
I.transform *= 0.75
// Set the direction of the icon animation.
var/direction = get_dir(src, A)
if(direction & NORTH)
I.pixel_y = -16
else if(direction & SOUTH)
I.pixel_y = 16
if(direction & EAST)
I.pixel_x = -16
else if(direction & WEST)
I.pixel_x = 16
if(!direction) // Attacked self?!
I.pixel_z = 16
var/matrix/M = new
M.Turn(pick(-20, 20))
// And animate the attack!
animate(I, alpha = 175, pixel_x = initial_pixel_x, pixel_y = initial_pixel_y, pixel_z = 0, time = 2, easing = CUBIC_EASING)
sleep(2)
animate(I, transform = M, time = 1) // apply the fancy matrix
sleep(1)
animate(I, transform = matrix(), time = 1) // back to a default matrix
sleep(1)
animate(I, alpha = 0, time = 1)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING, flags = ANIMATION_PARALLEL)
/mob/proc/spin(spintime, speed)
spawn()
@@ -255,7 +255,7 @@
if(!attack)
return 0
H.do_attack_animation(src)
H.do_attack_animation(src, attack.attack_effect)
if(!attack_message)
attack.show_attack(H, src, hit_zone, rand_damage)
else
@@ -363,7 +363,7 @@
src.attack_log += "\[[time_stamp()]\] <font color='orange'>Has been disarmed by [M.name] ([M.ckey])</font>"
msg_admin_attack("[key_name(M)] disarmed [src.name] ([src.ckey]) (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[M.x];Y=[M.y];Z=[M.z]'>JMP</a>)",ckey=key_name(M),ckey_target=key_name(src))
M.do_attack_animation(src)
M.do_attack_animation(src, ATTACK_EFFECT_DISARM)
if(w_uniform)
w_uniform.add_fingerprint(M)
@@ -512,7 +512,7 @@
to_chat(H, cpr_attempt_message)
H.do_attack_animation(src, null, image('icons/hud/mob/generic.dmi', src, "cpr", src.layer + 1))
H.do_attack_animation(src, ATTACK_EFFECT_CPR)
var/starting_pixel_y = pixel_y
animate(src, pixel_y = starting_pixel_y + 4, time = 2)
animate(src, pixel_y = starting_pixel_y, time = 2)
@@ -7,6 +7,7 @@
edge = TRUE
damage = 5
attack_name = "sharp bite"
attack_effect = ATTACK_EFFECT_BITE
/datum/unarmed_attack/diona
attack_verb = list("lashed", "bludgeoned")
@@ -28,6 +29,7 @@
edge = TRUE
damage = 5
attack_name = "claws"
attack_effect = ATTACK_EFFECT_CLAW
/datum/unarmed_attack/claws/show_attack(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone, var/attack_damage)
var/obj/item/organ/external/affecting = target.get_organ(zone)
@@ -124,6 +126,7 @@
attack_name = "heavy fist"
shredding = TRUE
sparring_variant_type = /datum/unarmed_attack/pain_strike/heavy
attack_effect = ATTACK_EFFECT_SMASH
/datum/unarmed_attack/industrial/heavy
damage = 9
@@ -140,6 +143,7 @@
attack_sound = 'sound/weapons/beartrap_shut.ogg'
attack_name = "power fist"
shredding = TRUE
attack_effect = ATTACK_EFFECT_SMASH
/datum/unarmed_attack/terminator/apply_effects(var/mob/living/carbon/human/user,var/mob/living/carbon/human/target,var/armor,var/attack_damage,var/zone)
..()
@@ -225,6 +229,7 @@
attack_sound = 'sound/weapons/heavysmash.ogg'
attack_name = "crushing fist"
shredding = TRUE
attack_effect = ATTACK_EFFECT_SMASH
/datum/unarmed_attack/shocking
attack_verb = list("prodded", "touched")
@@ -265,6 +270,7 @@
attack_door = 20
crowbar_door = TRUE
sparring_variant_type = /datum/unarmed_attack/pain_strike/heavy
attack_effect = ATTACK_EFFECT_SMASH
/datum/unarmed_attack/vaurca_bulwark/apply_effects(var/mob/living/carbon/human/user,var/mob/living/carbon/human/target,var/armor,var/zone)
..()
@@ -290,6 +296,7 @@
attack_sound = 'sound/weapons/beartrap_shut.ogg'
attack_name = "industrial claw"
shredding = TRUE
attack_effect = ATTACK_EFFECT_SMASH
/datum/unarmed_attack/tesla_body/apply_effects(var/mob/living/carbon/human/user,var/mob/living/carbon/human/target,var/armor,var/attack_damage,var/zone)
..()
@@ -9,6 +9,8 @@ GLOBAL_LIST_EMPTY(sparring_attack_cache)
var/armor_penetration = 0
var/attack_sound = SFX_PUNCH
var/miss_sound = SFX_PUNCH_MISS
/// The attack effect played with this attack.
var/attack_effect = ATTACK_EFFECT_PUNCH
var/shredding = 0 // Calls the old attack_alien() behavior on objects/mobs when on harm intent.
var/attack_door = 0 // Whether the attack can damage airlocks and how much damage it does
var/crowbar_door = FALSE
@@ -166,6 +168,7 @@ GLOBAL_LIST_EMPTY(sparring_attack_cache)
sharp = 0
edge = FALSE
attack_name = "bite"
attack_effect = ATTACK_EFFECT_BITE
/datum/unarmed_attack/bite/is_usable(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone)
if(user.incapacitated())
@@ -254,6 +257,7 @@ GLOBAL_LIST_EMPTY(sparring_attack_cache)
desc = "A high risk, pretty low reward move. It could be useful if your shoes has a knife sticking out the front, or if you're a trained martial arts master. Make sure to target the lower parts of the body, or else you won't be able to reach!"
damage = 0
attack_name = "kick"
attack_effect = ATTACK_EFFECT_KICK
/datum/unarmed_attack/kick/is_usable(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone)
if(user.legcuffed || user.incapacitated())
@@ -86,6 +86,9 @@
var/burn_mod = 1
var/brute_mod = 1
/// Override for the visual attack effect shown on 'do_attack_animation()'.
var/attack_vis_effect
/// used to limit people from queuing up limb-breaks
var/limb_breaking = FALSE
/// Basically a catch-all aura/force-field thing.
@@ -21,6 +21,7 @@
melee_damage_lower = 15
melee_damage_upper = 15
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
environment_smash = 1
@@ -57,6 +58,7 @@
melee_damage_lower = 15
melee_damage_upper = 15
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
faction = "Adhomai"
@@ -13,6 +13,7 @@
melee_damage_lower = 5
melee_damage_upper = 15
attacktext = "mauls"
attack_vis_effect = ATTACK_EFFECT_CLAW
cold_damage_per_tick = 0
speak_chance = 5
speak = list("Hruuugh!","Hrunnph")
@@ -34,6 +35,7 @@
melee_damage_lower = 1
melee_damage_upper = 8
attacktext = "gouges"
attack_vis_effect = ATTACK_EFFECT_SLASH
cold_damage_per_tick = 0
speak_chance = 5
speak = list("Awrr?","Aowrl!","Worrl")
@@ -59,6 +61,7 @@
melee_damage_lower = 3
melee_damage_upper = 12
attacktext = "gouges"
attack_vis_effect = ATTACK_EFFECT_SLASH
cold_damage_per_tick = 0
speak_chance = 5
speak = list("Shuhn","Shrunnph?","Shunpf")
@@ -24,6 +24,7 @@
melee_damage_upper = 10
resist_mod = 10 // can't grab a cloud of bats easily
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
//Space carp aren't affected by atmos.
@@ -35,6 +35,7 @@
var/turns_since_hit = 0//If the bear chases someone too long without hitting them, it will try to change to another nearby target instead
attacktext = null//This allows custom attacking emotes
attack_vis_effect = ATTACK_EFFECT_CLAW
var/quiet_sounds = list('sound/effects/creatures/bear_quiet_1.ogg',
'sound/effects/creatures/bear_quiet_2.ogg',
@@ -31,6 +31,7 @@
mob_size = 25
environment_smash = 2
attacktext = "mangles"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bloodyslice.ogg'
lighting_alpha = LIGHTING_PLANE_ALPHA_SOMEWHAT_INVISIBLE
@@ -34,6 +34,7 @@
mob_size = 25
environment_smash = 2
attacktext = "mangles"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bloodyslice.ogg'
emote_sounds = list('sound/effects/creatures/bear_loud_1.ogg', 'sound/effects/creatures/bear_loud_2.ogg', 'sound/effects/creatures/bear_loud_3.ogg', 'sound/effects/creatures/bear_loud_4.ogg')
@@ -165,6 +166,7 @@
mob_size = 15
attacktext = "mangles"
attack_sound = 'sound/weapons/bloodyslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
lighting_alpha = LIGHTING_PLANE_ALPHA_SOMEWHAT_INVISIBLE
@@ -39,6 +39,8 @@
butchering_products = list(/obj/item/clothing/head/bearpelt = 1)
meat_amount = 5
attack_vis_effect = ATTACK_EFFECT_CLAW
/mob/living/simple_animal/hostile/commanded/bear/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone)
. = ..()
if(.)
@@ -45,6 +45,7 @@
destroy_surroundings = FALSE
attack_emote = "growls at"
attack_vis_effect = ATTACK_EFFECT_BITE
butchering_products = list(/obj/item/stack/material/animalhide = 2)
@@ -11,6 +11,7 @@
melee_damage_upper = 30
organ_names = list("meaty core")
attacktext = "chomps"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
faction = "creature"
speed = 4
@@ -21,6 +21,7 @@
melee_damage_lower = 15
melee_damage_upper = 15
attacktext = "grips"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/hallucinations/growl1.ogg'
min_oxy = 0
@@ -46,6 +46,7 @@
smart_melee = FALSE
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_emote = "skitters toward"
attack_sound = 'sound/weapons/bite.ogg'
emote_sounds = list('sound/effects/creatures/spider_critter.ogg')
@@ -43,6 +43,8 @@
psi_pingable = FALSE
sample_data = null
attack_vis_effect = ATTACK_EFFECT_SLASH
/// Weakref to the beacon that potentially spawned us.
var/datum/weakref/parent_beacon
@@ -12,6 +12,7 @@
armor_penetration = 20
attack_sound = 'sound/weapons/smash.ogg'
attacktext = "smashes"
attack_vis_effect = ATTACK_EFFECT_SMASH
faction = "hivebot"
min_oxy = 0
max_oxy = 0
@@ -29,6 +29,7 @@
mob_size = 30
environment_smash = 2
attacktext = "chomps"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bloodyslice.ogg'
lighting_alpha = LIGHTING_PLANE_ALPHA_SOMEWHAT_INVISIBLE
@@ -159,6 +160,7 @@
melee_damage_lower = 5
melee_damage_upper = 8
attacktext = "rams"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/punch4_bass.ogg'
environment_smash = 1
@@ -24,6 +24,7 @@
mob_size = 30
environment_smash = 2
attacktext = "chomps"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
faction = "worm"
@@ -20,6 +20,7 @@
melee_damage_lower = 30
melee_damage_upper = 30
attacktext = "slashes"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bladeslice.ogg'
min_oxy = 5
@@ -36,6 +36,7 @@
mob_size = 5
attacktext = "slashes"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bladeslice.ogg'
speed = 2
@@ -174,6 +175,7 @@
melee_damage_upper = 5
attacktext = "smashed"
attack_sound = 'sound/weapons/genhit1.ogg'
attack_vis_effect = ATTACK_EFFECT_SMASH
speed = 1
ranged = TRUE
@@ -47,6 +47,7 @@ ABSTRACT_TYPE(/mob/living/simple_animal/hostile/retaliate/aquatic)
melee_damage_lower = 40
melee_damage_upper = 70
armor_penetration = 80
attack_vis_effect = ATTACK_EFFECT_BITE
//Admin shark for admin fun because why not, not meant for normal gameplay
/mob/living/simple_animal/hostile/retaliate/aquatic/thresher/deep_water
@@ -24,6 +24,7 @@
melee_damage_lower = 10
melee_damage_upper = 10
attacktext = "chomps"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
speed = 4
projectiletype = /obj/projectile/beam/cavern
@@ -27,6 +27,7 @@
)
emote_see = list("skitters","oozes liquid from its mouth", "scratches at the ground", "clicks its claws")
attack_vis_effect = ATTACK_EFFECT_CLAW
/mob/living/simple_animal/hostile/retaliate/beast/charbaby
name = "charbaby"
@@ -66,5 +67,6 @@
icon_state = "lavadog"
icon_living = "lavadog"
icon_dead = "lavadog_dead"
attack_vis_effect = ATTACK_EFFECT_BITE
speak = list("Karuph!", "Karump!")
@@ -21,6 +21,7 @@
melee_damage_upper = 30
armor_penetration = 20
attacktext = "claws"
attack_vis_effect = ATTACK_EFFECT_CLAW
attack_sound = 'sound/weapons/slice.ogg'
meat_amount = 8
@@ -15,6 +15,7 @@
melee_damage_upper = 8
armor_penetration = 5
attacktext = "slices"
attack_vis_effect = ATTACK_EFFECT_SLASH
faction = "silicon"
min_oxy = 0
minbodytemp = 0
@@ -34,6 +34,7 @@
armor_penetration = 5
attack_flags = DAMAGE_FLAG_EDGE
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
//Space carp aren't affected by atmos.
@@ -299,6 +300,7 @@
melee_damage_lower = 5
melee_damage_upper = 5
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
min_oxy = 0
@@ -38,6 +38,7 @@
mob_push_flags = ALLMOBS
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE
attack_sound = 'sound/weapons/bite.ogg'
pass_flags = PASSTABLE|PASSRAILING
@@ -24,6 +24,7 @@
melee_damage_lower = 8
melee_damage_upper = 12
attacktext = "bites"
attack_vis_effect = ATTACK_EFFECT_BITE //what how
attack_sound = 'sound/weapons/bite.ogg'
//Space carp aren't affected by atmos.
@@ -25,6 +25,7 @@
mob_size = 15
environment_smash = 2
attacktext = "mangles"
attack_vis_effect = ATTACK_EFFECT_CLAW
attack_emote = "charges toward"
attack_sound = 'sound/effects/creatures/vannatusk_attack.ogg'
emote_sounds = list('sound/effects/creatures/vannatusk_sound.ogg', 'sound/effects/creatures/vannatusk_sound_2.ogg')
@@ -12,6 +12,7 @@
armor_penetration = 20
density = 0
attacktext = "cuts"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bladeslice.ogg'
blood_overlay_icon = null
faction = "syndicate"
@@ -620,14 +620,14 @@
if(!actual_damage)
actual_damage = harm_intent_damage ? rand(1, harm_intent_damage) : 0
apply_damage(actual_damage, attack.damage_type)
user.do_attack_animation(src, FIST_ATTACK_ANIMATION)
user.do_attack_animation(src)
return
simple_harm_attack(user)
/mob/living/simple_animal/proc/simple_harm_attack(var/mob/living/user)
apply_damage(harm_intent_damage, damage_type, used_weapon = "Attack by [user.name]")
user.visible_message(SPAN_WARNING("<b>\The [user]</b> [response_harm] \the [src]!"), SPAN_WARNING("You [response_harm] \the [src]!"))
user.do_attack_animation(src, FIST_ATTACK_ANIMATION)
user.do_attack_animation(src)
poke(TRUE)
/mob/living/simple_animal/attackby(obj/item/attacking_item, mob/user)
@@ -1142,6 +1142,16 @@
return TRUE
return FALSE
/mob/living/simple_animal/do_attack_animation(atom/attacked_atom, visual_effect_icon, used_item, no_effect)
if(!no_effect && !visual_effect_icon && melee_damage_upper)
if(attack_vis_effect && !iswall(attacked_atom)) // override the standard visual effect.
visual_effect_icon = attack_vis_effect
else if(melee_damage_upper < 10)
visual_effect_icon = ATTACK_EFFECT_PUNCH
else
visual_effect_icon = ATTACK_EFFECT_SMASH
..()
#undef BLOOD_NONE
#undef BLOOD_LIGHT
#undef BLOOD_MEDIUM