Adds Kidan Ship Ruin (#32081)

* Kidan Warriors

* Linters

* Kidan mob AI adjustments and stat adjustments. Kidan Princess Megafauna

* Bug spray vulnerability

* Ragnarok props and kidan spear update

* The ship

* Fixes and adjustments

* Map update

* Linters
This commit is contained in:
PollardTheDragon
2026-06-24 21:49:27 +00:00
committed by GitHub
parent d4055e6aa1
commit 8f654dfcc7
22 changed files with 9730 additions and 6 deletions
@@ -123,6 +123,11 @@ GLOBAL_LIST_INIT(aalarm_modes, list(
req_access = list(ACCESS_SYNDICATE)
req_one_access = list()
/// General space ruin air alarms
/obj/machinery/alarm/ruin
report_danger_level = FALSE
remote_control = FALSE
/obj/machinery/alarm/monitor/server
preset = AALARM_PRESET_SERVER
@@ -1236,6 +1241,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/alarm/engine, 32, 32)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/alarm/monitor, 32, 32)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/alarm/server, 32, 32)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/alarm/syndicate, 32, 32)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/alarm/ruin, 32, 32)
/*
AIR ALARM CIRCUIT
@@ -0,0 +1,65 @@
/// Used to track warrior/servant deaths
GLOBAL_VAR_INIT(ragnarok_kill_count, 0)
/obj/structure/environmental_storytelling_holopad/ragnarok_ship
speaking_name = "Princess Zrexx"
loop_sleep_time = 6 SECONDS
/// Did we say everything
var/done_talking = FALSE
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/start_message(mob/living/carbon/human/H)
activated = TRUE
QDEL_NULL(proximity_monitor)
icon_state = "holopad1"
update_icon(UPDATE_OVERLAYS)
var/obj/effect/overlay/hologram = new(get_turf(src))
our_holo = hologram
hologram.icon = getHologramIcon(icon('icons/mob/simple_human.dmi', "kidan_princess"), colour = null, opacity = 0.8, colour_blocking = TRUE)
hologram.alpha = 166
hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
hologram.layer = FLY_LAYER
hologram.anchored = TRUE
hologram.name = speaking_name
hologram.set_light(2)
hologram.bubble_icon = "swarmer"
hologram.pixel_y = 16
for(var/I in things_to_say)
hologram.atom_say("[I]")
sleep(loop_sleep_time)
qdel(hologram)
done_talking = TRUE
icon_state = "holopad0"
update_icon(UPDATE_OVERLAYS)
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/update_overlays()
. = ..()
underlays.Cut()
if(activated && !done_talking)
underlays += emissive_appearance(icon, "holopad1_lightmask")
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/dining_hall
things_to_say = list("Come, stranger, enjoy a feast. My servants have prepared a mighty banquet.",
"My guards will ensure you have a secure time, but they are a bit touchy, alas.",
"Do sit down and enjoy yourself, stranger.")
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/gardens
things_to_say = list("Explore my gardens, for they contain some of the final flora from our homeworld.",
"Take a seat and have a drink, yes?",
"If you don't stop to smell the flowers, you will find that the peaks in life come to dull your mind.")
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/baths
things_to_say = list("You're tense. Why don't you relax in the baths?",
"Join my soldiers and servants in their revelry. Enjoy the view of the stars from my windows.",
"The royal reagents infused in the waters will invigorate your body and mind. It is my honor for you to enjoy.")
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/bridge
things_to_say = list("There is a strangeness to this system. The proximity to the planet jams our navigation and sensor arrays.",
"However, it is such a wonderful source of the all-important plasma. Quite useful for creating the royal nectar.",
"Leave my soldiers to their work, stranger. You will not find opulence in such a place of focus.")
/obj/structure/environmental_storytelling_holopad/ragnarok_ship/vault
things_to_say = list("Know this, stranger, that you have spilled royal blood this day.",
"Take my yacht. Enjoy it as a trophy to your endeavors. There is much for us to learn from one another.",
"Take my weapon, may it serve you well. I will have another made, to fit this fresh body.",
"These gifts to you will be a reminder of my lineage and right to rule. Princess Zrexx has spoken.")
@@ -79,7 +79,7 @@
// Monkeys are a snowflake because you can't remove accessories once added.
if(!ishuman(target) || ismonkeybasic(target))
return ..()
var/mob/living/carbon/human/H = target
if(H.wear_suit && H.wear_suit.flags_inv & HIDEJUMPSUIT)
to_chat(user, "[H]'s body is covered, and you cannot attach [src].")
@@ -329,6 +329,10 @@
name = "vetus speculator HRD-MDE award"
desc = "An award which represents major contributions to the HRD-MDE project in the form of analysing the Vetus Speculator."
/obj/item/clothing/accessory/medal/plasma/princess
name = "kidan princess's prowess award"
desc = "An award given to those who enrage the Kidan Princess and triumphed."
/*
Holobadges are worn on the belt or neck, and can be used to show that the holder is an authorized
Security agent - the user details can be imprinted on the badge with a Security-access ID card,
@@ -398,7 +402,7 @@
/obj/item/clothing/accessory/holobadge/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(target == user || !ismob(target))
return ..()
user.visible_message(
SPAN_WARNING("[user] invades [target]'s personal space, thrusting [src] into [target.p_their()] face insistently."),
SPAN_WARNING("You invade [target]'s personal space, thrusting [src] into [target.p_their()] face insistently. You are THE LAW!"))
@@ -0,0 +1,198 @@
#define KIDAN_THRONEROOM "KIDAN_THRONEROOM"
/mob/living/basic/kidan_warrior
name = "Kidan Warrior"
desc = "A bulky insect-man wielding a large spear."
icon = 'icons/mob/simple_human.dmi'
icon_state = "kidan_warrior"
icon_living = "kidan_warrior"
icon_dead = "kidan_warrior_dead" // Does not actually exist. Dust on death
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID | MOB_BUG
death_sound = 'sound/voice/scream_kidan.ogg'
response_help_continuous = "pushes the"
response_help_continuous = "push the"
response_harm_continuous = "attacks"
response_harm_simple = "attack"
speed = 0
harm_intent_damage = 5
obj_damage = 40
melee_damage_lower = 15
melee_damage_upper = 20
melee_attack_cooldown_min = 1 SECONDS
attack_verb_continuous = "stabs"
attack_verb_simple = "stab"
attack_sound = 'sound/weapons/bladeslice.ogg'
is_ranged = TRUE
projectile_type = /obj/projectile/bullet/reusable/magspear
projectile_sound = 'sound/weapons/grenadelaunch.ogg'
ranged_cooldown = 1 SECONDS
a_intent = INTENT_HARM
damage_coeff = list(BRUTE = 0.8, BURN = 1, TOX = 1.5, STAMINA = 0, OXY = 1)
minimum_survivable_temperature = 0
ai_controller = /datum/ai_controller/basic_controller/simple/kidan_warrior
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
speak_emote = list("clacks")
blood_color = "#FB9800"
loot = list(
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/ash)
faction = list("kidan_royalty")
basic_mob_flags = DEL_ON_DEATH
sentience_type = SENTIENCE_OTHER
step_type = FOOTSTEP_MOB_SHOE
/mob/living/basic/kidan_warrior/Initialize(mapload)
. = ..()
AddComponent(/datum/component/aggro_emote, aggro_sound = 'sound/effects/Kidanclack.ogg', emote_chance = 100)
add_language("Galactic Common")
add_language("Chittin")
set_default_language(GLOB.all_languages["Chittin"])
AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback)))
var/datum/language/lang = GLOB.all_languages["Chittin"]
name = lang.get_random_name(gender)
real_name = name
if(prob(25))
loot = list(
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/ash,
/obj/item/kidan_spear)
/mob/living/basic/kidan_warrior/proc/retaliate_callback(mob/living/attacker)
if(!istype(attacker))
return
GLOB.kidan_shitlist += attacker
/mob/living/basic/kidan_warrior/ruin/death(gibbed)
GLOB.ragnarok_kill_count += 1
if(GLOB.ragnarok_kill_count >= 10)
unlock_blast_doors(KIDAN_THRONEROOM)
src.visible_message(SPAN_NOTICE("Somewhere, a heavy door has opened."))
return ..(gibbed)
/mob/living/basic/kidan_warrior/ruin/proc/unlock_blast_doors(target_id_tag)
for(var/obj/machinery/door/poddoor/P in GLOB.airlocks)
if(P.density && P.id_tag == target_id_tag && P.z == z && !P.operating)
P.open()
/datum/ai_controller/basic_controller/simple/kidan_warrior
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/kidan_warrior,
BB_AGGRO_RANGE = 9,
BB_SEARCH_RANGE = 10,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
)
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/ranged_skirmish/avoid_friendly,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
ai_movement = /datum/ai_movement/jps
max_target_distance = 15
/datum/targeting_strategy/basic/kidan_warrior
/datum/targeting_strategy/basic/kidan_warrior/can_attack(mob/living/living_mob, atom/the_target, vision_range)
. = ..()
if(!isliving(the_target))
return .
if(the_target in GLOB.kidan_shitlist)
return TRUE
return FALSE
/mob/living/basic/kidan_warrior/throneguard
name = "Kidan Throneguard"
desc = "The loyal guards of Kidan nobility."
icon_state = "kidan_throneguard"
icon_living = "kidan_throneguard"
damage_coeff = list(BRUTE = 0.6, BURN = 0.8, TOX = 1.5, STAMINA = 0, OXY = 1)
melee_damage_lower = 25
melee_damage_upper = 35
ai_controller = /datum/ai_controller/basic_controller/simple/kidan_warrior/throneguard
/datum/ai_controller/basic_controller/simple/kidan_warrior/throneguard
idle_behavior = null
/mob/living/basic/kidan_servant
name = "Kidan Servant"
desc = "A kidan who works in service of their nobility."
icon = 'icons/mob/simple_human.dmi'
icon_state = "kidan_servant"
icon_living = "kidan_servant"
icon_dead = "kidan_servant_dead" // Does not actually exist. Dust on death
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID | MOB_BUG
death_sound = 'sound/voice/scream_kidan.ogg'
response_help_continuous = "pushes the"
response_help_continuous = "push the"
response_harm_continuous = "attacks"
response_harm_simple = "attack"
speed = 0
harm_intent_damage = 5
obj_damage = 10
melee_damage_lower = 5
melee_damage_upper = 10
melee_attack_cooldown_min = 1 SECONDS
attack_verb_continuous = "punches"
attack_verb_simple = "punch"
damage_coeff = list(BRUTE = 0.8, BURN = 1, TOX = 1.5, STAMINA = 0, OXY = 1)
minimum_survivable_temperature = 0
ai_controller = /datum/ai_controller/basic_controller/kidan_servant
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
speak_emote = list("clacks")
blood_color = "#FB9800"
loot = list(
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/ash)
faction = list("kidan_royalty")
basic_mob_flags = DEL_ON_DEATH
sentience_type = SENTIENCE_OTHER
step_type = FOOTSTEP_MOB_SHOE
/mob/living/basic/kidan_servant/Initialize(mapload)
. = ..()
AddComponent(/datum/component/aggro_emote, aggro_sound = 'sound/voice/scream_kidan.ogg', emote_chance = 100)
add_language("Galactic Common")
add_language("Chittin")
set_default_language(GLOB.all_languages["Chittin"])
AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback)))
var/datum/language/lang = GLOB.all_languages["Chittin"]
name = lang.get_random_name(gender)
real_name = name
var/bonus_loot_type = rand(1, 5)
switch(bonus_loot_type)
if(1)
loot += /obj/item/clothing/neck/necklace/long
if(2)
loot += /obj/item/stack/sheet/mineral/gold
if(3)
loot += pick(list(/obj/item/reagent_containers/drinks/bottle/wine, /obj/item/reagent_containers/drinks/bottle/white_wine, /obj/item/reagent_containers/drinks/bottle/goldschlager))
if(4)
for(var/i in 1 to 9)
loot += /obj/item/coin/gold
if(5)
loot += /obj/item/food/beary_pie
/mob/living/basic/kidan_servant/proc/retaliate_callback(mob/living/attacker)
if(!istype(attacker))
return
GLOB.kidan_shitlist += attacker
/mob/living/basic/kidan_servant/ruin/death(gibbed)
GLOB.ragnarok_kill_count -= 1
return ..(gibbed)
/datum/ai_controller/basic_controller/kidan_servant
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/kidan_warrior,
BB_AGGRO_RANGE = 9,
BB_SEARCH_RANGE = 10,
)
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
/datum/ai_planning_subtree/flee_target,
)
ai_movement = /datum/ai_movement/jps
max_target_distance = 15
#undef KIDAN_THRONEROOM
@@ -0,0 +1,169 @@
#define KIDAN_BOSS_STORAGE "KIDAN_BOSS_STORAGE"
/mob/living/basic/megafauna/kidan_princess
name = "Princess Zrexx"
desc = "One of the claimants to the throne of the shattered Kidan empire, Princess Zrexx is one of the more extreme in keeping loyal to the ways of old."
health = 1500
maxHealth = 1500
gender = FEMALE
icon = 'icons/mob/simple_human.dmi'
icon_state = "kidan_princess"
icon_living = "kidan_princess"
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID | MOB_BUG | MOB_EPIC
speak_emote = list("clacks")
death_sound = 'sound/voice/scream_kidan.ogg'
melee_attack_cooldown_min = 0.75 SECONDS
damage_coeff = list(BRUTE = 0.7, BURN = 0.9, TOX = 1.5, STAMINA = 0, OXY = 1)
melee_damage_lower = 30
melee_damage_upper = 40
obj_damage = 80
attack_verb_simple = "stab"
attack_verb_continuous = "stabs"
attack_sound = 'sound/weapons/bladeslice.ogg'
response_help_continuous = "hugs"
response_help_continuous = "hug"
response_harm_continuous = "attacks"
response_harm_simple = "attack"
move_force = MOVE_FORCE_NORMAL
see_in_dark = 20 // I see you
step_type = FOOTSTEP_MOB_SHOE
initial_traits = list(TRAIT_NOFIRE)
blood_color = "#FB9800"
loot = list(
/obj/item/kidan_princess_halberd,
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/ash)
enraged_loot = /obj/item/clothing/accessory/medal/plasma/princess
faction = list("kidan_royalty")
ai_controller = /datum/ai_controller/basic_controller/kidan_princess
internal_gps = /obj/item/gps/internal/kidan_princess
medal_type = BOSS_MEDAL_PRINCESS
score_type = PRINCESS_SCORE
basic_mob_flags = DEL_ON_DEATH
sentience_type = SENTIENCE_OTHER
step_type = FOOTSTEP_MOB_SHOE
innate_actions = list(
/datum/action/cooldown/mob_cooldown/kidan_princess/summon_mobs = BB_KIDAN_PRINCESS_SUMMON_MOBS_ACTION,
/datum/action/cooldown/mob_cooldown/kidan_princess/charge = BB_KIDAN_PRINCESS_CHARGE_ACTION,
/datum/action/cooldown/mob_cooldown/kidan_princess/stomp = BB_KIDAN_PRINCESS_STOMP_ACTION,
/datum/action/cooldown/mob_cooldown/kidan_princess/martial_rush = BB_KIDAN_PRINCESS_RUSH_ACTION)
/// Is our martial rush active
var/martial_rushing = FALSE
/// Is this mob associated with a ruin
var/ruin_boss = FALSE
/obj/item/gps/internal/kidan_princess
icon_state = null
gpstag = "Royal Invitation"
desc = "You've been invited aboard the Ragnarok. Will you accept?"
/mob/living/basic/megafauna/kidan_princess/Initialize(mapload)
. = ..()
AddComponent(/datum/component/aggro_emote, aggro_sound = 'sound/effects/Kidanclack.ogg', emote_chance = 100, say_list = list("You seek to challenge me? Very well! Show me your strength!"))
add_language("Galactic Common")
add_language("Chittin")
set_default_language(GLOB.all_languages["Galactic Common"])
AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(retaliate_callback)))
/mob/living/basic/megafauna/kidan_princess/update_icon_state()
. = ..()
if(martial_rushing)
icon_state = "kidan_princess_unarmed"
icon_living = "kidan_princess_unarmed"
return
icon_state = "kidan_princess"
icon_living = "kidan_princess"
/mob/living/basic/megafauna/kidan_princess/Process_Spacemove(movement_dir, continuous_move)
return TRUE
/mob/living/basic/megafauna/kidan_princess/melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(ismob(target) && martial_rushing)
var/mob/victim = target
var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src)))
victim.throw_at(throw_target, 1, 2)
/mob/living/basic/megafauna/kidan_princess/proc/retaliate_callback(mob/living/attacker)
if(!istype(attacker))
return
GLOB.kidan_shitlist += attacker
ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, attacker)
if(!prob(10))
return
var/taunt = pick(list(
"Is that all you can do?",
"Yes, show me more!",
"Show me your power!",
"My blood spilled, bravo! Bravo!",
"That one actually hurt a bit! Haha!",
"Come then, strike again!",
"Not enough! More effort! More!",
"If I am to be a queen, then I must grow stronger!",
"Strike me down! Prove yourself strong, so that I may grow ever stronger!",
"Fight me!",
"Are you worthy, or are you going to hide?",
"Show some guts and hit me again!",
"We fight like the ways of old! This is wonderful!"
))
say(taunt)
/mob/living/basic/megafauna/kidan_princess/devour(mob/living/L)
if(!L)
return FALSE
visible_message(
"<span class='danger'>[src] brings down her halberd, obliterating [L] with a heavy blow!</span>",
"<span class='userdanger'>You bring down your halberd, obliterating [L] with a heavy blow!</span>")
playsound(loc, 'sound/effects/supermatter.ogg', 50, TRUE)
L.dust()
return TRUE
/mob/living/basic/megafauna/kidan_princess/death(gibbed)
if(can_die() && ruin_boss)
unlock_blast_doors(KIDAN_BOSS_STORAGE)
src.visible_message(SPAN_NOTICE("Somewhere, a heavy door has opened."))
return ..(gibbed)
/mob/living/basic/megafauna/kidan_princess/proc/unlock_blast_doors(target_id_tag)
for(var/obj/machinery/door/poddoor/P in GLOB.airlocks)
if(P.density && P.id_tag == target_id_tag && P.z == z && !P.operating)
P.open()
/mob/living/basic/megafauna/kidan_princess/ruin
ruin_boss = TRUE
/mob/living/basic/megafauna/kidan_princess/enrage()
. = ..()
say("You DARE sully my form with such filth?!")
melee_attack_cooldown_max = 1.25 SECONDS
/// Special Halberd loot
/obj/item/kidan_princess_halberd
name = "Zrexx's modified supermatter halberd"
desc = "The personal weapon of the kidan princess Zrexx. Damaged in the fight, it no longer can dust, but the edge remains incredibly sharp."
lefthand_file = 'icons/mob/inhands/weapons_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons_righthand.dmi'
icon = 'icons/obj/weapons/magical_weapons.dmi'
icon_state = "smhalberd0"
base_icon_state = "smhalberd"
force = 5
sharp = TRUE
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
throwforce = 15
toolspeed = 0.25
attack_verb = list("cleaved", "stabbed", "whacked", "slashed", "stabbed")
hitsound = 'sound/weapons/bladeslice.ogg'
resistance_flags = FIRE_PROOF
/obj/item/kidan_princess_halberd/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SUPERMATTER_IMMUNE, ROUNDSTART_TRAIT) // so it can't be dusted by the SM
AddComponent(/datum/component/forces_doors_open)
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.25, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (4 / 3) SECONDS, _requires_two_hands = TRUE) // 0.3333 seconds of cooldown for 75% uptime
AddComponent(/datum/component/two_handed, force_wielded = 30, force_unwielded = force, icon_wielded = "[base_icon_state]1")
/obj/item/kidan_princess_halberd/update_icon_state()
icon_state = "[base_icon_state]0"
#undef KIDAN_BOSS_STORAGE
@@ -0,0 +1,192 @@
/datum/action/cooldown/mob_cooldown/kidan_princess/summon_mobs
name = "Summon Throneguards"
button_icon = 'icons/mob/actions/actions_cult.dmi'
button_icon_state = "telerune"
desc = "Summon a pair of throneguards to protect you."
click_to_activate = FALSE
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
cooldown_time = 90 SECONDS
shared_cooldown = NONE
/datum/action/cooldown/mob_cooldown/kidan_princess/summon_mobs/Activate(atom/target)
var/mob/living/basic/megafauna/kidan_princess/summoner = owner
if(!istype(summoner))
to_chat(owner, "<span class='warning'>I am unable to summon servants!</span>")
return
var/list/all_possible_dirs = GLOB.alldirs
for(var/i in 1 to 2)
var/mob/living/new_mob
var/spawn_loc = get_step(summoner, pick_n_take(all_possible_dirs))
new_mob = new /mob/living/basic/kidan_warrior/throneguard(spawn_loc)
do_sparks(3, TRUE, new_mob)
summoner.say("Guards! To me!")
StartCooldown()
/datum/action/cooldown/mob_cooldown/kidan_princess/charge
name = "Royal Charge"
desc = "Charge forward into the fray!"
button_icon_state = "OH_YEAAAAH"
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
cooldown_time = 20 SECONDS
shared_cooldown = NONE
/datum/action/cooldown/mob_cooldown/kidan_princess/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)
if(ismineralturf(T))
break
if(iswallturf(T))
break
for(var/obj/structure/window/W in T.contents)
break
for(var/obj/machinery/door/D in T.contents)
break
owner.visible_message("<span class='danger'>[owner] prepares to charge!</span>")
addtimer(CALLBACK(src, PROC_REF(charge_to), dir_to_target, 0), 0.3 SECONDS)
StartCooldown()
/datum/action/cooldown/mob_cooldown/kidan_princess/charge/proc/charge_to(move_dir, times_ran, list/hit_targets)
if(times_ran >= 9)
return
var/turf/T = get_step(get_turf(owner), move_dir)
if(ismineralturf(T))
return
if(iswallturf(T))
return
for(var/obj/structure/window/W in T.contents)
return
for(var/obj/machinery/door/D in T.contents)
return
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] slams into [L]!</span>")
to_chat(L, "<span class='userdanger'>[owner] charges into you and smashes 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)
/datum/action/cooldown/mob_cooldown/kidan_princess/stomp
name = "Shockwave"
desc = "You slam your foot into the ground sending a powerful shockwave through the station's hull, sending people flying away."
button_icon_state = "seismic_stomp"
click_to_activate = FALSE
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
cooldown_time = 40 SECONDS
shared_cooldown = NONE
var/max_range = 8
/datum/action/cooldown/mob_cooldown/kidan_princess/stomp/Activate(atom/target)
var/mob/living/basic/megafauna/kidan_princess/princess = owner
var/turf/T = get_turf(princess)
playsound(T, 'sound/effects/meteorimpact.ogg', 100, TRUE)
playsound(T, 'sound/magic/invoke_general.ogg', 300, TRUE, 5)
princess.say("Kneel!")
addtimer(CALLBACK(src, PROC_REF(hit_check), 1, T, princess), 0.2 SECONDS)
new /obj/effect/temp_visual/stomp(T)
/datum/action/cooldown/mob_cooldown/kidan_princess/stomp/proc/hit_check(range, turf/start_turf, mob/user, safe_targets = list())
// gets the two outermost turfs in a ring, we get two so people cannot "walk over" the shockwave
var/list/targets = view(range, start_turf) - view(range - 2, start_turf)
for(var/turf/simulated/floor/flooring in targets)
if(prob(100 - (range * 20)))
flooring.ex_act(EXPLODE_LIGHT)
for(var/mob/living/L in targets)
if(istype(L, /mob/living/basic/kidan_warrior))
continue
if(L in safe_targets)
continue
if(L.throwing) // no double hits
continue
if(L.move_resist > MOVE_FORCE_VERY_STRONG)
continue
var/throw_target = get_edge_target_turf(L, get_dir(start_turf, L))
INVOKE_ASYNC(L, TYPE_PROC_REF(/atom/movable, throw_at), throw_target, 3, 4)
L.KnockDown(2 SECONDS)
L.adjustBruteLoss(20)
safe_targets += L
var/new_range = range + 1
if(new_range <= max_range)
addtimer(CALLBACK(src, PROC_REF(hit_check), new_range, start_turf, user, safe_targets), 0.2 SECONDS)
/datum/action/cooldown/mob_cooldown/kidan_princess/martial_rush
name = "Martial Rush"
button_icon = 'icons/obj/surgery.dmi'
button_icon_state = "sandy"
desc = "Declare your intent to win, disgarding your halberd in favor of rapid punching attacks for ten seconds."
click_to_activate = FALSE
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
cooldown_time = 75 SECONDS
shared_cooldown = NONE
/datum/action/cooldown/mob_cooldown/kidan_princess/martial_rush/Activate(atom/target)
var/mob/living/basic/megafauna/kidan_princess/princess = owner
if(!istype(princess))
return
RegisterSignal(princess, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement))
princess.next_move_modifier -= 0.5
princess.speed -= 1
princess.attack_verb_continuous = "punches"
princess.attack_verb_simple = "punch"
if(princess.enraged)
princess.melee_attack_cooldown_min = 0.2 SECONDS
princess.melee_attack_cooldown_max = 0.3 SECONDS
princess.melee_damage_lower = 10
princess.melee_damage_upper = 15
else
princess.melee_attack_cooldown_min = 0.3 SECONDS
princess.melee_attack_cooldown_max = 0.4 SECONDS
princess.melee_damage_lower = 5
princess.melee_damage_upper = 10
princess.attack_sound ='sound/weapons/punch1.ogg'
princess.martial_rushing = TRUE
princess.update_icon(UPDATE_ICON_STATE)
addtimer(CALLBACK(src, PROC_REF(end_rush)), 10 SECONDS)
playsound(get_turf(princess), 'sound/magic/invoke_general.ogg', 300, TRUE, 5)
princess.say("FACE ME!")
StartCooldown()
/// Leaves an afterimage behind the mob when they move
/datum/action/cooldown/mob_cooldown/kidan_princess/martial_rush/proc/on_movement(mob/living/L, atom/old_loc)
SIGNAL_HANDLER
if(HAS_TRAIT(L, TRAIT_IMMOBILIZED))
return NONE
new /obj/effect/temp_visual/decoy/mephedrone_afterimage(old_loc, L, 1.25 SECONDS)
/datum/action/cooldown/mob_cooldown/kidan_princess/martial_rush/proc/end_rush()
var/mob/living/basic/megafauna/kidan_princess/princess = owner
if(!istype(princess))
return
UnregisterSignal(princess, COMSIG_MOVABLE_MOVED)
princess.next_move_modifier = initial(princess.move_speed)
princess.speed = initial(princess.speed)
princess.attack_verb_continuous = initial(princess.attack_verb_continuous)
princess.attack_verb_simple = initial(princess.attack_verb_simple)
princess.melee_attack_cooldown_min = initial(princess.melee_attack_cooldown_min)
if(princess.enraged)
princess.melee_attack_cooldown_max = 1.25 SECONDS
else
princess.melee_attack_cooldown_max = initial(princess.melee_attack_cooldown_max)
princess.melee_damage_lower = initial(princess.melee_damage_lower)
princess.melee_damage_upper = initial(princess.melee_damage_upper)
princess.attack_sound = initial(princess.attack_sound)
princess.martial_rushing = FALSE
princess.update_icon(UPDATE_ICON_STATE)
@@ -0,0 +1,39 @@
/datum/ai_controller/basic_controller/kidan_princess
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/kidan_warrior,
BB_AGGRO_RANGE = 9,
BB_SEARCH_RANGE = 10,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
)
planning_subtrees = list(
/datum/ai_planning_subtree/find_and_hunt_target/corpses/human,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/targeted_mob_ability/kidan_princess_spellcasting,
/datum/ai_planning_subtree/attack_obstacle_in_path/walls,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
ai_movement = /datum/ai_movement/jps
interesting_dist = AI_MEGAFAUNA_INTERESTING_DIST
max_target_distance = 15
/datum/ai_planning_subtree/targeted_mob_ability/kidan_princess_spellcasting
ability_key = BB_KIDAN_PRINCESS_CHARGE_ACTION
/// Minimum time between any spells
var/kidan_princess_global_spellcasting_delay = 6 SECONDS
/datum/ai_planning_subtree/targeted_mob_ability/kidan_princess_spellcasting/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
if(controller.blackboard[BB_KIDAN_PRINCESS_SPELL_COOLDOWN] >= world.time)
return
var/list/ability_keys = list(BB_KIDAN_PRINCESS_CHARGE_ACTION, BB_KIDAN_PRINCESS_SUMMON_MOBS_ACTION, BB_KIDAN_PRINCESS_RUSH_ACTION, BB_KIDAN_PRINCESS_STOMP_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]
if(ability_key == BB_KIDAN_PRINCESS_RUSH_ACTION)
controller.set_blackboard_key(BB_KIDAN_PRINCESS_SPELL_COOLDOWN, world.time + 16 SECONDS)
else
controller.set_blackboard_key(BB_KIDAN_PRINCESS_SPELL_COOLDOWN, world.time + kidan_princess_global_spellcasting_delay)
return ..()
@@ -1220,6 +1220,10 @@
if(M.mob_biotypes & MOB_BUG)
var/damage = min(round(0.4 * volume, 0.1), 10)
M.adjustToxLoss(damage)
if(istype(M, /mob/living/basic/megafauna/kidan_princess))
var/mob/living/basic/megafauna/kidan_princess/princess = M
if(!princess.enraged)
princess.enrage()
if(iscarbon(M))
var/mob/living/carbon/C = M
var/damage = 1