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"