From 5bb6f405c283926d398e328ee791e2167eca70ae Mon Sep 17 00:00:00 2001 From: mistakenot4892 Date: Thu, 8 Jan 2026 21:24:42 +1100 Subject: [PATCH] Working on implementing anima. Removes the 'veer' language stolen from Erickson. Working commit for additional spells. --- maps/karzerfeste/karzerfeste.dm | 1 + maps/shaded_hills/shaded_hills.dm | 2 +- maps/shaded_hills/shaded_hills_skills.dm | 6 - mods/content/anima/_anima.dm | 15 -- mods/content/anima/_anima.dme | 14 -- mods/content/anima/spell_archetypes.dm | 29 ---- mods/content/anima/spell_datum.dm | 14 -- mods/content/anima/spell_effect.dm | 100 ------------- mods/content/anima/spellcasting.dm | 6 - mods/content/wyrd/_wyrd.dm | 19 +++ mods/content/wyrd/_wyrd.dme | 28 ++++ mods/content/wyrd/anima/_anima.dm | 76 ++++++++++ mods/content/wyrd/anima/anima_aura.dm | 40 ++++++ mods/content/wyrd/anima/anima_source.dm | 28 ++++ mods/content/wyrd/anima/area.dm | 26 ++++ mods/content/wyrd/anima/culture.dm | 40 ++++++ mods/content/wyrd/anima/turf.dm | 39 +++++ mods/content/wyrd/icons/abilities.dmi | Bin 0 -> 752 bytes .../anima_blank.dmi => wyrd/icons/blanks.dmi} | Bin 424 -> 424 bytes .../{anima => wyrd}/icons/runestone_basic.dmi | Bin .../icons/runestone_gilded.dmi | Bin .../icons/runestone_layered.dmi | Bin mods/content/wyrd/icons/workings.dmi | Bin 0 -> 457 bytes .../potentia/_potentia.dm} | 33 +++-- .../potentia}/potentia_stack.dm | 8 +- .../wyrd/spells/practices/_practice.dm | 3 + .../wyrd/spells/practices/black_art.dm | 9 ++ .../wyrd/spells/practices/hedge_magic.dm | 6 + .../content/wyrd/spells/practices/rose_art.dm | 42 ++++++ .../content/wyrd/spells/practices/sky_sign.dm | 20 +++ .../wyrd/spells/practices/solar_arts.dm | 6 + .../wyrd/spells/practices/white_art.dm | 7 + .../spells/runestones/_runestones.dm} | 72 +++++----- .../spells/runestones}/spellscribing.dm | 32 ++--- mods/content/wyrd/spells/spell_archetypes.dm | 28 ++++ mods/content/wyrd/spells/spell_datum.dm | 14 ++ mods/content/wyrd/spells/spell_effect.dm | 136 ++++++++++++++++++ mods/content/wyrd/spells/spellcasting.dm | 80 +++++++++++ .../datum => content/wyrd}/traits/_wyrd.dm | 0 .../wyrd/traits/wyrd_signs.dm} | 69 +++++---- .../wyrd}/traits/wyrd_wild.dm | 26 +++- mods/content/wyrd/wyrd_abilities.dm | 53 +++++++ .../mask.dm => content/wyrd/wyrdling_mask.dm} | 50 +++---- mods/pyrelight/_pyrelight.dme | 6 - mods/pyrelight/datum/wyrdling/ears.dm | 2 - mods/pyrelight/datum/wyrdling/tails.dm | 2 - mods/~compatibility/patches/fantasy.dm | 6 +- .../patches/fantasy/wyrd_fantasy.dm | 16 +++ test/check-paths.sh | 2 +- 49 files changed, 882 insertions(+), 329 deletions(-) delete mode 100644 mods/content/anima/_anima.dm delete mode 100644 mods/content/anima/_anima.dme delete mode 100644 mods/content/anima/spell_archetypes.dm delete mode 100644 mods/content/anima/spell_datum.dm delete mode 100644 mods/content/anima/spell_effect.dm delete mode 100644 mods/content/anima/spellcasting.dm create mode 100644 mods/content/wyrd/_wyrd.dm create mode 100644 mods/content/wyrd/_wyrd.dme create mode 100644 mods/content/wyrd/anima/_anima.dm create mode 100644 mods/content/wyrd/anima/anima_aura.dm create mode 100644 mods/content/wyrd/anima/anima_source.dm create mode 100644 mods/content/wyrd/anima/area.dm create mode 100644 mods/content/wyrd/anima/culture.dm create mode 100644 mods/content/wyrd/anima/turf.dm create mode 100644 mods/content/wyrd/icons/abilities.dmi rename mods/content/{anima/icons/anima_blank.dmi => wyrd/icons/blanks.dmi} (69%) rename mods/content/{anima => wyrd}/icons/runestone_basic.dmi (100%) rename mods/content/{anima => wyrd}/icons/runestone_gilded.dmi (100%) rename mods/content/{anima => wyrd}/icons/runestone_layered.dmi (100%) create mode 100644 mods/content/wyrd/icons/workings.dmi rename mods/content/{anima/potentia_definition.dm => wyrd/potentia/_potentia.dm} (58%) rename mods/content/{anima => wyrd/potentia}/potentia_stack.dm (85%) create mode 100644 mods/content/wyrd/spells/practices/_practice.dm create mode 100644 mods/content/wyrd/spells/practices/black_art.dm create mode 100644 mods/content/wyrd/spells/practices/hedge_magic.dm create mode 100644 mods/content/wyrd/spells/practices/rose_art.dm create mode 100644 mods/content/wyrd/spells/practices/sky_sign.dm create mode 100644 mods/content/wyrd/spells/practices/solar_arts.dm create mode 100644 mods/content/wyrd/spells/practices/white_art.dm rename mods/content/{anima/runestones.dm => wyrd/spells/runestones/_runestones.dm} (61%) rename mods/content/{anima => wyrd/spells/runestones}/spellscribing.dm (77%) create mode 100644 mods/content/wyrd/spells/spell_archetypes.dm create mode 100644 mods/content/wyrd/spells/spell_datum.dm create mode 100644 mods/content/wyrd/spells/spell_effect.dm create mode 100644 mods/content/wyrd/spells/spellcasting.dm rename mods/{pyrelight/datum => content/wyrd}/traits/_wyrd.dm (100%) rename mods/{pyrelight/datum/traits/_wyrd_categories.dm => content/wyrd/traits/wyrd_signs.dm} (54%) rename mods/{pyrelight/datum => content/wyrd}/traits/wyrd_wild.dm (58%) create mode 100644 mods/content/wyrd/wyrd_abilities.dm rename mods/{pyrelight/datum/wyrdling/mask.dm => content/wyrd/wyrdling_mask.dm} (88%) delete mode 100644 mods/pyrelight/datum/wyrdling/ears.dm delete mode 100644 mods/pyrelight/datum/wyrdling/tails.dm create mode 100644 mods/~compatibility/patches/fantasy/wyrd_fantasy.dm diff --git a/maps/karzerfeste/karzerfeste.dm b/maps/karzerfeste/karzerfeste.dm index f656491c44fa..513b935468e0 100644 --- a/maps/karzerfeste/karzerfeste.dm +++ b/maps/karzerfeste/karzerfeste.dm @@ -9,6 +9,7 @@ #include "../../mods/content/fantasy/_fantasy.dme" #include "../../mods/content/undead/_undead.dme" #include "../../mods/content/biomods/_biomods.dme" + #include "../../mods/content/wyrd/_wyrd.dme" #include "../../mods/pyrelight/_pyrelight.dme" // include after _fantasy.dme so overrides work #include "areas/_area.dm" diff --git a/maps/shaded_hills/shaded_hills.dm b/maps/shaded_hills/shaded_hills.dm index 9e43e4d16b09..a4e6d3e3dd1d 100644 --- a/maps/shaded_hills/shaded_hills.dm +++ b/maps/shaded_hills/shaded_hills.dm @@ -7,7 +7,7 @@ #include "../../mods/species/drakes/_drakes.dme" // include before _fantasy.dme so overrides work #include "../../mods/species/neoavians/_neoavians.dme" // include before _fantasy.dme so overrides work #include "../../mods/content/item_sharpening/_item_sharpening.dme" - #include "../../mods/content/anima/_anima.dme" // include before _fantasy.dme so skill overrides work + #include "../../mods/content/wyrd/_wyrd.dme" // include before _fantasy.dme so skill overrides work #include "../../mods/content/fantasy/_fantasy.dme" #include "../../mods/content/undead/_undead.dme" #include "../../mods/content/biomods/_biomods.dme" diff --git a/maps/shaded_hills/shaded_hills_skills.dm b/maps/shaded_hills/shaded_hills_skills.dm index a4e79ea78099..6b18d6a80b22 100644 --- a/maps/shaded_hills/shaded_hills_skills.dm +++ b/maps/shaded_hills/shaded_hills_skills.dm @@ -5,12 +5,6 @@ /obj/item/runestone work_skill = /decl/skill/crafting/artifice -/decl/material/solid/potentia - arcana_skill = SKILL_SCIENCE - -/decl/runestone_spell_archetype - arcana_skill = SKILL_SCIENCE - // Removal of space skills /datum/map/shaded_hills/get_available_skill_types() . = ..() diff --git a/mods/content/anima/_anima.dm b/mods/content/anima/_anima.dm deleted file mode 100644 index b55da0e3cbdb..000000000000 --- a/mods/content/anima/_anima.dm +++ /dev/null @@ -1,15 +0,0 @@ -#define ANIMA_SPELL_AOE "aoe" -#define ANIMA_SPELL_MELEE "melee" -#define ANIMA_SPELL_RANGED "ranged" - -/decl/modpack/anima - name = "Anima Content" - credits_topics = list("ANCIENT MAGIC", "ANCIENT ANIMA", "MAGICAL RITUALS", "MAGIC SPELLS") - credits_nouns = list("MAGIC", "ANIMA") - credits_adjectives = list("ANCIENT", "MAGICAL", "ARCANE", "DIVINE", "BEWITCHED", "ENCHANTED") - credits_crew_outcomes = list("BEWITCHED", "ENCHANTED", "MAGICKED", "CURSED") - dreams = list( - "anima", "potentia", "magic", "an ancient curse", "an arcane ritual", - "a magic spell", "a magician", "a wizard", "a witch", - "a necromancer", "an ancient scroll", "a magic crystal" - ) \ No newline at end of file diff --git a/mods/content/anima/_anima.dme b/mods/content/anima/_anima.dme deleted file mode 100644 index 0a690992ddfe..000000000000 --- a/mods/content/anima/_anima.dme +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef MODPACK_ANIMA -#define MODPACK_ANIMA -// BEGIN_INCLUDE -#include "_anima.dm" -#include "potentia_definition.dm" -#include "potentia_stack.dm" -#include "runestones.dm" -#include "spell_archetypes.dm" -#include "spell_datum.dm" -#include "spell_effect.dm" -#include "spellcasting.dm" -#include "spellscribing.dm" -// END_INCLUDE -#endif diff --git a/mods/content/anima/spell_archetypes.dm b/mods/content/anima/spell_archetypes.dm deleted file mode 100644 index 556ebd185f69..000000000000 --- a/mods/content/anima/spell_archetypes.dm +++ /dev/null @@ -1,29 +0,0 @@ -// Handles all effects associated with a spell. -/decl/runestone_spell_archetype - abstract_type = /decl/runestone_spell_archetype - var/name - var/decl/anima_spell_effect/base_effect - var/list/masterwork_effects - var/arcana_skill = SKILL_SCIENCE - -/decl/runestone_spell_archetype/Initialize() - masterwork_effects = decls_repository.get_decls_unassociated(masterwork_effects) - base_effect = GET_DECL(base_effect) - name = base_effect.name - return ..() - -/decl/runestone_spell_archetype/proc/has_effect_type(effect_type) - return TRUE - -/decl/runestone_spell_archetype/proc/get_masterwork_effects(mob/user, obj/item/implement) - // TODO: skill/trait/mind checks on user - return masterwork_effects - -/decl/runestone_spell_archetype/flash - base_effect = /decl/anima_spell_effect/flash - masterwork_effects = list( - /decl/anima_spell_effect/gloom - ) - -/decl/runestone_spell_archetype/flare - base_effect = /decl/anima_spell_effect/flare diff --git a/mods/content/anima/spell_datum.dm b/mods/content/anima/spell_datum.dm deleted file mode 100644 index 24862921764f..000000000000 --- a/mods/content/anima/spell_datum.dm +++ /dev/null @@ -1,14 +0,0 @@ -var/global/list/_anima_spell_effect_types = list( - "area of effect" = ANIMA_SPELL_AOE, - "close-range" = ANIMA_SPELL_MELEE, - "long-range" = ANIMA_SPELL_RANGED -) - -/datum/anima_working - var/effect_type = ANIMA_SPELL_AOE - var/effect_strength = 1 - var/decl/runestone_spell_archetype/spell_master - var/decl/anima_spell_effect/masterwork_effect - -/datum/anima_working/New(_effect) - effect_type = _effect \ No newline at end of file diff --git a/mods/content/anima/spell_effect.dm b/mods/content/anima/spell_effect.dm deleted file mode 100644 index 753dd6c4a5a5..000000000000 --- a/mods/content/anima/spell_effect.dm +++ /dev/null @@ -1,100 +0,0 @@ -/decl/anima_spell_effect - abstract_type = /decl/anima_spell_effect - var/name - var/description - var/cost = 1 - -/decl/anima_spell_effect/proc/evoke_spell(mob/user, atom/target, obj/item/runestone/runestone, caster_effect, caster_strength, in_proximity, deliberate = TRUE) - // Get our spell parameters. - var/spent_cost = runestone ? min(runestone.anima_density, cost) : cost - var/evoke_effect = caster_effect || runestone?.stored_spell?.effect_type || ANIMA_SPELL_AOE - if(!in_proximity && evoke_effect == ANIMA_SPELL_MELEE) - evoke_effect = ANIMA_SPELL_RANGED - var/evoke_strength = caster_strength || runestone?.anima_density || 1 - - // Cast the actual spell. - do_evocation(user, target, evoke_effect, evoke_strength, deliberate) - - // Expend resources. - if(runestone) - if(runestone.anima_density > spent_cost && evoke_effect != ANIMA_SPELL_AOE) - runestone.anima_density -= spent_cost - runestone.update_icon() - runestone.update_strings() - else if(!QDELETED(runestone)) - to_chat(user, SPAN_DANGER("Your runestone crumbles to dust!")) - qdel(runestone) - // else - // take anima from caster's pool - -/decl/anima_spell_effect/proc/show_primed_message(mob/user, effect_type) - to_chat(user, SPAN_NOTICE("Raw anima wreathes your [parse_zone(user.get_active_held_item_slot())], ready to be directed.")) - -/decl/anima_spell_effect/proc/do_evocation(mob/user, atom/target, evoke_effect = ANIMA_SPELL_AOE, evoke_strength = 1, deliberate = TRUE) - -/decl/anima_spell_effect/flash - name = "Flash" - description = "Release the channeled anima in a blinding flash of light." - -/decl/anima_spell_effect/flash/do_evocation(mob/user, atom/target, evoke_effect = ANIMA_SPELL_AOE, evoke_strength = 1, deliberate = TRUE) - - if(evoke_effect == ANIMA_SPELL_AOE) - user.visible_message("\The [user] emits a blinding flash of light ([evoke_strength])!") - for(var/mob/living/victim in view(evoke_strength, user)) - if(deliberate && victim == user) - continue - victim.handle_flashed(rand(evoke_strength, evoke_strength*2 + 1)) // min is 1 to 2, max is 3 to 7; base flash is 2 to 7 in a range of 3 tiles - else - user.visible_message("\The [user] drowns \the [target] in a blinding flash of light ([evoke_strength])!") - if(isliving(target)) - var/mob/living/victim = target - victim.handle_flashed(evoke_strength * 2 + 1) // since we're only attacking one target, we just go with the max instead of a random value - return TRUE - -/decl/anima_spell_effect/gloom - name = "Gloom" - description = "Burn the channeled anima to create a burst of unnatural darkness." - -/decl/anima_spell_effect/gloom/do_evocation(mob/user, atom/target, evoke_effect = ANIMA_SPELL_AOE, evoke_strength = 1, deliberate = TRUE) - if(evoke_effect == ANIMA_SPELL_AOE) - user.visible_message("\The [user] emits a burst of unnatural darkness ([evoke_strength])!") - var/atom/movable/gloom/gloom = new(target, evoke_strength) // with AOE spell, target is user's turf - QDEL_IN_CLIENT_TIME(gloom, evoke_strength * 5 SECONDS) - else - user.visible_message("\The [user] drowns \the [target] in unnatural darkness ([evoke_strength])!") - if(isliving(target)) - var/mob/living/victim = target - victim.overlay_fullscreen("gloom", /obj/screen/fullscreen/blackout) - addtimer(CALLBACK(victim, TYPE_PROC_REF(/mob, clear_fullscreen), "gloom"), evoke_strength * 3 SECONDS) - return TRUE - -/atom/movable/gloom - simulated = FALSE - mouse_opacity = MOUSE_OPACITY_UNCLICKABLE - -/atom/movable/gloom/Initialize(ml, strength) - . = ..() - set_light(strength, -10, "#ffffff") // negative light, cleared on destroy - -/decl/anima_spell_effect/flare - name = "Flare" - description = "Release the channeled anima in an undirected burst of flame." - -/decl/anima_spell_effect/flare/do_evocation(mob/user, atom/target, evoke_effect = ANIMA_SPELL_AOE, evoke_strength = 1, deliberate = TRUE) - var/turf/source_turf = get_turf(user) - // level 1 flare is hot enough to boil water. level 3 flare is hot enough to light a campfire and ignite wood - // divided by 0.9 and plus one to ensure inefficiency doesn't stop us from lighting campfires - var/fire_temperature = Interpolate(T100C, /decl/material/solid/organic/wood::ignition_point / 0.9 + 1, (evoke_strength - 1) / 3) - if(evoke_effect == ANIMA_SPELL_AOE) - user.visible_message("\The [user] is wreathed in a blast of fire ([evoke_strength])!") - for(var/turf/T in RANGE_TURFS(source_turf, evoke_strength)) - T = T.resolve_to_actual_turf() - new /obj/effect/fake_fire/variable/owned(T, fire_temperature, evoke_strength SECONDS, deliberate ? user : null) - else - user.visible_message("\The [user] hurls a blast of fire over \the [target] ([evoke_strength])!") - var/obj/item/projectile/fireball/projectile = new(source_turf) - projectile.fire_lifetime = evoke_strength SECONDS - // level 1 flare is hot enough to boil water. level 3 flare is hot enough to light a campfire and ignite wood - projectile.fire_temperature = Interpolate(T100C, projectile::fire_temperature, (evoke_strength - 1) / 3) - projectile.launch_from_gun(target, user.get_target_zone(), user) - return TRUE diff --git a/mods/content/anima/spellcasting.dm b/mods/content/anima/spellcasting.dm deleted file mode 100644 index e4c57c4652ca..000000000000 --- a/mods/content/anima/spellcasting.dm +++ /dev/null @@ -1,6 +0,0 @@ -/datum/ability_handler/anima_channeling - var/datum/anima_working/prepared_spell - var/list/anima_reservoir - var/list/anima_capacity - -/datum/ability_handler/anima_channeling diff --git a/mods/content/wyrd/_wyrd.dm b/mods/content/wyrd/_wyrd.dm new file mode 100644 index 000000000000..ac626ef63ff7 --- /dev/null +++ b/mods/content/wyrd/_wyrd.dm @@ -0,0 +1,19 @@ +/decl/modpack/wyrd + name = "Pyrelight Magic Content" + credits_topics = list("ANCIENT MAGIC", "ANCIENT ANIMA", "MAGICAL RITUALS", "MAGIC SPELLS") + credits_nouns = list("MAGIC", "ANIMA", "WYRD") + credits_adjectives = list("ANCIENT", "MAGICAL", "ARCANE", "DIVINE", "BEWITCHED", "ENCHANTED") + credits_crew_outcomes = list("BEWITCHED", "ENCHANTED", "MAGICKED", "CURSED") + dreams = list( + "wyrd", "anima", "potentia", "magic", "an ancient curse", "an arcane ritual", + "a magic spell", "a magician", "a wizard", "a witch", + "a necromancer", "an ancient scroll", "a magic crystal" + ) + +/* + Notes to self on my own system structure: + - A spell as a concept is /decl/wyrd_archetype + - Archetype provides /decl/wyrd_effect for basic effect, metamagic effect, etc. + - /decl/wyrd_effect is the specific mechanical interaction when the spell is cast. + - /datum/wyrd_working is a temporary holder for a /decl/wyrd_effect and /decl/wyrd_archetype as well as cast strength and cast mode (AOE, single target, etc) +*/ \ No newline at end of file diff --git a/mods/content/wyrd/_wyrd.dme b/mods/content/wyrd/_wyrd.dme new file mode 100644 index 000000000000..56c40e970911 --- /dev/null +++ b/mods/content/wyrd/_wyrd.dme @@ -0,0 +1,28 @@ +#ifndef MODPACK_WYRD +#define MODPACK_WYRD +// BEGIN_INCLUDE +#include "_wyrd.dm" +#include "wyrd_abilities.dm" +#include "wyrdling_mask.dm" +#include "anima\_anima.dm" +#include "anima\anima_aura.dm" +#include "anima\anima_source.dm" +#include "anima\area.dm" +#include "anima\culture.dm" +#include "anima\turf.dm" +#include "potentia\_potentia.dm" +#include "potentia\potentia_stack.dm" +#include "spells\spell_archetypes.dm" +#include "spells\spell_datum.dm" +#include "spells\spell_effect.dm" +#include "spells\spellcasting.dm" +#include "spells\practices\hedge_magic.dm" +#include "spells\practices\rose_art.dm" +#include "spells\practices\sky_sign.dm" +#include "spells\runestones\_runestones.dm" +#include "spells\runestones\spellscribing.dm" +#include "traits\_wyrd.dm" +#include "traits\wyrd_signs.dm" +#include "traits\wyrd_wild.dm" +// END_INCLUDE +#endif diff --git a/mods/content/wyrd/anima/_anima.dm b/mods/content/wyrd/anima/_anima.dm new file mode 100644 index 000000000000..99f4fe2dbb49 --- /dev/null +++ b/mods/content/wyrd/anima/_anima.dm @@ -0,0 +1,76 @@ +// Anima system: radiant energy or potential of your current location. +// Influenced by natural background levels (based on area) and other sources (that may move around) +// TODO: temporary/decaying radiant anima resulting from spells cast in a location. +// TODO: visual effects for very high anima in an area/on a turf. + +// Anima: +// - Very broad categories of magical energy. +// - Influences things within the area of effect (high levels of water anima/low levels of fire anima -> harder to light things on fire). +// - Modifies the chances of spellcasting in the area of effect (high levels of sky anima makes air spells easier). +// - Higher or lower intensities associated with entities/areas. +// - Has an associated subset of potentia that can be refined from local anima (sky sign -> air and water anima, etc) + +// Local events that create temporary powerful anima sources: +// - sacrificing an animal spikes waning and blood anima in the immediate vicinity to boost/enable particular workings +// - burning incense, taking drugs, scribing a circle + +// Process notes: +// 1. mob wants to cast a spell +// 2. retrieve local anima (turf + area) +// 3. retrieve personal anima (aura extension) +// a. effective anima - local anima +/- (personal anima * strength of will) +// b. very high will + personal anima can override local anima +// 4. retrieve spellcasting condition of the mob +// a. stressors mean poor spellcasting focus +// b. nutrition/stamina are needed for fuelling the spell +// 4. if anima state or conditions do not allow spell, fizzles +// 5. spellcaster's personal aura and will are applied agains the target's personal aura/will (for mobs) or just ambient anima for inanimate objects +// 6. effect of spell is scaled based on how much stronger (or more effectively aligned) the spellcaster is + +/decl/anima + abstract_type = /decl/anima + decl_flags = DECL_FLAG_MANDATORY_UID + var/name + + var/const/ANIMA_DEPLETED = 0 + var/const/ANIMA_NEGLIGIBLE = 1 + var/const/ANIMA_NOTABLE = 2 + var/const/ANIMA_DENSE = 3 + var/const/ANIMA_RICH = 4 + var/const/ANIMA_SATURATED = 5 + +/decl/anima/proc/get_personal_anima_description(_density, decl/background_detail/_culture) + return _culture.get_personal_anima_description(src, _density) + +/decl/anima/proc/get_ambient_anima_description(_density, decl/background_detail/_culture) + return _culture.get_ambient_anima_description(src, _density) + +// burning sign: label for potentia-based spellcasting, not a 'real' type of anima +// wild sign: complex category of workings, not a specific single type of anima + +/decl/anima/sky + name = "Sky" + uid = "anima_sky" + // air, stars, moon, truth, purity + +/decl/anima/waning + name = "Waning" + uid = "anima_waning" + // transition, change, death, birth + +/decl/anima/deep + name = "Deep" + uid = "anima_deep" + // concealment, earth, mystery + +/decl/anima/blood + name = "Blood" + uid = "anima_blood" + // growth, life, heat, violence + // source of volatile potentia - fire, mania + +/decl/anima/breath + name = "Breath" + uid = "anima_breath" + // growth, life, heat, violence + // source of volatile potentia - fire, mania diff --git a/mods/content/wyrd/anima/anima_aura.dm b/mods/content/wyrd/anima/anima_aura.dm new file mode 100644 index 000000000000..0c5a640f2575 --- /dev/null +++ b/mods/content/wyrd/anima/anima_aura.dm @@ -0,0 +1,40 @@ +// Inner anima state used for spellcasting. +/datum/extension/anima_aura + expected_type = /mob + base_type = /datum/extension/anima_aura + var/alist/pool + +/datum/extension/anima_aura/proc/update_totals() + return + +/mob/proc/get_personal_anima(anima_type) + var/alist/pool = get_personal_anima_pool() + var/decl/anima/anima = RESOLVE_TO_DECL(anima_type) + return (istype(pool) && pool[anima.type]) || /decl/anima::ANIMA_DEPLETED + +/mob/proc/get_personal_anima_pool() + var/datum/extension/anima_aura/aura = get_extension(src, /datum/extension/anima_aura) + if(aura) + return aura.pool?.Copy() + return alist( + /decl/anima/sky = /decl/anima::ANIMA_DEPLETED, + /decl/anima/waning = /decl/anima::ANIMA_DEPLETED, + /decl/anima/deep = /decl/anima::ANIMA_DEPLETED, + /decl/anima/blood = /decl/anima::ANIMA_DEPLETED + ) + +/mob/proc/adjust_personal_anima(anima_type, anima_amount, skip_update = FALSE) + return set_personal_anima(anima_type, get_personal_anima(anima_type) + anima_amount, skip_update) + +/mob/proc/set_personal_anima(anima_type, anima_amount, skip_update = FALSE) + var/datum/extension/anima_aura/aura = (anima_amount == /decl/anima::ANIMA_DEPLETED) ? get_extension(src, /datum/extension/anima_aura) : get_or_create_extension(src, /datum/extension/anima_aura) + if(!istype(aura) || anima_amount == get_personal_anima(anima_type)) + return FALSE + var/decl/anima/anima = RESOLVE_TO_DECL(anima_type) + if(anima_amount == 0) + LAZYREMOVE(aura.pool, anima.type) + else + LAZYSET(aura.pool, anima.type, anima_amount) + if(!skip_update) + aura.update_totals() + return TRUE diff --git a/mods/content/wyrd/anima/anima_source.dm b/mods/content/wyrd/anima/anima_source.dm new file mode 100644 index 000000000000..a7aef6589fb3 --- /dev/null +++ b/mods/content/wyrd/anima/anima_source.dm @@ -0,0 +1,28 @@ +/datum/extension/anima_source + expected_type = /atom/movable + var/radiant_range = 1 + var/alist/anima_contribution // Can also be negatives for anima suppression. + var/list/affecting_turfs = list() + +/datum/extension/anima_source/New(datum/holder) + . = ..() + if(istype(holder, expected_type)) + events_repository.register(/decl/observ/moved, holder, src, PROC_REF(update_radiant_anima)) + +/datum/extension/anima_source/Destroy() + for(var/turf/turf in affecting_turfs) + turf.remove_affecting_anima(src) + affecting_turfs.Cut() + events_repository.unregister(/decl/observ/moved, holder, src) + . = ..() + +/datum/extension/anima_source/proc/update_radiant_anima() + var/turf/my_turf = get_turf(holder) + var/list/new_turfs = istype(my_turf) ? RANGE_TURFS(my_turf, radiant_range) : null + for(var/turf/turf in affecting_turfs) + if(turf in new_turfs) + new_turfs -= turf + else + turf.remove_affecting_anima(src) + for(var/turf/turf in new_turfs) + turf.add_affecting_anima(src) diff --git a/mods/content/wyrd/anima/area.dm b/mods/content/wyrd/anima/area.dm new file mode 100644 index 000000000000..3b5ffe9f729a --- /dev/null +++ b/mods/content/wyrd/anima/area.dm @@ -0,0 +1,26 @@ +/area + var/alist/background_anima = alist( + /decl/anima/sky = /decl/anima::ANIMA_NEGLIGIBLE, + /decl/anima/waning = /decl/anima::ANIMA_NEGLIGIBLE, + /decl/anima/deep = /decl/anima::ANIMA_NEGLIGIBLE, + /decl/anima/blood = /decl/anima::ANIMA_NEGLIGIBLE + ) + +/area/New() + ..() + // Outside areas have a higher minimum value of Sky Sign. + if(is_outside == OUTSIDE_YES) + background_anima[/decl/anima/sky] = max(background_anima[/decl/anima/sky], /decl/anima::ANIMA_DENSE) + +/area/proc/get_background_anima() + RETURN_TYPE(/alist) + return background_anima + +/area/proc/adjust_background_anima(_anima, _amount) + // Update our ambient background anima list. + if(!background_anima) + background_anima = alist() + background_anima[_anima] += _amount + // Invalidate cache for our turfs. + for(var/turf/area_turf in contents) + area_turf.last_anima = null diff --git a/mods/content/wyrd/anima/culture.dm b/mods/content/wyrd/anima/culture.dm new file mode 100644 index 000000000000..2db85832c182 --- /dev/null +++ b/mods/content/wyrd/anima/culture.dm @@ -0,0 +1,40 @@ +/decl/background_detail + var/anima_failed_working_insufficient_1p = SPAN_WARNING("The skein here is too thin to weave $SPELL$!") + var/anima_failed_working_excess_1p = SPAN_WARNING("The skein here churns too violently to weave $SPELL$!") + var/anima_failed_exhaustion_1p = SPAN_WARNING("You are too exhausted to weave $SPELL$.") + +// Plan here is that specific cultures can have different interpretations of different anima. +/decl/background_detail/proc/get_cultural_anima_name(decl/anima/_anima) + return _anima.name + +/decl/background_detail/proc/get_personal_anima_description(decl/anima/_anima, _density) + var/anima_name = get_cultural_anima_name(_anima) + switch(_density) + if(/decl/anima::ANIMA_DEPLETED) + return "The [anima_name] cannot be heard within your soul." + if(/decl/anima::ANIMA_NEGLIGIBLE) + return "The [anima_name] whispers within your soul." + if(/decl/anima::ANIMA_NOTABLE) + return "The [anima_name] hums gently within your soul." + if(/decl/anima::ANIMA_DENSE) + return "The [anima_name] fills the vault of your soul." + if(/decl/anima::ANIMA_RICH) + return "The [anima_name] sings powerfully within your soul, swelling bright and loud." + if(/decl/anima::ANIMA_SATURATED) + return "The [anima_name] colours your entire being with an almost deafening tone." + +/decl/background_detail/proc/get_ambient_anima_description(decl/anima/_anima, _density) + var/anima_name = get_cultural_anima_name(_anima) + switch(_density) + if(/decl/anima::ANIMA_DEPLETED) + return "The [anima_name] is silent here." + if(/decl/anima::ANIMA_NEGLIGIBLE) + return "The [anima_name] only whispers here." + if(/decl/anima::ANIMA_NOTABLE) + return "The [anima_name] hums and eddies quietly around you." + if(/decl/anima::ANIMA_DENSE) + return "The [anima_name] flows steadily all around you." + if(/decl/anima::ANIMA_RICH) + return "The [anima_name] suffuses all around you with a powerful song." + if(/decl/anima::ANIMA_SATURATED) + return "The [anima_name] thunders here in a continuous deluge." diff --git a/mods/content/wyrd/anima/turf.dm b/mods/content/wyrd/anima/turf.dm new file mode 100644 index 000000000000..eb76e000a448 --- /dev/null +++ b/mods/content/wyrd/anima/turf.dm @@ -0,0 +1,39 @@ +/turf + var/alist/last_anima + var/list/_affecting_anima + +/turf/ChangeTurf(turf/N, tell_universe, force_lighting_update, keep_air, update_open_turfs_above, keep_height) + . = ..() + last_anima = null + +/turf/proc/add_affecting_anima(datum/extension/anima_source/_source) + LAZYDISTINCTADD(_affecting_anima, _source) + LAZYDISTINCTADD(_source.affecting_turfs, src) + last_anima = null + +/turf/proc/remove_affecting_anima(datum/extension/anima_source/_source) + LAZYREMOVE(_affecting_anima, _source) + LAZYREMOVE(_source.affecting_turfs, src) + last_anima = null + +/turf/proc/update_anima_values() + last_anima = alist() + for(var/datum/extension/anima_source/source in _affecting_anima) + for(var/atype,avalue in source.anima_contribution) + last_anima[atype] += avalue + var/area/my_area = get_area(src) + for(var/atype,avalue in my_area?.get_background_anima()) + last_anima[atype] += avalue + +/atom/proc/get_ambient_anima() + var/turf/turf = get_turf(src) + return turf?.get_ambient_anima() + +/turf/get_ambient_anima() + // If nothing is affecting our anima, don't even bother caching it. + if(!length(_affecting_anima)) + var/area/area = get_area(src) + return area?.get_background_anima()?.Copy() + if(isnull(last_anima)) + last_anima = update_anima_values() + return last_anima diff --git a/mods/content/wyrd/icons/abilities.dmi b/mods/content/wyrd/icons/abilities.dmi new file mode 100644 index 0000000000000000000000000000000000000000..c30b116d94d03c37e7c8067c3a53ffdc7c7b27ee GIT binary patch literal 752 zcmVSc*OAUU3$oTHL!w zKZy3(^HpQ?5X4Pf4@b^6ugIlxIdkT;w~qD_Ns>;n8q>6Z?}{_!F{agtGlO#0d;Wn9 z7?Z`Xg$P0;NAjyK)L8}#Z4y=1`B22nB%>=)n7_bI0P{api*9JY&-nnZsd&M)ZcN1h z00G=dL_t(oh3%A0ma8BTg~=d24&nyT6=1Oenpyx8=ZxI{5*|MhQDfT5>v!J7>j|d7 z&$zV?*C(I_KvV$$od8)(@+Z&_8Gt9CZ3gfOl#0|0G};b;gO~w<(Uolv zfTruU;T)ms5+Fchj1k}<1Aq>58>~alfQX?l-ChD=#PkYecwh#MHmKeK4WJ#`1qeli z5{>`?s@J>)Su7ciyp1W2idMb(HdYRE(ZP`op6pT4TuRePBc;e0xKaj`QL6o|4AdIP zvqWA8@=DXcCF<8o-w)IsN8Z!qQ-gXgkx#x$r?j2tpLDC%Nu2{q0&xydac?E@lfe8u znoof{8`pEx_dK iBD39UmM&fTFZu&_pw0000GI#h5ZYB diff --git a/mods/content/anima/icons/runestone_basic.dmi b/mods/content/wyrd/icons/runestone_basic.dmi similarity index 100% rename from mods/content/anima/icons/runestone_basic.dmi rename to mods/content/wyrd/icons/runestone_basic.dmi diff --git a/mods/content/anima/icons/runestone_gilded.dmi b/mods/content/wyrd/icons/runestone_gilded.dmi similarity index 100% rename from mods/content/anima/icons/runestone_gilded.dmi rename to mods/content/wyrd/icons/runestone_gilded.dmi diff --git a/mods/content/anima/icons/runestone_layered.dmi b/mods/content/wyrd/icons/runestone_layered.dmi similarity index 100% rename from mods/content/anima/icons/runestone_layered.dmi rename to mods/content/wyrd/icons/runestone_layered.dmi diff --git a/mods/content/wyrd/icons/workings.dmi b/mods/content/wyrd/icons/workings.dmi new file mode 100644 index 0000000000000000000000000000000000000000..c03b082752e5468a1fe919b733ea7bbecdc05f6a GIT binary patch literal 457 zcmV;)0XF`LP)V=-0C=2J zR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+(=$pSoZ^zil2jm5 zDH%wo=NDBfadD<(78L`947oVdiV|~EA#4nVy7_5oxD+cZxca$(odE#jCMeBOTQq$D z008MpL_t(Ijjfa05ri-ZMcoeMN(0b=V+Gks|1~YdH9=<7k6nHolEcefrs;QkQLyaK zb^r}#2g?@BBOoK70qgL0fND255d5V9T&tfI{sQ(bEq(x`R11VVz^x%ZfZ~qzDhE*B zst5oC5EZ8e5HrA|MJNL11wdQ8A^@0bO1y^0JLS$>kQ<;O%GN>>>ig>cnLX| z$p69Tz|DUU@(iJbPjx><>VZ;;{WS+*uPI`kbbz;7x3xdF-7DF>TxY13flJ7c^k8}- z*HIS?B}JVz$yt++s(#vwyjjtu^=TF7KkW7gCIDZJZ^hD>00000NkvXXu0mjfj7z#o literal 0 HcmV?d00001 diff --git a/mods/content/anima/potentia_definition.dm b/mods/content/wyrd/potentia/_potentia.dm similarity index 58% rename from mods/content/anima/potentia_definition.dm rename to mods/content/wyrd/potentia/_potentia.dm index bdd4c1587230..5e9b26e7edf7 100644 --- a/mods/content/anima/potentia_definition.dm +++ b/mods/content/wyrd/potentia/_potentia.dm @@ -1,25 +1,30 @@ +// Potentia: +// - Purified/degenerate energy extracted or processed from anima (Novu Uso). +// - 'I cast Fireball' -> fire and air potentia expended +// - 'rune of greater flood' -> etched onto crystalline water potentia + /decl/material/solid/potentia name = "potentia" solid_name = "crystalline potentia" - uid = "mat_anima" + uid = "mat_potentia" opacity = 0.7 color = COLOR_GRAY40 abstract_type = /decl/material/solid/potentia - uid = "mat_anima_generic" - var/anima_type = "unaspected" + uid = "mat_potentia_generic" + var/potentia_type = "unaspected" var/runestone_glow_intensity = 0.3 /// Simple spells that can be scribed onto runestones or cast on the fly. var/list/cantrips /// Spell used when a blank is cracked. - var/decl/runestone_spell_archetype/undirected_spell + var/decl/wyrd_archetype/undirected_spell /// Skill used for general spell knowledge. var/arcana_skill = SKILL_SCIENCE // TODO: arcana or magic skill /decl/material/solid/potentia/Initialize() - name = "[anima_type] potentia" - solid_name = "crystalline [anima_type] potentia" - liquid_name = "molten [anima_type] potentia" - gas_name = "gaseous [anima_type] potentia" + name = "[potentia_type] potentia" + solid_name = "crystalline [potentia_type] potentia" + liquid_name = "molten [potentia_type] potentia" + gas_name = "gaseous [potentia_type] potentia" for(var/spell in cantrips) cantrips -= spell cantrips |= GET_DECL(spell) @@ -31,17 +36,17 @@ /decl/material/solid/potentia/proc/get_cantrips_by_effect_type(mob/user, effect_type) // TODO: check arcana_skill on user - for(var/decl/runestone_spell_archetype/cantrip in cantrips) + for(var/decl/wyrd_archetype/cantrip in cantrips) if(cantrip.has_effect_type(effect_type)) LAZYDISTINCTADD(., cantrip) /decl/material/solid/potentia/fire - anima_type = "fire" + potentia_type = "fire" color = COLOR_ORANGE runestone_glow_intensity = 0.6 cantrips = list( - /decl/runestone_spell_archetype/flash, - /decl/runestone_spell_archetype/flare + /decl/wyrd_archetype/flash, + /decl/wyrd_archetype/flare ) - undirected_spell = /decl/runestone_spell_archetype/flare - uid = "mat_anima_fire" + undirected_spell = /decl/wyrd_archetype/flare + uid = "mat_potentia_fire" diff --git a/mods/content/anima/potentia_stack.dm b/mods/content/wyrd/potentia/potentia_stack.dm similarity index 85% rename from mods/content/anima/potentia_stack.dm rename to mods/content/wyrd/potentia/potentia_stack.dm index c8f3897f79a3..46e521e03e4c 100644 --- a/mods/content/anima/potentia_stack.dm +++ b/mods/content/wyrd/potentia/potentia_stack.dm @@ -3,10 +3,10 @@ desc = "A condensed, crystalline form of magical energy, cut into rough, unworked rounds and ready for etching." singular_name = "blank" plural_name = "blanks" - icon_state = "anima" - icon = 'mods/content/anima/icons/anima_blank.dmi' - plural_icon_state = "anima-mult" - max_icon_state = "anima-max" + icon_state = "blank" + icon = 'mods/content/wyrd/icons/blanks.dmi' + plural_icon_state = "blank-mult" + max_icon_state = "blank-max" stack_merge_type = /obj/item/stack/material/potentia crafting_stack_type = /obj/item/stack/material/potentia material_alteration = MAT_FLAG_ALTERATION_COLOR | MAT_FLAG_ALTERATION_NAME | MAT_FLAG_ALTERATION_DESC diff --git a/mods/content/wyrd/spells/practices/_practice.dm b/mods/content/wyrd/spells/practices/_practice.dm new file mode 100644 index 000000000000..4b46b5708be8 --- /dev/null +++ b/mods/content/wyrd/spells/practices/_practice.dm @@ -0,0 +1,3 @@ +/decl/wyrd_practice + var/name + var/description diff --git a/mods/content/wyrd/spells/practices/black_art.dm b/mods/content/wyrd/spells/practices/black_art.dm new file mode 100644 index 000000000000..daec3a5ca36b --- /dev/null +++ b/mods/content/wyrd/spells/practices/black_art.dm @@ -0,0 +1,9 @@ +/decl/wyrd_practice/lotus + name = "Sable Arts" + description = "The Sable Arts descend from the ancestral practices of the steppe and \ + the traditions of the hrok. The breaking of binds and unravelling of workings, speaking \ + with the dead, and drawing power from decay and endings all fall under the Sable Arts. The Schola \ + of the Imperial Aegis had little interest in the more 'distasteful' aspects of wyrd-working \ + practiced on the continent, and the rumoured Ordo Padma has never been supported with \ + archaeological evidence." + diff --git a/mods/content/wyrd/spells/practices/hedge_magic.dm b/mods/content/wyrd/spells/practices/hedge_magic.dm new file mode 100644 index 000000000000..fad6c8ac306e --- /dev/null +++ b/mods/content/wyrd/spells/practices/hedge_magic.dm @@ -0,0 +1,6 @@ +/decl/wyrd_practice/hedge_arts + name = "Hedge Arts" + description = "A syncretic collection of cantrips and rites that are ubiquitous \ + across many peoples and cultures, even if the exact method of working differs. \ + In the downlands, many of these workings descend from old wyrdling practices, or \ + from the legacy of the long-defunct Ordo Helianthus." diff --git a/mods/content/wyrd/spells/practices/rose_art.dm b/mods/content/wyrd/spells/practices/rose_art.dm new file mode 100644 index 000000000000..6fa153e41d4b --- /dev/null +++ b/mods/content/wyrd/spells/practices/rose_art.dm @@ -0,0 +1,42 @@ +/decl/wyrd_practice/rose + name = "Rose Arts" + description = "Healing, flesh-shaping, and necromancy all fall beneath the Rose \ + Arts, though some workings are more widely accepted than others. The modern Rose \ + Arts are a fusion of the practices of the long-dead Ordo Rosarum, and a large body \ + of medical practice developed in places like Sulgi." + +// Rose Art, Rose Sign +// 'healing' magic, biomancy, physical side of necromancy, flesh shaping + +// - Comfort: +// - basic: applies a significant positive stressor +// - upgraded: actively increases regeneration rate +// - Close Wounds: +// - basic: reduce size of open wounds +// - upgraded: also mends arterial bleeds +// - Cleanse: +// - basic: sterilises a limb or item +// - upgraded: also applies a modifier that lowers infection in wounds for X time +// - Fuse Bone: +// - basic: applies a magical splint for X time +// - upgraded also applies a modifier that fixes a fracture after X time +// - Bind Vitae: +// - basic: prevents surface wounds from bleeding for X time +// - upgraded: prevents arterial bleeding for X time +// - Quiet Suffering: +// - basic: reduces pain in limb/mob for X time +// - upgraded: numb a limb/mob for X time +// - Organ repair? Scarring removal? +// - Restart the heart/revive the dead? + +// Non-medical spells +// - Sanctify: prevents corpse rising as undead +// - Desecrate: +// - basic: undoes Sanctify and makes the corpse rise as undead faster, more likely to be aligned to the caster +// - upgraded: creates a revenant which allows a player ghost to enter it to do your evil bidding + +// Spooky spells +// - Flesh To Bone: calcify a limb (SKELETAL and BRITTLE flags) +// - Resculpt: mutate (or unmutate) a limb +// - Fusing arbitrary organs/limbs into targets +// - Changeling-style mutations of your own limbs (blade arms, tendrils, etc) \ No newline at end of file diff --git a/mods/content/wyrd/spells/practices/sky_sign.dm b/mods/content/wyrd/spells/practices/sky_sign.dm new file mode 100644 index 000000000000..afcf44d2e344 --- /dev/null +++ b/mods/content/wyrd/spells/practices/sky_sign.dm @@ -0,0 +1,20 @@ +// Steppe shamanism - weatherworking, speaking to spirits, bonecasting + +// Weatherworking +// - Dowse: +// - basic: points you to the closest water source. +// - upgraded: tells you how far away it is. +// - Wind Step: +// - basic: negative negative wind movement penalty +// - upgraded: always move with maximum wind bonus to movement +// - Draw Wind +// - basic: change the direction of the wind +// - basic: change the strength of the wind +// - Call Weather +// - basic: cause basic weather to occur in X time, unless worse weather is already present (cannot downgrade a storm to a squall) +// - upgraded: immediately change basic weather, or cause any other weather condition in X time +// + +// Bonecasting +// - Burn/consume bone for buffs/to cause effects. +// - Craft item decorations/jewelry from bone that grant effects. diff --git a/mods/content/wyrd/spells/practices/solar_arts.dm b/mods/content/wyrd/spells/practices/solar_arts.dm new file mode 100644 index 000000000000..bc4ead337359 --- /dev/null +++ b/mods/content/wyrd/spells/practices/solar_arts.dm @@ -0,0 +1,6 @@ +/decl/wyrd_practice/sunflower + name = "Solar Arts" + description = "Binding power into inanimate matter to bring life. Manipulating and \ + binding spirits. Creation of constructs and enchantment of weapons and equipment. \ + Many techniques pioneered by Ordo Helicanthus as working complex magic in the field was \ + often impractical." diff --git a/mods/content/wyrd/spells/practices/white_art.dm b/mods/content/wyrd/spells/practices/white_art.dm new file mode 100644 index 000000000000..7b4f4e3a6b16 --- /dev/null +++ b/mods/content/wyrd/spells/practices/white_art.dm @@ -0,0 +1,7 @@ +/decl/wyrd_practice/jasmine + name = "Pale Arts" + description = "Transmutation and purification of inert substances, the directing and \ + shaping of forces, and breathing life into the never-living make up the Pale Arts. \ + Descending from the maligned legacy of the Order Aenean, these practices are less useful \ + day to day than hedge magic, and much less prestigous than the Rose Arts, but have enjoyed \ + renewed interest alongside the fledgling Novu Usu." diff --git a/mods/content/anima/runestones.dm b/mods/content/wyrd/spells/runestones/_runestones.dm similarity index 61% rename from mods/content/anima/runestones.dm rename to mods/content/wyrd/spells/runestones/_runestones.dm index e4874bde0225..e8b4d963d3a2 100644 --- a/mods/content/anima/runestones.dm +++ b/mods/content/wyrd/spells/runestones/_runestones.dm @@ -1,7 +1,7 @@ /obj/item/runestone name = "runestone" - desc = "An etched, faceted round of crystalline anima, scribed with a complex rune. Shatter the runestone to evoke the spell scribed upon it." - icon = 'mods/content/anima/icons/runestone_basic.dmi' + desc = "An etched, faceted round of crystallised magic, scribed with a complex rune. Shatter the runestone to evoke the spell scribed upon it." + icon = 'mods/content/wyrd/icons/runestone_basic.dmi' icon_state = ICON_STATE_WORLD material = /decl/material/solid/potentia w_class = ITEM_SIZE_SMALL @@ -9,14 +9,14 @@ var/work_skill = SKILL_CONSTRUCTION var/work_tool = TOOL_SCALPEL // TODO: TOOL_CHISEL - var/anima_density = 1 + var/potentia_density = 1 var/cracked = FALSE - var/datum/anima_working/stored_spell + var/datum/wyrd_working/stored_spell - var/static/list/anima_density_labels = list( - 'mods/content/anima/icons/runestone_basic.dmi' = "basic", - 'mods/content/anima/icons/runestone_layered.dmi' = "layered", - 'mods/content/anima/icons/runestone_gilded.dmi' = "gilded" + var/static/list/potentia_density_labels = list( + 'mods/content/wyrd/icons/runestone_basic.dmi' = "basic", + 'mods/content/wyrd/icons/runestone_layered.dmi' = "layered", + 'mods/content/wyrd/icons/runestone_gilded.dmi' = "gilded" ) /obj/item/runestone/Initialize() @@ -28,15 +28,15 @@ var/list/new_name = list() if(cracked) new_name += "cracked" - new_name += anima_density_labels[icon] - var/decl/material/solid/potentia/anima = material - new_name += istype(anima) ? anima.anima_type : "unaspected" + new_name += potentia_density_labels[icon] + var/decl/material/solid/potentia/potentia = material + new_name += istype(potentia) ? potentia.potentia_type : "unaspected" new_name += initial(name) if(stored_spell) if(stored_spell.spell_master) new_name += "of" - if(stored_spell.masterwork_effect) - new_name += stored_spell.masterwork_effect.name + if(stored_spell.variant) + new_name += stored_spell.variant.name else new_name += stored_spell.spell_master.name else @@ -59,27 +59,27 @@ to_chat(user, SPAN_WARNING("\The [src] is made of [material], not [W.material].")) return TRUE - if(anima_density >= length(anima_density_labels)) + if(potentia_density >= length(potentia_density_labels)) to_chat(user, SPAN_WARNING("\The [src] is as pure and dense as it can be without shattering.")) return TRUE - var/obj/item/stack/material/potentia/anima = W - if(anima.get_amount() < anima_density) - to_chat(user, SPAN_WARNING("You need at least [anima_density] blank\s to refine \the [src] further.")) + var/obj/item/stack/material/potentia/potentia = W + if(potentia.get_amount() < potentia_density) + to_chat(user, SPAN_WARNING("You need at least [potentia_density] blank\s to refine \the [src] further.")) return TRUE if(work_skill) - if(!user.do_skilled((2 SECONDS) + (anima_density SECONDS), work_skill, src, check_holding = TRUE)) + if(!user.do_skilled((2 SECONDS) + (potentia_density SECONDS), work_skill, src, check_holding = TRUE)) return TRUE // Repeat a bunch of checks due to the time delay. - if(QDELETED(src) || QDELETED(anima) || anima.loc != user || anima.get_amount() < anima_density) + if(QDELETED(src) || QDELETED(potentia) || potentia.loc != user || potentia.get_amount() < potentia_density) return TRUE - if(anima_density >= length(anima_density_labels) || !anima.material?.type || material?.type != anima.material?.type) + if(potentia_density >= length(potentia_density_labels) || !potentia.material?.type || material?.type != potentia.material?.type) return TRUE - if(anima.use(anima_density)) - to_chat(user, SPAN_NOTICE("You fold [anima_density] blank\s into \the [src], increasing its potency.")) - anima_density++ + if(potentia.use(potentia_density)) + to_chat(user, SPAN_NOTICE("You fold [potentia_density] blank\s into \the [src], increasing its potency.")) + potentia_density++ update_strings() return TRUE @@ -107,21 +107,21 @@ // Incomplete runestones or AOE spells are activated immediately. if(!stored_spell?.spell_master) - var/decl/material/solid/potentia/anima = material - if(istype(anima) && anima.undirected_spell?.base_effect) - anima.undirected_spell.base_effect.evoke_spell(user, get_turf(user), null, deliberate = deliberate) + var/decl/material/solid/potentia/potentia = material + if(istype(potentia) && potentia.undirected_spell?.base_effect) + potentia.undirected_spell.base_effect.evoke_spell(user, get_turf(user), null, deliberate = deliberate) else new /obj/item/shard(get_turf(user), material?.type) qdel(src) return TRUE - var/decl/anima_spell_effect/casting_spell = stored_spell.masterwork_effect || stored_spell.spell_master.base_effect + var/decl/wyrd_effect/casting_spell = stored_spell.variant || stored_spell.spell_master.base_effect if(casting_spell) - if(stored_spell.effect_type == ANIMA_SPELL_AOE) + if(stored_spell.effect_type == /decl/wyrd_effect::WYRD_AOE) casting_spell.evoke_spell(user, get_turf(user), src) qdel(src) return TRUE - to_chat(user, casting_spell.show_primed_message(user, stored_spell.effect_type)) + to_chat(user, casting_spell.show_runestone_primed_message(user, src)) update_strings() return TRUE @@ -130,7 +130,7 @@ return cracked ? FALSE : ..() /obj/item/runestone/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - var/decl/anima_spell_effect/casting_spell = cracked && stored_spell && (stored_spell.masterwork_effect || stored_spell.spell_master.base_effect) + var/decl/wyrd_effect/casting_spell = cracked && stored_spell && (stored_spell.variant || stored_spell.spell_master.base_effect) if(casting_spell) casting_spell.evoke_spell(user, target, src, in_proximity = proximity_flag) return TRUE @@ -143,9 +143,9 @@ if(overlay && cracked && istype(material, /decl/material/solid/potentia)) var/check_state = "[overlay.icon_state]-glow" if(check_state_in_icon(check_state, overlay.icon)) - var/decl/material/solid/potentia/anima_mat = material + var/decl/material/solid/potentia/potentia_mat = material var/image/I = image(overlay.icon, check_state) - I.alpha = 255 * anima_mat.runestone_glow_intensity + I.alpha = 255 * potentia_mat.runestone_glow_intensity I.appearance_flags |= RESET_ALPHA overlay.overlays += I return ..() @@ -153,7 +153,7 @@ /obj/item/runestone/on_update_icon() . = ..() - var/new_icon = anima_density_labels[anima_density] + var/new_icon = potentia_density_labels[potentia_density] if(icon != new_icon) icon = new_icon @@ -161,14 +161,14 @@ if(cracked) icon_state = "[icon_state]-cracked" if(istype(material, /decl/material/solid/potentia)) - var/decl/material/solid/potentia/anima_mat = material + var/decl/material/solid/potentia/potentia_mat = material var/image/I = image(icon, "[icon_state]-glow") - I.alpha = 255 * anima_mat.runestone_glow_intensity + I.alpha = 255 * potentia_mat.runestone_glow_intensity I.appearance_flags |= RESET_ALPHA add_overlay(I) compile_overlays() else if(stored_spell) - if(stored_spell.masterwork_effect) + if(stored_spell.variant) icon_state = "[icon_state]-etched" else if(stored_spell.effect_type) icon_state = "[icon_state]-complex" diff --git a/mods/content/anima/spellscribing.dm b/mods/content/wyrd/spells/runestones/spellscribing.dm similarity index 77% rename from mods/content/anima/spellscribing.dm rename to mods/content/wyrd/spells/runestones/spellscribing.dm index 8d4ac30e5a16..1d171ee51fc1 100644 --- a/mods/content/anima/spellscribing.dm +++ b/mods/content/wyrd/spells/runestones/spellscribing.dm @@ -15,7 +15,7 @@ // // All uses have a long delay and a skill check, failing the skill check: // - stage 1 will just fail -// - stage 2 will destroy the spell and produce anima shards for recycling or potions +// - stage 2 will destroy the spell and produce potentia shards for recycling or potions // - stage 3 will set the blank off like an AOE spell and destroy it in the process (bad for dangerous spells) /obj/item/runestone/proc/can_scribe(mob/user, obj/item/implement) @@ -42,11 +42,11 @@ return FALSE . = TRUE // We mostly don't care after this point. Return type is only used for attackby() return. - var/decl/material/solid/potentia/anima_mat = material + var/decl/material/solid/potentia/potentia_mat = material // Stage one: choose range. if(!stored_spell) - var/effect_type = input(user, "What type of effect do you wish this runestone to evoke?", "Spellscribing") as null|anything in global._anima_spell_effect_types + var/effect_type = input(user, "What type of effect do you wish this runestone to evoke?", "Spellscribing") as null|anything in global._wyrd_spell_effect_types if(QDELETED(src) || !effect_type || stored_spell) return if(!scribe_check(user, tool, RUNESCRIBE_DIFFICULTY_EASY)) @@ -54,32 +54,32 @@ // Recheck due to do_after if(QDELETED(src) || stored_spell) return - stored_spell = new(global._anima_spell_effect_types[effect_type]) + stored_spell = new(global._wyrd_spell_effect_types[effect_type]) to_chat(user, SPAN_NOTICE("You carefully etch \the [src] with channels suitable for a [stored_spell.effect_type] evocation.")) update_strings() return // Stage two: choose specific spell. if(!stored_spell.spell_master) - var/list/possible_spells = anima_mat.get_cantrips_by_effect_type(user, stored_spell.effect_type) + var/list/possible_spells = potentia_mat.get_cantrips_by_effect_type(user, stored_spell.effect_type) if(!length(possible_spells)) to_chat(user, SPAN_WARNING("You do not know any suitable workings to etch into \the [src].")) return - var/decl/runestone_spell_archetype/chosen_spell = input(user, "Which spell do you wish this runestone to evoke?", "Spellscribing") as null|anything in possible_spells + var/decl/wyrd_archetype/chosen_spell = input(user, "Which spell do you wish this runestone to evoke?", "Spellscribing") as null|anything in possible_spells if(!chosen_spell) return if(!scribe_check(user, tool, RUNESCRIBE_DIFFICULTY_HARD)) if(can_scribe(user, tool)) // if it's just a tool failure then be nice, if it's a skill or do_after fail have a little bit of mercy - if(anima_density <= 1 || prob(15)) + if(potentia_density <= 1 || prob(15)) to_chat(user, SPAN_DANGER("You slip up, and \the [src] cracks apart!")) crack_runestone(user, FALSE) // whoopsie poopsie else to_chat(user, SPAN_DANGER("You fumble, and the existing runework is damaged.")) - anima_density-- + potentia_density-- update_strings() return // Refresh spell list for checking. - possible_spells = anima_mat.get_cantrips_by_effect_type(user, stored_spell.effect_type) + possible_spells = potentia_mat.get_cantrips_by_effect_type(user, stored_spell.effect_type) if(!(chosen_spell in possible_spells) || QDELETED(src) || stored_spell.spell_master) return to_chat(user, SPAN_NOTICE("You painstakingly etch the runes required to evoke [chosen_spell] onto \the [src].")) @@ -87,28 +87,28 @@ update_strings() return - if(stored_spell.masterwork_effect) + if(stored_spell.variant) to_chat(user, SPAN_WARNING("\The [src] is completely covered in runework, and cannot be further etched.")) return // Stage three: choose masterwork rune effect. - var/list/masterwork_effects = stored_spell.spell_master.get_masterwork_effects(user, tool) - if(!length(masterwork_effects)) + var/list/variants = stored_spell.spell_master.get_variants(user, tool) + if(!length(variants)) to_chat(user, SPAN_WARNING("You cannot see any way to refine the runes etched into \the [src].")) return - var/decl/anima_spell_effect/chosen_effect = input(user, "Which masterwork effect do you wish this runestone to evoke?", "Spellscribing") as null|anything in masterwork_effects - if(!chosen_effect || !stored_spell || !stored_spell.spell_master || stored_spell.masterwork_effect || !(chosen_effect in stored_spell.spell_master.get_masterwork_effects(user, tool))) + var/decl/wyrd_effect/chosen_effect = input(user, "Which masterwork effect do you wish this runestone to evoke?", "Spellscribing") as null|anything in variants + if(!chosen_effect || !stored_spell || !stored_spell.spell_master || stored_spell.variant || !(chosen_effect in stored_spell.spell_master.get_variants(user, tool))) return if(!scribe_check(user, tool, RUNESCRIBE_DIFFICULTY_MASTER)) if(can_scribe(user, tool)) // if it's just a tool failure then be nice, if it's a skill or do_after fail have no mercy to_chat(user, SPAN_DANGER("You slip up, and \the [src] cracks apart!")) crack_runestone(user, FALSE) // whoopsie poopsie return - if(!chosen_effect || !stored_spell || !stored_spell.spell_master || stored_spell.masterwork_effect || !(chosen_effect in stored_spell.spell_master.get_masterwork_effects(user, tool))) + if(!chosen_effect || !stored_spell || !stored_spell.spell_master || stored_spell.variant || !(chosen_effect in stored_spell.spell_master.get_variants(user, tool))) return to_chat(user, SPAN_NOTICE("You delicately etch cross-links and esoteric runes that translate [stored_spell.spell_master] into [chosen_effect].")) - stored_spell.masterwork_effect = chosen_effect + stored_spell.variant = chosen_effect update_strings() #undef RUNESCRIBE_DIFFICULTY_EASY diff --git a/mods/content/wyrd/spells/spell_archetypes.dm b/mods/content/wyrd/spells/spell_archetypes.dm new file mode 100644 index 000000000000..5769c6ecd4ac --- /dev/null +++ b/mods/content/wyrd/spells/spell_archetypes.dm @@ -0,0 +1,28 @@ +// Handles all effects associated with a spell. +/decl/wyrd_archetype + abstract_type = /decl/wyrd_archetype + var/name + var/decl/wyrd_effect/base_effect + var/list/variants + +/decl/wyrd_archetype/Initialize() + variants = decls_repository.get_decls_unassociated(variants) + base_effect = GET_DECL(base_effect) + name = base_effect.name + return ..() + +/decl/wyrd_archetype/proc/has_effect_type(effect_type) + return TRUE + +/decl/wyrd_archetype/proc/get_variants(mob/user, obj/item/implement) + // TODO: skill/trait/mind checks on user + return variants + +/decl/wyrd_archetype/flash + base_effect = /decl/wyrd_effect/flash + variants = list( + /decl/wyrd_effect/gloom + ) + +/decl/wyrd_archetype/flare + base_effect = /decl/wyrd_effect/flare diff --git a/mods/content/wyrd/spells/spell_datum.dm b/mods/content/wyrd/spells/spell_datum.dm new file mode 100644 index 000000000000..77631c4a9a02 --- /dev/null +++ b/mods/content/wyrd/spells/spell_datum.dm @@ -0,0 +1,14 @@ +var/global/list/_wyrd_spell_effect_types = list( + "area of effect" = /decl/wyrd_effect::WYRD_AOE, + "close-range" = /decl/wyrd_effect::WYRD_MELEE, + "long-range" = /decl/wyrd_effect::WYRD_RANGED +) + +/datum/wyrd_working + var/effect_type = /decl/wyrd_effect::WYRD_AOE + var/effect_strength = 1 + var/decl/wyrd_archetype/spell_master + var/decl/wyrd_effect/variant + +/datum/wyrd_working/New(_effect) + effect_type = _effect \ No newline at end of file diff --git a/mods/content/wyrd/spells/spell_effect.dm b/mods/content/wyrd/spells/spell_effect.dm new file mode 100644 index 000000000000..e633b8ae0a41 --- /dev/null +++ b/mods/content/wyrd/spells/spell_effect.dm @@ -0,0 +1,136 @@ +/decl/wyrd_effect + abstract_type = /decl/wyrd_effect + var/name + var/description + var/cost = 1 + var/arcana_skill = SKILL_SCIENCE // TODO: arcana or magic skill + var/stamina_cost = 20 + var/alist/minimum_anima + var/alist/maximum_anima + var/cast_sound = 'sound/effects/bamf.ogg' + var/cast_volume = 60 + + var/const/WYRD_AOE = 0 + var/const/WYRD_MELEE = 1 + var/const/WYRD_RANGED = 2 + var/const/ANIMA_INSUFFICIENT = 0 + var/const/ANIMA_SUITABLE = 1 + var/const/ANIMA_OVERSATURATED = 2 + +/decl/wyrd_effect/proc/evoke_spell(mob/user, atom/target, obj/item/runestone/runestone, caster_effect, caster_strength, in_proximity, deliberate = TRUE) + // Get our spell parameters. + var/spent_cost = runestone ? min(runestone.potentia_density, cost) : cost + var/evoke_effect = caster_effect || runestone?.stored_spell?.effect_type || WYRD_AOE + if(!in_proximity && evoke_effect == WYRD_MELEE) + evoke_effect = WYRD_RANGED + var/evoke_strength = caster_strength || runestone?.potentia_density || 1 + + // Cast the actual spell. + do_evocation(user, target, evoke_effect, evoke_strength, deliberate) + + // Expend resources. + if(runestone) + if(runestone.potentia_density > spent_cost && evoke_effect != WYRD_AOE) + runestone.potentia_density -= spent_cost + runestone.update_icon() + runestone.update_strings() + else if(!QDELETED(runestone)) + to_chat(user, SPAN_DANGER("Your runestone crumbles to dust!")) + qdel(runestone) + else if(stamina_cost) + user.adjust_stamina(-(stamina_cost)) + +/decl/wyrd_effect/proc/show_runestone_primed_message(mob/user, obj/item/runestone/stone) + to_chat(user, SPAN_NOTICE("Raw potentia wisps from \the [stone] and wreathes your [parse_zone(user.get_active_held_item_slot())], ready to be directed.")) + +/decl/wyrd_effect/proc/do_evocation(mob/user, atom/target, evoke_effect = WYRD_AOE, evoke_strength = 1, deliberate = TRUE) + SHOULD_CALL_PARENT(TRUE) + if(cast_sound && cast_volume) + playsound(get_turf(user), cast_sound, cast_volume) + +/decl/wyrd_effect/proc/can_be_worked_at(mob/user, turf/location) + + // TODO: compare against user and target anima + if(!length(minimum_anima) && !length(maximum_anima)) + return ANIMA_SUITABLE + + var/alist/local_anima = location.get_ambient_anima() + if(length(maximum_anima)) + for(var/atype,avalue in maximum_anima) + if(local_anima[atype] > maximum_anima[atype]) + return ANIMA_OVERSATURATED + if(length(minimum_anima)) + for(var/atype,avalue in minimum_anima) + if(local_anima[atype] < minimum_anima[atype]) + return ANIMA_INSUFFICIENT + + return ANIMA_SUITABLE + +/decl/wyrd_effect/flash + name = "Flash" + description = "Release the channeled potentia in a blinding flash of light." + +/decl/wyrd_effect/flash/do_evocation(mob/user, atom/target, evoke_effect = WYRD_AOE, evoke_strength = 1, deliberate = TRUE) + . = ..() + if(evoke_effect == WYRD_AOE) + user.visible_message(SPAN_DANGER("\The [user] emits a blinding flash of light!")) + for(var/mob/living/victim in view(evoke_strength, user)) + if(deliberate && victim == user) + continue + victim.handle_flashed(rand(evoke_strength, evoke_strength*2 + 1)) // min is 1 to 2, max is 3 to 7; base flash is 2 to 7 in a range of 3 tiles + else + user.visible_message(SPAN_DANGER("\The [user] drowns \the [target] in a blinding flash of light!")) + if(isliving(target)) + var/mob/living/victim = target + victim.handle_flashed(evoke_strength * 2 + 1) // since we're only attacking one target, we just go with the max instead of a random value + return TRUE + +/decl/wyrd_effect/gloom + name = "Gloom" + description = "Burn the channeled potentia to create a burst of cloying darkness." + +/decl/wyrd_effect/gloom/do_evocation(mob/user, atom/target, evoke_effect = WYRD_AOE, evoke_strength = 1, deliberate = TRUE) + . = ..() + if(evoke_effect == WYRD_AOE) + user.visible_message(SPAN_DANGER("\The [user] emits a burst of cloying darkness!")) + var/atom/movable/gloom/gloom = new(target, evoke_strength) // with AOE spell, target is user's turf + QDEL_IN_CLIENT_TIME(gloom, evoke_strength * 5 SECONDS) + else + user.visible_message(SPAN_DANGER("\The [user] drowns \the [target] in darkness!")) + if(isliving(target)) + var/mob/living/victim = target + victim.overlay_fullscreen("gloom", /obj/screen/fullscreen/blackout) + addtimer(CALLBACK(victim, TYPE_PROC_REF(/mob, clear_fullscreen), "gloom"), evoke_strength * 3 SECONDS) + return TRUE + +/atom/movable/gloom + simulated = FALSE + mouse_opacity = MOUSE_OPACITY_UNCLICKABLE + +/atom/movable/gloom/Initialize(ml, strength) + . = ..() + set_light(strength, -10, "#ffffff") // negative light, cleared on destroy + +/decl/wyrd_effect/flare + name = "Flare" + description = "Release the channeled potentia in an undirected burst of flame." + +/decl/wyrd_effect/flare/do_evocation(mob/user, atom/target, evoke_effect = WYRD_AOE, evoke_strength = 1, deliberate = TRUE) + . = ..() + var/turf/source_turf = get_turf(user) + // level 1 flare is hot enough to boil water. level 3 flare is hot enough to light a campfire and ignite wood + // divided by 0.9 and plus one to ensure inefficiency doesn't stop us from lighting campfires + var/fire_temperature = Interpolate(T100C, /decl/material/solid/organic/wood::ignition_point / 0.9 + 1, (evoke_strength - 1) / 3) + if(evoke_effect == WYRD_AOE) + user.visible_message(SPAN_DANGER("\The [user] is wreathed in a blast of fire!")) + for(var/turf/T in RANGE_TURFS(source_turf, evoke_strength)) + T = T.resolve_to_actual_turf() + new /obj/effect/fake_fire/variable/owned(T, fire_temperature, evoke_strength SECONDS, deliberate ? user : null) + else + user.visible_message(SPAN_DANGER("\The [user] hurls a blast of fire at \the [target]!")) + var/obj/item/projectile/fireball/projectile = new(source_turf) + projectile.fire_lifetime = evoke_strength SECONDS + // level 1 flare is hot enough to boil water. level 3 flare is hot enough to light a campfire and ignite wood + projectile.fire_temperature = Interpolate(T100C, projectile::fire_temperature, (evoke_strength - 1) / 3) + projectile.launch_from_gun(target, user.get_target_zone(), user) + return TRUE diff --git a/mods/content/wyrd/spells/spellcasting.dm b/mods/content/wyrd/spells/spellcasting.dm new file mode 100644 index 000000000000..1bf923b090b9 --- /dev/null +++ b/mods/content/wyrd/spells/spellcasting.dm @@ -0,0 +1,80 @@ +// Use a second ability handler so that spells are categorised separately to general wyrd abilities. +/datum/ability_handler/wyrd_workings + category_toggle_type = /obj/screen/ability/category/wyrd_workings + +/obj/screen/ability/category/wyrd_workings + name = "Wyrd Workings" + icon = 'mods/content/wyrd/icons/workings.dmi' + +// TODO: condition + skill +/mob/proc/get_wyrd_casting_strength() + return 1 + +/decl/ability/wyrd/spell + abstract_type = /decl/ability/wyrd/spell + target_selector = /decl/ability_targeting/single_atom/can_target_user + prep_cast = TRUE + is_melee_invocation = TRUE + is_ranged_invocation = TRUE + end_prep_on_cast = FALSE + associated_handler_type = /datum/ability_handler/wyrd_workings + + ready_ability_1p_str = SPAN_NOTICE("You weave potential, preparing yourself to cast $SPELL$.") + cancel_ability_1p_str = SPAN_NOTICE("You shake loose the woven energy, abandoning $SPELL$.") + fail_cast_1p_str = SPAN_WARNING("Your working unravels!") + + var/decl/wyrd_effect/effect + var/force_effect_type + +/decl/ability/wyrd/spell/prepare_to_cast(mob/user, atom/target, list/metadata, datum/ability_handler/handler) + if(!(. = ..())) + return + + var/decl/background_detail/culture = user.get_background_datum(/decl/background_category/faction) + switch(effect.can_be_worked_at(user, get_turf(target))) + if(effect.ANIMA_INSUFFICIENT) + to_chat(user, replacetext(culture.anima_failed_working_insufficient_1p, "$SPELL$", "[effect.name]")) + return FALSE + if(effect.ANIMA_OVERSATURATED) + to_chat(user, replacetext(culture.anima_failed_working_excess_1p, "$SPELL$", "[effect.name]")) + return FALSE + + if(effect.stamina_cost && user.get_stamina() < effect.stamina_cost) + to_chat(user, replacetext(culture.anima_failed_exhaustion_1p, "$SPELL$", "[effect.name]")) + return FALSE + +/decl/ability/wyrd/spell/validate() + . = ..() + if(!istype(effect, /decl/wyrd_effect)) + . += "missing or malformed effect type: '[effect]'" + +/decl/ability/wyrd/spell/Initialize() + effect = GET_DECL(effect) + var/effect_name = "[effect.name]" + ready_ability_1p_str = replacetext(ready_ability_1p_str, "$SPELL$", effect_name) + cancel_ability_1p_str = replacetext(cancel_ability_1p_str, "$SPELL$", effect_name) + . = ..() + +/decl/ability/wyrd/spell/apply_ability_effect_to(mob/living/user, atom/target, list/metadata) + . = ..() + var/target_self = (user == target) + var/proximity = target_self || (get_dist(user, target) <= 1 && user.Adjacent(target)) + var/range_effect = target_self ? /decl/wyrd_effect::WYRD_AOE : (proximity ? /decl/wyrd_effect::WYRD_MELEE : /decl/wyrd_effect::WYRD_RANGED) + effect.evoke_spell(user, target, caster_effect = range_effect, caster_strength = user.get_wyrd_casting_strength(), in_proximity = proximity) + +/decl/ability/wyrd/spell/flash + name = "Flash" + effect = /decl/wyrd_effect/flash + ability_icon_state = "flash" + +/decl/ability/wyrd/spell/gloom + name = "Gloom" + effect = /decl/wyrd_effect/gloom + ability_icon_state = "gloom" + +/decl/ability/wyrd/spell/flare + name = "Flare" + effect = /decl/wyrd_effect/flare + ability_icon_state = "flare" + // don't let people set themselves alight + target_selector = /decl/ability_targeting/single_atom diff --git a/mods/pyrelight/datum/traits/_wyrd.dm b/mods/content/wyrd/traits/_wyrd.dm similarity index 100% rename from mods/pyrelight/datum/traits/_wyrd.dm rename to mods/content/wyrd/traits/_wyrd.dm diff --git a/mods/pyrelight/datum/traits/_wyrd_categories.dm b/mods/content/wyrd/traits/wyrd_signs.dm similarity index 54% rename from mods/pyrelight/datum/traits/_wyrd_categories.dm rename to mods/content/wyrd/traits/wyrd_signs.dm index c2e38cb2338b..f9e42ec01789 100644 --- a/mods/pyrelight/datum/traits/_wyrd_categories.dm +++ b/mods/content/wyrd/traits/wyrd_signs.dm @@ -1,14 +1,22 @@ -// TODO: actual effects -// TODO: integrate with wyrdling -/* +/decl/trait/wyrd + var/alist/modify_personal_anima = alist() + +/decl/trait/wyrd/apply_trait(mob/living/holder) + . = ..() + for(var/anima_type,anima_amount in modify_personal_anima) + holder.adjust_personal_anima(anima_type, anima_amount) + /decl/trait/wyrd/fire name = "Burning Sign" description = "Affinity for the New School of alchemical working (energy, force, fire, lightning). TODO." incompatible_with = list( /decl/trait/wyrd/deep, /decl/trait/wyrd/sky, - /decl/trait/wyrd/wild, - /decl/trait/wyrd/flesh + /decl/trait/wyrd/flesh, + /decl/trait/wyrd/waning + ) + modify_personal_anima = alist( + /decl/anima/blood = /decl/anima::ANIMA_NOTABLE ) uid = "trait_wyrd_flame" @@ -18,8 +26,11 @@ incompatible_with = list( /decl/trait/wyrd/deep, /decl/trait/wyrd/fire, - /decl/trait/wyrd/wild, - /decl/trait/wyrd/flesh + /decl/trait/wyrd/flesh, + /decl/trait/wyrd/waning + ) + modify_personal_anima = alist( + /decl/anima/sky = /decl/anima::ANIMA_NOTABLE ) uid = "trait_wyrd_sky" @@ -29,35 +40,39 @@ incompatible_with = list( /decl/trait/wyrd/sky, /decl/trait/wyrd/fire, - /decl/trait/wyrd/wild, - /decl/trait/wyrd/flesh + /decl/trait/wyrd/flesh, + /decl/trait/wyrd/waning + ) + modify_personal_anima = alist( + /decl/anima/deep = /decl/anima::ANIMA_NOTABLE ) uid = "trait_wyrd_hollow" -/decl/trait/wyrd/flesh - name = "Rose Sign" - description = "Affinity for blood, flesh, bone - healing, necromancy, blood alchemy, manipulation of living material. TODO." +/decl/trait/wyrd/waning + name = "Moon Sign" + description = "Affinity for twilight, transience, change. TODO." incompatible_with = list( /decl/trait/wyrd/sky, /decl/trait/wyrd/fire, - /decl/trait/wyrd/wild, + /decl/trait/wyrd/flesh, /decl/trait/wyrd/deep ) - uid = "trait_wyrd_flesh" -*/ -/decl/trait/wyrd/wild - name = "Wild Sign" - description = "A wyrdling is a human whose soul has been touched by the primeveal \ - anima of the wilds, carried down through blood and manifesting in strange ways. The \ - wyrdmarked are often treated with mistrust or fear by the general populace, leading \ - many to cover their wyrdmarks and hide their nature." - permitted_species = list(/decl/species/human::uid) - uid = "trait_wyrd_wild" -/* + modify_personal_anima = alist( + /decl/anima/waning = /decl/anima::ANIMA_NOTABLE + ) + uid = "trait_wyrd_moon" + +/decl/trait/wyrd/flesh + name = "Rose Sign" + description = "Affinity for blood, flesh, bone - healing, necromancy, blood alchemy, manipulation of living material. TODO." incompatible_with = list( - /decl/trait/wyrd/fire, /decl/trait/wyrd/sky, + /decl/trait/wyrd/fire, /decl/trait/wyrd/deep, - /decl/trait/wyrd/flesh + /decl/trait/wyrd/waning ) -*/ \ No newline at end of file + modify_personal_anima = alist( + /decl/anima/blood = /decl/anima::ANIMA_NEGLIGIBLE, + /decl/anima/waning = /decl/anima::ANIMA_NEGLIGIBLE + ) + uid = "trait_wyrd_flesh" diff --git a/mods/pyrelight/datum/traits/wyrd_wild.dm b/mods/content/wyrd/traits/wyrd_wild.dm similarity index 58% rename from mods/pyrelight/datum/traits/wyrd_wild.dm rename to mods/content/wyrd/traits/wyrd_wild.dm index ea9a4dc4e95d..a37861f1a52e 100644 --- a/mods/pyrelight/datum/traits/wyrd_wild.dm +++ b/mods/content/wyrd/traits/wyrd_wild.dm @@ -1,11 +1,25 @@ +/decl/trait/wyrd/wild + name = "Wild Blood" + description = "A wyrdling is a human whose soul has been touched by the primeveal \ + anima of the wilds, carried down through blood and manifesting in strange ways. The \ + wyrdmarked are often treated with mistrust or fear by the general populace, leading \ + many to cover their wyrdmarks and hide their nature." + permitted_species = list(/decl/species/human::uid) + modify_personal_anima = alist( + /decl/anima/sky = /decl/anima::ANIMA_NEGLIGIBLE, + /decl/anima/waning = /decl/anima::ANIMA_NEGLIGIBLE + ) + uid = "trait_wyrd_wild" + /decl/trait/wyrd/wild/animal_form abstract_type = /decl/trait/wyrd/wild/animal_form name = "Animal Semblance" - description = "Some wyrdlings possess the ability to 'veer' into the form of an \ - animal, known as the 'semblance'. Such wyrdlings use masks of bone or wood to focus \ - and control the veering, making it difficult for them to conceal their abilities." + description = "Some wyrdlings possess the ability to shift into the form of an animal, \ + known as the 'semblance'. Such wyrdlings use masks of bone or wood to focus and control \ + the sembling, making it difficult for them to conceal their abilities." parent = /decl/trait/wyrd/wild incompatible_with = null + modify_personal_anima = null // Already applied by parent. var/mask_type /decl/trait/wyrd/wild/animal_form/Initialize() @@ -48,3 +62,9 @@ name = "Wolf Semblance" mask_type = /obj/item/clothing/mask/ghost_caul/wolf uid = "trait_wyrd_wild_wolf" + +/decl/sprite_accessory/ears/biomods/animal + required_traits = list(/decl/trait/wyrd/wild) + +/decl/sprite_accessory/tail/biomods + required_traits = list(/decl/trait/wyrd/wild) diff --git a/mods/content/wyrd/wyrd_abilities.dm b/mods/content/wyrd/wyrd_abilities.dm new file mode 100644 index 000000000000..fc9e70150a59 --- /dev/null +++ b/mods/content/wyrd/wyrd_abilities.dm @@ -0,0 +1,53 @@ +// Handler for a basic set of wyrd abilities - primarily your sensitivity to local anima and ability to check your own anima. +/datum/ability_handler/wyrd_general + category_toggle_type = /obj/screen/ability/category/wyrd_general + +/datum/ability_handler/wyrd/general + +/obj/screen/ability/category/wyrd_general + name = "Wyrd Practice" + icon = 'mods/content/wyrd/icons/abilities.dmi' + +/obj/screen/ability/button/wyrd + icon = 'mods/content/wyrd/icons/abilities.dmi' + +/decl/ability/wyrd + abstract_type = /decl/ability/wyrd + ability_icon = 'mods/content/wyrd/icons/abilities.dmi' + target_selector = /decl/ability_targeting/target_self + associated_handler_type = /datum/ability_handler/wyrd_general + ui_element_type = /obj/screen/ability/button/wyrd + +/decl/ability/wyrd/check_ambient + name = "Dowse Aura" + ability_icon_state = "outward" + +/decl/ability/wyrd/check_ambient/apply_ability_effect_to(mob/living/user, atom/target, list/metadata) + . = ..() + for(var/atype,avalue in user.get_ambient_anima()) + var/decl/anima/anima = GET_DECL(atype) + to_chat(user, anima.get_ambient_anima_description(avalue, user.get_background_datum(/decl/background_category/faction))) + +/decl/ability/wyrd/check_personal + name = "Self-Reflection" + ability_icon_state = "inward" + +/decl/ability/wyrd/check_personal/apply_ability_effect_to(mob/living/user, atom/target, list/metadata) + . = ..() + for(var/atype,avalue in user.get_personal_anima_pool()) + var/decl/anima/anima = GET_DECL(atype) + to_chat(user, anima.get_personal_anima_description(avalue, user.get_background_datum(/decl/background_category/faction))) + +/mob/living/verb/debug_anima_verb() + set name = "Debug Anima" + set category = "Debug" + set src = usr + + set_extension(src, /datum/extension/anima_aura) + + add_ability(/decl/ability/wyrd/check_ambient) + add_ability(/decl/ability/wyrd/check_personal) + + add_ability(/decl/ability/wyrd/spell/flash) + add_ability(/decl/ability/wyrd/spell/gloom) + add_ability(/decl/ability/wyrd/spell/flare) diff --git a/mods/pyrelight/datum/wyrdling/mask.dm b/mods/content/wyrd/wyrdling_mask.dm similarity index 88% rename from mods/pyrelight/datum/wyrdling/mask.dm rename to mods/content/wyrd/wyrdling_mask.dm index a4195e91a765..edb089db9973 100644 --- a/mods/pyrelight/datum/wyrdling/mask.dm +++ b/mods/content/wyrd/wyrdling_mask.dm @@ -71,23 +71,6 @@ new_color = initial(eye_color) eye_color = new_color -/decl/loadout_option/fantasy/mask/ghost_caul - name = "customised wyrdling mask" - path = /obj/item/clothing/mask/ghost_caul - uid = "gear_misc_wyrdling_mask" - apply_to_existing_if_possible = TRUE - required_traits = list(/decl/trait/wyrd/wild) - available_materials = list( - /decl/material/solid/organic/bone, - /decl/material/solid/stone/marble, - /decl/material/solid/stone/basalt, - /decl/material/solid/organic/wood/oak, - /decl/material/solid/organic/wood/mahogany, - /decl/material/solid/organic/wood/maple, - /decl/material/solid/organic/wood/ebony, - /decl/material/solid/organic/wood/walnut - ) - /obj/item/clothing/mask/ghost_caul name = "mask" abstract_type = /obj/item/clothing/mask/ghost_caul @@ -100,8 +83,8 @@ var/const/preview_offset = 28 var/transformation_trait var/transformation_mob_type + var/mob/living/_our_owner VAR_PRIVATE/mob/living/_our_animal - var/mob/living/our_owner /obj/item/clothing/mask/ghost_caul/get_preview_screen_locs() var/static/list/override_preview_screen_locs = list( @@ -114,15 +97,18 @@ /obj/item/clothing/mask/ghost_caul/Destroy() // Only qdel our animal if they are not out and about in the world. - if(_our_animal && !_our_animal.key && (_our_animal.loc == src || isnull(_our_animal.loc))) - qdel(_our_animal) + if(_our_animal) + if(!_our_animal.key && (_our_animal.loc == src || isnull(_our_animal.loc))) + qdel(_our_animal) + else + to_chat(_our_animal, SPAN_DANGER("You feel a dull, diffuse pain in your spirit as something precious is lost...")) _our_animal = null - our_owner = null + _our_owner = null . = ..() -/mob/proc/revert_veering() +/mob/proc/wyrdling_revert_sembling() - set name = "Veer To Human" + set name = "Semble Human" set category = "IC" set src = usr @@ -130,17 +116,17 @@ return var/obj/item/clothing/mask/ghost_caul/mask = locate() in src - if(!mask?.our_owner) + if(!mask?._our_owner) to_chat(src, SPAN_WARNING("You feel only an emptiness where your human self used to reside.")) - verbs -= /mob/proc/revert_veering + verbs -= /mob/proc/wyrdling_revert_sembling return visible_message( SPAN_NOTICE("\The [src] stills and closes their eyes."), SPAN_NOTICE("You close your eyes and focus on your human self.") ) - if(do_after(src, 5 SECONDS) && mask.our_owner) - wyrd_transform_into(mask.our_owner, mask) + if(do_after(src, 5 SECONDS) && mask._our_owner) + wyrd_transform_into(mask._our_owner, mask) /mob/proc/wyrd_transform_into(mob/living/target, obj/item/clothing/mask/ghost_caul/mask) @@ -183,7 +169,7 @@ transfer_key_from_mob_to_mob(src, target) target.forceMove(get_turf(src)) forceMove(null) - target.visible_message(SPAN_NOTICE("\The [src] blurs, distorts and veers into \a [target].")) + target.visible_message(SPAN_NOTICE("\The [src] blurs, distorts and transforms into \a [target].")) return TRUE // Used to provide wyrdling mob preview. @@ -193,7 +179,7 @@ return null if(!_our_animal) _our_animal = new transformation_mob_type - _our_animal.verbs |= /mob/proc/revert_veering + _our_animal.verbs |= /mob/proc/wyrdling_revert_sembling _our_animal.remove_from_living_mob_list() // don't process! _our_animal.copy_wyrd_from(owner) return _our_animal @@ -221,7 +207,7 @@ return var/mob/living/user_living = user if(user_living.get_equipped_slot_for_item(src) == slot_wear_mask_str && user_living.has_trait(/decl/trait/wyrd/wild)) - action_button_name = "Veer" + action_button_name = "Semble" else if(action) action_button_name = null user_living.actions -= action @@ -238,10 +224,10 @@ user.visible_message( SPAN_NOTICE("\The [user] becomes still, concentrating."), - SPAN_NOTICE("You close your eyes and turn your focus inward, preparing to veer into your semblance.") + SPAN_NOTICE("You close your eyes and turn your focus inward, preparing to discard your human semblance.") ) if(!do_after(user, 5 SECONDS, src) || QDELETED(user) || user.get_equipped_item(slot_wear_mask_str) != src) - to_chat(user, SPAN_WARNING("You must remain still and wear your mask to veer.")) + to_chat(user, SPAN_WARNING("You must remain still and wear your mask to semble.")) return TRUE user.wyrd_transform_into(get_wyrd_animal(user), src) diff --git a/mods/pyrelight/_pyrelight.dme b/mods/pyrelight/_pyrelight.dme index 1644ac27db6b..c44d0764637f 100644 --- a/mods/pyrelight/_pyrelight.dme +++ b/mods/pyrelight/_pyrelight.dme @@ -2,12 +2,6 @@ #ifndef MODPACK_PYRELIGHT #define MODPACK_PYRELIGHT #include "_pyrelight.dm" -#include "datum\traits\_wyrd.dm" -#include "datum\traits\_wyrd_categories.dm" -#include "datum\traits\wyrd_wild.dm" -#include "datum\wyrdling\ears.dm" -#include "datum\wyrdling\mask.dm" -#include "datum\wyrdling\tails.dm" #include "datum\culture.dm" #include "datum\factions.dm" #include "datum\locations.dm" diff --git a/mods/pyrelight/datum/wyrdling/ears.dm b/mods/pyrelight/datum/wyrdling/ears.dm deleted file mode 100644 index 278dd93c7dba..000000000000 --- a/mods/pyrelight/datum/wyrdling/ears.dm +++ /dev/null @@ -1,2 +0,0 @@ -/decl/sprite_accessory/ears/biomods/animal - required_traits = list(/decl/trait/wyrd/wild) diff --git a/mods/pyrelight/datum/wyrdling/tails.dm b/mods/pyrelight/datum/wyrdling/tails.dm deleted file mode 100644 index 44203ca2433d..000000000000 --- a/mods/pyrelight/datum/wyrdling/tails.dm +++ /dev/null @@ -1,2 +0,0 @@ -/decl/sprite_accessory/tail/biomods - required_traits = list(/decl/trait/wyrd/wild) diff --git a/mods/~compatibility/patches/fantasy.dm b/mods/~compatibility/patches/fantasy.dm index 090e04dca00a..58c04a7a8c55 100644 --- a/mods/~compatibility/patches/fantasy.dm +++ b/mods/~compatibility/patches/fantasy.dm @@ -9,4 +9,8 @@ #ifdef MODPACK_UNDEAD #include "fantasy/undead_fantasy.dm" -#endif \ No newline at end of file +#endif + +#ifdef MODPACK_WYRD +#include "fantasy/wyrd_fantasy.dm" +#endif diff --git a/mods/~compatibility/patches/fantasy/wyrd_fantasy.dm b/mods/~compatibility/patches/fantasy/wyrd_fantasy.dm new file mode 100644 index 000000000000..43b72c7ccd7d --- /dev/null +++ b/mods/~compatibility/patches/fantasy/wyrd_fantasy.dm @@ -0,0 +1,16 @@ +/decl/loadout_option/fantasy/mask/ghost_caul + name = "customised wyrdling mask" + path = /obj/item/clothing/mask/ghost_caul + uid = "gear_misc_wyrdling_mask" + apply_to_existing_if_possible = TRUE + required_traits = list(/decl/trait/wyrd/wild) + available_materials = list( + /decl/material/solid/organic/bone, + /decl/material/solid/stone/marble, + /decl/material/solid/stone/basalt, + /decl/material/solid/organic/wood/oak, + /decl/material/solid/organic/wood/mahogany, + /decl/material/solid/organic/wood/maple, + /decl/material/solid/organic/wood/ebony, + /decl/material/solid/organic/wood/walnut + ) diff --git a/test/check-paths.sh b/test/check-paths.sh index f59aa02ce4c7..44b988aff5ea 100755 --- a/test/check-paths.sh +++ b/test/check-paths.sh @@ -41,7 +41,7 @@ exactly 0 "incorrect indentations" '^( {4,})' -P exactly 22 "text2path uses" 'text2path' exactly 4 "update_icon() overrides" '\/update_icon\(' -P exactly 0 "goto uses" '\bgoto\b' -exactly 10 "atom/New uses" '^/(obj|atom|area|mob|turf).*/New\(' +exactly 11 "atom/New uses" '^/(obj|atom|area|mob|turf).*/New\(' exactly 1 "decl/New uses" '^/decl.*/New\(' exactly 3 "tag uses" '(?