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
@@ -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 ..()