[EE] Adds space whale migration event as part of Event Extravaganza (#31946)

* Adding space whales

* Applies Pollard's requested fixes

* Update code/modules/events/whale_mirgration.dm

Co-authored-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Signed-off-by: Darkmight9 <dominojohny@gmail.com>

---------

Signed-off-by: Darkmight9 <dominojohny@gmail.com>
Co-authored-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
This commit is contained in:
Darkmight9
2026-05-13 22:24:02 +00:00
committed by GitHub
co-authored by PollardTheDragon
parent 8fe4bf5ded
commit 29c7691130
9 changed files with 182 additions and 0 deletions
@@ -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)
@@ -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("<span class='danger'>[owner] prepares to charge!</span>")
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("<span class='danger'>[owner] rams into [L]!</span>")
to_chat(L, "<span class='userdanger'>[owner] rams into you and bashes you away!</span>")
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
@@ -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 ..()