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 000000000000..c30b116d94d0
Binary files /dev/null and b/mods/content/wyrd/icons/abilities.dmi differ
diff --git a/mods/content/anima/icons/anima_blank.dmi b/mods/content/wyrd/icons/blanks.dmi
similarity index 69%
rename from mods/content/anima/icons/anima_blank.dmi
rename to mods/content/wyrd/icons/blanks.dmi
index c218a1e03a7e..870e8e49b660 100644
Binary files a/mods/content/anima/icons/anima_blank.dmi and b/mods/content/wyrd/icons/blanks.dmi differ
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 000000000000..c03b082752e5
Binary files /dev/null and b/mods/content/wyrd/icons/workings.dmi differ
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" '(?