diff --git a/code/_helpers/turfs.dm b/code/_helpers/turfs.dm
index d74b742f97a5..c39b745f6b03 100644
--- a/code/_helpers/turfs.dm
+++ b/code/_helpers/turfs.dm
@@ -120,9 +120,9 @@
if(target)
if(base_area)
- ChangeArea(target, get_area(source))
+ target.ChangeArea(get_area(source))
. += transport_turf_contents(source, target, ignore_background, translate_air, angle = angle)
- ChangeArea(source, base_area)
+ source.ChangeArea(base_area)
else
. += transport_turf_contents(source, target, ignore_background, translate_air, angle = angle)
//change the old turfs
diff --git a/code/datums/extensions/abilities/ability_button.dm b/code/datums/extensions/abilities/ability_button.dm
index 5ab2d44e66b8..7bebb71a1993 100644
--- a/code/datums/extensions/abilities/ability_button.dm
+++ b/code/datums/extensions/abilities/ability_button.dm
@@ -65,7 +65,7 @@
/obj/screen/ability/button/handle_click(mob/user, params)
if(owning_handler.prepared_ability == ability)
owning_handler.cancel_prepared_ability()
- else if(ability.use_ability(user, get_turf(user), owning_handler)) // tmp, needs better/multi-step target selection
+ else if(ability.use_ability(user, user, owning_handler)) // tmp, needs better/multi-step target selection
update_icon()
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_icon)), ability.get_cooldown_time(ability.get_metadata_for_user(user)) + 1)
diff --git a/code/datums/extensions/abilities/ability_decl.dm b/code/datums/extensions/abilities/ability_decl.dm
index a3cb3726f89f..1c11a89414d4 100644
--- a/code/datums/extensions/abilities/ability_decl.dm
+++ b/code/datums/extensions/abilities/ability_decl.dm
@@ -182,7 +182,7 @@
else
// Otherwise, just apply to the target directly.
- apply_effect(user, target, metadata)
+ apply_ability_effect(user, target, metadata)
if(end_prep_on_cast && handler.prepared_ability == src)
handler.cancel_prepared_ability()
@@ -325,7 +325,7 @@
return TRUE
-/decl/ability/proc/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+/decl/ability/proc/apply_ability_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
SHOULD_CALL_PARENT(TRUE)
if(use_sound)
playsound(get_turf(user), use_sound, use_sound_volume, 1)
@@ -339,7 +339,7 @@
show_ability_cast_msg(user, targets, metadata)
while(length(targets))
var/target = targets[1]
- apply_effect_to(user, target, metadata)
+ apply_ability_effect_to(user, target, metadata)
targets = prune_targets(user, target, targets, metadata)
finish_casting(user, hit_target, metadata)
@@ -366,7 +366,7 @@
ability_overlay.set_density(FALSE)
QDEL_IN(ability_overlay, overlay_lifespan)
-/decl/ability/proc/apply_effect_to(mob/user, atom/target, list/metadata)
+/decl/ability/proc/apply_ability_effect_to(mob/living/user, atom/target, list/metadata)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
apply_visuals(user, target, metadata)
diff --git a/code/datums/extensions/abilities/ability_handler.dm b/code/datums/extensions/abilities/ability_handler.dm
index 0b773cb469d5..d81a8bd023b8 100644
--- a/code/datums/extensions/abilities/ability_handler.dm
+++ b/code/datums/extensions/abilities/ability_handler.dm
@@ -125,7 +125,7 @@
add_screen_element(category_toggle, "toggle", TRUE)
toggle_category_visibility(TRUE)
-/datum/ability_handler/proc/refresh_element_positioning(row = 1, col = 1)
+/datum/ability_handler/proc/refresh_element_positioning(row = 1, col = 0)
if(!LAZYLEN(screen_elements))
return 0
var/button_pos = col
@@ -134,14 +134,14 @@
for(var/ability in screen_elements)
var/obj/screen/element = screen_elements[ability]
if(istype(element, /obj/screen/ability/category))
- element.screen_loc = "RIGHT-[col]:-4,TOP-[row]"
+ element.screen_loc = "RIGHT-[col]:-4,TOP-[row]:-24"
else if(!element.invisibility)
button_pos++
if((button_pos-col) > 5)
button_row++
.++
button_pos = col+1
- element.screen_loc = "RIGHT-[button_pos]:-4,TOP-[button_row]"
+ element.screen_loc = "RIGHT-[button_pos]:-4,TOP-[button_row]:-24"
/datum/ability_handler/proc/toggle_category_visibility(force_state)
showing_abilities = isnull(force_state) ? !showing_abilities : force_state
diff --git a/code/datums/extensions/abilities/ability_item.dm b/code/datums/extensions/abilities/ability_item.dm
index b4838fd41458..666d6d268e36 100644
--- a/code/datums/extensions/abilities/ability_item.dm
+++ b/code/datums/extensions/abilities/ability_item.dm
@@ -54,8 +54,8 @@
// Fire a projectile if that is how this ability works.
ability.fire_projectile_at(user, target, metadata)
else
- // Otherwise, apply to the target. Range checking etc. will be handled in apply_effect().
- ability.apply_effect(user, target, metadata)
+ // Otherwise, apply to the target. Range checking etc. will be handled in apply_ability_effect().
+ ability.apply_ability_effect(user, target, metadata)
// Clean up our item if needed.
if(ability.item_end_on_cast)
diff --git a/code/datums/extensions/abilities/ability_projectile.dm b/code/datums/extensions/abilities/ability_projectile.dm
index 896acba2e95a..5b5f2f10b373 100644
--- a/code/datums/extensions/abilities/ability_projectile.dm
+++ b/code/datums/extensions/abilities/ability_projectile.dm
@@ -22,10 +22,10 @@
/obj/item/projectile/ability/Bump(var/atom/A, forced=0)
if(loc && carried_ability && !expended)
- carried_ability.apply_effect(owner, A, ability_metadata, src)
+ carried_ability.apply_ability_effect(owner, A, ability_metadata, src)
return TRUE
/obj/item/projectile/ability/on_impact(var/atom/A)
if(loc && carried_ability && !expended)
- carried_ability.apply_effect(owner, A, ability_metadata, src)
+ carried_ability.apply_ability_effect(owner, A, ability_metadata, src)
return TRUE
diff --git a/code/datums/extensions/abilities/ability_targeting.dm b/code/datums/extensions/abilities/ability_targeting.dm
index 52f7ee8166c5..e93a59c3a793 100644
--- a/code/datums/extensions/abilities/ability_targeting.dm
+++ b/code/datums/extensions/abilities/ability_targeting.dm
@@ -72,19 +72,35 @@
return FALSE
return TRUE
+/decl/ability_targeting/target_self
+ target_turf = FALSE
+
+/decl/ability_targeting/target_self/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ return target == user
+
+/decl/ability_targeting/target_self/get_affected(mob/user, atom/hit_target, list/metadata, decl/ability/ability, obj/item/projectile/ability/projectile)
+ return list(user)
+
/decl/ability_targeting/clear_turf
ignore_dense_turfs = TRUE
/decl/ability_targeting/clear_turf/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
. = ..() && isturf(target)
if(.)
- var/turf/target_turf = target
- return !target_turf.contains_dense_objects(user)
+ var/turf/turf_to_target = target
+ return !turf_to_target.contains_dense_objects(user)
+
+/decl/ability_targeting/single_atom
+ target_turf = FALSE
+ user_is_immune = TRUE
+
+/decl/ability_targeting/single_atom/can_target_user
+ user_is_immune = FALSE
-/decl/ability_targeting/living_mob
- target_turf = FALSE
+/decl/ability_targeting/single_atom/get_affected(mob/user, atom/hit_target, list/metadata, decl/ability/ability, obj/item/projectile/ability/projectile)
+ return list(hit_target)
-/decl/ability_targeting/living_mob/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+/decl/ability_targeting/single_atom/living_mob/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
. = ..() && isliving(target)
if(.)
var/mob/living/victim = target
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 452a1a0f3fa2..2b29252a014a 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -138,43 +138,43 @@ var/global/list/areas = list()
// Changes the area of T to A. Do not do this manually.
// Area is expected to be a non-null instance.
-/proc/ChangeArea(var/turf/T, var/area/A)
+/turf/proc/ChangeArea(var/area/A)
if(!istype(A))
CRASH("Area change attempt failed: invalid area supplied.")
- var/old_outside = T.is_outside()
- var/area/old_area = get_area(T)
+ var/old_outside = is_outside()
+ var/area/old_area = get_area(src)
if(old_area == A)
return
var/old_area_ambience = old_area?.interior_ambient_light_modifier
- A.contents.Add(T)
+ A.contents.Add(src)
if(old_area)
- old_area.Exited(T, A)
- for(var/atom/movable/AM as anything in T)
+ old_area.Exited(src, A)
+ for(var/atom/movable/AM as anything in src)
old_area.Exited(AM, A) // Note: this _will_ raise exited events.
- A.Entered(T, old_area)
- for(var/atom/movable/AM as anything in T)
+ A.Entered(src, old_area)
+ for(var/atom/movable/AM as anything in src)
A.Entered(AM, old_area) // Note: this will _not_ raise moved or entered events. If you change this, you must also change everything which uses them.
- for(var/obj/machinery/M in T)
+ for(var/obj/machinery/M in src)
M.area_changed(old_area, A) // They usually get moved events, but this is the one way an area can change without triggering one.
- T.update_registrations_on_adjacent_area_change()
+ update_registrations_on_adjacent_area_change()
for(var/direction in global.cardinal)
- var/turf/adjacent_turf = get_step(T, direction)
+ var/turf/adjacent_turf = get_step(src, direction)
if(adjacent_turf)
adjacent_turf.update_registrations_on_adjacent_area_change()
// Handle updating weather and atmos if the outside status of the turf changed.
- if(T.is_outside == OUTSIDE_AREA)
- T.update_external_atmos_participation() // Refreshes outside status and adds exterior air to turf air if necessary.
+ if(is_outside == OUTSIDE_AREA)
+ update_external_atmos_participation() // Refreshes outside status and adds exterior air to turf air if necessary.
- if(T.is_outside() != old_outside)
- T.update_weather()
- AMBIENCE_QUEUE_TURF(T)
+ if(is_outside() != old_outside)
+ update_weather()
+ AMBIENCE_QUEUE_TURF(src)
else if(A.interior_ambient_light_modifier != old_area_ambience)
- AMBIENCE_QUEUE_TURF(T)
+ AMBIENCE_QUEUE_TURF(src)
/turf/proc/update_registrations_on_adjacent_area_change()
for(var/obj/machinery/door/firedoor/door in src)
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index 0c4d7107c015..f84412b077d2 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -58,7 +58,7 @@
/turf/space/LateInitialize()
if(SSmapping.base_floor_area)
var/area/new_area = locate(SSmapping.base_floor_area) || new SSmapping.base_floor_area
- ChangeArea(src, new_area)
+ ChangeArea(new_area)
ChangeTurf(SSmapping.base_floor_type)
/turf/space/proc/toggle_transit(var/direction)
diff --git a/code/modules/admin/buildmode/mode_areas.dm b/code/modules/admin/buildmode/mode_areas.dm
index 4df3b7e41797..92366800d9ad 100644
--- a/code/modules/admin/buildmode/mode_areas.dm
+++ b/code/modules/admin/buildmode/mode_areas.dm
@@ -119,7 +119,7 @@
if(!istype(T) || !istype(area_mode))
return FALSE
if (area_mode.selected_area)
- ChangeArea(T, area_mode.selected_area)
+ T.ChangeArea(area_mode.selected_area)
to_chat(build_mode.user, SPAN_NOTICE("Set area of turf [T.name] to [area_mode.selected_area.proper_name]"))
return TRUE
to_chat(build_mode.user, SPAN_WARNING("Pick or create an area first"))
diff --git a/code/modules/item_effects/item_effect_charges.dm b/code/modules/item_effects/item_effect_charges.dm
index 06e8126b16fe..1c396925f9a7 100644
--- a/code/modules/item_effects/item_effect_charges.dm
+++ b/code/modules/item_effects/item_effect_charges.dm
@@ -19,9 +19,26 @@
damage = 20
atom_damage_type = BURN
damage_flags = DAM_DISPERSED // burn all over
+ layer = ABOVE_LIGHTING_LAYER
+ plane = ABOVE_LIGHTING_PLANE
+ light_range = 1
+ light_power = 1
+ light_color = LIGHT_COLOR_FIRE
+ var/datum/composite_sound/fire_crackles/fire_loop
var/fire_lifetime = 2 SECONDS
var/fire_temperature = (288 CELSIUS) / 0.9 + 1 // hot enough to ignite wood! divided by 0.9 and plus one to ensure we can light firepits
+/obj/item/projectile/fireball/Initialize()
+ . = ..()
+ fire_loop = new(list(src), FALSE)
+ fire_loop.start(src)
+
+/obj/item/projectile/fireball/Destroy()
+ . = ..()
+ if(fire_loop?.started)
+ fire_loop.stop(src)
+ QDEL_NULL(fire_loop)
+
/obj/effect/fake_fire/variable
name = "fire"
anchored = TRUE
diff --git a/code/modules/maps/reader.dm b/code/modules/maps/reader.dm
index 71bedba36393..42448df2ec62 100644
--- a/code/modules/maps/reader.dm
+++ b/code/modules/maps/reader.dm
@@ -333,7 +333,7 @@ var/global/dmm_suite/preloader/_preloader = new
instance = new atype(null)
initialized_areas_by_type[atype] = instance
if(crds)
- ChangeArea(crds, instance)
+ crds.ChangeArea(instance)
//then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
diff --git a/code/modules/mob/living/human/human.dm b/code/modules/mob/living/human/human.dm
index a296f4413444..9a7cd0ec0753 100644
--- a/code/modules/mob/living/human/human.dm
+++ b/code/modules/mob/living/human/human.dm
@@ -891,20 +891,20 @@
if(!defer_language_update)
update_languages()
-/mob/living/proc/get_background_datum_by_flag(background_flag)
+/mob/proc/get_background_datum_by_flag(background_flag)
var/list/all_categories = global.using_map.get_background_categories()
for(var/cat_type in all_categories)
var/decl/background_category/background_cat = all_categories[cat_type]
if(background_cat.background_flags && (background_cat.background_flags & background_flag))
return get_background_datum(cat_type)
-/mob/living/proc/get_background_datum(cat_type)
- return null
+/mob/proc/get_background_datum(cat_type)
+ return global.using_map.default_background_info[cat_type]
/mob/living/human/get_background_datum(cat_type)
. = LAZYACCESS(background_info, cat_type)
if(!istype(., /decl/background_detail))
- . = global.using_map.default_background_info[cat_type]
+ . = ..()
PRINT_STACK_TRACE("get_background_datum() tried to return a non-instance value for background category '[cat_type]' - full background list: [json_encode(background_info)] default species culture list: [json_encode(global.using_map.default_background_info)]")
/mob/living/human/get_digestion_product()
diff --git a/code/modules/mob/observer/eye/blueprints_eye.dm b/code/modules/mob/observer/eye/blueprints_eye.dm
index c7bae7b5f9c7..6309e82b166d 100644
--- a/code/modules/mob/observer/eye/blueprints_eye.dm
+++ b/code/modules/mob/observer/eye/blueprints_eye.dm
@@ -66,7 +66,7 @@
var/area/A = new
A.SetName(area_name)
for(var/turf/T in selected_turfs)
- ChangeArea(T, A)
+ T.ChangeArea(A)
finalize_area(A)
remove_selection() // Reset the selection for clarity.
@@ -90,7 +90,7 @@
var/datum/level_data/our_level_data = SSmapping.levels_by_z[our_turf.z]
var/area/base_area = our_level_data.get_base_area_instance()
for(var/turf/T in A.contents)
- ChangeArea(T, base_area)
+ T.ChangeArea(base_area)
if(!(locate(/turf) in A))
qdel(A) // uh oh, is this safe?
@@ -316,7 +316,7 @@
var/datum/level_data/our_level_data = SSmapping.levels_by_z[our_turf.z]
var/area/base_area = our_level_data.get_base_area_instance()
for(var/turf/T in A.contents)
- ChangeArea(T, base_area)
+ T.ChangeArea(base_area)
if(!(locate(/turf) in A))
qdel(A) // uh oh, is this safe?
diff --git a/code/modules/multiz/level_data.dm b/code/modules/multiz/level_data.dm
index 389f3e24923b..5eec9b40a320 100644
--- a/code/modules/multiz/level_data.dm
+++ b/code/modules/multiz/level_data.dm
@@ -228,7 +228,7 @@
if(change_turf)
T = T.ChangeTurf(picked_turf)
if(change_area)
- ChangeArea(T, A)
+ T.ChangeArea(A)
///Prepare level for being used. Setup borders, lateral z connections, ambient lighting, atmosphere, etc..
/datum/level_data/proc/setup_level_data(var/skip_gen = FALSE)
diff --git a/code/modules/overmap/_overmap.dm b/code/modules/overmap/_overmap.dm
index 6f63a9edf783..64d3ecb22d51 100644
--- a/code/modules/overmap/_overmap.dm
+++ b/code/modules/overmap/_overmap.dm
@@ -43,7 +43,7 @@
square = square.ChangeTurf(overmap_edge_type)
else
square = square.ChangeTurf(overmap_turf_type)
- ChangeArea(square, A)
+ square.ChangeArea(A)
/datum/overmap/proc/generate_overmap()
testing("Building overmap [name]...")
diff --git a/code/modules/random_map/random_map.dm b/code/modules/random_map/random_map.dm
index ad1d3c26c0ec..cea16848c171 100644
--- a/code/modules/random_map/random_map.dm
+++ b/code/modules/random_map/random_map.dm
@@ -177,7 +177,8 @@ var/global/list/map_count = list()
. = (newpath && !istype(T, newpath)) ? T.ChangeTurf(newpath) : T
get_additional_spawns(map[current_cell], ., get_spawn_dir(x, y))
if(use_area)
- ChangeArea(., use_area)
+ var/turf/turf = .
+ turf.ChangeArea(use_area)
/datum/random_map/proc/get_spawn_dir()
return 0
diff --git a/code/modules/turbolift/turbolift_map.dm b/code/modules/turbolift/turbolift_map.dm
index bbea91dd8cc4..d69a39fd5bb8 100644
--- a/code/modules/turbolift/turbolift_map.dm
+++ b/code/modules/turbolift/turbolift_map.dm
@@ -186,8 +186,8 @@ INITIALIZE_IMMEDIATE(/obj/abstract/turbolift_spawner)
var/area_path = areas_to_use[az]
var/area/A = locate(area_path) || new area_path()
- for(var/T in floor_turfs)
- ChangeArea(T, A)
+ for(var/turf/T as anything in floor_turfs)
+ T.ChangeArea(A)
cfloor.set_area_ref("\ref[A]")
// Place exterior doors.
diff --git a/maps/karzerfeste/karzerfeste.dm b/maps/karzerfeste/karzerfeste.dm
index 6b8304b79614..6cb4e5236e08 100644
--- a/maps/karzerfeste/karzerfeste.dm
+++ b/maps/karzerfeste/karzerfeste.dm
@@ -8,6 +8,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 d680a153227f..6da42ffb4dcb 100644
--- a/maps/shaded_hills/shaded_hills.dm
+++ b/maps/shaded_hills/shaded_hills.dm
@@ -6,7 +6,7 @@
#include "../../mods/content/scaling_descriptors.dm"
#include "../../mods/species/drakes/_drakes.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.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/anima/_anima.dm b/mods/content/wyrd/_wyrd.dm
similarity index 60%
rename from mods/content/anima/_anima.dm
rename to mods/content/wyrd/_wyrd.dm
index b55da0e3cbdb..d59c5e3c2ce4 100644
--- a/mods/content/anima/_anima.dm
+++ b/mods/content/wyrd/_wyrd.dm
@@ -1,15 +1,11 @@
-#define ANIMA_SPELL_AOE "aoe"
-#define ANIMA_SPELL_MELEE "melee"
-#define ANIMA_SPELL_RANGED "ranged"
-
-/decl/modpack/anima
- name = "Anima Content"
+/decl/modpack/wyrd
+ name = "Pyrelight Magic Content"
credits_topics = list("ANCIENT MAGIC", "ANCIENT ANIMA", "MAGICAL RITUALS", "MAGIC SPELLS")
- credits_nouns = list("MAGIC", "ANIMA")
+ 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(
- "anima", "potentia", "magic", "an ancient curse", "an arcane ritual",
+ "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"
)
\ 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..895083c1e879
--- /dev/null
+++ b/mods/content/wyrd/_wyrd.dme
@@ -0,0 +1,21 @@
+#ifndef MODPACK_WYRD
+#define MODPACK_WYRD
+// BEGIN_INCLUDE
+#include "_wyrd.dm"
+#include "wyrd_abilities.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\runestones\_runestones.dm"
+#include "spells\runestones\spellscribing.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..f993362fbd68
--- /dev/null
+++ b/mods/content/wyrd/anima/_anima.dm
@@ -0,0 +1,70 @@
+// 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 Sign"
+ uid = "anima_sky"
+ // air, stars, moon, truth, purity
+
+/decl/anima/waning
+ name = "Waning Sign"
+ uid = "anima_waning"
+ // transition, change, death, birth
+
+/decl/anima/hollow
+ name = "Hollow Sign"
+ uid = "anima_hollow"
+ // concealment, earth, mystery
+
+/decl/anima/blood
+ name = "Blood Sign"
+ uid = "anima_blood"
+ // 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..15f3a666fd4d
--- /dev/null
+++ b/mods/content/wyrd/anima/anima_aura.dm
@@ -0,0 +1,21 @@
+// Inner anima state used for spellcasting.
+/datum/extension/anima_aura
+ expected_type = /mob
+ base_type = /datum/extension/anima_aura
+ var/alist/pool = alist(
+ /decl/anima/sky = /decl/anima::ANIMA_NEGLIGIBLE,
+ /decl/anima/waning = /decl/anima::ANIMA_NEGLIGIBLE,
+ /decl/anima/hollow = /decl/anima::ANIMA_NEGLIGIBLE,
+ /decl/anima/blood = /decl/anima::ANIMA_NEGLIGIBLE
+ )
+
+/mob/proc/get_personal_anima()
+ 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/hollow = /decl/anima::ANIMA_DEPLETED,
+ /decl/anima/blood = /decl/anima::ANIMA_DEPLETED
+ )
\ No newline at end of file
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..79ab22c0fc0e
--- /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/hollow = /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/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/content/wyrd/wyrd_abilities.dm b/mods/content/wyrd/wyrd_abilities.dm
new file mode 100644
index 000000000000..005f29edd642
--- /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())
+ 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/gamemodes/cult/abilities/construct.dm b/mods/gamemodes/cult/abilities/construct.dm
index 60138eaba356..bbf1a5bc0de3 100644
--- a/mods/gamemodes/cult/abilities/construct.dm
+++ b/mods/gamemodes/cult/abilities/construct.dm
@@ -2,10 +2,9 @@
/decl/ability/cult/construct
name = "Artificer"
desc = "This spell conjures a construct which may be controlled by shades."
- target_selector = /decl/ability_targeting/clear_turf
+ target_selector = /decl/ability_targeting/clear_turf/construct
overlay_icon = 'mods/gamemodes/cult/icons/effects.dmi'
overlay_icon_state = "sparkles"
- target_selector = /decl/ability_targeting/clear_turf/construct
var/summon_type = /obj/structure/constructshell
/decl/ability_targeting/clear_turf/construct/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
@@ -14,14 +13,14 @@
return FALSE
return ..() && !istype(target, cult_ability.summon_type) && !(locate(cult_ability.summon_type) in target)
-/decl/ability/cult/construct/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+/decl/ability/cult/construct/apply_ability_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
. = ..()
var/turf/target_turf = get_turf(hit_target)
if(istype(target_turf))
if(ispath(summon_type, /turf))
target_turf = target_turf.ChangeTurf(summon_type, TRUE, FALSE, TRUE, TRUE, FALSE)
if(target_turf) // We reapply effects as target no longer exists.
- apply_effect_to(user, target_turf, metadata)
+ apply_ability_effect_to(user, target_turf, metadata)
else if(ispath(summon_type, /atom))
new summon_type(target_turf)
@@ -87,7 +86,7 @@
return TRUE
return FALSE
-/decl/ability/cult/construct/pylon/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+/decl/ability/cult/construct/pylon/apply_ability_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
for(var/obj/structure/cult/pylon/P in get_turf(hit_target))
if(P.isbroken)
P.repair(user)
diff --git a/mods/gamemodes/cult/abilities/harvest.dm b/mods/gamemodes/cult/abilities/harvest.dm
index 49569d6a25ef..dc8bfc1002ff 100644
--- a/mods/gamemodes/cult/abilities/harvest.dm
+++ b/mods/gamemodes/cult/abilities/harvest.dm
@@ -9,7 +9,7 @@
prepare_message_3p_str = "Space around $USER$ begins to bubble and decay as a terrible vista begins to intrude..."
prepare_message_1p_str = "You bore through space and time, seeking the essence of the Geometer of Blood."
fail_cast_1p_str = "Reality reasserts itself, preventing your return to Nar-Sie."
- target_selector = /decl/ability_targeting/living_mob
+ target_selector = /decl/ability_targeting/single_atom/living_mob
/decl/ability/cult/construct/harvest/can_use_ability(mob/user, list/metadata, silent)
. = ..()
@@ -22,7 +22,7 @@
to_chat(user, SPAN_DANGER("You cannot sense the Geometer of Blood!"))
return FALSE
-/decl/ability/cult/construct/harvest/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+/decl/ability/cult/construct/harvest/apply_ability_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
..()
var/destination = null
for(var/obj/effect/narsie/N in global.narsie_list)
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" '(?