From 07f2214ac455f540d8a8f21ea796d47f25369fe6 Mon Sep 17 00:00:00 2001 From: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Date: Sat, 4 Nov 2023 11:22:19 -0500 Subject: [PATCH] Basic Wizards (#79476) ## About The Pull Request Makes NPC wizard mobs into basic mobs. These aren't actually used anywhere, since their away mission was removed, so I figured I'd have some fun with them. ![image](https://github.com/tgstation/tgstation/assets/105025397/901f3a92-74a3-492b-8435-43813ad2afa8) Noteworthy changes are as follows: - Wizard mobs can now wear any of the four basic colors of wizard robe and hat. Rarely, they will wear the witch ("Marisa") outfit instead. There was going to be a rare Tape Wizard spawn too, but the outfit doesn't work correctly for some reason (see comments below). - Wizard mobs no longer have a set spell loadout. Instead, they always receive a random **targeted, primary spell**, a random **untargeted, secondary spell**, and the spell **Blink**. - Wizard subtypes (or var-edited wizards) can have any of the above specified rather than random. - Wizard AI will try to avoid ever being in melee, and will fire off spells whenever possible with the priority order of primary > secondary > blink. There is a mandatory 1-second waiting period between casts. - Wizard mobs use "lesser" versions of Fireball and Blink. Lesser Fireball does a little less damage and has a smaller explosion, though it is still extremely dangerous. Lesser Blink simply has a smaller blink radius so that wizard mobs don't just decide to leave. Depending on their spell loadout, wizards can be _incredibly_ dangerous mobs - stunning you with tesla blasts, shooting you with fireballs, and more. Even weakened, fireball is the nastiest of these by far, able to set you on fire or remove your limbs if you aren't properly protected. Watch out! The random lists have been kept pretty small, since I only wanted to use spells that I know for sure work. Spell cards is pretty weak in AI hands, since they don't take advantage of the fact that a human player can spam it, but I wanted there to be a little variety in primary attacks. I included an UpdatePaths script here in case a downstream is using these, but I doubt it was necessary. ## Why It's Good For The Game Removes another simplemob. The actual impact of this change is negligible, since this is an unused mob, but it's possible that this lays the groundwork for these actually being used - particularly since mappers can make them as powerful or weak as they want by specifying a spell loadout. Wizards may make for a tough boss challenge for a ruin or away mission, or a very mean surprise for an admin to drop on an unsuspecting station. ## Changelog As this is an unused mob, there are no player-facing changes. --- code/__DEFINES/ai/monsters.dm | 10 +++ code/_globalvars/phobias.dm | 4 +- .../basic/ruin_defender/wizard/wizard.dm | 90 +++++++++++++++++++ .../basic/ruin_defender/wizard/wizard_ai.dm | 53 +++++++++++ .../ruin_defender/wizard/wizard_spells.dm | 17 ++++ .../living/simple_animal/hostile/wizard.dm | 77 ---------------- code/modules/mob_spawn/corpses/mob_corpses.dm | 36 ++++++++ .../unit_tests/simple_animal_freeze.dm | 1 - tgstation.dme | 4 +- .../Scripts/79476_simple_to_basic_wizard.txt | 1 + 10 files changed, 212 insertions(+), 81 deletions(-) create mode 100644 code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm create mode 100644 code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm create mode 100644 code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/wizard.dm create mode 100644 tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 0aa0c97de8b..7fd7556125b 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -189,3 +189,13 @@ #define BB_MOOK_MUSIC_AUDIENCE "music_audience" /// the bonfire we will light up #define BB_MOOK_BONFIRE_TARGET "bonfire_target" + +//Wizard AI keys +/// Key where we store our main targeted spell +#define BB_WIZARD_TARGETED_SPELL "BB_wizard_targeted_spell" +/// Key where we store our secondary, untargeted spell +#define BB_WIZARD_SECONDARY_SPELL "BB_wizard_secondary_spell" +/// Key where we store our blink spell +#define BB_WIZARD_BLINK_SPELL "BB_wizard_blink_spell" +/// Key for the next time we can cast a spell +#define BB_WIZARD_SPELL_COOLDOWN "BB_wizard_spell_cooldown" diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 9cfa92231a6..36a4329ffbf 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -96,12 +96,12 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/basic/faithless, /mob/living/basic/ghost, /mob/living/basic/heretic_summon, + /mob/living/basic/revenant, /mob/living/basic/shade, /mob/living/basic/skeleton, - /mob/living/basic/revenant, + /mob/living/basic/wizard, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, )), )) diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm new file mode 100644 index 00000000000..7c35184af37 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.dm @@ -0,0 +1,90 @@ +/mob/living/basic/wizard + name = "Space Wizard" + desc = "A wizard is never early. Nor is he late. He arrives exactly at the worst possible moment." + icon = 'icons/mob/simple/simple_human.dmi' + icon_state = "wizard" + icon_living = "wizard" + icon_dead = "wizard_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + sentience_type = SENTIENCE_HUMANOID + speed = 0 + maxHealth = 100 + health = 100 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + combat_mode = TRUE + habitable_atmos = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 7.5 + faction = list(ROLE_WIZARD) + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/wizard + + /// A list of possible wizard corpses, and therefore wizard outfits, to select from + var/static/list/wizard_outfits = list( + /obj/effect/mob_spawn/corpse/human/wizard = 5, + /obj/effect/mob_spawn/corpse/human/wizard/red = 3, + /obj/effect/mob_spawn/corpse/human/wizard/yellow = 3, + /obj/effect/mob_spawn/corpse/human/wizard/black = 3, + /obj/effect/mob_spawn/corpse/human/wizard/marisa = 1, + //The tape wizard should go here, but its hat doesn't render correctly for some reason. + ) + /// A specified wizard corpse spawner to use. If null, picks from the list above instead. + var/selected_outfit + + /// Typepath for the wizard's targeted spell. If null, selects randomly. + var/targeted_spell_path + /// List of possible targeted spells to pick from + var/static/list/targeted_spell_list = list( + /datum/action/cooldown/spell/pointed/projectile/fireball/lesser, + /datum/action/cooldown/spell/pointed/projectile/lightningbolt, + ) + + /// Typepath for the wizard's secondary spell. If null, selects randomly. + var/secondary_spell_path + /// List of possible secondary spells to pick from + var/static/list/secondary_spell_list = list( + /datum/action/cooldown/spell/aoe/magic_missile, + /datum/action/cooldown/spell/charged/beam/tesla, + /datum/action/cooldown/spell/aoe/repulse, + /datum/action/cooldown/spell/conjure/the_traps, + ) + +/mob/living/basic/wizard/Initialize(mapload) + . = ..() + if(!selected_outfit) + selected_outfit = pick_weight(wizard_outfits) + apply_dynamic_human_appearance(src, mob_spawn_path = selected_outfit, r_hand = /obj/item/staff) + var/list/remains = string_list(list( + selected_outfit, + /obj/item/staff + )) + AddElement(/datum/element/death_drops, remains) + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + + if(isnull(targeted_spell_path)) + targeted_spell_path = pick(targeted_spell_list) + if(isnull(secondary_spell_path)) + secondary_spell_path = pick(secondary_spell_list) + + var/datum/action/cooldown/spell/targeted_spell = new targeted_spell_path(src) + targeted_spell.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + targeted_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_TARGETED_SPELL, targeted_spell) + + var/datum/action/cooldown/spell/secondary_spell = new secondary_spell_path(src) + secondary_spell.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + secondary_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_SECONDARY_SPELL, secondary_spell) + + var/datum/action/cooldown/spell/teleport/radius_turf/blink/lesser/blink_spell = new(src) + blink_spell.Grant(src) + ai_controller.set_blackboard_key(BB_WIZARD_BLINK_SPELL, blink_spell) + +/// Uses the colors and loadout of the original wizard simplemob +/mob/living/basic/wizard/classic + selected_outfit = /obj/effect/mob_spawn/corpse/human/wizard + targeted_spell_path = /datum/action/cooldown/spell/pointed/projectile/fireball/lesser + secondary_spell_path = /datum/action/cooldown/spell/aoe/magic_missile diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm new file mode 100644 index 00000000000..ad3fd51b0f9 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm @@ -0,0 +1,53 @@ +#define WIZARD_SPELL_COOLDOWN (1 SECONDS) + +/** + * Wizards run away from their targets while flinging spells at them and blinking constantly. + */ +/datum/ai_controller/basic_controller/wizard + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary, + /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink, + ) + +/** + * Cast a wizard spell. There is a minimum cooldown between spellcasts to prevent overwhelming spam. + * + * Though only the primary spell is actually targeted, all spells use targeted behavior so that they + * only get used in combat. + */ +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell + use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/wizard_spell + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if (controller.blackboard[BB_WIZARD_SPELL_COOLDOWN] > world.time) + return + return ..() + +/datum/ai_behavior/targeted_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) + . = ..() + controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary + ability_key = BB_WIZARD_TARGETED_SPELL + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary + ability_key = BB_WIZARD_SECONDARY_SPELL + +/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink + ability_key = BB_WIZARD_BLINK_SPELL + +/datum/ai_behavior/use_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) + . = ..() + controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) + +#undef WIZARD_SPELL_COOLDOWN diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm new file mode 100644 index 00000000000..c49d87c730a --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_spells.dm @@ -0,0 +1,17 @@ +// Lesser versions of wizard spells to be used by AI wizards + +/// Lesser fireball, which is slightly less "instant death" than the normal one +/datum/action/cooldown/spell/pointed/projectile/fireball/lesser + name = "Lesser Fireball" + projectile_type = /obj/projectile/magic/fireball/lesser + cooldown_time = 10 SECONDS + +/obj/projectile/magic/fireball/lesser + damage = 0 + exp_light = 1 + +/// Lesser Blink, shorter range than the normal blink spell +/datum/action/cooldown/spell/teleport/radius_turf/blink/lesser + name = "Lesser Blink" + outer_tele_radius = 3 + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm deleted file mode 100644 index 59b44077661..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/wizard.dm +++ /dev/null @@ -1,77 +0,0 @@ -/mob/living/simple_animal/hostile/wizard - name = "Space Wizard" - desc = "EI NATH?" - icon = 'icons/mob/simple/simple_human.dmi' - icon_state = "wizard" - icon_living = "wizard" - icon_dead = "wizard_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speak_chance = 0 - turns_per_move = 3 - speed = 0 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 5 - melee_damage_upper = 5 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/weapons/punch1.ogg' - combat_mode = TRUE - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(ROLE_WIZARD) - status_flags = CANPUSH - footstep_type = FOOTSTEP_MOB_SHOE - - retreat_distance = 3 //out of fireball range - minimum_distance = 3 - del_on_death = 1 - loot = list( - /obj/effect/mob_spawn/corpse/human/wizard, - /obj/item/staff, - ) - - var/next_cast = 0 - var/datum/action/cooldown/spell/pointed/projectile/fireball/fireball - var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink - var/datum/action/cooldown/spell/aoe/magic_missile/magic_missile - -/mob/living/simple_animal/hostile/wizard/Initialize(mapload) - . = ..() - apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/wizard, r_hand = /obj/item/staff) - var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(src) - exiled.implant(src) - - fireball = new(src) - fireball.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - fireball.Grant(src) - - magic_missile = new(src) - magic_missile.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - magic_missile.Grant(src) - - blink = new(src) - blink.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) - blink.outer_tele_radius = 3 - blink.Grant(src) - -/mob/living/simple_animal/hostile/wizard/handle_automated_action() - . = ..() - if(target && next_cast < world.time) - if((get_dir(src, target) in list(SOUTH, EAST, WEST, NORTH)) && fireball.can_cast_spell(feedback = FALSE)) - setDir(get_dir(src, target)) - fireball.Trigger(null, target) - next_cast = world.time + 1 SECONDS - return - - if(magic_missile.IsAvailable()) - magic_missile.Trigger(null, target) - next_cast = world.time + 1 SECONDS - return - - if(blink.IsAvailable()) // Spam Blink when you can - blink.Trigger(null, src) - next_cast = world.time + 1 SECONDS - return diff --git a/code/modules/mob_spawn/corpses/mob_corpses.dm b/code/modules/mob_spawn/corpses/mob_corpses.dm index 476c3f70a84..f83dc13f1ed 100644 --- a/code/modules/mob_spawn/corpses/mob_corpses.dm +++ b/code/modules/mob_spawn/corpses/mob_corpses.dm @@ -221,6 +221,21 @@ facial_haircolor = COLOR_WHITE skin_tone = "caucasian1" +/obj/effect/mob_spawn/corpse/human/wizard/red + outfit = /datum/outfit/wizardcorpse/red + +/obj/effect/mob_spawn/corpse/human/wizard/yellow + outfit = /datum/outfit/wizardcorpse/yellow + +/obj/effect/mob_spawn/corpse/human/wizard/black + outfit = /datum/outfit/wizardcorpse/black + +/obj/effect/mob_spawn/corpse/human/wizard/marisa + outfit = /datum/outfit/wizardcorpse/marisa + +/obj/effect/mob_spawn/corpse/human/wizard/tape + outfit = /datum/outfit/wizardcorpse/tape + /datum/outfit/wizardcorpse name = "Space Wizard Corpse" uniform = /obj/item/clothing/under/color/lightpurple @@ -228,6 +243,27 @@ shoes = /obj/item/clothing/shoes/sandal/magic head = /obj/item/clothing/head/wizard +/datum/outfit/wizardcorpse/red + suit = /obj/item/clothing/suit/wizrobe/red + head = /obj/item/clothing/head/wizard/red + +/datum/outfit/wizardcorpse/yellow + suit = /obj/item/clothing/suit/wizrobe/yellow + head = /obj/item/clothing/head/wizard/yellow + +/datum/outfit/wizardcorpse/black + suit = /obj/item/clothing/suit/wizrobe/black + head = /obj/item/clothing/head/wizard/black + +/datum/outfit/wizardcorpse/marisa + suit = /obj/item/clothing/suit/wizrobe/marisa + head = /obj/item/clothing/head/wizard/marisa + shoes = /obj/item/clothing/shoes/sneakers/marisa + +/datum/outfit/wizardcorpse/tape + suit = /obj/item/clothing/suit/wizrobe/tape + head = /obj/item/clothing/head/wizard/tape + /obj/effect/mob_spawn/corpse/human/wizard/dark name = "Dark Wizard Corpse" outfit = /datum/outfit/wizardcorpse/dark diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index c25aa2cd2e5..3d0f60ae072 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -109,7 +109,6 @@ /mob/living/simple_animal/hostile/retaliate/goose, /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/simple_animal/hostile/vatbeast, - /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, /mob/living/simple_animal/parrot, /mob/living/simple_animal/parrot/natural, diff --git a/tgstation.dme b/tgstation.dme index 9446949bf2f..1e8fc247e29 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4508,6 +4508,9 @@ #include "code\modules\mob\living\basic\ruin_defender\living_floor.dm" #include "code\modules\mob\living\basic\ruin_defender\skeleton.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_ai.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_spells.dm" #include "code\modules\mob\living\basic\space_fauna\ant.dm" #include "code\modules\mob\living\basic\space_fauna\cat_surgeon.dm" #include "code\modules\mob\living\basic\space_fauna\faithless.dm" @@ -4788,7 +4791,6 @@ #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" -#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" diff --git a/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt b/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt new file mode 100644 index 00000000000..55758f2bbdf --- /dev/null +++ b/tools/UpdatePaths/Scripts/79476_simple_to_basic_wizard.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/wizard : /mob/living/basic/wizard{@OLD}