diff --git a/code/__DEFINES/ai/space_whale_defines.dm b/code/__DEFINES/ai/space_whale_defines.dm
new file mode 100644
index 00000000000..64ece795915
--- /dev/null
+++ b/code/__DEFINES/ai/space_whale_defines.dm
@@ -0,0 +1,2 @@
+#define BB_WHALE_CHARGE_ACTION "BB_WHALE_CHARGE_ACTION"
+#define BB_WHALE_SPELL_COOLDOWN "BB_WHALE_SPELL_COOLDOWN"
diff --git a/code/datums/ai/basic_mobs/hunting_behavior/find_and_hunt_target.dm b/code/datums/ai/basic_mobs/hunting_behavior/find_and_hunt_target.dm
index 77aeef11270..45401fd4f28 100644
--- a/code/datums/ai/basic_mobs/hunting_behavior/find_and_hunt_target.dm
+++ b/code/datums/ai/basic_mobs/hunting_behavior/find_and_hunt_target.dm
@@ -57,3 +57,10 @@
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/clean
hunt_targets = list(/obj/effect/decal/cleanable)
hunt_range = 4
+
+/// Find and hunt fish
+/datum/ai_planning_subtree/find_and_hunt_target/fish
+ hunt_targets = list(/mob/living/basic/carp,
+ /mob/living/simple_animal/hostile/retaliate/carp)
+ hunt_chance = 50
+ hunt_range = 16
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 8300801df9c..353c9051ff3 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -225,6 +225,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_morph, 16, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_uplifted_primitive, 16, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/aurora_caelus, 5, is_one_shot = TRUE),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/carp_migration/whale, 10, is_one_shot = TRUE),
//new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_pulsedemon, 20, is_one_shot = TRUE)
)
diff --git a/code/modules/events/whale_mirgration.dm b/code/modules/events/whale_mirgration.dm
new file mode 100644
index 00000000000..4809199ba2b
--- /dev/null
+++ b/code/modules/events/whale_mirgration.dm
@@ -0,0 +1,7 @@
+/datum/event/carp_migration/whale
+ name = "Whale Pod"
+ spawned_mobs = list(
+ /mob/living/basic/megafauna/space_whale)
+
+/datum/event/carp_migration/whale/start()
+ spawn_fish(rand(1, 2), 1, 3) // Whales are rather big and can cause good damage if angered so keeping numbers low
diff --git a/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale.dm b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale.dm
new file mode 100644
index 00000000000..366df27e617
--- /dev/null
+++ b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale.dm
@@ -0,0 +1,59 @@
+/mob/living/basic/megafauna/space_whale
+ name = "space whale"
+ desc = "A massive, generally non-hostile, creature swimming on the solar currents and eating space fish for food. Beware threatening them or their kin however."
+ health = 1500
+ maxHealth = 1500
+ icon = 'icons/mob/lavaland/192x192megafauna.dmi'
+ icon_state = "space_whale"
+ icon_living = "space_whale"
+ icon_dead = "space_whale_dead"
+ icon_gib = "carp_gib"
+ butcher_results = list(/obj/item/food/meat = 30,
+ /obj/item/stack/sheet/leather = 30) // In the future might add more unique loot,
+ speak_emote = list("calls")
+ melee_attack_cooldown_min = 1 SECONDS
+ damage_coeff = list(BRUTE = 0.75, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ melee_damage_lower = 35
+ melee_damage_upper = 45 // It's a megafauna. Don't get hit nerd.
+ attack_verb_simple = "bites"
+ attack_verb_continuous = "bites"
+ attack_sound = 'sound/effects/bite.ogg'
+ see_in_dark = 20 // I see you
+ pixel_x = -80
+ pixel_y = -96
+ faction = list("neutral", "whale")
+ step_type = FOOTSTEP_MOB_HEAVY
+ true_spawn = FALSE
+ ai_controller = /datum/ai_controller/basic_controller/space_whale
+ innate_actions = list(
+ /datum/action/cooldown/mob_cooldown/space_whale/charge = BB_WHALE_CHARGE_ACTION)
+
+ /// List of stuff (space fish) that we want to eat
+ var/static/list/edibles = list(
+ /mob/living/basic/carp,
+ /mob/living/basic/carp/megacarp,
+ /mob/living/simple_animal/hostile/retaliate/carp,
+ /mob/living/simple_animal/hostile/retaliate/carp/koi,
+ /mob/living/simple_animal/hostile/retaliate/carp/koi/honk,
+ )
+
+/mob/living/basic/megafauna/space_whale/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback)))
+ AddElement(/datum/element/basic_eating, heal_amt_ = 30, food_types_ = edibles)
+ ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(edibles))
+
+/mob/living/basic/megafauna/space_whale/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
+ return TRUE
+
+/mob/living/basic/megafauna/space_whale/proc/retaliate_callback(mob/living/attacker)
+ if(!istype(attacker))
+ return
+ if(attacker.ai_controller) // Don't chain retaliates.
+ var/list/shitlist = attacker.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]
+ if(src in shitlist)
+ return
+ for(var/mob/living/basic/megafauna/space_whale/harbringer in oview(src, 7))
+ if(harbringer == attacker) // Do not commit suicide attacking yourself
+ continue
+ harbringer.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker)
diff --git a/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_actions.dm b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_actions.dm
new file mode 100644
index 00000000000..7df9f6c1fc6
--- /dev/null
+++ b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_actions.dm
@@ -0,0 +1,61 @@
+/datum/action/cooldown/mob_cooldown/space_whale/charge
+ name = "Violent Charge"
+ button_icon_state = "OH_YEAAAAH"
+ desc = "Rear back and charge forward!"
+ melee_cooldown_time = CLICK_CD_CLICK_ABILITY
+ cooldown_time = 20 SECONDS
+ shared_cooldown = NONE
+
+/datum/action/cooldown/mob_cooldown/space_whale/charge/Activate(atom/target)
+ . = ..()
+ var/dir_to_target = get_dir(get_turf(owner), get_turf(target))
+ var/turf/T = get_step(get_turf(owner), dir_to_target)
+ for(var/i in 1 to 9)
+ new /obj/effect/temp_visual/dragon_swoop/space_whale(T)
+ T = get_step(T, dir_to_target)
+ owner.visible_message("[owner] prepares to charge!")
+ addtimer(CALLBACK(src, PROC_REF(charge_to), dir_to_target, 0), 4)
+ StartCooldown()
+
+/datum/action/cooldown/mob_cooldown/space_whale/charge/proc/charge_to(move_dir, times_ran, list/hit_targets = list())
+ var/mob/living/basic/whale = owner
+ if(times_ran >= 9)
+ return
+ var/turf/T = get_step(get_turf(owner), move_dir)
+ if(ismineralturf(T))
+ var/turf/simulated/mineral/M = T
+ M.gets_drilled()
+ if(iswallturf(T))
+ T.dismantle_wall(TRUE)
+ for(var/obj/structure/window/W in T.contents)
+ W.take_damage(whale.obj_damage, BRUTE)
+ for(var/obj/machinery/door/D in T.contents)
+ D.take_damage(whale.obj_damage, BRUTE)
+ if(T.x > world.maxx - 2 || T.x < 2) // Keep them from runtiming
+ return
+ if(T.y > world.maxy - 2 || T.y < 2)
+ return
+ owner.forceMove(T)
+ playsound(owner, 'sound/effects/bang.ogg', 200, 1)
+ var/throwtarget = get_edge_target_turf(owner, move_dir)
+ for(var/mob/living/L in T.contents - owner)
+ if(owner.faction_check_mob(L))
+ continue
+ owner.visible_message("[owner] rams into [L]!")
+ to_chat(L, "[owner] rams into you and bashes you away!")
+ L.throw_at(throwtarget, 10, 1, owner)
+ L.Weaken(1 SECONDS) // Pain Train has no breaks.
+ if(L in hit_targets)
+ L.adjustBruteLoss(15)
+ else
+ hit_targets += L
+ L.adjustBruteLoss(25)
+ addtimer(CALLBACK(src, PROC_REF(charge_to), move_dir, (times_ran + 1), hit_targets), 0.7)
+
+// The visual effect which appears in front of the whale when it goes to charge.
+/obj/effect/temp_visual/dragon_swoop/space_whale
+ icon_state = "warning"
+
+/obj/effect/temp_visual/dragon_swoop/space_whale/Initialize(mapload)
+ . = ..()
+ transform *= 0.33
diff --git a/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_ai.dm b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_ai.dm
new file mode 100644
index 00000000000..dc659fa3b4c
--- /dev/null
+++ b/code/modules/mob/living/basic/hostile/megafauna/space_whale/space_whale_ai.dm
@@ -0,0 +1,40 @@
+/datum/ai_controller/basic_controller/space_whale
+ movement_delay = 1 SECONDS
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
+ BB_AGGRO_RANGE = 7,
+ )
+ ai_movement = /datum/ai_movement/jps
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ interesting_dist = AI_MEGAFAUNA_INTERESTING_DIST
+ max_target_distance = 15
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/target_retaliate,
+ /datum/ai_planning_subtree/find_and_hunt_target/fish,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/targeted_mob_ability/whale_offensive_spellcasting,
+ /datum/ai_planning_subtree/attack_obstacle_in_path/walls,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ /datum/ai_planning_subtree/find_and_hunt_target/prowl,
+ /datum/ai_planning_subtree/attack_obstacle_in_path/prowl/walls,
+ )
+
+/datum/ai_planning_subtree/targeted_mob_ability/whale_offensive_spellcasting
+ ability_key = BB_WHALE_CHARGE_ACTION
+ /// Minimum time between any spells
+ var/whale_global_spellcasting_delay = 5 SECONDS
+
+/datum/ai_planning_subtree/targeted_mob_ability/whale_offensive_spellcasting/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(controller.blackboard[BB_WHALE_SPELL_COOLDOWN] >= world.time)
+ return
+ var/list/ability_keys = list(BB_WHALE_CHARGE_ACTION)
+ ability_key = pick_n_take(ability_keys)
+ var/datum/action/cooldown/mob_cooldown/selected_action = controller.blackboard[ability_key]
+ while(selected_action && !selected_action.IsAvailable())
+ if(!ability_keys.len) // All spells are on cooldown
+ return
+ ability_key = pick_n_take(ability_keys)
+ selected_action = controller.blackboard[ability_key]
+ controller.set_blackboard_key(BB_WHALE_SPELL_COOLDOWN, world.time + whale_global_spellcasting_delay)
+ return ..()
diff --git a/icons/mob/lavaland/192x192megafauna.dmi b/icons/mob/lavaland/192x192megafauna.dmi
new file mode 100644
index 00000000000..02de75936fd
Binary files /dev/null and b/icons/mob/lavaland/192x192megafauna.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 212034021fd..26e14303614 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -169,6 +169,7 @@
#include "code\__DEFINES\ai\hostile_mech_blackboard_defines.dm"
#include "code\__DEFINES\ai\incursion_blackboard_defines.dm"
#include "code\__DEFINES\ai\monkey_ai_defines.dm"
+#include "code\__DEFINES\ai\space_whale_defines.dm"
#include "code\__DEFINES\ai\swarmer_defines.dm"
#include "code\__DEFINES\dcs\ai_signals.dm"
#include "code\__DEFINES\dcs\area_signals.dm"
@@ -2313,6 +2314,7 @@
#include "code\modules\events\traders.dm"
#include "code\modules\events\vent_clog.dm"
#include "code\modules\events\wallrot.dm"
+#include "code\modules\events\whale_mirgration.dm"
#include "code\modules\events\wormholes.dm"
#include "code\modules\events\blob\blob_mobs.dm"
#include "code\modules\events\blob\blob_mobs_ai.dm"
@@ -2721,6 +2723,9 @@
#include "code\modules\mob\living\basic\hostile\megafauna\bluespace_horror\bluespace_horror.dm"
#include "code\modules\mob\living\basic\hostile\megafauna\bluespace_horror\bluespace_horror_actions.dm"
#include "code\modules\mob\living\basic\hostile\megafauna\bluespace_horror\bluespace_horror_ai.dm"
+#include "code\modules\mob\living\basic\hostile\megafauna\space_whale\space_whale.dm"
+#include "code\modules\mob\living\basic\hostile\megafauna\space_whale\space_whale_actions.dm"
+#include "code\modules\mob\living\basic\hostile\megafauna\space_whale\space_whale_ai.dm"
#include "code\modules\mob\living\basic\hostile\megafauna\vox_armalis\vox_armalis.dm"
#include "code\modules\mob\living\basic\hostile\megafauna\vox_armalis\vox_armalis_actions.dm"
#include "code\modules\mob\living\basic\hostile\nether_mobs\blankbody.dm"