[MIRROR] Refactors Crystal Warp Theme datum to use Dimensional Theme datum [MDB IGNORE] (#25934)

* Refactors Crystal Warp Theme datum to use Dimensional Theme datum (#80661)

## About The Pull Request

`/datum/crystal_warp_theme` and `/datum/dimension_theme` both do very
similar, if not identical behaviors, so we can combine them pretty
painlessly.

## Why It's Good For The Game

This gives the crystal more options for themes to pick from, and the
dimensional anomaly more wacky themes to pick from.

## Changelog

🆑 Melbert
refactor: Refactored the area transformation colossus crystal effect to
use the same system dimensional anomalies use. That means the colossus
crystal now has access to some dimensional themes (bamboo, plasma,
glass) and the dimensional anomaly now has access to some colossus
themes (jungle, alien).
/🆑

* Refactors Crystal Warp Theme datum to use Dimensional Theme datum

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-12-31 21:43:40 +01:00
committed by GitHub
parent af1d15c2bc
commit e6bd089ef7
5 changed files with 177 additions and 159 deletions
@@ -8,7 +8,7 @@
/// Range of effect, if left alone anomaly will convert a 2(range)+1 squared area.
var/range = 3
/// List of turfs this anomaly will try to transform before relocating
var/list/turf/target_turfs = new()
var/list/turf/target_turfs = list()
/// Current anomaly 'theme', dictates what tiles to create.
var/datum/dimension_theme/theme
/// Effect displaying on the anomaly to represent the theme.
@@ -38,7 +38,7 @@
var/turf/affected_turf = target_turfs[1]
new /obj/effect/temp_visual/transmute_tile_flash(affected_turf)
theme.apply_theme(affected_turf)
target_turfs.Remove(affected_turf)
target_turfs -= affected_turf
/**
* Prepare a new area for transformation into a new theme.
@@ -50,11 +50,10 @@
theme = new new_theme_path()
apply_theme_icon()
target_turfs = new()
var/list/turfs = spiral_range_turfs(range, src)
for (var/turf/turf in turfs)
target_turfs = list()
for (var/turf/turf as anything in spiral_range_turfs(range, src))
if (theme.can_convert(turf))
target_turfs.Add(turf)
target_turfs += turf
/**
* Applies an overlay icon based on the current theme.
@@ -3,6 +3,8 @@
* Datum which describes a theme and replaces turfs and objects in specified locations to match that theme
*/
/datum/dimension_theme
/// Human readable name of the theme
var/name = "Unnamed Theme"
/// An icon to display to represent the theme
var/icon/icon
/// Icon state to use to represent the theme
@@ -16,11 +18,16 @@
/// Typepath of turf to replace walls with.
var/turf/replace_walls = /turf/closed/wall/material
/// List of weighted lists for object replacement. Key is an original typepath, value is a weighted list of typepaths to replace it with.
var/list/replace_objs = list(\
/obj/structure/chair = list(/obj/structure/chair/greyscale = 1), \
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/material = 1, /obj/machinery/door/airlock/material/glass = 1), \
/obj/structure/table = list(/obj/structure/table/greyscale = 1), \
/obj/structure/toilet = list(/obj/structure/toilet/greyscale = 1),)
var/list/replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/greyscale = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/material = 1, /obj/machinery/door/airlock/material/glass = 1),
/obj/structure/table = list(/obj/structure/table/greyscale = 1),
/obj/structure/toilet = list(/obj/structure/toilet/greyscale = 1),
)
/// List of random spawns to place in completely open turfs
var/list/random_spawns
/// Prob of placing a random spawn in a completely open turf
var/random_spawn_chance = 0
/// Typepath of full-size windows which will replace existing ones
/// These need to be separate from replace_objs because we don't want to replace dir windows with full ones and they share typepath
var/obj/structure/window/replace_window
@@ -38,15 +45,33 @@
* Arguments
* * affected_turf - Turf to transform.
*/
/datum/dimension_theme/proc/apply_theme(turf/affected_turf)
/datum/dimension_theme/proc/apply_theme(turf/affected_turf, skip_sound = FALSE)
if (!replace_turf(affected_turf))
return
playsound(affected_turf, sound, 100, TRUE)
if (!skip_sound)
playsound(affected_turf, sound, 100, TRUE)
for (var/obj/object in affected_turf)
replace_object(object)
if (length(random_spawns) && prob(random_spawn_chance) && !affected_turf.is_blocked_turf(exclude_mobs = TRUE))
var/random_spawn_picked = pick(random_spawns)
new random_spawn_picked(affected_turf)
if (material)
apply_materials(affected_turf)
/**
* Applies the transformation to a list of turfs, ensuring a sound is only played every few turfs to reduce noice spam
*
* Arguments
* * list/turf/all_turfs - List of turfs to transform.
*/
/datum/dimension_theme/proc/apply_theme_to_list_of_turfs(list/turf/all_turfs)
var/every_nth_turf = 0
for (var/turf/turf as anything in all_turfs)
if(can_convert(turf))
apply_theme(turf, skip_sound = (every_nth_turf % 7 != 0))
every_nth_turf++
CHECK_TICK
/**
* Returns true if you actually can transform the provided turf.
*
@@ -75,6 +100,8 @@
* * affected_turf - Turf to transform.
*/
/datum/dimension_theme/proc/replace_turf(turf/affected_turf)
PROTECTED_PROC(TRUE)
if (isfloorturf(affected_turf))
if (isindestructiblefloor(affected_turf))
return FALSE
@@ -96,6 +123,8 @@
* * affected_floor - Floor turf to transform.
*/
/datum/dimension_theme/proc/transform_floor(turf/open/floor/affected_floor)
PROTECTED_PROC(TRUE)
if (replace_floors.len == 0)
return FALSE
affected_floor.replace_floor(pick_weight(replace_floors), flags = CHANGETURF_INHERIT_AIR)
@@ -108,6 +137,8 @@
* * object - Object to replace.
*/
/datum/dimension_theme/proc/replace_object(obj/object)
PROTECTED_PROC(TRUE)
if (istype(object, /obj/structure/window))
transform_window(object)
return
@@ -128,10 +159,11 @@
* * object - Object to transform.
*/
/datum/dimension_theme/proc/get_replacement_object_typepath(obj/object)
PROTECTED_PROC(TRUE)
for (var/type in replace_objs)
if (istype(object, type))
return pick_weight(replace_objs[type])
return
/**
* Replaces a window with a different window and recolours it.
@@ -141,6 +173,8 @@
* * object - Object to transform.
*/
/datum/dimension_theme/proc/transform_window(obj/structure/window/window)
PROTECTED_PROC(TRUE)
if (!window.fulltile)
return
if (!replace_window)
@@ -166,10 +200,10 @@
* * object - Object to transform.
*/
/datum/dimension_theme/proc/permit_replace_material(obj/object)
for (var/type in PERMITTED_MATERIAL_REPLACE_TYPES)
if (istype(object, type))
return TRUE
return FALSE
PROTECTED_PROC(TRUE)
return is_type_in_list(object, PERMITTED_MATERIAL_REPLACE_TYPES)
/**
* Applies a new custom material to the contents of a provided turf.
@@ -178,6 +212,8 @@
* * affected_turf - Turf to transform.
*/
/datum/dimension_theme/proc/apply_materials(turf/affected_turf)
PROTECTED_PROC(TRUE)
var/list/custom_materials = list(GET_MATERIAL_REF(material) = SHEET_MATERIAL_AMOUNT)
if (istype(affected_turf, /turf/open/floor/material) || istype(affected_turf, /turf/closed/wall/material))
@@ -193,79 +229,110 @@
/////////////////////
/datum/dimension_theme/gold
name = "Gold"
icon = 'icons/obj/stack_objects.dmi'
icon_state = "sheet-gold_2"
material = /datum/material/gold
/datum/dimension_theme/plasma
name = "Plasma"
icon = 'icons/obj/clothing/masks.dmi'
icon_state = "gas_alt"
material = /datum/material/plasma
/datum/dimension_theme/clown
name = "Clown"
icon = 'icons/obj/clothing/masks.dmi'
icon_state = "clown"
material = /datum/material/bananium
sound = 'sound/items/bikehorn.ogg'
/datum/dimension_theme/radioactive
name = "Radioactive"
icon = 'icons/obj/ore.dmi'
icon_state = "Uranium ore"
material = /datum/material/uranium
sound = 'sound/items/welder.ogg'
/datum/dimension_theme/meat
name = "Meat"
icon = 'icons/obj/food/meat.dmi'
icon_state = "meat"
material = /datum/material/meat
sound = 'sound/items/eatfood.ogg'
/datum/dimension_theme/pizza
name = "Pizza"
icon = 'icons/obj/food/pizza.dmi'
icon_state = "pizzamargherita"
material = /datum/material/pizza
sound = 'sound/items/eatfood.ogg'
/datum/dimension_theme/natural
name = "Natural"
icon = 'icons/obj/service/hydroponics/harvest.dmi'
icon_state = "map_flower"
window_colour = "#00f7ff"
replace_floors = list(/turf/open/floor/grass = 1)
replace_walls = /turf/closed/wall/mineral/wood/nonmetal
replace_objs = list(\
/obj/structure/chair = list(/obj/structure/chair/wood = 3, /obj/structure/chair/wood/wings = 1), \
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \
/obj/structure/table = list(/obj/structure/table/wood = 5, /obj/structure/table/wood/fancy = 1),)
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/wood = 3, /obj/structure/chair/wood/wings = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1),
/obj/structure/table = list(/obj/structure/table/wood = 5, /obj/structure/table/wood/fancy = 1),
)
/datum/dimension_theme/bamboo
name = "Bamboo"
icon = 'icons/obj/service/hydroponics/harvest.dmi'
icon_state = "bamboo"
replace_floors = list(/turf/open/floor/bamboo = 1)
replace_walls = /turf/closed/wall/mineral/bamboo
replace_window = /obj/structure/window/paperframe
replace_objs = list(\
/obj/structure/chair = list(/obj/structure/chair/stool/bamboo = 1), \
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \
/obj/structure/table = list(/obj/structure/table/wood = 1),)
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/stool/bamboo = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1),
/obj/structure/table = list(/obj/structure/table/wood = 1),
)
/datum/dimension_theme/icebox
icon = 'icons/obj/clothing/shoes.dmi'
icon_state = "iceboots"
name = "Winter"
icon = 'icons/obj/clothing/head/costume.dmi'
icon_state = "snowman_h"
window_colour = "#00f7ff"
material = /datum/material/snow
replace_floors = list(/turf/open/floor/fake_snow = 10, /turf/open/floor/fakeice/slippery = 1)
replace_walls = /turf/closed/wall/mineral/snow
random_spawns = list(
/obj/structure/flora/grass/both/style_random,
/obj/structure/flora/grass/brown/style_random,
/obj/structure/flora/grass/green/style_random,
)
random_spawn_chance = 8
/datum/dimension_theme/icebox/winter_cabin
name = "Winter Cabin"
icon = 'icons/obj/clothing/shoes.dmi'
icon_state = "iceboots"
replace_walls = /turf/closed/wall/mineral/wood
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/wood = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1),
/obj/structure/table = list(/obj/structure/table/wood = 1),
)
/datum/dimension_theme/lavaland
name = "Lavaland"
icon = 'icons/obj/stack_objects.dmi'
icon_state = "goliath_hide"
window_colour = "#860000"
replace_floors = list(/turf/open/floor/fakebasalt = 5, /turf/open/floor/fakepit = 1)
replace_walls = /turf/closed/wall/mineral/cult
replace_objs = list(\
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/external/glass/ruin = 1))
replace_objs = list(/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/external/glass/ruin = 1))
random_spawns = list(/mob/living/basic/mining/goldgrub)
random_spawn_chance = 1
/datum/dimension_theme/space
name = "Space"
icon = 'icons/effects/effects.dmi'
icon_state = "blessed"
window_colour = "#000000"
@@ -275,6 +342,7 @@
replace_objs = list(/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/external/glass/ruin = 1))
/datum/dimension_theme/glass
name = "Glass"
icon = 'icons/obj/debris.dmi'
icon_state = "small"
material = /datum/material/glass
@@ -282,6 +350,7 @@
sound = SFX_SHATTER
/datum/dimension_theme/fancy
name = "Fancy"
icon = 'icons/obj/clothing/head/costume.dmi'
icon_state = "fancycrown"
replace_walls = /turf/closed/wall/mineral/wood/nonmetal
@@ -301,15 +370,18 @@
/turf/open/floor/carpet/royalblue,)
/datum/dimension_theme/fancy/New()
. = ..()
replace_floors = list(pick(FANCY_CARPETS) = 1)
replace_objs = list(\
/obj/structure/chair = list(/obj/structure/chair/comfy = 1), \
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \
/obj/structure/table/wood = list(pick(subtypesof(/obj/structure/table/wood/fancy)) = 1),)
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/comfy = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1),
/obj/structure/table/wood = list(pick(subtypesof(/obj/structure/table/wood/fancy)) = 1),
)
#undef FANCY_CARPETS
/datum/dimension_theme/disco
name = "Disco"
icon = 'icons/obj/lighting.dmi'
icon_state = "lbulb"
material = /datum/material/glass
@@ -322,3 +394,40 @@
var/turf/open/floor/light/disco_floor = affected_floor
disco_floor.currentcolor = pick(disco_floor.coloredlights)
disco_floor.update_appearance()
/datum/dimension_theme/jungle
name = "Jungle"
icon = 'icons/obj/tiles.dmi'
icon_state = "tile_grass"
sound = SFX_CRUNCHY_BUSH_WHACK
replace_floors = list(/turf/open/floor/grass = 1)
replace_walls = /turf/closed/wall/mineral/wood
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/wood = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1),
/obj/structure/table = list(/obj/structure/table/wood = 1),
)
random_spawns = list(
/mob/living/carbon/human/species/monkey,
/obj/structure/flora/bush/ferny/style_random,
/obj/structure/flora/bush/grassy/style_random,
/obj/structure/flora/bush/leavy/style_random,
/obj/structure/flora/tree/palm/style_random,
/obj/structure/flora/bush/sparsegrass/style_random,
/obj/structure/flora/bush/sunny/style_random,
)
random_spawn_chance = 20
/datum/dimension_theme/ayylmao
name = "Alien"
icon = 'icons/obj/antags/abductor.dmi'
icon_state = "sheet-abductor"
material = /datum/material/alloy/alien
replace_walls = /turf/closed/wall/mineral/abductor
replace_floors = list(/turf/open/floor/mineral/abductor = 1)
replace_objs = list(
/obj/structure/chair = list(/obj/structure/chair/greyscale = 9, /obj/structure/bed/abductor = 1),
/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/material = 1, /obj/machinery/door/airlock/material/glass = 2),
/obj/structure/table = list(/obj/structure/table/greyscale = 9, /obj/structure/table/abductor = 1),
/obj/structure/toilet = list(/obj/structure/toilet/greyscale = 1),
)
@@ -39,8 +39,4 @@
addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator)
/datum/grand_finale/midas/proc/transform_area(list/turfs)
for (var/turf/transform_turf as anything in turfs)
if (!chosen_theme.can_convert(transform_turf))
continue
chosen_theme.apply_theme(transform_turf)
CHECK_TICK
chosen_theme.apply_theme_to_list_of_turfs(turfs)
@@ -74,8 +74,7 @@
addtimer(CALLBACK(src, PROC_REF(staggered_transform), theme, range_turfs), (0.5 SECONDS) * iterator)
/datum/grand_side_effect/transmogrify_area/proc/staggered_transform(datum/dimension_theme/theme, list/transform_turfs)
for (var/turf/target_turf as anything in transform_turfs)
theme.apply_theme(target_turf)
theme.apply_theme_to_list_of_turfs(transform_turfs)
/// Minimum number of anomalies to create
#define MIN_ANOMALIES_CREATED 1
@@ -238,7 +238,6 @@
var/use_time = 0
/// If we are being used
var/active = FALSE
var/list/affected_targets = list()
var/activation_sound = 'sound/effects/break_stone.ogg'
COOLDOWN_DECLARE(cooldown_timer)
@@ -320,14 +319,28 @@
possible_methods = list(ACTIVATE_MOB_BUMP, ACTIVATE_SPEECH)
activation_sound = 'sound/items/bikehorn.ogg'
use_time = 3 SECONDS
/// List of REFs to mobs that have been turned into a clown
var/list/clowned_mob_refs = list()
/obj/machinery/anomalous_crystal/honk/ActivationReaction(mob/user)
if(..() && ishuman(user) && !(user in affected_targets) && (user in viewers(src)))
var/mob/living/carbon/human/new_clown = user
for(var/obj/item/to_strip in new_clown)
new_clown.dropItemToGround(to_strip)
new_clown.dress_up_as_job(SSjob.GetJobType(/datum/job/clown))
affected_targets.Add(new_clown)
. = ..()
if(!.)
return FALSE
if(!ishuman(user))
return FALSE
if(!(user in viewers(src)))
return FALSE
var/clown_ref = REF(user)
if(clown_ref in clowned_mob_refs)
return FALSE
var/mob/living/carbon/human/new_clown = user
for(var/obj/item/to_strip in new_clown.get_equipped_items())
new_clown.dropItemToGround(to_strip)
new_clown.dress_up_as_job(SSjob.GetJobType(/datum/job/clown))
clowned_mob_refs += clown_ref
return TRUE
/// Transforms the area to look like a new one
/obj/machinery/anomalous_crystal/theme_warp
@@ -335,130 +348,32 @@
activation_method = ACTIVATE_TOUCH
cooldown_add = 20 SECONDS
use_time = 5 SECONDS
var/datum/crystal_warp_theme/terrain_theme
/// Theme which we turn areas into on activation
var/datum/dimension_theme/terrain_theme
/// List of all areas we've affected
var/list/converted_areas = list()
/obj/machinery/anomalous_crystal/theme_warp/Initialize(mapload)
. = ..()
var/terrain_type = pick(subtypesof(/datum/crystal_warp_theme))
var/terrain_type = pick(subtypesof(/datum/dimension_theme))
terrain_theme = new terrain_type()
observer_desc = "This crystal changes the area around it to match the theme of \"[terrain_theme]\"."
observer_desc = "This crystal changes the area around it to match the theme of \"[terrain_theme.name]\"."
/obj/machinery/anomalous_crystal/theme_warp/ActivationReaction(mob/user, method)
. = ..()
if (!.)
return FALSE
var/area/current_area = get_area(src)
if (current_area in affected_targets)
if (current_area in converted_areas)
return FALSE
if (terrain_theme.transform_area(current_area))
affected_targets += current_area
terrain_theme.apply_theme_to_list_of_turfs(current_area.get_contained_turfs())
converted_areas += current_area
return TRUE
/// Transforms an area's turfs and objects into a different theme
/datum/crystal_warp_theme
/// Friendly name of theme
var/name = ""
/// Typepath of floor to replace open turfs with
var/floor
/// Typepath of wall to replace closed turfs with
var/wall
/// Typepath of object to replace chairs with
var/chair
/// Typepath of object to replace tables with
var/table
/// Typepath of things to potentially spawn on transformed open turfs
var/list/flora_and_fauna = list()
/// Chance per turf to create the things in the list above
var/flora_and_fauna_chance = 8
/// Change appropriate objects in provided area to those matching our theme, and spawn some plants or animals
/datum/crystal_warp_theme/proc/transform_area(area/target_area)
if (target_area.outdoors)
return FALSE
for(var/atom/thing in target_area)
if(isturf(thing))
replace_turf(thing)
continue
if(chair && istype(thing, /obj/structure/chair))
replace_object(thing, chair)
continue
if(table && istype(thing, /obj/structure/table))
replace_object(thing, table)
continue
return TRUE
/// Replaces a turf with a different themed turf
/datum/crystal_warp_theme/proc/replace_turf(turf/target_turf)
if(isindestructiblefloor(target_turf) || isindestructiblewall(target_turf) || isopenspaceturf(target_turf))
return
if(wall && iswallturf(target_turf))
target_turf.ChangeTurf(wall)
return
if(!isopenturf(target_turf))
return
if(length(flora_and_fauna) && !target_turf.is_blocked_turf(exclude_mobs = TRUE) && prob(flora_and_fauna_chance))
var/atom/new_flora_and_fauna = pick(flora_and_fauna)
new new_flora_and_fauna(target_turf)
if(floor)
var/turf/open/open_turf = target_turf
open_turf.replace_floor(floor, flags = CHANGETURF_IGNORE_AIR)
/// Replaces an object with a different themed object
/datum/crystal_warp_theme/proc/replace_object(atom/original, new_type)
var/atom/new_thing = new new_type(original.loc)
new_thing.setDir(original.dir)
qdel(original)
// Depressurizes the place... and free cult metal, I guess.
/datum/crystal_warp_theme/lavaland
name = "lavaland"
floor = /turf/open/floor/fakebasalt
wall = /turf/closed/wall/mineral/cult
flora_and_fauna = list(/mob/living/basic/mining/goldgrub)
flora_and_fauna_chance = 1
// Snow terrain is slow to move in and cold! Get the assistants to shovel your driveway.
/datum/crystal_warp_theme/winter
name = "winter"
floor = /turf/open/misc/snow/actually_safe
wall = /turf/closed/wall/mineral/wood
chair = /obj/structure/chair/wood
table = /obj/structure/table/glass
flora_and_fauna = list(
/obj/structure/flora/grass/both/style_random,
/obj/structure/flora/grass/brown/style_random,
/obj/structure/flora/grass/green/style_random,
)
// Beneficial due to actually having breathable air. Plus, monkeys and bows and arrows.
/datum/crystal_warp_theme/jungle
name = "jungle"
floor = /turf/open/floor/grass
wall = /turf/closed/wall/mineral/wood
chair = /obj/structure/chair/wood
table = /obj/structure/table/wood
flora_and_fauna = list(
/mob/living/carbon/human/species/monkey,
/obj/structure/flora/bush/ferny/style_random,
/obj/structure/flora/bush/grassy/style_random,
/obj/structure/flora/bush/leavy/style_random,
/obj/structure/flora/tree/palm/style_random,
/obj/structure/flora/bush/sparsegrass/style_random,
/obj/structure/flora/bush/sunny/style_random,
)
flora_and_fauna_chance = 20
// Beneficial, turns stuff into alien alloy which is useful to cargo and research. Also repairs atmos.
/datum/crystal_warp_theme/ayylmao
name = "ayy lmao"
floor = /turf/open/floor/mineral/abductor
wall = /turf/closed/wall/mineral/abductor
chair = /obj/structure/bed/abductor //ayys apparently don't have chairs. An entire species of people who only recline.
table = /obj/structure/table/abductor
/obj/machinery/anomalous_crystal/theme_warp/Destroy()
QDEL_NULL(terrain_theme)
converted_areas.Cut()
return ..()
/obj/machinery/anomalous_crystal/emitter //Generates a projectile when interacted with
observer_desc = "This crystal generates a projectile when activated."