diff --git a/code/datums/components/swarming.dm b/code/datums/components/swarming.dm
new file mode 100644
index 00000000000..c9d20f1f702
--- /dev/null
+++ b/code/datums/components/swarming.dm
@@ -0,0 +1,55 @@
+/datum/component/swarming
+ var/offset_x = 0
+ var/offset_y = 0
+ var/is_swarming = FALSE
+ var/list/swarm_members = list()
+
+/datum/component/swarming/Initialize(max_x = 24, max_y = 24)
+ if(!ismovableatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ offset_x = rand(-max_x, max_x)
+ offset_y = rand(-max_y, max_y)
+
+ RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/join_swarm)
+ RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/leave_swarm)
+
+/datum/component/swarming/Destroy()
+ for(var/other in swarm_members)
+ var/datum/component/swarming/other_swarm = other
+ other_swarm.swarm_members -= src
+ if(!other_swarm.swarm_members.len)
+ other_swarm.unswarm()
+ swarm_members = null
+ return ..()
+
+/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/AM)
+ var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
+ if(!other_swarm)
+ return
+ swarm()
+ swarm_members |= other_swarm
+ other_swarm.swarm()
+ other_swarm.swarm_members |= src
+
+/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/AM)
+ var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
+ if(!other_swarm || !(other_swarm in swarm_members))
+ return
+ swarm_members -= other_swarm
+ if(!swarm_members.len)
+ unswarm()
+ other_swarm.swarm_members -= src
+ if(!other_swarm.swarm_members.len)
+ other_swarm.unswarm()
+
+/datum/component/swarming/proc/swarm()
+ var/atom/movable/owner = parent
+ if(!is_swarming)
+ is_swarming = TRUE
+ animate(owner, pixel_x = owner.pixel_x + offset_x, pixel_y = owner.pixel_y + offset_y, time = 2)
+
+/datum/component/swarming/proc/unswarm()
+ var/atom/movable/owner = parent
+ if(is_swarming)
+ animate(owner, pixel_x = owner.pixel_x - offset_x, pixel_y = owner.pixel_y - offset_y, time = 2)
+ is_swarming = FALSE
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index ac77d3d4553..9f2a0e09aee 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -233,6 +233,9 @@
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src)
+/atom/movable/Uncrossed(atom/movable/AM)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
+
/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump().
if(A && yes)
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
@@ -501,7 +504,7 @@
target.fingerprintshidden += fingerprintshidden
target.fingerprintslast = fingerprintslast
-/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y)
+/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(A, visual_effect_icon, used_item)
@@ -509,10 +512,6 @@
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
- var/final_pixel_y = initial(pixel_y)
- if(end_pixel_y)
- final_pixel_y = end_pixel_y
-
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = 8
@@ -525,7 +524,7 @@
pixel_x_diff = -8
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 = final_pixel_y, time = 2)
+ animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2)
/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item)
var/image/I
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 610436406d8..4234064874a 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -1454,7 +1454,7 @@
..()
-/obj/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y)
+/obj/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect)
if(selected)
used_item = selected
diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm
index d97663759af..7af5d7a998f 100644
--- a/code/game/objects/effects/spiders.dm
+++ b/code/game/objects/effects/spiders.dm
@@ -98,6 +98,7 @@
pixel_x = rand(6,-6)
pixel_y = rand(6,-6)
START_PROCESSING(SSobj, src)
+ AddComponent(/datum/component/swarming)
/obj/structure/spider/spiderling/Destroy()
STOP_PROCESSING(SSobj, src)
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm
index e7f783acd72..3174879caac 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm
@@ -55,7 +55,7 @@
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
visible_message("[M] has attempted to disarm [src]!")
-/mob/living/carbon/alien/humanoid/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y)
+/mob/living/carbon/alien/humanoid/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_CLAW
..()
diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
index 69e12b6ae19..d13dab884b2 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
@@ -31,7 +31,7 @@
step_away(src, user, 15)
return TRUE
-/mob/living/carbon/alien/larva/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y)
+/mob/living/carbon/alien/larva/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_BITE
..()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index dfa4bb61c9d..9f768ffc76d 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -740,7 +740,8 @@
clear_alert("weightless")
else
throw_alert("weightless", /obj/screen/alert/weightless)
- float(!has_gravity)
+ if(!flying)
+ float(!has_gravity)
/mob/living/proc/float(on)
if(throwing)
@@ -825,8 +826,7 @@
spawn_dust()
gib()
-/mob/living/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y)
- end_pixel_y = get_standard_pixel_y_offset(lying)
+/mob/living/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!used_item)
used_item = get_active_hand()
..()
diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm
index 2cfc5e746dc..b42f3275997 100644
--- a/code/modules/mob/living/simple_animal/animal_defense.dm
+++ b/code/modules/mob/living/simple_animal/animal_defense.dm
@@ -118,7 +118,7 @@
/mob/living/simple_animal/blob_act(obj/structure/blob/B)
adjustBruteLoss(20)
-/mob/living/simple_animal/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect, end_pixel_y)
+/mob/living/simple_animal/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
if(!no_effect && !visual_effect_icon && melee_damage_upper)
if(melee_damage_upper < 10)
visual_effect_icon = ATTACK_EFFECT_PUNCH
diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm
index c1e7548d9a3..3276416fe7f 100644
--- a/code/modules/mob/living/simple_animal/hostile/bees.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bees.dm
@@ -60,6 +60,7 @@
/mob/living/simple_animal/hostile/poison/bees/New()
..()
generate_bee_visuals()
+ AddComponent(/datum/component/swarming)
/mob/living/simple_animal/hostile/poison/bees/Destroy()
beegent = null
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
index ca9b632e06a..a5248e98bb6 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
@@ -90,6 +90,7 @@
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize(mapload)
. = ..()
addtimer(CALLBACK(src, .proc/death), 100)
+ AddComponent(/datum/component/swarming)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/blood
diff --git a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
index fd569984920..07f26031ed5 100644
--- a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
+++ b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
@@ -335,7 +335,7 @@
//Jiggle the whole worm forwards towards the next segment
-/mob/living/simple_animal/hostile/spaceWorm/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect, end_pixel_y)
+/mob/living/simple_animal/hostile/spaceWorm/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
..()
if(previousWorm)
previousWorm.do_attack_animation(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
index 23970e88826..6f40dfe2a3f 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
@@ -349,7 +349,8 @@
icon = 'icons/mob/critter.dmi'
icon_state = "viscerator_attack"
icon_living = "viscerator_attack"
- pass_flags = PASSTABLE
+ pass_flags = PASSTABLE | PASSMOB
+ a_intent = INTENT_HARM
health = 15
maxHealth = 15
obj_damage = 0
@@ -365,3 +366,7 @@
gold_core_spawnable = HOSTILE_SPAWN
del_on_death = 1
deathmessage = "is smashed into pieces!"
+
+/mob/living/simple_animal/hostile/viscerator/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/swarming)
diff --git a/code/modules/mob/living/simple_animal/posessed_object.dm b/code/modules/mob/living/simple_animal/posessed_object.dm
index bde995d8edb..c3cb61d5970 100644
--- a/code/modules/mob/living/simple_animal/posessed_object.dm
+++ b/code/modules/mob/living/simple_animal/posessed_object.dm
@@ -27,7 +27,7 @@
. += "[src] appears to be having trouble staying afloat!"
-/mob/living/simple_animal/possessed_object/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect, end_pixel_y)
+/mob/living/simple_animal/possessed_object/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
..()
animate_ghostly_presence(src, -1, 20, 1) // Restart the floating animation after the attack animation, as it will be cancelled.
diff --git a/paradise.dme b/paradise.dme
index c137faf236f..7393d7c6bb7 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -302,6 +302,7 @@
#include "code\datums\components\paintable.dm"
#include "code\datums\components\spawner.dm"
#include "code\datums\components\squeak.dm"
+#include "code\datums\components\swarming.dm"
#include "code\datums\components\waddling.dm"
#include "code\datums\diseases\_disease.dm"
#include "code\datums\diseases\_MobProcs.dm"