diff --git a/baystation12.dme b/baystation12.dme
index 025113613b..e951f7c430 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -50,6 +50,7 @@
#include "code\_onclick\hud\other_mobs.dm"
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
+#include "code\_onclick\hud\spell_screen_objects.dm"
#include "code\ATMOSPHERICS\_atmos_setup.dm"
#include "code\ATMOSPHERICS\_atmospherics_helpers.dm"
#include "code\ATMOSPHERICS\atmospherics.dm"
@@ -733,6 +734,7 @@
#include "code\game\objects\structures\crates_lockers\closets\job_closets.dm"
#include "code\game\objects\structures\crates_lockers\closets\l3closet.dm"
#include "code\game\objects\structures\crates_lockers\closets\malfunction.dm"
+#include "code\game\objects\structures\crates_lockers\closets\statue.dm"
#include "code\game\objects\structures\crates_lockers\closets\syndicate.dm"
#include "code\game\objects\structures\crates_lockers\closets\utility_closets.dm"
#include "code\game\objects\structures\crates_lockers\closets\walllocker.dm"
@@ -755,6 +757,7 @@
#include "code\game\objects\structures\stool_bed_chair_nest\wheelchair.dm"
#include "code\game\turfs\simulated.dm"
#include "code\game\turfs\turf.dm"
+#include "code\game\turfs\turf_flick_animations.dm"
#include "code\game\turfs\unsimulated.dm"
#include "code\game\turfs\simulated\floor.dm"
#include "code\game\turfs\simulated\floor_types.dm"
@@ -1364,6 +1367,7 @@
#include "code\modules\power\singularity\field_generator.dm"
#include "code\modules\power\singularity\generator.dm"
#include "code\modules\power\singularity\investigate.dm"
+#include "code\modules\power\singularity\narsie.dm"
#include "code\modules\power\singularity\singularity.dm"
#include "code\modules\power\singularity\particle_accelerator\particle.dm"
#include "code\modules\power\singularity\particle_accelerator\particle_accelerator.dm"
@@ -1556,24 +1560,39 @@
#include "code\modules\shuttles\shuttle_specops.dm"
#include "code\modules\shuttles\shuttle_supply.dm"
#include "code\modules\shuttles\shuttles_multi.dm"
-#include "code\modules\spells\area_teleport.dm"
-#include "code\modules\spells\conjure.dm"
-#include "code\modules\spells\dumbfire.dm"
-#include "code\modules\spells\emplosion.dm"
-#include "code\modules\spells\ethereal_jaunt.dm"
-#include "code\modules\spells\explosion.dm"
-#include "code\modules\spells\genetic.dm"
-#include "code\modules\spells\horsemask.dm"
-#include "code\modules\spells\inflict_handler.dm"
-#include "code\modules\spells\knock.dm"
-#include "code\modules\spells\mind_transfer.dm"
-#include "code\modules\spells\projectile.dm"
-#include "code\modules\spells\spell.dm"
+#include "code\modules\spells\artifacts.dm"
+#include "code\modules\spells\construct_spells.dm"
+#include "code\modules\spells\no_clothes.dm"
+#include "code\modules\spells\spell_code.dm"
+#include "code\modules\spells\spell_projectile.dm"
#include "code\modules\spells\spellbook.dm"
-#include "code\modules\spells\trigger.dm"
-#include "code\modules\spells\turf_teleport.dm"
-#include "code\modules\spells\wizard_artifacts.dm"
-#include "code\modules\spells\wizard_spells.dm"
+#include "code\modules\spells\spells.dm"
+#include "code\modules\spells\aoe_turf\aoe_turf.dm"
+#include "code\modules\spells\aoe_turf\blink.dm"
+#include "code\modules\spells\aoe_turf\charge.dm"
+#include "code\modules\spells\aoe_turf\disable_tech.dm"
+#include "code\modules\spells\aoe_turf\knock.dm"
+#include "code\modules\spells\aoe_turf\smoke.dm"
+#include "code\modules\spells\aoe_turf\summons.dm"
+#include "code\modules\spells\aoe_turf\conjure\conjure.dm"
+#include "code\modules\spells\aoe_turf\conjure\construct.dm"
+#include "code\modules\spells\aoe_turf\conjure\forcewall.dm"
+#include "code\modules\spells\general\area_teleport.dm"
+#include "code\modules\spells\general\rune_write.dm"
+#include "code\modules\spells\targeted\disintegrate.dm"
+#include "code\modules\spells\targeted\ethereal_jaunt.dm"
+#include "code\modules\spells\targeted\flesh_to_stone.dm"
+#include "code\modules\spells\targeted\genetic.dm"
+#include "code\modules\spells\targeted\harvest.dm"
+#include "code\modules\spells\targeted\horsemask.dm"
+#include "code\modules\spells\targeted\mind_transfer.dm"
+#include "code\modules\spells\targeted\shift.dm"
+#include "code\modules\spells\targeted\subjugate.dm"
+#include "code\modules\spells\targeted\targeted.dm"
+#include "code\modules\spells\targeted\projectile\dumbfire.dm"
+#include "code\modules\spells\targeted\projectile\fireball.dm"
+#include "code\modules\spells\targeted\projectile\magic_missile.dm"
+#include "code\modules\spells\targeted\projectile\projectile.dm"
#include "code\modules\supermatter\supermatter.dm"
#include "code\modules\surgery\bones.dm"
#include "code\modules\surgery\brainrepair.dm"
diff --git a/code/__HELPERS/atom_pool.dm b/code/__HELPERS/atom_pool.dm
index 83a8a1d67b..d5978592c2 100644
--- a/code/__HELPERS/atom_pool.dm
+++ b/code/__HELPERS/atom_pool.dm
@@ -37,7 +37,7 @@ var/global/list/GlobalPool = list()
AM = new get_type (arglist(second_arg))
else
AM = new get_type (second_arg)
-
+ else
if(AM)
return AM
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 44973d3f0a..69f46a4c9a 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -119,3 +119,6 @@
#define ui_iarrowleft "SOUTH-1,11"
#define ui_iarrowright "SOUTH-1,13"
+
+#define ui_spell_master "14:16,14:16"
+#define ui_genetic_master "14:16,12:16"
diff --git a/code/_onclick/hud/spell_screen_objects.dm b/code/_onclick/hud/spell_screen_objects.dm
new file mode 100644
index 0000000000..ed208a588d
--- /dev/null
+++ b/code/_onclick/hud/spell_screen_objects.dm
@@ -0,0 +1,175 @@
+/obj/screen/movable/spell_master
+ name = "Spells"
+ icon = 'icons/mob/screen_spells.dmi'
+ icon_state = "wiz_spell_ready"
+ var/list/obj/screen/spell/spell_objects = list()
+ var/showing = 0
+
+ var/open_state = "master_open"
+ var/closed_state = "master_closed"
+
+ screen_loc = ui_spell_master
+
+ var/mob/spell_holder
+
+/obj/screen/movable/spell_master/MouseDrop()
+ if(showing)
+ return
+
+ return ..()
+
+/obj/screen/movable/spell_master/Click()
+ if(!spell_objects.len)
+ qdel(src)
+ return
+
+ toggle_open()
+
+/obj/screen/movable/spell_master/proc/toggle_open(var/forced_state = 0)
+ if(showing && (forced_state != 2))
+ for(var/obj/screen/spell/O in spell_objects)
+ if(spell_holder && spell_holder.client)
+ spell_holder.client.screen -= O
+ O.handle_icon_updates = 0
+ showing = 0
+ overlays.len = 0
+ overlays.Add(closed_state)
+ else if(forced_state != 1)
+ var/temp_loc = screen_loc
+
+ var/x_position = text2num(copytext(temp_loc, 1, findtext(temp_loc, ":")))
+ var/x_pix = text2num(copytext(temp_loc, findtext(temp_loc, ":") + 1, findtext(temp_loc, ",")))
+ temp_loc = copytext(temp_loc, findtext(temp_loc, ",") + 1)
+ var/y_position = text2num(copytext(temp_loc, 1, findtext(temp_loc, ":")))
+ var/y_pix = text2num(copytext(temp_loc, findtext(temp_loc, ":")+1))
+
+ for(var/i = 1; i <= spell_objects.len; i++)
+ var/obj/screen/spell/S = spell_objects[i]
+ S.screen_loc = "[x_position + (x_position < 8 ? 1 : -1)*(i%7)]:[x_pix],[y_position + (y_position < 8 ? round(i/7) : -round(i/7))]:[y_pix]"
+ if(spell_holder && spell_holder.client)
+ spell_holder.client.screen += S
+ S.handle_icon_updates = 1
+ update_spells(1)
+ showing = 1
+ overlays.len = 0
+ overlays.Add(open_state)
+
+/obj/screen/movable/spell_master/proc/add_spell(var/spell/spell)
+ if(!spell) return
+
+ for(var/obj/screen/spell/spellscreen in spell_objects)
+ if(spellscreen.spell == spell)
+ return
+
+ if(spell.spell_flags & NO_BUTTON) //no button to add if we don't get one
+ return
+
+ var/obj/screen/spell/newscreen = new
+
+ newscreen.spell = spell
+ if(!spell.override_base) //if it's not set, we do basic checks
+ if(spell.spell_flags & CONSTRUCT_CHECK)
+ newscreen.spell_base = "const" //construct spells
+ else
+ newscreen.spell_base = "wiz" //wizard spells
+ else
+ newscreen.spell_base = spell.override_base
+ newscreen.name = spell.name
+ newscreen.update_charge(1)
+ spell_objects.Add(newscreen)
+ toggle_open(2) //forces the icons to refresh on screen
+
+/obj/screen/movable/spell_master/proc/remove_spell(var/spell/spell)
+ for(var/obj/screen/spell/s_object in spell_objects)
+ if(s_object.spell == spell)
+ spell_objects.Remove(s_object)
+ qdel(s_object)
+ break
+
+ if(spell_objects.len)
+ toggle_open(showing + 1)
+ else
+ spell_holder.spell_masters.Remove(src)
+ qdel(src)
+
+/obj/screen/movable/spell_master/proc/silence_spells(var/amount)
+ for(var/obj/screen/spell/spell in spell_objects)
+ spell.spell.silenced = amount
+ spell.update_charge(1)
+
+/obj/screen/movable/spell_master/proc/update_spells(forced = 0, mob/user)
+ if(user && user.client)
+ if(!(src in user.client.screen))
+ user.client.screen += src
+ for(var/obj/screen/spell/spell in spell_objects)
+ spell.update_charge(forced)
+
+
+/obj/screen/movable/spell_master/genetic
+ name = "Mutant Powers"
+ icon_state = "genetic_spell_ready"
+
+ open_state = "genetics_open"
+ closed_state = "genetics_closed"
+
+ screen_loc = ui_genetic_master
+
+//////////////ACTUAL SPELLS//////////////
+//This is what you click to cast things//
+/////////////////////////////////////////
+/obj/screen/spell
+ icon = 'icons/mob/screen_spells.dmi'
+ icon_state = "wiz_spell_base"
+ var/spell_base = "wiz"
+ var/last_charge = 0 //not a time, but the last remembered charge value
+
+ var/spell/spell = null
+ var/handle_icon_updates = 0
+
+ var/icon/last_charged_icon
+
+/obj/screen/spell/proc/update_charge(var/forced_update = 0)
+ if(!spell)
+ qdel(src)
+ return
+
+ if((last_charge == spell.charge_counter || !handle_icon_updates) && !forced_update)
+ return //nothing to see here
+
+ overlays -= spell.hud_state
+
+ if(spell.charge_type == Sp_RECHARGE || spell.charge_type == Sp_CHARGES)
+ if(spell.charge_counter < spell.charge_max)
+ icon_state = "[spell_base]_spell_base"
+ if(spell.charge_counter > 0)
+ var/icon/partial_charge = icon(src.icon, "[spell_base]_spell_ready")
+ partial_charge.Crop(1, 1, partial_charge.Width(), round(partial_charge.Height() * spell.charge_counter / spell.charge_max))
+ overlays += partial_charge
+ if(last_charged_icon)
+ overlays -= last_charged_icon
+ last_charged_icon = partial_charge
+ else if(last_charged_icon)
+ overlays -= last_charged_icon
+ last_charged_icon = null
+ else
+ icon_state = "[spell_base]_spell_ready"
+ if(last_charged_icon)
+ overlays -= last_charged_icon
+ else
+ icon_state = "[spell_base]_spell_ready"
+
+ overlays += spell.hud_state
+
+ last_charge = spell.charge_counter
+
+ overlays -= "silence"
+ if(spell.silenced)
+ overlays += "silence"
+
+/obj/screen/spell/Click()
+ if(!usr || !spell)
+ qdel(src)
+ return
+
+ spell.perform(usr)
+ update_charge(1)
diff --git a/code/defines/obj.dm b/code/defines/obj.dm
index e105d67a5b..2647354c96 100644
--- a/code/defines/obj.dm
+++ b/code/defines/obj.dm
@@ -319,3 +319,6 @@ var/global/ManifestJSON
/obj/effect/spawner
name = "object spawner"
+
+/obj/proc/cultify()
+ qdel(src)
diff --git a/code/defines/procs/admin.dm b/code/defines/procs/admin.dm
index 1ae066d640..5901f3b9c9 100644
--- a/code/defines/procs/admin.dm
+++ b/code/defines/procs/admin.dm
@@ -1,6 +1,6 @@
-proc/log_and_message_admins(var/message as text)
- log_admin(usr ? "[key_name(usr)] [message]" : "EVENT [message]")
- message_admins(usr ? "[key_name(usr)] [message]" : "EVENT [message]")
+proc/log_and_message_admins(var/message as text, var/mob/user = usr)
+ log_admin(user ? "[key_name(user)] [message]" : "EVENT [message]")
+ message_admins(user ? "[key_name(user)] [message]" : "EVENT [message]")
proc/log_and_message_admins_many(var/list/mob/users, var/message)
if(!users || !users.len)
diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm
index 7b410cc7bc..2923008a7b 100644
--- a/code/game/antagonist/outsider/wizard.dm
+++ b/code/game/antagonist/outsider/wizard.dm
@@ -95,21 +95,31 @@ var/datum/antagonist/wizard/wizards
//To batch-remove wizard spells. Linked to mind.dm.
/mob/proc/spellremove(var/mob/M as mob)
- for(var/obj/effect/proc_holder/spell/spell_to_remove in src.spell_list)
- del(spell_to_remove)
+ for(var/spell/spell_to_remove in src.spell_list)
+ remove_spell(spell_to_remove)
-/*Checks if the wizard can cast spells.
+obj/item/clothing
+ var/wizard_garb = 0
+
+// Does this clothing slot count as wizard garb? (Combines a few checks)
+/proc/is_wiz_garb(var/obj/item/clothing/C)
+ return C && C.wizard_garb
+
+/*Checks if the wizard is wearing the proper attire.
Made a proc so this is not repeated 14 (or more) times.*/
-/mob/proc/casting()
-//Removed the stat check because not all spells require clothing now.
- if(!istype(usr:wear_suit, /obj/item/clothing/suit/wizrobe))
- usr << "I don't feel strong enough without my robe."
+/mob/proc/wearing_wiz_garb()
+ src << "Silly creature, you're not a human. Only humans can cast this spell."
+ return 0
+
+// Humans can wear clothes.
+/mob/living/carbon/human/wearing_wiz_garb()
+ if(!is_wiz_garb(src.wear_suit))
+ src << "I don't feel strong enough without my robe."
return 0
- if(!istype(usr:shoes, /obj/item/clothing/shoes/sandal))
- usr << "I don't feel strong enough without my sandals."
+ if(!is_wiz_garb(src.shoes))
+ src << "I don't feel strong enough without my sandals."
return 0
- if(!istype(usr:head, /obj/item/clothing/head/wizard))
- usr << "I don't feel strong enough without my hat."
+ if(!is_wiz_garb(src.head))
+ src << "I don't feel strong enough without my hat."
return 0
- else
- return 1
+ return 1
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index bf2124ebb5..f78d7109c4 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -18,8 +18,52 @@
name = "Pylon"
desc = "A floating crystal that hums with an unearthly energy"
icon_state = "pylon"
+ var/isbroken = 0
luminosity = 5
+ l_color = "#3e0000"
+ var/obj/item/wepon = null
+/obj/structure/cult/pylon/attack_hand(mob/M as mob)
+ attackpylon(M, 5)
+
+/obj/structure/cult/pylon/attack_generic(var/mob/user, var/damage)
+ attackpylon(user, damage)
+
+/obj/structure/cult/pylon/attackby(obj/item/W as obj, mob/user as mob)
+ attackpylon(user, W.force)
+
+/obj/structure/cult/pylon/proc/attackpylon(mob/user as mob, var/damage)
+ if(!isbroken)
+ if(prob(1+ damage * 5))
+ user << "You hit the pylon, and its crystal breaks apart!"
+ for(var/mob/M in viewers(src))
+ if(M == user)
+ continue
+ M.show_message("[user.name] smashed the pylon!", 3, "You hear a tinkle of crystal shards", 2)
+ playsound(get_turf(src), 'sound/effects/Glassbr3.ogg', 75, 1)
+ isbroken = 1
+ density = 0
+ icon_state = "pylon-broken"
+ SetLuminosity(0)
+ else
+ user << "You hit the pylon!"
+ playsound(get_turf(src), 'sound/effects/Glasshit.ogg', 75, 1)
+ else
+ if(prob(damage * 2))
+ user << "You pulverize what was left of the pylon!"
+ qdel(src)
+ else
+ user << "You hit the pylon!"
+ playsound(get_turf(src), 'sound/effects/Glasshit.ogg', 75, 1)
+
+
+/obj/structure/cult/pylon/proc/repair(mob/user as mob)
+ if(isbroken)
+ user << "You repair the pylon."
+ isbroken = 0
+ density = 1
+ icon_state = "pylon"
+ SetLuminosity(5)
/obj/structure/cult/tome
name = "Desk"
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 7f5b1802bd..662e5d0080 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -2,7 +2,8 @@
var/cultwords = list()
var/runedec = 0
-var/engwords = list("travel", "blood", "join", "hell", "destroy", "technology", "self", "see", "other", "hide")
+var/global/list/engwords = list("travel", "blood", "join", "hell", "destroy", "technology", "self", "see", "other", "hide")
+var/global/list/rnwords = list("ire","ego","nahlizet","certum","veri","jatkaa","mgar","balaq", "karazet", "geeri")
/client/proc/check_words() // -- Urist
set category = "Special Verbs"
@@ -14,7 +15,7 @@ var/engwords = list("travel", "blood", "join", "hell", "destroy", "technology",
usr << "[cultwords[word]] is [word]"
/proc/runerandom() //randomizes word meaning
- var/list/runewords=list("ire","ego","nahlizet","certum","veri","jatkaa","mgar","balaq", "karazet", "geeri") ///"orkan" and "allaq" removed.
+ var/list/runewords=rnwords
for (var/word in engwords)
cultwords[word] = pick(runewords)
runewords-=cultwords[word]
@@ -33,7 +34,7 @@ var/engwords = list("travel", "blood", "join", "hell", "destroy", "technology",
var/word2
var/word3
var/list/converting = list()
-
+
// Places these combos are mentioned: this file - twice in the rune code, once in imbued tome, once in tome's HTML runes.dm - in the imbue rune code. If you change a combination - dont forget to change it everywhere.
// travel self [word] - Teleport to random [rune with word destination matching]
diff --git a/code/game/gamemodes/cult/talisman.dm b/code/game/gamemodes/cult/talisman.dm
index 0f5ab245d1..df5b9a4965 100644
--- a/code/game/gamemodes/cult/talisman.dm
+++ b/code/game/gamemodes/cult/talisman.dm
@@ -106,7 +106,7 @@
if("soulstone")
new /obj/item/device/soulstone(get_turf(usr))
if("construct")
- new /obj/structure/constructshell(get_turf(usr))
+ new /obj/structure/constructshell/cult(get_turf(usr))
src.uses--
supply()
return
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index a1af754229..a5715624bb 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -2,6 +2,10 @@
name = "overlay"
unacidable = 1
var/i_attached//Added for possible image attachments to objects. For hallucinations and the like.
+
+/obj/effect/overlay/Destroy()
+ PlaceInPool(src)
+ return 1 //cancels the GCing
/obj/effect/overlay/beam//Not actually a projectile, just an effect.
name="beam"
diff --git a/code/game/objects/structures/crates_lockers/closets/statue.dm b/code/game/objects/structures/crates_lockers/closets/statue.dm
new file mode 100644
index 0000000000..644f2d7531
--- /dev/null
+++ b/code/game/objects/structures/crates_lockers/closets/statue.dm
@@ -0,0 +1,133 @@
+/obj/structure/closet/statue
+ name = "statue"
+ desc = "An incredibly lifelike marble carving"
+ icon = 'icons/obj/statue.dmi'
+ icon_state = "human_male"
+ density = 1
+ anchored = 1
+ health = 0 //destroying the statue kills the mob within
+ var/intialTox = 0 //these are here to keep the mob from taking damage from things that logically wouldn't affect a rock
+ var/intialFire = 0 //it's a little sloppy I know but it was this or the GODMODE flag. Lesser of two evils.
+ var/intialBrute = 0
+ var/intialOxy = 0
+ var/timer = 240 //eventually the person will be freed
+
+/obj/structure/closet/statue/New(loc, var/mob/living/L)
+ if(L && (ishuman(L) || L.isMonkey() || iscorgi(L)))
+ if(L.buckled)
+ L.buckled = 0
+ L.anchored = 0
+ if(L.client)
+ L.client.perspective = EYE_PERSPECTIVE
+ L.client.eye = src
+ L.loc = src
+ L.sdisabilities |= MUTE
+ health = L.health + 100 //stoning damaged mobs will result in easier to shatter statues
+ intialTox = L.getToxLoss()
+ intialFire = L.getFireLoss()
+ intialBrute = L.getBruteLoss()
+ intialOxy = L.getOxyLoss()
+ if(ishuman(L))
+ name = "statue of [L.name]"
+ if(L.gender == "female")
+ icon_state = "human_female"
+ else if(L.isMonkey())
+ name = "statue of a monkey"
+ icon_state = "monkey"
+ else if(iscorgi(L))
+ name = "statue of a corgi"
+ icon_state = "corgi"
+ desc = "If it takes forever, I will wait for you..."
+
+ if(health == 0) //meaning if the statue didn't find a valid target
+ del(src)
+ return
+
+ processing_objects.Add(src)
+ ..()
+
+/obj/structure/closet/statue/process()
+ timer--
+ for(var/mob/living/M in src) //Go-go gadget stasis field
+ M.setToxLoss(intialTox)
+ M.adjustFireLoss(intialFire - M.getFireLoss())
+ M.adjustBruteLoss(intialBrute - M.getBruteLoss())
+ M.setOxyLoss(intialOxy)
+ if (timer <= 0)
+ dump_contents()
+ processing_objects.Remove(src)
+ del(src)
+
+/obj/structure/closet/statue/dump_contents()
+
+ for(var/obj/O in src)
+ O.loc = src.loc
+
+ for(var/mob/living/M in src)
+ M.loc = src.loc
+ M.sdisabilities &= ~MUTE
+ M.take_overall_damage((M.health - health - 100),0) //any new damage the statue incurred is transfered to the mob
+ if(M.client)
+ M.client.eye = M.client.mob
+ M.client.perspective = MOB_PERSPECTIVE
+
+/obj/structure/closet/statue/open()
+ return
+
+/obj/structure/closet/statue/close()
+ return
+
+/obj/structure/closet/statue/toggle()
+ return
+
+/obj/structure/closet/statue/bullet_act(var/obj/item/projectile/Proj)
+ health -= Proj.damage
+ if(health <= 0)
+ for(var/mob/M in src)
+ shatter(M)
+
+ return
+
+/obj/structure/closet/statue/attack_generic(var/mob/user, damage, attacktext, environment_smash)
+ if(damage && environment_smash)
+ for(var/mob/M in src)
+ shatter(M)
+
+/obj/structure/closet/statue/blob_act()
+ for(var/mob/M in src)
+ shatter(M)
+
+/obj/structure/closet/statue/meteorhit(obj/O as obj)
+ if(O.icon_state == "flaming")
+ for(var/mob/M in src)
+ M.meteorhit(O)
+ shatter(M)
+
+/obj/structure/closet/statue/attackby(obj/item/I as obj, mob/user as mob)
+ health -= I.force
+ visible_message("[user] strikes [src] with [I].")
+ if(health <= 0)
+ for(var/mob/M in src)
+ shatter(M)
+
+/obj/structure/closet/statue/MouseDrop_T()
+ return
+
+/obj/structure/closet/statue/relaymove()
+ return
+
+/obj/structure/closet/statue/attack_hand()
+ return
+
+/obj/structure/closet/statue/verb_toggleopen()
+ return
+
+/obj/structure/closet/statue/update_icon()
+ return
+
+/obj/structure/closet/statue/proc/shatter(mob/user as mob)
+ if (user)
+ user.dust()
+ dump_contents()
+ visible_message("[src] shatters!.")
+ del(src)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 29f425d4a3..c500b055c2 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -26,6 +26,9 @@
var/has_resources
var/list/resources
+ // Flick animation
+ var/atom/movable/overlay/c_animation = null
+
/turf/New()
..()
for(var/atom/movable/AM as mob|obj in src)
diff --git a/code/game/turfs/turf_flick_animations.dm b/code/game/turfs/turf_flick_animations.dm
new file mode 100644
index 0000000000..59bcfd56d7
--- /dev/null
+++ b/code/game/turfs/turf_flick_animations.dm
@@ -0,0 +1,21 @@
+/turf/proc/turf_animation(var/anim_icon,var/anim_state,var/anim_x=0, var/anim_y=0, var/anim_layer=MOB_LAYER+1, var/anim_sound=null, var/anim_color=null)
+ if(!c_animation)//spamming turf animations can have unintended effects, such as the overlays never disapearing. hence this check.
+ if(anim_sound)
+ playsound(src, anim_sound, 50, 1)
+ c_animation = PoolOrNew(/atom/movable/overlay, src)
+ c_animation.name = "turf_animation"
+ c_animation.density = 0
+ c_animation.anchored = 1
+ c_animation.icon = anim_icon
+ c_animation.icon_state = anim_state
+ c_animation.layer = anim_layer
+ c_animation.master = src
+ c_animation.pixel_x = anim_x
+ c_animation.pixel_y = anim_y
+ if(anim_color)
+ c_animation.color = anim_color
+ flick("turf_animation",c_animation)
+ spawn(10)
+ if(c_animation)
+ PlaceInPool(c_animation)
+ c_animation = null
diff --git a/code/global.dm b/code/global.dm
index 3836229649..c7a1738127 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -1,4 +1,7 @@
//#define TESTING
+#if DM_VERSION < 506
+#warn This compiler is out of date. You may experience issues with projectile animations.
+#endif
// Items that ask to be called every cycle.
var/global/obj/effect/datacore/data_core = null
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 59e40e2a44..a038d8329f 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -944,3 +944,14 @@ var/list/admin_verbs_mentor = list(
log_admin("[key_name(usr)] told everyone to man up and deal with it.")
message_admins("\blue [key_name_admin(usr)] told everyone to man up and deal with it.", 1)
+
+/client/proc/give_spell(mob/T as mob in mob_list) // -- Urist
+ set category = "Fun"
+ set name = "Give Spell"
+ set desc = "Gives a spell to a mob."
+ var/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spells
+ if(!S) return
+ T.spell_list += new S
+ feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
+ message_admins("\blue [key_name_admin(usr)] gave [key_name(T)] the spell [S].", 1)
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index 38b252b1ff..234de0cb4f 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -45,6 +45,8 @@
species_restricted = null
body_parts_covered = 0
+ wizard_garb = 1
+
/obj/item/clothing/shoes/sandal/marisa
desc = "A pair of magic, black shoes."
name = "magic shoes"
diff --git a/code/modules/clothing/spacesuits/void/wizard.dm b/code/modules/clothing/spacesuits/void/wizard.dm
index bddf9f7f2e..56863356f4 100644
--- a/code/modules/clothing/spacesuits/void/wizard.dm
+++ b/code/modules/clothing/spacesuits/void/wizard.dm
@@ -10,6 +10,7 @@
siemens_coefficient = 0.7
sprite_sheets_refit = null
sprite_sheets_obj = null
+ wizard_garb = 1
/obj/item/clothing/suit/space/void/wizard
icon_state = "rig-wiz"
@@ -22,4 +23,5 @@
armor = list(melee = 40, bullet = 20, laser = 20,energy = 20, bomb = 35, bio = 100, rad = 60)
siemens_coefficient = 0.7
sprite_sheets_refit = null
- sprite_sheets_obj = null
\ No newline at end of file
+ sprite_sheets_obj = null
+ wizard_garb = 1
diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm
index 03240b2f3c..69e51093f9 100644
--- a/code/modules/clothing/suits/wiz_robe.dm
+++ b/code/modules/clothing/suits/wiz_robe.dm
@@ -5,6 +5,7 @@
//Not given any special protective value since the magic robes are full-body protection --NEO
siemens_coefficient = 0.8
body_parts_covered = 0
+ wizard_garb = 1
/obj/item/clothing/head/wizard/red
name = "red wizard hat"
@@ -55,6 +56,7 @@
allowed = list(/obj/item/weapon/teleportation_scroll)
flags_inv = HIDEJUMPSUIT
siemens_coefficient = 0.8
+ wizard_garb = 1
/obj/item/clothing/suit/wizrobe/red
name = "red wizard robe"
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index edfe9bea0b..00e49b659c 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -117,10 +117,6 @@
if (src.client.statpanel == "Status")
show_silenced()
- if (proc_holder_list.len)//Generic list for proc_holder objects.
- for(var/obj/effect/proc_holder/P in proc_holder_list)
- statpanel("[P.panel]","",P)
-
/mob/living/silicon/pai/check_eye(var/mob/user as mob)
if (!src.current)
return null
diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm
index 12d868e110..4a64e1412e 100644
--- a/code/modules/mob/living/simple_animal/constructs/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm
@@ -100,7 +100,7 @@
attack_sound = 'sound/weapons/punch3.ogg'
status_flags = 0
resistance = 10
- construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/lesserforcewall)
+ construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser)
/mob/living/simple_animal/construct/armoured/Life()
weakened = 0
@@ -149,7 +149,7 @@
environment_smash = 1
see_in_dark = 7
attack_sound = 'sound/weapons/bladeslice.ogg'
- construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift)
+ construct_spells = list(/spell/targeted/ethereal_jaunt/shift)
/////////////////////////////Artificer/////////////////////////
@@ -173,10 +173,12 @@
speed = 0
environment_smash = 2
attack_sound = 'sound/weapons/punch2.ogg'
- construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser,
- /obj/effect/proc_holder/spell/aoe_turf/conjure/wall,
- /obj/effect/proc_holder/spell/aoe_turf/conjure/floor,
- /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone,)
+ construct_spells = list(/spell/aoe_turf/conjure/construct/lesser,
+ /spell/aoe_turf/conjure/wall,
+ /spell/aoe_turf/conjure/floor,
+ /spell/aoe_turf/conjure/soulstone,
+ /spell/aoe_turf/conjure/pylon
+ )
/////////////////////////////Behemoth/////////////////////////
@@ -203,6 +205,7 @@
resistance = 10
var/energy = 0
var/max_energy = 1000
+ construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser)
////////////////////////Harvester////////////////////////////////
@@ -226,7 +229,7 @@
attack_sound = 'sound/weapons/pierce.ogg'
construct_spells = list(
- //spell/targeted/harvest,
- //spell/aoe_turf/knock/harvester,
- //spell/rune_write
+ /spell/targeted/harvest,
+ /spell/aoe_turf/knock/harvester,
+ /spell/rune_write
)
diff --git a/code/modules/mob/living/simple_animal/constructs/soulstone.dm b/code/modules/mob/living/simple_animal/constructs/soulstone.dm
index 339654558b..4a7e00c0c2 100644
--- a/code/modules/mob/living/simple_animal/constructs/soulstone.dm
+++ b/code/modules/mob/living/simple_animal/constructs/soulstone.dm
@@ -86,7 +86,14 @@
name = "empty shell"
icon = 'icons/obj/wizard.dmi'
icon_state = "construct"
- desc = "A wicked machine used by those skilled in magical arts. It is inactive"
+ desc = "A wicked machine used by those skilled in magical arts. It is inactive."
+
+/obj/structure/constructshell/cultify()
+ return
+
+/obj/structure/constructshell/cult
+ icon_state = "construct-cult"
+ desc = "This eerie contraption looks like it would come alive if supplied with a missing ingredient."
/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob)
if(istype(O, /obj/item/device/soulstone))
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 5af7e711ca..0af4b0a235 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -787,19 +787,6 @@ note dizziness decrements automatically in the mob's Life() proc.
continue
statpanel(listed_turf.name, null, A)
- if(spell_list && spell_list.len)
- for(var/obj/effect/proc_holder/spell/S in spell_list)
- switch(S.charge_type)
- if("recharge")
- statpanel("Spells","[S.charge_counter/10.0]/[S.charge_max/10]",S)
- if("charges")
- statpanel("Spells","[S.charge_counter]/[S.charge_max]",S)
- if("holdervar")
- statpanel("Spells","[S.holder_var_type] [S.holder_var_amount]",S)
-
-
-
-
// facing verbs
/mob/proc/canface()
if(!canmove) return 0
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 831798251a..8e22962f21 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -34,6 +34,9 @@
var/obj/screen/gun/run/gun_run_icon = null
var/obj/screen/gun/mode/gun_setting_icon = null
+ //spells hud icons - this interacts with add_spell and remove_spell
+ var/list/obj/screen/movable/spell_master/spell_masters = null
+
/*A bunch of this stuff really needs to go under their own defines instead of being globally attached to mob.
A variable should only be globally attached to turfs/objects/whatever, when it is in fact needed as such.
The current method unnecessarily clusters up the variable list, especially for humans (although rearranging won't really clean it up a lot but the difference will be noticable for other mobs).
@@ -183,7 +186,7 @@
var/mob/living/carbon/LAssailant = null
//Wizard mode, but can be used in other modes thanks to the brand new "Give Spell" badmin button
- var/obj/effect/proc_holder/spell/list/spell_list = list()
+ var/spell/list/spell_list = list()
//Changlings, but can be used in other modes
// var/obj/effect/proc_holder/changpower/list/power_list = list()
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 370a921d3c..101af1d94f 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -92,8 +92,8 @@
/mob/living/silicon/ai/isAI()
return 1
-
-/mob/proc/isRobot()
+
+/mob/proc/isRobot()
return 0
/mob/living/silicon/robot/isRobot()
@@ -108,6 +108,15 @@
/mob/living/silicon/isSynthetic()
return 1
+/mob/living/carbon/human/isMonkey()
+ return istype(species, /datum/species/monkey)
+
+/mob/proc/isMonkey()
+ return 0
+
+/mob/living/carbon/human/isMonkey()
+ return istype(species, /datum/species/monkey)
+
/proc/ispAI(A)
if(istype(A, /mob/living/silicon/pai))
return 1
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
new file mode 100644
index 0000000000..01e671c1ec
--- /dev/null
+++ b/code/modules/power/singularity/narsie.dm
@@ -0,0 +1,133 @@
+var/global/narsie_behaviour = "CultStation13"
+var/global/narsie_cometh = 0
+var/global/list/narsie_list = list()
+
+/obj/machinery/singularity/narsie //Moving narsie to a child object of the singularity so it can be made to function differently. --NEO
+ name = "Nar-Sie"
+ desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees."
+ icon = 'icons/obj/magic_terror.dmi'
+ pixel_x = -89
+ pixel_y = -85
+ current_size = 9 //It moves/eats like a max-size singulo, aside from range. --NEO
+ contained = 0 //Are we going to move around?
+ dissipate = 0 //Do we lose energy over time?
+ move_self = 1 //Do we move on our own?
+ grav_pull = 10 //How many tiles out do we pull?
+ consume_range = 3 //How many tiles out do we eat
+ var/last_boom = 0
+
+/obj/machinery/singularity/narsie/large
+ name = "Nar-Sie"
+ icon = 'icons/obj/narsie.dmi'
+ // Pixel stuff centers Narsie.
+ pixel_x = -236
+ pixel_y = -256
+ current_size = 12
+ move_self = 1 //Do we move on our own?
+ consume_range = 12 //How many tiles out do we eat
+
+/obj/machinery/singularity/narsie/large/New()
+ ..()
+ narsie_list.Add(src)
+ world << "NAR-SIE HAS RISEN"
+ if(emergency_shuttle && emergency_shuttle.can_call())
+ emergency_shuttle.call_evac()
+ emergency_shuttle.launch_time = 0 // Cannot recall
+
+/obj/machinery/singularity/narsie/large/Destroy()
+ narsie_list.Remove(src)
+ ..()
+
+/obj/machinery/singularity/narsie/process()
+ eat()
+ if(!target || prob(5))
+ pickcultist()
+ move()
+ if(prob(25))
+ mezzer()
+
+/obj/machinery/singularity/narsie/consume(var/atom/A) //Has its own consume proc because it doesn't need energy and I don't want BoHs to explode it. --NEO
+ if(is_type_in_list(A, uneatable))
+ return 0
+ if (istype(A,/mob/living))//Mobs get gibbed
+ A:gib()
+ else if(istype(A,/obj))
+ var/obj/O = A
+ machines -= O
+ processing_objects -= O
+ O.loc = null
+ else if(isturf(A))
+ var/turf/T = A
+ if(T.intact)
+ for(var/obj/O in T.contents)
+ if(O.level != 1)
+ continue
+ if(O.invisibility == 101)
+ src.consume(O)
+ A:ChangeTurf(/turf/space)
+ if(last_boom + 100 < world.time && prob(5))
+ explosion(loc, -1, -1, -1, 1, 0) //Since we're not exploding everything in consume() toss out an explosion effect every now and again
+ last_boom = world.time
+ return
+
+/obj/machinery/singularity/narsie/ex_act() //No throwing bombs at it either. --NEO
+ return
+
+/obj/machinery/singularity/narsie/proc/pickcultist() //Narsie rewards his cultists with being devoured first, then picks a ghost to follow. --NEO
+ var/list/cultists = list()
+ if(cult && cult.current_antagonists.len)
+ for(var/datum/mind/cult_nh_mind in cult.current_antagonists)
+ if(!cult_nh_mind.current)
+ continue
+ if(cult_nh_mind.current.stat)
+ continue
+ var/turf/pos = get_turf(cult_nh_mind.current)
+ if(pos.z != src.z)
+ continue
+ cultists += cult_nh_mind.current
+ if(cultists.len)
+ acquire(pick(cultists))
+ return
+ //If there was living cultists, it picks one to follow.
+ for(var/mob/living/carbon/human/food in living_mob_list)
+ if(food.stat)
+ continue
+ var/turf/pos = get_turf(food)
+ if(pos.z != src.z)
+ continue
+ cultists += food
+ if(cultists.len)
+ acquire(pick(cultists))
+ return
+ //no living cultists, pick a living human instead.
+ for(var/mob/dead/observer/ghost in player_list)
+ if(!ghost.client)
+ continue
+ var/turf/pos = get_turf(ghost)
+ if(pos.z != src.z)
+ continue
+ cultists += ghost
+ if(cultists.len)
+ acquire(pick(cultists))
+ return
+ //no living humans, follow a ghost instead.
+
+/obj/machinery/singularity/narsie/proc/acquire(var/mob/food)
+ target << "\blue NAR-SIE HAS LOST INTEREST IN YOU"
+ target = food
+ if(ishuman(target))
+ target << "\red NAR-SIE HUNGERS FOR YOUR SOUL"
+ else
+ target << "\red NAR-SIE HAS CHOSEN YOU TO LEAD HIM TO HIS NEXT MEAL"
+
+//Wizard narsie
+
+/obj/machinery/singularity/narsie/wizard
+ grav_pull = 0
+
+/obj/machinery/singularity/narsie/wizard/eat()
+ set background = 1
+ for(var/atom/X in orange(consume_range,src))
+ if(isturf(X) || istype(X, /atom/movable))
+ consume(X)
+ return
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index cecdf46a2c..a2901ac270 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -440,130 +440,3 @@ var/global/list/uneatable = list(
if(get_dist(R, src) <= 15) // Better than using orange() every process
R.receive_pulse(energy)
return
-
-
-
-/obj/machinery/singularity/narsie //Moving narsie to a child object of the singularity so it can be made to function differently. --NEO
- name = "Nar-Sie"
- desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees."
- icon = 'icons/obj/magic_terror.dmi'
- pixel_x = -89
- pixel_y = -85
- current_size = 9 //It moves/eats like a max-size singulo, aside from range. --NEO
- contained = 0 //Are we going to move around?
- dissipate = 0 //Do we lose energy over time?
- move_self = 1 //Do we move on our own?
- grav_pull = 10 //How many tiles out do we pull?
- consume_range = 3 //How many tiles out do we eat
- var/last_boom = 0
-
-/obj/machinery/singularity/narsie/large
- name = "Nar-Sie"
- icon = 'icons/obj/narsie.dmi'
- // Pixel stuff centers Narsie.
- pixel_x = -236
- pixel_y = -256
- current_size = 12
- move_self = 1 //Do we move on our own?
- consume_range = 12 //How many tiles out do we eat
-
-/obj/machinery/singularity/narsie/large/New()
- ..()
- world << "NAR-SIE HAS RISEN"
- if(emergency_shuttle && emergency_shuttle.can_call())
- emergency_shuttle.call_evac()
- emergency_shuttle.launch_time = 0 // Cannot recall
-
-/obj/machinery/singularity/narsie/process()
- eat()
- if(!target || prob(5))
- pickcultist()
- move()
- if(prob(25))
- mezzer()
-
-/obj/machinery/singularity/narsie/consume(var/atom/A) //Has its own consume proc because it doesn't need energy and I don't want BoHs to explode it. --NEO
- if(is_type_in_list(A, uneatable))
- return 0
- if (istype(A,/mob/living))//Mobs get gibbed
- A:gib()
- else if(istype(A,/obj))
- var/obj/O = A
- machines -= O
- processing_objects -= O
- O.loc = null
- else if(isturf(A))
- var/turf/T = A
- if(T.intact)
- for(var/obj/O in T.contents)
- if(O.level != 1)
- continue
- if(O.invisibility == 101)
- src.consume(O)
- A:ChangeTurf(/turf/space)
- if(last_boom + 100 < world.time && prob(5))
- explosion(loc, -1, -1, -1, 1, 0) //Since we're not exploding everything in consume() toss out an explosion effect every now and again
- last_boom = world.time
- return
-
-/obj/machinery/singularity/narsie/ex_act() //No throwing bombs at it either. --NEO
- return
-
-/obj/machinery/singularity/narsie/proc/pickcultist() //Narsie rewards his cultists with being devoured first, then picks a ghost to follow. --NEO
- var/list/cultists = list()
- if(cult && cult.current_antagonists.len)
- for(var/datum/mind/cult_nh_mind in cult.current_antagonists)
- if(!cult_nh_mind.current)
- continue
- if(cult_nh_mind.current.stat)
- continue
- var/turf/pos = get_turf(cult_nh_mind.current)
- if(pos.z != src.z)
- continue
- cultists += cult_nh_mind.current
- if(cultists.len)
- acquire(pick(cultists))
- return
- //If there was living cultists, it picks one to follow.
- for(var/mob/living/carbon/human/food in living_mob_list)
- if(food.stat)
- continue
- var/turf/pos = get_turf(food)
- if(pos.z != src.z)
- continue
- cultists += food
- if(cultists.len)
- acquire(pick(cultists))
- return
- //no living cultists, pick a living human instead.
- for(var/mob/dead/observer/ghost in player_list)
- if(!ghost.client)
- continue
- var/turf/pos = get_turf(ghost)
- if(pos.z != src.z)
- continue
- cultists += ghost
- if(cultists.len)
- acquire(pick(cultists))
- return
- //no living humans, follow a ghost instead.
-
-/obj/machinery/singularity/narsie/proc/acquire(var/mob/food)
- target << "\blue NAR-SIE HAS LOST INTEREST IN YOU"
- target = food
- if(ishuman(target))
- target << "\red NAR-SIE HUNGERS FOR YOUR SOUL"
- else
- target << "\red NAR-SIE HAS CHOSEN YOU TO LEAD HIM TO HIS NEXT MEAL"
-
-//Wizard narsie
-
-/obj/machinery/singularity/narsie/wizard
- grav_pull = 0
-
-/obj/machinery/singularity/narsie/wizard/eat()
- set background = 1
- for(var/atom/X in orange(consume_range,src))
- if(isturf(X) || istype(X, /atom/movable))
- consume(X)
- return
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index ed595a344a..98293ebc7f 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -51,17 +51,18 @@
var/drowsy = 0
var/agony = 0
var/embed = 0 // whether or not the projectile can embed itself in the mob
-
- var/hitscan = 0 // whether the projectile should be hitscan
+
+ var/hitscan = 0 // whether the projectile should be hitscan
+ var/step_delay = 1 // the delay between iterations if not a hitscan projectile
// effect types to be used
var/muzzle_type
var/tracer_type
var/impact_type
-
+
var/datum/plot_vector/trajectory // used to plot the path of the projectile
var/datum/vector_loc/location // current location of the projectile in pixel space
- var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't
+ var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't
// have to be recreated multiple times
//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not
@@ -113,12 +114,12 @@
if(user == target) //Shooting yourself
user.bullet_act(src, target_zone)
on_impact(user)
- del(src)
+ qdel(src)
return 0
if(targloc == curloc) //Shooting something in the same turf
target.bullet_act(src, target_zone)
on_impact(target)
- del(src)
+ qdel(src)
return 0
original = target
@@ -256,7 +257,7 @@
density = 0
invisibility = 101
- del(src)
+ qdel(src)
return 1
/obj/item/projectile/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
@@ -276,19 +277,20 @@
spawn while(src)
if(kill_count-- < 1)
on_impact(src.loc) //for any final impact behaviours
- del(src)
+ qdel(src)
if((!( current ) || loc == current))
current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z)
if((x == 1 || x == world.maxx || y == 1 || y == world.maxy))
- del(src)
+ qdel(src)
return
trajectory.increment() // increment the current location
location = trajectory.return_location(location) // update the locally stored location data
if(!location)
- del(src) // if it's left the world... kill it
+ qdel(src) // if it's left the world... kill it
+ before_move()
Move(location.return_turf())
if(first_step)
@@ -302,14 +304,20 @@
if(!(original in permutated))
Bump(original)
+
if(!hitscan)
- sleep(1) //add delay between movement iterations if it's not a hitscan weapon
+ sleep(step_delay) //add delay between movement iterations if it's not a hitscan weapon
+
+/obj/item/projectile/proc/process_step(first_step = 0)
+
+
+/obj/item/projectile/proc/before_move()
/obj/item/projectile/proc/setup_trajectory()
// plot the initial trajectory
trajectory = new()
trajectory.setup(starting, original, pixel_x, pixel_y)
-
+
// generate this now since all visual effects the projectile makes can use it
effect_transform = new()
effect_transform.Scale(trajectory.return_hypotenuse(), 1)
@@ -321,7 +329,7 @@
if(ispath(muzzle_type))
var/obj/effect/projectile/M = new muzzle_type(get_turf(src))
-
+
if(istype(M))
M.set_transform(T)
M.pixel_x = location.pixel_x
@@ -331,7 +339,7 @@
/obj/item/projectile/proc/tracer_effect(var/matrix/M)
if(ispath(tracer_type))
var/obj/effect/projectile/P = new tracer_type(location.loc)
-
+
if(istype(P))
P.set_transform(M)
P.pixel_x = location.pixel_x
@@ -341,7 +349,7 @@
/obj/item/projectile/proc/impact_effect(var/matrix/M)
if(ispath(tracer_type))
var/obj/effect/projectile/P = new impact_type(location.loc)
-
+
if(istype(P))
P.set_transform(M)
P.pixel_x = location.pixel_x
@@ -411,5 +419,5 @@
trace.pass_flags = pass_flags //And the pass flags to that of the real projectile...
trace.firer = user
var/output = trace.process() //Test it!
- del(trace) //No need for it anymore
+ qdel(trace) //No need for it anymore
return output //Send it back to the gun!
diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm
index 24feb08771..051ae913f6 100644
--- a/code/modules/projectiles/projectile/change.dm
+++ b/code/modules/projectiles/projectile/change.dm
@@ -79,8 +79,8 @@
A.randomize_appearance_for(H)
if(new_mob)
- for (var/obj/effect/proc_holder/spell/S in M.spell_list)
- new_mob.spell_list += new S.type
+ for (var/spell/S in M.spell_list)
+ new_mob.add_spell(new S.type)
new_mob.a_intent = "hurt"
if(M.mind)
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 1a25162526..20c4445e55 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -26,7 +26,7 @@
|| istype(A, /obj/item/weapon/reagent_containers) || istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart))
return
- if(istype(A, /obj/effect/proc_holder/spell))
+ if(istype(A, /spell))
return
if(istype(A, /obj/structure/reagent_dispensers) && get_dist(src,A) <= 1) //this block copypasted from reagent_containers/glass, for lack of a better solution
diff --git a/code/modules/spells/aoe_turf/aoe_turf.dm b/code/modules/spells/aoe_turf/aoe_turf.dm
new file mode 100644
index 0000000000..9a70eb18c6
--- /dev/null
+++ b/code/modules/spells/aoe_turf/aoe_turf.dm
@@ -0,0 +1,25 @@
+/*
+Aoe turf spells target a ring of tiles around the user
+This ring has an outer radius (range) and an inner radius (inner_radius)
+Aoe turf spells have two useful flags: IGNOREDENSE and IGNORESPACE. These are explained in setup.dm
+*/
+
+/spell/aoe_turf //affects all turfs in view or range (depends)
+ spell_flags = IGNOREDENSE
+ var/inner_radius = -1 //for all your ring spell needs
+
+/spell/aoe_turf/choose_targets(mob/user = usr)
+ var/list/targets = list()
+
+ for(var/turf/target in view_or_range(range,user,selection_type))
+ if(!(target in view_or_range(inner_radius,user,selection_type)))
+ if(target.density && (spell_flags & IGNOREDENSE))
+ continue
+ if(istype(target, /turf/space) && (spell_flags & IGNORESPACE))
+ continue
+ targets += target
+
+ if(!targets.len) //doesn't waste the spell
+ return
+
+ return targets
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/blink.dm b/code/modules/spells/aoe_turf/blink.dm
new file mode 100644
index 0000000000..f78572a3ba
--- /dev/null
+++ b/code/modules/spells/aoe_turf/blink.dm
@@ -0,0 +1,34 @@
+/spell/aoe_turf/blink
+ name = "Blink"
+ desc = "This spell randomly teleports you a short distance."
+
+ school = "abjuration"
+ charge_max = 20
+ spell_flags = Z2NOCAST | IGNOREDENSE | IGNORESPACE
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 7
+ inner_radius = 1
+ cooldown_min = 5 //4 deciseconds reduction per rank
+ hud_state = "wiz_blink"
+
+/spell/aoe_turf/blink/cast(var/list/targets, mob/user)
+ if(!targets.len)
+ return
+
+ var/turf/T = pick(targets)
+ var/turf/starting = get_turf(user)
+ if(T)
+ if(user.buckled)
+ user.buckled = null
+ user.forceMove(T)
+
+ var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
+ smoke.set_up(3, 0, starting)
+ smoke.start()
+
+ smoke = new()
+ smoke.set_up(3, 0, T)
+ smoke.start()
+
+ return
diff --git a/code/modules/spells/aoe_turf/charge.dm b/code/modules/spells/aoe_turf/charge.dm
new file mode 100644
index 0000000000..eb8fb76de2
--- /dev/null
+++ b/code/modules/spells/aoe_turf/charge.dm
@@ -0,0 +1,69 @@
+/spell/aoe_turf/charge
+ name = "Charge"
+ desc = "This spell can be used to charge up spent magical artifacts, among other things."
+
+ school = "transmutation"
+ charge_max = 600
+ spell_flags = 0
+ invocation = "DIRI CEL"
+ invocation_type = SpI_WHISPER
+ range = 0
+ cooldown_min = 400 //50 deciseconds reduction per rank
+
+ hud_state = "wiz_charge"
+
+/spell/aoe_turf/charge/cast(var/list/targets, mob/user)
+ for(var/turf/T in targets)
+ depth_cast(T)
+
+/spell/aoe_turf/charge/proc/depth_cast(var/list/targets)
+ for(var/atom/A in targets)
+ if(A.contents.len)
+ depth_cast(A.contents)
+ cast_charge(A)
+
+/spell/aoe_turf/charge/proc/mob_charge(var/mob/living/M)
+ if(M.spell_list.len != 0)
+ for(var/spell/S in M.spell_list)
+ if(!istype(S, /spell/aoe_turf/charge))
+ S.charge_counter = S.charge_max
+ M <<"You feel raw magic flowing through you, it feels good!"
+ else
+ M <<"You feel very strange for a moment, but then it passes."
+ return M
+
+/spell/aoe_turf/charge/proc/cast_charge(var/atom/target)
+ var/atom/charged_item
+
+ if(istype(target, /mob/living))
+ charged_item = mob_charge(target)
+
+ if(istype(target, /obj/item/weapon/grab))
+ var/obj/item/weapon/grab/G = target
+ if(G.affecting)
+ var/mob/M = G.affecting
+ charged_item = mob_charge(M)
+
+ if(istype(target, /obj/item/weapon/spellbook/oneuse))
+ var/obj/item/weapon/spellbook/oneuse/I = target
+ if(prob(50))
+ I.visible_message("[I] catches fire!")
+ del(I)
+ else
+ I.used = 0
+ charged_item = I
+
+ if(istype(target, /obj/item/weapon/cell/))
+ var/obj/item/weapon/cell/C = target
+ if(prob(80))
+ C.maxcharge -= 200
+ if(C.maxcharge <= 1) //Div by 0 protection
+ C.maxcharge = 1
+ C.charge = C.maxcharge
+ charged_item = C
+
+ if(!charged_item)
+ return 0
+ else
+ charged_item.visible_message("[charged_item] suddenly sparks with energy!")
+ return 1
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/conjure/conjure.dm b/code/modules/spells/aoe_turf/conjure/conjure.dm
new file mode 100644
index 0000000000..98377948fa
--- /dev/null
+++ b/code/modules/spells/aoe_turf/conjure/conjure.dm
@@ -0,0 +1,74 @@
+/*
+Conjure spells spawn things (mobs, objs, turfs) in their summon_type
+How they spawn stuff is decided by behaviour vars, which are explained below
+*/
+
+/spell/aoe_turf/conjure
+ name = "Conjure"
+ desc = "This spell conjures objs of the specified types in range."
+
+ school = "conjuration" //funny, that
+
+ var/list/summon_type = list() //determines what exactly will be summoned
+ //should be text, like list("/obj/machinery/bot/ed209")
+
+ range = 0 //default values: only spawn on the player tile
+ selection_type = "view"
+
+ duration = 0 // 0=permanent, any other time in deciseconds - how long the summoned objects last for
+ var/summon_amt = 1 //amount of objects summoned
+ var/summon_exclusive = 0 //spawn one of everything, instead of random things
+
+ var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet
+ //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example
+
+ cast_sound = 'sound/items/welder.ogg'
+
+/spell/aoe_turf/conjure/cast(list/targets, mob/user)
+ playsound(get_turf(user), cast_sound, 50, 1)
+
+ for(var/i=1,i <= summon_amt,i++)
+ if(!targets.len)
+ break
+ var/summoned_object_type
+ if(summon_exclusive)
+ if(!summon_type.len)
+ break
+ summoned_object_type = summon_type[1]
+ summon_type -= summoned_object_type
+ else
+ summoned_object_type = pick(summon_type)
+ var/turf/spawn_place = pick(targets)
+ if(spell_flags & IGNOREPREV)
+ targets -= spawn_place
+
+ var/atom/summoned_object
+ if(ispath(summoned_object_type,/turf))
+ if(istype(get_turf(user),/turf/simulated/shuttle) || istype(spawn_place, /turf/simulated/shuttle))
+ user << ""
+ continue
+ spawn_place.ChangeTurf(summoned_object_type)
+ summoned_object = spawn_place
+ else
+ summoned_object = new summoned_object_type(spawn_place)
+ var/atom/movable/overlay/animation = new /atom/movable/overlay(spawn_place)
+ animation.name = "conjure"
+ animation.density = 0
+ animation.anchored = 1
+ animation.icon = 'icons/effects/effects.dmi'
+ animation.layer = 3
+ animation.master = summoned_object
+
+ for(var/varName in newVars)
+ if(varName in summoned_object.vars)
+ summoned_object.vars[varName] = newVars[varName]
+
+ if(duration)
+ spawn(duration)
+ if(summoned_object && !istype(summoned_object, /turf))
+ qdel(summoned_object)
+ conjure_animation(animation, spawn_place)
+ return
+
+/spell/aoe_turf/conjure/proc/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+ del(animation)
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/conjure/construct.dm b/code/modules/spells/aoe_turf/conjure/construct.dm
new file mode 100644
index 0000000000..fb04db349b
--- /dev/null
+++ b/code/modules/spells/aoe_turf/conjure/construct.dm
@@ -0,0 +1,137 @@
+//////////////////////////////Construct Spells/////////////////////////
+
+/spell/aoe_turf/conjure/construct
+ name = "Artificer"
+ desc = "This spell conjures a construct which may be controlled by Shades"
+
+ school = "conjuration"
+ charge_max = 600
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/constructshell)
+
+ hud_state = "artificer"
+
+/spell/aoe_turf/conjure/construct/lesser
+ charge_max = 1800
+ summon_type = list(/obj/structure/constructshell/cult)
+ hud_state = "const_shell"
+ override_base = "const"
+
+/spell/aoe_turf/conjure/floor
+ name = "Floor Construction"
+ desc = "This spell constructs a cult floor"
+
+ charge_max = 20
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/turf/simulated/floor/engine/cult)
+
+ hud_state = "const_floor"
+
+/spell/aoe_turf/conjure/floor/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+ animation.icon_state = "cultfloor"
+ flick("cultfloor",animation)
+ spawn(10)
+ del(animation)
+
+/spell/aoe_turf/conjure/wall
+ name = "Lesser Construction"
+ desc = "This spell constructs a cult wall"
+
+ charge_max = 100
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/turf/simulated/wall/cult)
+
+ hud_state = "const_wall"
+
+/spell/aoe_turf/conjure/wall/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+ animation.icon_state = "cultwall"
+ flick("cultwall",animation)
+ spawn(10)
+ del(animation)
+
+/spell/aoe_turf/conjure/wall/reinforced
+ name = "Greater Construction"
+ desc = "This spell constructs a reinforced metal wall"
+
+ charge_max = 300
+ spell_flags = Z2NOCAST
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ cast_delay = 50
+
+ summon_type = list(/turf/simulated/wall/r_wall)
+
+/spell/aoe_turf/conjure/soulstone
+ name = "Summon Soulstone"
+ desc = "This spell reaches into Nar-Sie's realm, summoning one of the legendary fragments across time and space"
+
+ charge_max = 3000
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/item/device/soulstone)
+
+ hud_state = "const_stone"
+ override_base = "const"
+
+/spell/aoe_turf/conjure/pylon
+ name = "Red Pylon"
+ desc = "This spell conjures a fragile crystal from Nar-Sie's realm. Makes for a convenient light source."
+
+ charge_max = 200
+ spell_flags = CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/cult/pylon)
+
+ hud_state = "const_pylon"
+
+/spell/aoe_turf/conjure/pylon/cast(list/targets)
+ ..()
+ var/turf/spawn_place = pick(targets)
+ for(var/obj/structure/cult/pylon/P in spawn_place.contents)
+ if(P.isbroken)
+ P.repair(usr)
+ continue
+ return
+
+/spell/aoe_turf/conjure/forcewall/lesser
+ name = "Shield"
+ desc = "Allows you to pull up a shield to protect yourself and allies from incoming threats"
+
+ charge_max = 300
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/obj/effect/forcefield/cult)
+ duration = 200
+
+ hud_state = "const_juggwall"
+
+//Code for the Juggernaut construct's forcefield, that seemed like a good place to put it.
+/obj/effect/forcefield/cult
+ desc = "That eerie looking obstacle seems to have been pulled from another dimension through sheer force"
+ name = "Juggerwall"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "m_shield_cult"
+ l_color = "#B40000"
+ luminosity = 2
+
+/obj/effect/forcefield/cult/cultify()
+ return
diff --git a/code/modules/spells/aoe_turf/conjure/forcewall.dm b/code/modules/spells/aoe_turf/conjure/forcewall.dm
new file mode 100644
index 0000000000..0c5ada5bd1
--- /dev/null
+++ b/code/modules/spells/aoe_turf/conjure/forcewall.dm
@@ -0,0 +1,52 @@
+/spell/aoe_turf/conjure/forcewall
+ name = "Forcewall"
+ desc = "Create a wall of pure energy at your location."
+ summon_type = list(/obj/effect/forcefield)
+ duration = 300
+ charge_max = 100
+ spell_flags = 0
+ range = 0
+ cast_sound = null
+
+ hud_state = "wiz_shield"
+
+/spell/aoe_turf/conjure/forcewall/mime
+ name = "Invisible wall"
+ desc = "Create an invisible wall on your location."
+ school = "mime"
+ panel = "Mime"
+ summon_type = list(/obj/effect/forcefield/mime)
+ invocation_type = SpI_EMOTE
+ invocation = "mimes placing their hands on a flat surfacing, and pushing against it."
+ charge_max = 300
+ cast_sound = null
+
+ override_base = "grey"
+ hud_state = "mime_wall"
+
+/obj/effect/forcefield
+ desc = "A space wizard's magic wall."
+ name = "FORCEWALL"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "m_shield"
+ anchored = 1.0
+ opacity = 0
+ density = 1
+ unacidable = 1
+
+/obj/effect/forcefield/bullet_act(var/obj/item/projectile/Proj, var/def_zone)
+ var/turf/T = get_turf(src.loc)
+ if(T)
+ for(var/mob/M in T)
+ Proj.on_hit(M,M.bullet_act(Proj, def_zone))
+ return
+
+/obj/effect/forcefield/mime
+ icon_state = "empty"
+ name = "invisible wall"
+ desc = "You have a bad feeling about this."
+
+/obj/effect/forcefield/cultify()
+ new /obj/effect/forcefield/cult(get_turf(src))
+ qdel(src)
+ return
diff --git a/code/modules/spells/aoe_turf/disable_tech.dm b/code/modules/spells/aoe_turf/disable_tech.dm
new file mode 100644
index 0000000000..5192ecb664
--- /dev/null
+++ b/code/modules/spells/aoe_turf/disable_tech.dm
@@ -0,0 +1,23 @@
+/spell/aoe_turf/disable_tech
+ name = "Disable Tech"
+ desc = "This spell disables all weapons, cameras and most other technology in range."
+ charge_max = 400
+ spell_flags = NEEDSCLOTHES
+ invocation = "NEC CANTIO"
+ invocation_type = SpI_SHOUT
+ selection_type = "range"
+ range = 0
+ inner_radius = -1
+
+ cooldown_min = 200 //50 deciseconds reduction per rank
+
+ var/emp_heavy = 6
+ var/emp_light = 10
+
+ hud_state = "wiz_tech"
+
+/spell/aoe_turf/disable_tech/cast(list/targets)
+
+ for(var/turf/target in targets)
+ empulse(get_turf(target), emp_heavy, emp_light)
+ return
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/knock.dm b/code/modules/spells/aoe_turf/knock.dm
new file mode 100644
index 0000000000..3b3cae83d6
--- /dev/null
+++ b/code/modules/spells/aoe_turf/knock.dm
@@ -0,0 +1,44 @@
+/spell/aoe_turf/knock
+ name = "Knock"
+ desc = "This spell opens nearby doors and does not require wizard garb."
+
+ school = "transmutation"
+ charge_max = 100
+ spell_flags = 0
+ invocation = "AULIE OXIN FIERA"
+ invocation_type = SpI_WHISPER
+ range = 3
+ cooldown_min = 20 //20 deciseconds reduction per rank
+
+ hud_state = "wiz_knock"
+
+/spell/aoe_turf/knock/cast(list/targets)
+ for(var/turf/T in targets)
+ for(var/obj/machinery/door/door in T.contents)
+ spawn(1)
+ if(istype(door,/obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/AL = door //casting is important
+ AL.locked = 0
+ door.open()
+ return
+
+
+//Construct version
+/spell/aoe_turf/knock/harvester
+ name = "Disintegrate Doors"
+ desc = "No door shall stop you."
+
+ spell_flags = CONSTRUCT_CHECK
+
+ charge_max = 100
+ invocation = ""
+ invocation_type = "silent"
+ range = 5
+
+ hud_state = "const_knock"
+
+/spell/aoe_turf/knock/harvester/cast(list/targets)
+ for(var/turf/T in targets)
+ for(var/obj/machinery/door/door in T.contents)
+ spawn door.cultify()
+ return
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/smoke.dm b/code/modules/spells/aoe_turf/smoke.dm
new file mode 100644
index 0000000000..e055fc702f
--- /dev/null
+++ b/code/modules/spells/aoe_turf/smoke.dm
@@ -0,0 +1,17 @@
+/spell/aoe_turf/smoke
+ name = "Smoke"
+ desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb."
+
+ school = "conjuration"
+ charge_max = 120
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 1
+ inner_radius = -1
+ cooldown_min = 20 //25 deciseconds reduction per rank
+
+ smoke_spread = 2
+ smoke_amt = 5
+
+ hud_state = "wiz_smoke"
diff --git a/code/modules/spells/aoe_turf/summons.dm b/code/modules/spells/aoe_turf/summons.dm
new file mode 100644
index 0000000000..f9f1242bc9
--- /dev/null
+++ b/code/modules/spells/aoe_turf/summons.dm
@@ -0,0 +1,41 @@
+/spell/aoe_turf/conjure/summonEdSwarm //test purposes
+ name = "Dispense Wizard Justice"
+ desc = "This spell dispenses wizard justice."
+
+ summon_type = list(/obj/machinery/bot/secbot/ed209)
+ summon_amt = 10
+ range = 3
+ newVars = list("emagged" = 1,"name" = "Wizard's Justicebot")
+
+ hud_state = "wiz_ed"
+
+/spell/aoe_turf/conjure/carp
+ name = "Summon Carp"
+ desc = "This spell conjures a simple carp."
+
+ school = "conjuration"
+ charge_max = 1200
+ spell_flags = NEEDSCLOTHES
+ invocation = "NOUK FHUNMM SACP RISSKA"
+ invocation_type = SpI_SHOUT
+ range = 1
+
+ summon_type = list(/mob/living/simple_animal/hostile/carp)
+
+ hud_state = "wiz_carp"
+
+/spell/aoe_turf/conjure/creature
+ name = "Summon Creature Swarm"
+ desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth"
+
+ school = "conjuration"
+ charge_max = 1200
+ spell_flags = 0
+ invocation = "IA IA"
+ invocation_type = SpI_SHOUT
+ summon_amt = 10
+ range = 3
+
+ summon_type = list(/mob/living/simple_animal/hostile/creature)
+
+ hud_state = "wiz_creature"
\ No newline at end of file
diff --git a/code/modules/spells/area_teleport.dm b/code/modules/spells/area_teleport.dm
deleted file mode 100644
index 05a8e655ed..0000000000
--- a/code/modules/spells/area_teleport.dm
+++ /dev/null
@@ -1,82 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/area_teleport
- name = "Area teleport"
- desc = "This spell teleports you to a type of area of your selection."
-
- var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list
- var/invocation_area = 1 //if the invocation appends the selected area
-
-/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1)
- var/thearea = before_cast(targets)
- if(!thearea || !cast_check(1))
- revert_cast()
- return
- invocation(thearea)
- spawn(0)
- if(charge_type == "recharge" && recharge)
- start_recharge()
- cast(targets,thearea)
- after_cast(targets)
-
-/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets)
- var/A = null
-
- if(!randomise_selection)
- A = input("Area to teleport to", "Teleport", A) in teleportlocs
- else
- A = pick(teleportlocs)
-
- var/area/thearea = teleportlocs[A]
-
- return thearea
-
-/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea)
- for(var/mob/living/target in targets)
- var/list/L = list()
- for(var/turf/T in get_area_turfs(thearea.type))
- if(!T.density)
- var/clear = 1
- for(var/obj/O in T)
- if(O.density)
- clear = 0
- break
- if(clear)
- L+=T
-
- if(!L.len)
- usr <<"The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry."
- return
-
- if(target && target.buckled)
- target.buckled.unbuckle_mob()
-
- var/list/tempL = L
- var/attempt = null
- var/success = 0
- while(tempL.len)
- attempt = pick(tempL)
- success = target.Move(attempt)
- if(!success)
- tempL.Remove(attempt)
- else
- break
-
- if(!success)
- target.loc = pick(L)
-
- return
-
-/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null)
- if(!invocation_area || !chosenarea)
- ..()
- else
- switch(invocation_type)
- if("shout")
- usr.say("[invocation] [uppertext(chosenarea.name)]")
- if(usr.gender==MALE)
- playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- else
- playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- if("whisper")
- usr.whisper("[invocation] [uppertext(chosenarea.name)]")
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/artifacts.dm b/code/modules/spells/artifacts.dm
new file mode 100644
index 0000000000..0a96b6aa2e
--- /dev/null
+++ b/code/modules/spells/artifacts.dm
@@ -0,0 +1,19 @@
+//////////////////////Scrying orb//////////////////////
+
+/obj/item/weapon/scrying
+ name = "scrying orb"
+ desc = "An incandescent orb of otherworldly energy, staring into it gives you vision beyond mortal means."
+ icon = 'icons/obj/projectiles.dmi'
+ icon_state = "bluespace"
+ throw_speed = 3
+ throw_range = 7
+ throwforce = 10
+ damtype = BURN
+ force = 10
+ hitsound = 'sound/items/welder2.ogg'
+
+/obj/item/weapon/scrying/attack_self(mob/user as mob)
+ user << "You can see... everything!"
+ visible_message("[usr] stares into [src], their eyes glazing over.")
+ announce_ghost_joinleave(user.ghostize(1), 1, "You feel that they used a powerful artifact to [pick("invade","disturb","disrupt","infest","taint","spoil","blight")] this place with their presence.")
+ return
diff --git a/code/modules/spells/conjure.dm b/code/modules/spells/conjure.dm
deleted file mode 100644
index 2034bf931f..0000000000
--- a/code/modules/spells/conjure.dm
+++ /dev/null
@@ -1,87 +0,0 @@
-/obj/effect/proc_holder/spell/aoe_turf/conjure
- name = "Conjure"
- desc = "This spell conjures objs of the specified types in range."
-
- var/list/summon_type = list() //determines what exactly will be summoned
- //should be text, like list("/obj/machinery/bot/ed209")
-
- var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds
- var/summon_amt = 1 //amount of objects summoned
- var/summon_ignore_density = 0 //if set to 1, adds dense tiles to possible spawn places
- var/summon_ignore_prev_spawn_points = 0 //if set to 1, each new object is summoned on a new spawn point
-
- var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet
- //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example
- var/delay = 1//Go Go Gadget Inheritance
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets)
-
- for(var/turf/T in targets)
- if(T.density && !summon_ignore_density)
- targets -= T
- playsound(src.loc, 'sound/items/welder.ogg', 50, 1)
-
- if(do_after(usr,delay))
- for(var/i=0,iSome strange aura is blocking the way!"
- src.canmove = 0
- spawn(2) src.canmove = 1
-
-/obj/effect/dummy/spell_jaunt/ex_act(blah)
- return
-/obj/effect/dummy/spell_jaunt/bullet_act(blah)
- return
\ No newline at end of file
diff --git a/code/modules/spells/explosion.dm b/code/modules/spells/explosion.dm
deleted file mode 100644
index b93667ab47..0000000000
--- a/code/modules/spells/explosion.dm
+++ /dev/null
@@ -1,15 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/explosion
- name = "Explosion"
- desc = "This spell explodes an area."
-
- var/ex_severe = 1
- var/ex_heavy = 2
- var/ex_light = 3
- var/ex_flash = 4
-
-/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets)
-
- for(var/mob/living/target in targets)
- explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/general/area_teleport.dm b/code/modules/spells/general/area_teleport.dm
new file mode 100644
index 0000000000..77e1ee47c4
--- /dev/null
+++ b/code/modules/spells/general/area_teleport.dm
@@ -0,0 +1,85 @@
+/spell/area_teleport
+ name = "Teleport"
+ desc = "This spell teleports you to a type of area of your selection."
+
+ school = "abjuration"
+ charge_max = 600
+ spell_flags = NEEDSCLOTHES
+ invocation = "SCYAR NILA"
+ invocation_type = SpI_SHOUT
+ cooldown_min = 200 //100 deciseconds reduction per rank
+
+ smoke_spread = 1
+ smoke_amt = 5
+
+ var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list
+ var/invocation_area = 1 //if the invocation appends the selected area
+
+ cast_sound = 'sound/effects/teleport.ogg'
+
+ hud_state = "wiz_tele"
+
+/spell/area_teleport/before_cast()
+ return
+
+/spell/area_teleport/choose_targets()
+ var/A = null
+
+ if(!randomise_selection)
+ A = input("Area to teleport to", "Teleport", A) in teleportlocs
+ else
+ A = pick(teleportlocs)
+
+ var/area/thearea = teleportlocs[A]
+
+ return list(thearea)
+
+/spell/area_teleport/cast(area/thearea, mob/user)
+ if(!istype(thearea))
+ if(istype(thearea, /list))
+ thearea = thearea[1]
+ var/list/L = list()
+ for(var/turf/T in get_area_turfs(thearea.type))
+ if(!T.density)
+ var/clear = 1
+ for(var/obj/O in T)
+ if(O.density)
+ clear = 0
+ break
+ if(clear)
+ L+=T
+
+ if(!L.len)
+ user <<"The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry."
+ return
+
+ if(user && user.buckled)
+ user.buckled = null
+
+ var/attempt = null
+ var/success = 0
+ while(L.len)
+ attempt = pick(L)
+ success = user.Move(attempt)
+ if(!success)
+ L.Remove(attempt)
+ else
+ break
+
+ if(!success)
+ user.loc = pick(L)
+
+ return
+
+/spell/area_teleport/after_cast()
+ return
+
+/spell/area_teleport/invocation(mob/user, area/chosenarea)
+ if(!istype(chosenarea))
+ return //can't have that, can we
+ if(!invocation_area || !chosenarea)
+ ..()
+ else
+ invocation += "[uppertext(chosenarea.name)]"
+ ..()
+ return
\ No newline at end of file
diff --git a/code/modules/spells/general/rune_write.dm b/code/modules/spells/general/rune_write.dm
new file mode 100644
index 0000000000..c110a76ac1
--- /dev/null
+++ b/code/modules/spells/general/rune_write.dm
@@ -0,0 +1,175 @@
+/spell/rune_write
+ name = "Scribe a Rune"
+ desc = "Let's you instantly manifest a working rune."
+
+ school = "evocation"
+ charge_max = 100
+ charge_type = Sp_RECHARGE
+ invocation_type = SpI_NONE
+
+ spell_flags = CONSTRUCT_CHECK
+
+ hud_state = "const_rune"
+
+ smoke_amt = 1
+
+/spell/rune_write/choose_targets(mob/user = usr)
+ return list(user)
+
+/spell/rune_write/cast(null, mob/user = usr)
+ if(!cultwords["travel"])
+ runerandom()
+ var/list/runes = list("Teleport", "Teleport Other", "Spawn a Tome", "Change Construct Type", "Convert", "EMP", "Drain Blood", "See Invisible", "Resurrect", "Hide Runes", "Reveal Runes", "Astral Journey", "Manifest a Ghost", "Imbue Talisman", "Sacrifice", "Wall", "Free Cultist", "Summon Cultist", "Deafen", "Blind", "BloodBoil", "Communicate", "Stun")
+ var/r = input(user, "Choose a rune to scribe", "Rune Scribing") in runes //not cancellable.
+ var/obj/effect/rune/R = new /obj/effect/rune(user.loc)
+ if(istype(user.loc,/turf))
+ var/area/A = get_area(user)
+ log_and_message_admins("created \an [r] rune at \the [A.name] - [user.loc.x]-[user.loc.y]-[user.loc.z].", user)
+ switch(r)
+ if("Teleport")
+ if(cast_check(1))
+ var/beacon
+ if(user)
+ beacon = input(user, "Select the last rune", "Rune Scribing") in rnwords
+ R.word1=cultwords["travel"]
+ R.word2=cultwords["self"]
+ R.word3=beacon
+ R.check_icon()
+ if("Teleport Other")
+ if(cast_check(1))
+ var/beacon
+ if(user)
+ beacon = input(user, "Select the last rune", "Rune Scribing") in rnwords
+ R.word1=cultwords["travel"]
+ R.word2=cultwords["other"]
+ R.word3=beacon
+ R.check_icon()
+ if("Spawn a Tome")
+ if(cast_check(1))
+ R.word1=cultwords["see"]
+ R.word2=cultwords["blood"]
+ R.word3=cultwords["hell"]
+ R.check_icon()
+ if("Change Construct Type")
+ if(cast_check(1))
+ R.word1=cultwords["hell"]
+ R.word2=cultwords["destroy"]
+ R.word3=cultwords["other"]
+ R.check_icon()
+ if("Convert")
+ if(cast_check(1))
+ R.word1=cultwords["join"]
+ R.word2=cultwords["blood"]
+ R.word3=cultwords["self"]
+ R.check_icon()
+ if("EMP")
+ if(cast_check(1))
+ R.word1=cultwords["destroy"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["technology"]
+ R.check_icon()
+ if("Drain Blood")
+ if(cast_check(1))
+ R.word1=cultwords["travel"]
+ R.word2=cultwords["blood"]
+ R.word3=cultwords["self"]
+ R.check_icon()
+ if("See Invisible")
+ if(cast_check(1))
+ R.word1=cultwords["see"]
+ R.word2=cultwords["hell"]
+ R.word3=cultwords["join"]
+ R.check_icon()
+ if("Resurrect")
+ if(cast_check(1))
+ R.word1=cultwords["blood"]
+ R.word2=cultwords["join"]
+ R.word3=cultwords["hell"]
+ R.check_icon()
+ if("Hide Runes")
+ if(cast_check(1))
+ R.word1=cultwords["hide"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["blood"]
+ R.check_icon()
+ if("Astral Journey")
+ if(cast_check(1))
+ R.word1=cultwords["hell"]
+ R.word2=cultwords["travel"]
+ R.word3=cultwords["self"]
+ R.check_icon()
+ if("Manifest a Ghost")
+ if(cast_check(1))
+ R.word1=cultwords["blood"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["travel"]
+ R.check_icon()
+ if("Imbue Talisman")
+ if(cast_check(1))
+ R.word1=cultwords["hell"]
+ R.word2=cultwords["technology"]
+ R.word3=cultwords["join"]
+ R.check_icon()
+ if("Sacrifice")
+ if(cast_check(1))
+ R.word1=cultwords["hell"]
+ R.word2=cultwords["blood"]
+ R.word3=cultwords["join"]
+ R.check_icon()
+ if("Reveal Runes")
+ if(cast_check(1))
+ R.word1=cultwords["blood"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["hide"]
+ R.check_icon()
+ if("Wall")
+ if(cast_check(1))
+ R.word1=cultwords["destroy"]
+ R.word2=cultwords["travel"]
+ R.word3=cultwords["self"]
+ R.check_icon()
+ if("Freedom")
+ if(cast_check(1))
+ R.word1=cultwords["travel"]
+ R.word2=cultwords["technology"]
+ R.word3=cultwords["other"]
+ R.check_icon()
+ if("Cultsummon")
+ if(cast_check(1))
+ R.word1=cultwords["join"]
+ R.word2=cultwords["other"]
+ R.word3=cultwords["self"]
+ R.check_icon()
+ if("Deafen")
+ if(cast_check(1))
+ R.word1=cultwords["hide"]
+ R.word2=cultwords["other"]
+ R.word3=cultwords["see"]
+ R.check_icon()
+ if("Blind")
+ if(cast_check(1))
+ R.word1=cultwords["destroy"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["other"]
+ R.check_icon()
+ if("BloodBoil")
+ if(cast_check(1))
+ R.word1=cultwords["destroy"]
+ R.word2=cultwords["see"]
+ R.word3=cultwords["blood"]
+ R.check_icon()
+ if("Communicate")
+ if(cast_check(1))
+ R.word1=cultwords["self"]
+ R.word2=cultwords["other"]
+ R.word3=cultwords["technology"]
+ R.check_icon()
+ if("Stun")
+ if(cast_check(1))
+ R.word1=cultwords["join"]
+ R.word2=cultwords["hide"]
+ R.word3=cultwords["technology"]
+ R.check_icon()
+ else
+ user << " You do not have enough space to write a proper rune."
+ return
diff --git a/code/modules/spells/genetic.dm b/code/modules/spells/genetic.dm
deleted file mode 100644
index bcb7d04280..0000000000
--- a/code/modules/spells/genetic.dm
+++ /dev/null
@@ -1,31 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/genetic
- name = "Genetic"
- desc = "This spell inflicts a set of mutations and disabilities upon the target."
-
- var/disabilities = 0 //bits
- var/list/mutations = list() //mutation strings
- var/duration = 100 //deciseconds
- /*
- Disabilities
- 1st bit - ?
- 2nd bit - ?
- 3rd bit - ?
- 4th bit - ?
- 5th bit - ?
- 6th bit - ?
- */
-
-/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets)
-
- for(var/mob/living/target in targets)
- for(var/x in mutations)
- target.mutations.Add(x)
- target.disabilities |= disabilities
- target.update_mutations() //update target's mutation overlays
- spawn(duration)
- for(var/x in mutations)
- target.mutations.Remove(x)
- target.disabilities &= ~disabilities
- target.update_mutations()
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/horsemask.dm b/code/modules/spells/horsemask.dm
deleted file mode 100644
index 32e362f346..0000000000
--- a/code/modules/spells/horsemask.dm
+++ /dev/null
@@ -1,50 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/horsemask
- name = "Curse of the Horseman"
- desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
- school = "transmutation"
- charge_type = "recharge"
- charge_max = 150
- charge_counter = 0
- clothes_req = 0
- stat_allowed = 0
- invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
- invocation_type = "shout"
- range = 7
- selection_type = "range"
- var/list/compatible_mobs = list(/mob/living/carbon/human)
-
-/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr)
- if(!targets.len)
- user << "No target found in range."
- return
-
- var/mob/living/carbon/target = targets[1]
-
- if(!(target.type in compatible_mobs))
- user << "It'd be stupid to curse [target] with a horse's head!"
- return
-
- if(!(target in oview(range)))//If they are not in overview after selection.
- user << "They are too far away!"
- return
-
- var/obj/item/clothing/mask/horsehead/magic/magichead = new /obj/item/clothing/mask/horsehead/magic
- target.visible_message( "[target]'s face lights up in fire, and after the event a horse's head takes its place!", \
- "Your face burns up, and shortly after the fire you realise you have the face of a horse!")
- target.equip_to_slot(magichead, slot_wear_mask)
-
- flick("e_flash", target.flash)
-
-//item used by the horsehead spell
-/obj/item/clothing/mask/horsehead/magic
- //flags_inv = null //so you can still see their face... no. How can you recognize someone when their face is completely different?
- voicechange = 1 //NEEEEIIGHH
-
- dropped(mob/user as mob)
- canremove = 1
- ..()
-
- equipped(var/mob/user, var/slot)
- if (slot == slot_wear_mask)
- canremove = 0 //curses!
- ..()
diff --git a/code/modules/spells/inflict_handler.dm b/code/modules/spells/inflict_handler.dm
deleted file mode 100644
index 34b810d309..0000000000
--- a/code/modules/spells/inflict_handler.dm
+++ /dev/null
@@ -1,59 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/inflict_handler
- name = "Inflict Handler"
- desc = "This spell blinds and/or destroys/damages/heals and/or weakens/stuns the target."
-
- var/amt_weakened = 0
- var/amt_paralysis = 0
- var/amt_stunned = 0
-
- //set to negatives for healing
- var/amt_dam_fire = 0
- var/amt_dam_brute = 0
- var/amt_dam_oxy = 0
- var/amt_dam_tox = 0
-
- var/amt_eye_blind = 0
- var/amt_eye_blurry = 0
-
- var/destroys = "none" //can be "none", "gib" or "disintegrate"
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets)
-
- for(var/mob/living/target in targets)
- switch(destroys)
- if("gib")
- target.gib()
- if("gib_brain")
- if(ishuman(target) || issmall(target))
- var/mob/living/carbon/C = target
- if(!C.has_brain()) // Their brain is already taken out
- var/obj/item/organ/brain/B = new(C.loc)
- B.transfer_identity(C)
- target.gib()
- if("disintegrate")
- target.dust()
-
- if(!target)
- continue
- //damage
- if(amt_dam_brute > 0)
- if(amt_dam_fire >= 0)
- target.take_overall_damage(amt_dam_brute,amt_dam_fire)
- else if (amt_dam_fire < 0)
- target.take_overall_damage(amt_dam_brute,0)
- target.heal_overall_damage(0,amt_dam_fire)
- else if(amt_dam_brute < 0)
- if(amt_dam_fire > 0)
- target.take_overall_damage(0,amt_dam_fire)
- target.heal_overall_damage(amt_dam_brute,0)
- else if (amt_dam_fire <= 0)
- target.heal_overall_damage(amt_dam_brute,amt_dam_fire)
- target.adjustToxLoss(amt_dam_tox)
- target.oxyloss += amt_dam_oxy
- //disabling
- target.Weaken(amt_weakened)
- target.Paralyse(amt_paralysis)
- target.Stun(amt_stunned)
-
- target.eye_blind += amt_eye_blind
- target.eye_blurry += amt_eye_blurry
\ No newline at end of file
diff --git a/code/modules/spells/knock.dm b/code/modules/spells/knock.dm
deleted file mode 100644
index 9ebf664942..0000000000
--- a/code/modules/spells/knock.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/obj/effect/proc_holder/spell/aoe_turf/knock
- name = "Knock"
- desc = "This spell opens nearby doors and does not require wizard garb."
-
- school = "transmutation"
- charge_max = 100
- clothes_req = 0
- invocation = "AULIE OXIN FIERA"
- invocation_type = "whisper"
- range = 3
-
-/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets)
- for(var/turf/T in targets)
- for(var/obj/machinery/door/door in T.contents)
- spawn(1)
- if(istype(door,/obj/machinery/door/airlock))
- var/obj/machinery/door/airlock/A = door
- A.unlock(1) //forced because it's magic!
- door.open()
- return
diff --git a/code/modules/spells/mind_transfer.dm b/code/modules/spells/mind_transfer.dm
deleted file mode 100644
index 1fd8dc3498..0000000000
--- a/code/modules/spells/mind_transfer.dm
+++ /dev/null
@@ -1,116 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
- name = "Mind Transfer"
- desc = "This spell allows the user to switch bodies with a target."
-
- school = "transmutation"
- charge_max = 600
- clothes_req = 0
- invocation = "GIN'YU CAPAN"
- invocation_type = "whisper"
- range = 1
- var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell
- var/list/compatible_mobs = list(/mob/living/carbon/human) //which types of mobs are affected by the spell. NOTE: change at your own risk
- var/base_spell_loss_chance = 20 //base probability of the wizard losing a spell in the process
- var/spell_loss_chance_modifier = 7 //amount of probability of losing a spell added per spell (mind_transfer included)
- var/spell_loss_amount = 1 //the maximum amount of spells possible to lose during a single transfer
- var/msg_wait = 500 //how long in deciseconds it waits before telling that body doesn't feel right or mind swap robbed of a spell
- var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell
- var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell
-
-/*
-Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
-Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering.
-Also, you never added distance checking after target is selected. I've went ahead and did that.
-*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets,mob/user = usr)
- if(!targets.len)
- user << "No mind found."
- return
-
- if(targets.len > 1)
- user << "Too many minds! You're not a hive damnit!"//Whaa...aat?
- return
-
- var/mob/living/target = targets[1]
-
- if(!(target in oview(range)))//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- user << "They are too far away!"
- return
-
- if(!(target.type in compatible_mobs))
- user << "Their mind isn't compatible with yours."
- return
-
- if(target.stat == DEAD)
- user << "You didn't study necromancy back at the Space Wizard Federation academy."
- return
-
- if(!target.key || !target.mind)
- user << "They appear to be catatonic. Not even magic can affect their vacant mind."
- return
-
- if(target.mind.special_role in protected_roles)
- user << "Their mind is resisting your spell."
- return
-
- var/mob/living/victim = target//The target of the spell whos body will be transferred to.
- var/mob/caster = user//The wizard/whomever doing the body transferring.
-
- //SPELL LOSS BEGIN
- admin_attack_log(caster, victim, "Used mind transfer", "Was the victim of mind transfer", "used mind transfer on")
-
- //NOTE: The caster must ALWAYS keep mind transfer, even when other spells are lost.
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/m_transfer = locate() in user.spell_list//Find mind transfer directly.
- var/list/checked_spells = user.spell_list
- checked_spells -= m_transfer //Remove Mind Transfer from the list.
-
- if(caster.spell_list.len)//If they have any spells left over after mind transfer is taken out. If they don't, we don't need this.
- for(var/i=spell_loss_amount,(i>0&&checked_spells.len),i--)//While spell loss amount is greater than zero and checked_spells has spells in it, run this proc.
- for(var/j=checked_spells.len,(j>0&&checked_spells.len),j--)//While the spell list to check is greater than zero and has spells in it, run this proc.
- if(prob(base_spell_loss_chance))
- checked_spells -= pick(checked_spells)//Pick a random spell to remove.
- spawn(msg_wait)
- victim << "The mind transfer has robbed you of a spell."
- break//Spell lost. Break loop, going back to the previous for() statement.
- else//Or keep checking, adding spell chance modifier to increase chance of losing a spell.
- base_spell_loss_chance += spell_loss_chance_modifier
-
- checked_spells += m_transfer//Add back Mind Transfer.
- user.spell_list = checked_spells//Set user spell list to whatever the new list is.
- //SPELL LOSS END
-
- //MIND TRANSFER BEGIN
- if(caster.mind.special_verbs.len)//If the caster had any special verbs, remove them from the mob verb list.
- for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot.
- caster.verbs -= V//But a safety nontheless.
-
- if(victim.mind.special_verbs.len)//Now remove all of the victim's verbs.
- for(var/V in victim.mind.special_verbs)
- victim.verbs -= V
-
- var/mob/dead/observer/ghost = victim.ghostize(0)
- ghost.spell_list = victim.spell_list//If they have spells, transfer them. Now we basically have a backup mob.
-
- caster.mind.transfer_to(victim)
- victim.spell_list = caster.spell_list//Now they are inside the victim's body.
-
- if(victim.mind.special_verbs.len)//To add all the special verbs for the original caster.
- for(var/V in caster.mind.special_verbs)//Not too important but could come into play.
- caster.verbs += V
-
- ghost.mind.transfer_to(caster)
- caster.key = ghost.key //have to transfer the key since the mind was not active
- caster.spell_list = ghost.spell_list
-
- if(caster.mind.special_verbs.len)//If they had any special verbs, we add them here.
- for(var/V in caster.mind.special_verbs)
- caster.verbs += V
- //MIND TRANSFER END
-
- //Here we paralyze both mobs and knock them out for a time.
- caster.Paralyse(paralysis_amount_caster)
- victim.Paralyse(paralysis_amount_victim)
-
- //After a certain amount of time the victim gets a message about being in a different body.
- spawn(msg_wait)
- caster << "\red You feel woozy and lightheaded. Your body doesn't seem like your own."
diff --git a/code/modules/spells/no_clothes.dm b/code/modules/spells/no_clothes.dm
new file mode 100644
index 0000000000..782fe1909f
--- /dev/null
+++ b/code/modules/spells/no_clothes.dm
@@ -0,0 +1,5 @@
+/spell/noclothes
+ name = "No Clothes"
+ desc = "This is a placeholder for knowing if you dont need clothes for any spell."
+
+ spell_flags = NO_BUTTON
\ No newline at end of file
diff --git a/code/modules/spells/projectile.dm b/code/modules/spells/projectile.dm
deleted file mode 100644
index 24c79a0a1c..0000000000
--- a/code/modules/spells/projectile.dm
+++ /dev/null
@@ -1,83 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/projectile
- name = "Projectile"
- desc = "This spell summons projectiles which try to hit the targets."
-
- var/proj_icon = 'icons/obj/projectiles.dmi'
- var/proj_icon_state = "spell"
- var/proj_name = "a spell projectile"
-
- var/proj_trail = 0 //if it leaves a trail
- var/proj_trail_lifespan = 0 //deciseconds
- var/proj_trail_icon = 'icons/obj/wizard.dmi'
- var/proj_trail_icon_state = "trail"
-
- var/proj_type = "/obj/effect/proc_holder/spell/targeted" //IMPORTANT use only subtypes of this
-
- var/proj_lingering = 0 //if it lingers or disappears upon hitting an obstacle
- var/proj_homing = 1 //if it follows the target
- var/proj_insubstantial = 0 //if it can pass through dense objects or not
- var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target)
-
- var/proj_lifespan = 15 //in deciseconds * proj_step_delay
- var/proj_step_delay = 1 //lower = faster
-
-/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr)
-
- for(var/mob/living/target in targets)
- spawn(0)
- var/obj/effect/proc_holder/spell/targeted/projectile
- if(istext(proj_type))
- var/projectile_type = text2path(proj_type)
- projectile = new projectile_type(user)
- if(istype(proj_type,/obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- projectile:linked_spells += proj_type
- projectile.icon = proj_icon
- projectile.icon_state = proj_icon_state
- projectile.set_dir(get_dir(target,projectile))
- projectile.name = proj_name
-
- var/current_loc = usr.loc
-
- projectile.loc = current_loc
-
- for(var/i = 0,i < proj_lifespan,i++)
- if(!projectile)
- break
-
- if(proj_homing)
- if(proj_insubstantial)
- projectile.set_dir(get_dir(projectile,target))
- projectile.loc = get_step_to(projectile,target)
- else
- step_to(projectile,target)
- else
- if(proj_insubstantial)
- projectile.loc = get_step(projectile,dir)
- else
- step(projectile,dir)
-
- if(!proj_lingering && projectile.loc == current_loc) //if it didn't move since last time
- del(projectile)
- break
-
- if(proj_trail && projectile)
- spawn(0)
- if(projectile)
- var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc)
- trail.icon = proj_trail_icon
- trail.icon_state = proj_trail_icon_state
- trail.density = 0
- spawn(proj_trail_lifespan)
- del(trail)
-
- if(projectile.loc in range(target.loc,proj_trigger_range))
- projectile.perform(list(target))
- break
-
- current_loc = projectile.loc
-
- sleep(proj_step_delay)
-
- if(projectile)
- del(projectile)
\ No newline at end of file
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
deleted file mode 100644
index 2ae347ee4a..0000000000
--- a/code/modules/spells/spell.dm
+++ /dev/null
@@ -1,302 +0,0 @@
-/client/proc/give_spell(mob/T as mob in mob_list) // -- Urist
- set category = "Fun"
- set name = "Give Spell"
- set desc = "Gives a spell to a mob."
- var/list/spell_names = list()
- for(var/v in spells)
- // "/obj/effect/proc_holder/spell/" 30 symbols ~Intercross21
- spell_names.Add(copytext("[v]", 31, 0))
- var/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_names
- if(!S) return
- var/path = text2path("/obj/effect/proc_holder/spell/[S]")
- T.spell_list += new path
- feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
- message_admins("\blue [key_name_admin(usr)] gave [key_name(T)] the spell [S].", 1)
-
-/obj/effect/proc_holder
- var/panel = "Debug"//What panel the proc holder needs to go on.
-
-var/list/spells = typesof(/obj/effect/proc_holder/spell) //needed for the badmin verb for now
-
-/obj/effect/proc_holder/spell
- name = "Spell"
- desc = "A wizard spell"
- density = 0
- opacity = 0
-
- var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
-
- var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
-
- var/charge_max = 100 //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges"
- var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges"
-
- var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var"
- var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used
-
- var/clothes_req = 1 //see if it requires clothes
- var/stat_allowed = 0 //see if it requires being conscious/alive, need to set to 1 for ghostpells
- var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell
- var/invocation_type = "none" //can be none, whisper and shout
- var/range = 7 //the range of the spell; outer radius for aoe spells
- var/message = "" //whatever it says to the guy affected by it
- var/selection_type = "view" //can be "range" or "view"
-
- var/overlay = 0
- var/overlay_icon = 'icons/obj/wizard.dmi'
- var/overlay_icon_state = "spell"
- var/overlay_lifespan = 0
-
- var/sparks_spread = 0
- var/sparks_amt = 0 //cropped at 10
- var/smoke_spread = 0 //1 - harmless, 2 - harmful
- var/smoke_amt = 0 //cropped at 10
-
- var/critfailchance = 0
- var/centcomm_cancast = 1 //Whether or not the spell should be allowed on z2
-
-/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0,mob/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
-
- if(!(src in usr.spell_list))
- usr << "\red You shouldn't have this spell! Something's wrong."
- return 0
-
- if(usr.z == 2 && !centcomm_cancast) //Certain spells are not allowed on the centcomm zlevel
- return 0
-
- if(!skipcharge)
- switch(charge_type)
- if("recharge")
- if(charge_counter < charge_max)
- usr << "[name] is still recharging."
- return 0
- if("charges")
- if(!charge_counter)
- usr << "[name] has no charges left."
- return 0
-
- if(usr.stat && !stat_allowed)
- usr << "Not when you're incapacitated."
- return 0
-
- if(ishuman(usr) || issmall(usr))
- if(istype(usr.wear_mask, /obj/item/clothing/mask/muzzle))
- usr << "Mmmf mrrfff!"
- return 0
-
- if(clothes_req) //clothes check
- if(!istype(usr, /mob/living/carbon/human))
- usr << "You aren't a human, Why are you trying to cast a human spell, silly non-human? Casting human spells is for humans."
- return 0
- if(!istype(usr:wear_suit, /obj/item/clothing/suit/wizrobe) && !istype(user:wear_suit, /obj/item/clothing/suit/space/void/wizard))
- usr << "I don't feel strong enough without my robe."
- return 0
- if(!istype(usr:shoes, /obj/item/clothing/shoes/sandal))
- usr << "I don't feel strong enough without my sandals."
- return 0
- if(!istype(usr:head, /obj/item/clothing/head/wizard) && !istype(user:head, /obj/item/clothing/head/helmet/space/void/wizard))
- usr << "I don't feel strong enough without my hat."
- return 0
-
- if(!skipcharge)
- switch(charge_type)
- if("recharge")
- charge_counter = 0 //doesn't start recharging until the targets selecting ends
- if("charges")
- charge_counter-- //returns the charge if the targets selecting fails
- if("holdervar")
- adjust_var(user, holder_var_type, holder_var_amount)
-
- return 1
-
-/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount
-
- switch(invocation_type)
- if("shout")
- if(prob(50))//Auto-mute? Fuck that noise
- usr.say(invocation)
- else
- usr.say(replacetext(invocation," ","`"))
- if(usr.gender==MALE)
- playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- else
- playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
- if("whisper")
- if(prob(50))
- usr.whisper(invocation)
- else
- usr.whisper(replacetext(invocation," ","`"))
-
-/obj/effect/proc_holder/spell/New()
- ..()
-
- charge_counter = charge_max
-
-/obj/effect/proc_holder/spell/Click()
- if(cast_check())
- choose_targets()
- return 1
-
-/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf
- return
-
-/obj/effect/proc_holder/spell/proc/start_recharge()
- while(charge_counter < charge_max)
- sleep(1)
- charge_counter++
-
-/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1) //if recharge is started is important for the trigger spells
- before_cast(targets)
- invocation()
- spawn(0)
- if(charge_type == "recharge" && recharge)
- start_recharge()
- if(prob(critfailchance))
- critfail(targets)
- else
- cast(targets)
- after_cast(targets)
-
-/obj/effect/proc_holder/spell/proc/before_cast(list/targets)
- if(overlay)
- for(var/atom/target in targets)
- var/location
- if(istype(target,/mob/living))
- location = target.loc
- else if(istype(target,/turf))
- location = target
- var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
- spell.icon = overlay_icon
- spell.icon_state = overlay_icon_state
- spell.anchored = 1
- spell.density = 0
- spawn(overlay_lifespan)
- del(spell)
-
-/obj/effect/proc_holder/spell/proc/after_cast(list/targets)
- for(var/atom/target in targets)
- var/location
- if(istype(target,/mob/living))
- location = target.loc
- else if(istype(target,/turf))
- location = target
- if(istype(target,/mob/living) && message)
- target << text("[message]")
- if(sparks_spread)
- var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
- sparks.set_up(sparks_amt, 0, location) //no idea what the 0 is
- sparks.start()
- if(smoke_spread)
- if(smoke_spread == 1)
- var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
- smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
- smoke.start()
- else if(smoke_spread == 2)
- var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad()
- smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
- smoke.start()
-
-/obj/effect/proc_holder/spell/proc/cast(list/targets)
- return
-
-/obj/effect/proc_holder/spell/proc/critfail(list/targets)
- return
-
-/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
- switch(charge_type)
- if("recharge")
- charge_counter = charge_max
- if("charges")
- charge_counter++
- if("holdervar")
- adjust_var(user, holder_var_type, -holder_var_amount)
-
- return
-
-/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
- switch(type)
- if("bruteloss")
- target.adjustBruteLoss(amount)
- if("fireloss")
- target.adjustFireLoss(amount)
- if("toxloss")
- target.adjustToxLoss(amount)
- if("oxyloss")
- target.adjustOxyLoss(amount)
- if("stunned")
- target.AdjustStunned(amount)
- if("weakened")
- target.AdjustWeakened(amount)
- if("paralysis")
- target.AdjustParalysis(amount)
- else
- target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars
- return
-
-/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
- var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
- var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
- var/include_user = 0 //if it includes usr in the target list
-
-/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends)
- var/inner_radius = -1 //for all your ring spell needs
-
-/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr)
- var/list/targets = list()
-
- switch(max_targets)
- if(0) //unlimited
- for(var/mob/living/target in view_or_range(range, user, selection_type))
- targets += target
- if(1) //single target can be picked
- if(range < 0)
- targets += user
- else
- var/possible_targets = list()
-
- for(var/mob/living/M in view_or_range(range, user, selection_type))
- if(!include_user && user == M)
- continue
- possible_targets += M
-
- targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets
- else
- var/list/possible_targets = list()
- for(var/mob/living/target in view_or_range(range, user, selection_type))
- possible_targets += target
- for(var/i=1,i<=max_targets,i++)
- if(!possible_targets.len)
- break
- if(target_ignore_prev)
- var/target = pick(possible_targets)
- possible_targets -= target
- targets += target
- else
- targets += pick(possible_targets)
-
- if(!include_user && (user in targets))
- targets -= user
-
- if(!targets.len) //doesn't waste the spell
- revert_cast(user)
- return
-
- perform(targets)
-
- return
-
-/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
- var/list/targets = list()
-
- for(var/turf/target in view_or_range(range,user,selection_type))
- if(!(target in view_or_range(inner_radius,user,selection_type)))
- targets += target
-
- if(!targets.len) //doesn't waste the spell
- revert_cast()
- return
-
- perform(targets)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm
new file mode 100644
index 0000000000..0a3078507a
--- /dev/null
+++ b/code/modules/spells/spell_code.dm
@@ -0,0 +1,341 @@
+var/list/spells = typesof(/spell) //needed for the badmin verb for now
+
+/spell
+ name = "Spell"
+ desc = "A spell"
+ parent_type = /atom/movable
+ var/panel = "Spells"//What panel the proc holder needs to go on.
+
+ var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
+
+ var/charge_type = Sp_RECHARGE //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
+
+ var/charge_max = 100 //recharge time in deciseconds if charge_type = Sp_RECHARGE or starting charges if charge_type = Sp_CHARGES
+ var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = Sp_RECHARGE or -- each cast if charge_type = Sp_CHARGES
+ var/still_recharging_msg = "The spell is still recharging."
+
+ var/silenced = 0 //not a binary - the length of time we can't cast this for
+
+ var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var"
+ var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used
+
+ var/spell_flags = NEEDSCLOTHES
+ var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell
+ var/invocation_type = SpI_NONE //can be none, whisper, shout, and emote
+ var/range = 7 //the range of the spell; outer radius for aoe spells
+ var/message = "" //whatever it says to the guy affected by it
+ var/selection_type = "view" //can be "range" or "view"
+ var/atom/movable/holder //where the spell is. Normally the user, can be a projectile
+
+ var/duration = 0 //how long the spell lasts
+
+ var/list/spell_levels = list(Sp_SPEED = 0, Sp_POWER = 0) //the current spell levels - total spell levels can be obtained by just adding the two values
+ var/list/level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 0) //maximum possible levels in each category. Total does cover both.
+ var/cooldown_reduc = 0 //If set, defines how much charge_max drops by every speed upgrade
+ var/delay_reduc = 0
+ var/cooldown_min = 0 //minimum possible cooldown for a charging spell
+
+ var/overlay = 0
+ var/overlay_icon = 'icons/obj/wizard.dmi'
+ var/overlay_icon_state = "spell"
+ var/overlay_lifespan = 0
+
+ var/sparks_spread = 0
+ var/sparks_amt = 0 //cropped at 10
+ var/smoke_spread = 0 //1 - harmless, 2 - harmful
+ var/smoke_amt = 0 //cropped at 10
+
+ var/critfailchance = 0
+
+ var/cast_delay = 1
+ var/cast_sound = ""
+
+ var/hud_state = "" //name of the icon used in generating the spell hud object
+ var/override_base = ""
+
+///////////////////////
+///SETUP AND PROCESS///
+///////////////////////
+
+/spell/New()
+ ..()
+
+ //still_recharging_msg = "[name] is still recharging."
+ charge_counter = charge_max
+
+/spell/proc/process()
+ spawn while(charge_counter < charge_max)
+ charge_counter++
+ sleep(1)
+ return
+
+/spell/Click()
+ ..()
+
+ perform(usr)
+
+/////////////////
+/////CASTING/////
+/////////////////
+
+/spell/proc/choose_targets(mob/user = usr) //depends on subtype - see targeted.dm, aoe_turf.dm, dumbfire.dm, or code in general folder
+ return
+
+/spell/proc/perform(mob/user = usr, skipcharge = 0) //if recharge is started is important for the trigger spells
+ if(!holder)
+ holder = user //just in case
+ if(!cast_check(skipcharge, user))
+ return
+ if(cast_delay && !spell_do_after(user, cast_delay))
+ return
+ var/list/targets = choose_targets(user)
+ if(targets && targets.len)
+ invocation(user, targets)
+ take_charge(user, skipcharge)
+
+ before_cast(targets) //applies any overlays and effects
+ user.attack_log += text("\[[time_stamp()]\] [user.real_name] ([user.ckey]) cast the spell [name].")
+ if(prob(critfailchance))
+ critfail(targets, user)
+ else
+ cast(targets, user)
+ after_cast(targets) //generates the sparks, smoke, target messages etc.
+
+
+/spell/proc/cast(list/targets, mob/user) //the actual meat of the spell
+ return
+
+/spell/proc/critfail(list/targets, mob/user) //the wizman has fucked up somehow
+ return
+
+/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
+ switch(type)
+ if("bruteloss")
+ target.adjustBruteLoss(amount)
+ if("fireloss")
+ target.adjustFireLoss(amount)
+ if("toxloss")
+ target.adjustToxLoss(amount)
+ if("oxyloss")
+ target.adjustOxyLoss(amount)
+ if("stunned")
+ target.AdjustStunned(amount)
+ if("weakened")
+ target.AdjustWeakened(amount)
+ if("paralysis")
+ target.AdjustParalysis(amount)
+ else
+ target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars
+ return
+
+///////////////////////////
+/////CASTING WRAPPERS//////
+///////////////////////////
+
+/spell/proc/before_cast(list/targets)
+ var/valid_targets[0]
+ for(var/atom/target in targets)
+ // Check range again (fixes long-range EI NATH)
+ if(!(target in view_or_range(range,usr,selection_type)))
+ continue
+
+ valid_targets += target
+
+ if(overlay)
+ var/location
+ if(istype(target,/mob/living))
+ location = target.loc
+ else if(istype(target,/turf))
+ location = target
+ var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
+ spell.icon = overlay_icon
+ spell.icon_state = overlay_icon_state
+ spell.anchored = 1
+ spell.density = 0
+ spawn(overlay_lifespan)
+ del(spell)
+ return valid_targets
+
+/spell/proc/after_cast(list/targets)
+ for(var/atom/target in targets)
+ var/location = get_turf(target)
+ if(istype(target,/mob/living) && message)
+ target << text("[message]")
+ if(sparks_spread)
+ var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
+ sparks.set_up(sparks_amt, 0, location) //no idea what the 0 is
+ sparks.start()
+ if(smoke_spread)
+ if(smoke_spread == 1)
+ var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
+ smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
+ smoke.start()
+ else if(smoke_spread == 2)
+ var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad()
+ smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
+ smoke.start()
+
+/////////////////////
+////CASTING TOOLS////
+/////////////////////
+/*Checkers, cost takers, message makers, etc*/
+
+/spell/proc/cast_check(skipcharge = 0,mob/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
+
+ if(!(src in user.spell_list))
+ user << "You shouldn't have this spell! Something's wrong."
+ return 0
+
+ if(silenced > 0)
+ return
+
+ if(user.z == 2 && spell_flags & Z2NOCAST) //Certain spells are not allowed on the centcomm zlevel
+ return 0
+
+ if(spell_flags & CONSTRUCT_CHECK)
+ for(var/turf/T in range(holder, 1))
+ if(findNullRod(T))
+ return 0
+
+ if(istype(user, /mob/living/simple_animal))
+ var/mob/living/simple_animal/SA = user
+ if(SA.purge)
+ SA << "The nullrod's power interferes with your own!"
+ return 0
+
+ if(!src.check_charge(skipcharge, user)) //sees if we can cast based on charges alone
+ return 0
+
+ if(!(spell_flags & GHOSTCAST))
+ if(user.stat && !(spell_flags & STATALLOWED))
+ usr << "Not when you're incapacitated."
+ return 0
+
+ if(ishuman(user) && !(invocation_type in list(SpI_EMOTE, SpI_NONE)))
+ if(istype(user.wear_mask, /obj/item/clothing/mask/muzzle))
+ user << "Mmmf mrrfff!"
+ return 0
+
+ var/spell/noclothes/spell = locate() in user.spell_list
+ if((spell_flags & NEEDSCLOTHES) && !(spell && istype(spell)))//clothes check
+ if(!user.wearing_wiz_garb())
+ return 0
+
+ return 1
+
+/spell/proc/check_charge(var/skipcharge, mob/user)
+ if(!skipcharge)
+ switch(charge_type)
+ if(Sp_RECHARGE)
+ if(charge_counter < charge_max)
+ user << still_recharging_msg
+ return 0
+ if(Sp_CHARGES)
+ if(!charge_counter)
+ user << "[name] has no charges left."
+ return 0
+ return 1
+
+/spell/proc/take_charge(mob/user = user, var/skipcharge)
+ if(!skipcharge)
+ switch(charge_type)
+ if(Sp_RECHARGE)
+ charge_counter = 0 //doesn't start recharging until the targets selecting ends
+ src.process()
+ return 1
+ if(Sp_CHARGES)
+ charge_counter-- //returns the charge if the targets selecting fails
+ return 1
+ if(Sp_HOLDVAR)
+ adjust_var(user, holder_var_type, holder_var_amount)
+ return 1
+ return 0
+ return 1
+
+/spell/proc/invocation(mob/user = usr, var/list/targets) //spelling the spell out and setting it on recharge/reducing charges amount
+
+ switch(invocation_type)
+ if(SpI_SHOUT)
+ if(prob(50))//Auto-mute? Fuck that noise
+ user.say(invocation)
+ else
+ user.say(replacetext(invocation," ","`"))
+ if(SpI_WHISPER)
+ if(prob(50))
+ user.whisper(invocation)
+ else
+ user.whisper(replacetext(invocation," ","`"))
+ if(SpI_EMOTE)
+ user.emote("me", 1, invocation) //the 1 means it's for everyone in view, the me makes it an emote, and the invocation is written accordingly.
+
+/////////////////////
+///UPGRADING PROCS///
+/////////////////////
+
+/spell/proc/can_improve(var/upgrade_type)
+ if(level_max[Sp_TOTAL] <= ( spell_levels[Sp_SPEED] + spell_levels[Sp_POWER] )) //too many levels, can't do it
+ return 0
+
+ if(upgrade_type && upgrade_type in spell_levels && upgrade_type in level_max)
+ if(spell_levels[upgrade_type] >= level_max[upgrade_type])
+ return 0
+
+ return 1
+
+/spell/proc/empower_spell()
+ return
+
+/spell/proc/quicken_spell()
+ if(!can_improve(Sp_SPEED))
+ return 0
+
+ spell_levels[Sp_SPEED]++
+
+ if(delay_reduc && cast_delay)
+ cast_delay = max(0, cast_delay - delay_reduc)
+ else if(cast_delay)
+ cast_delay = round( max(0, initial(cast_delay) * ((level_max[Sp_SPEED] - spell_levels[Sp_SPEED]) / level_max[Sp_SPEED] ) ) )
+
+ if(charge_type == Sp_RECHARGE)
+ if(cooldown_reduc)
+ charge_max = max(cooldown_min, charge_max - cooldown_reduc)
+ else
+ charge_max = round( max(cooldown_min, initial(charge_max) * ((level_max[Sp_SPEED] - spell_levels[Sp_SPEED]) / level_max[Sp_SPEED] ) ) ) //the fraction of the way you are to max speed levels is the fraction you lose
+ if(charge_max < charge_counter)
+ charge_counter = charge_max
+
+ var/temp = ""
+ name = initial(name)
+ switch(level_max[Sp_SPEED] - spell_levels[Sp_SPEED])
+ if(3)
+ temp = "You have improved [name] into Efficient [name]."
+ name = "Efficient [name]"
+ if(2)
+ temp = "You have improved [name] into Quickened [name]."
+ name = "Quickened [name]"
+ if(1)
+ temp = "You have improved [name] into Free [name]."
+ name = "Free [name]"
+ if(0)
+ temp = "You have improved [name] into Instant [name]."
+ name = "Instant [name]"
+
+ return temp
+
+/spell/proc/spell_do_after(var/mob/user as mob, delay as num, var/numticks = 5)
+ if(!user || isnull(user))
+ return 0
+ if(numticks == 0)
+ return 1
+
+ var/delayfraction = round(delay/numticks)
+ var/Location = user.loc
+ var/originalstat = user.stat
+
+ for(var/i = 0, i"
- dat += "
"
- dat += "Memorize which spell:
"
- dat += "The number after the spell name is the cooldown time.
"
- dat += "Magic Missile (15)
"
- dat += "Fireball (10)
"
- //dat += "Disintegrate (60)
"
- dat += "Disable Technology (40)
"
- dat += "Smoke (12)
"
- dat += "Blind (30)
"
- dat += "Mind Transfer (60)
"
- dat += "Forcewall (10)
"
- dat += "Blink (2)
"
- dat += "Teleport (60)
"
- dat += "Mutate (40)
"
- dat += "Ethereal Jaunt (30)
"
- dat += "Knock (10)
"
- dat += "Curse of the Horseman (15)
"
-// if(op)
-// dat += "Summon Guns (One time use, global spell)
"
- dat += "
"
- dat += "Artefacts:
"
- dat += "Powerful items imbued with eldritch magics. Summoning one will count towards your maximum number of spells.
"
- dat += "It is recommended that only experienced wizards attempt to wield such artefacts.
"
- dat += "
"
- dat += "Staff of Change
"
- dat += "
"
- dat += "Mental Focus
"
- dat += "
"
- dat += "Six Soul Stone Shards and the spell Artificer
"
- dat += "
"
- dat += "Mastercrafted Armor Set
"
- dat += "
"
- dat += "Staff of Animation
"
- dat += "
"
- dat += "Scrying Orb
"
- dat += "
"
- if(op)
- dat += "Re-memorize Spells
"
- user << browse(dat, "window=radio")
- onclose(user, "radio")
- return
-
-/obj/item/weapon/spellbook/Topic(href, href_list)
- ..()
- var/mob/living/carbon/human/H = usr
-
- if(H.stat || H.restrained())
- return
- if(!istype(H, /mob/living/carbon/human))
- return 1
-
- if(loc == H || (in_range(src, H) && istype(loc, /turf)))
- H.set_machine(src)
- if(href_list["spell_choice"])
- if(href_list["spell_choice"] == "rememorize")
- var/area/wizard_station/A = locate()
- if(usr in A.contents)
- uses = max_uses
- H.spellremove(usr)
- temp = "All spells have been removed. You may now memorize a new set of spells."
- feedback_add_details("wizard_spell_learned","UM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- else
- temp = "You may only re-memorize spells whilst located inside the wizard sanctuary."
- else if(uses >= 1 && max_uses >=1)
- uses--
- /*
- */
- var/list/available_spells = list(magicmissile = "Magic Missile", fireball = "Fireball", disintegrate = "Disintegrate", disabletech = "Disable Tech", smoke = "Smoke", blind = "Blind", mindswap = "Mind Transfer", forcewall = "Forcewall", blink = "Blink", teleport = "Teleport", mutate = "Mutate", etherealjaunt = "Ethereal Jaunt", knock = "Knock", horseman = "Curse of the Horseman", summonguns = "Summon Guns", staffchange = "Staff of Change", mentalfocus = "Mental Focus", soulstone = "Six Soul Stone Shards and the spell Artificer", armor = "Mastercrafted Armor Set", staffanimate = "Staff of Animation")
- var/already_knows = 0
- for(var/obj/effect/proc_holder/spell/aspell in H.spell_list)
- if(available_spells[href_list["spell_choice"]] == aspell.name)
- already_knows = 1
- temp = "You already know that spell."
- uses++
- break
- /*
- */
- if(!already_knows)
- switch(href_list["spell_choice"])
- if("magicmissile")
- feedback_add_details("wizard_spell_learned","MM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(H)
- temp = "This spell fires several, slow moving, magic projectiles at nearby targets. If a projectile hits a target, the target is stunned for some time."
- if("fireball")
- feedback_add_details("wizard_spell_learned","FB") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/dumbfire/fireball(H)
- temp = "This spell fires a fireball in the direction you're facing and does not require wizard garb. Be careful not to fire it at people that are standing next to you."
- if("disintegrate")
- feedback_add_details("wizard_spell_learned","DG") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/inflict_handler/disintegrate(H)
- temp = "This spell instantly kills somebody adjacent to you with the vilest of magick. It has a long cooldown."
- if("disabletech")
- feedback_add_details("wizard_spell_learned","DT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech(H)
- temp = "This spell releases an EMP from your person disabling most technology within range; computers, doors, prosthetics, etc."
- if("smoke")
- feedback_add_details("wizard_spell_learned","SM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/smoke(H)
- temp = "This spell spawns a cloud of vision obscuring smoke at your location and does not require wizard garb."
- if("blind")
- feedback_add_details("wizard_spell_learned","BD") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/trigger/blind(H)
- temp = "This spell temporarly blinds a single person and does not require wizard garb."
- if("mindswap")
- feedback_add_details("wizard_spell_learned","MT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/mind_transfer(H)
- temp = "This spell allows the user to switch bodies with a target. Careful to not lose your memory in the process."
- if("forcewall")
- feedback_add_details("wizard_spell_learned","FW") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/aoe_turf/conjure/forcewall(H)
- temp = "This spell creates an unbreakable wall that lasts for 30 seconds and does not need wizard garb."
- if("blink")
- feedback_add_details("wizard_spell_learned","BL") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink(H)
- temp = "This spell randomly teleports you a short distance. Useful for evasion or getting into areas if you have patience."
- if("teleport")
- feedback_add_details("wizard_spell_learned","TP") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(H)
- temp = "This spell teleports you to an area of your selection, and creates a cloud of smoke around you upon arrival. Very useful if you are in danger.."
- if("mutate")
- feedback_add_details("wizard_spell_learned","MU") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/genetic/mutate(H)
- temp = "This spell causes you to turn into a hulk, gaining super strength and the ability to punch down walls! You also gain the ability to fire lasers from your eyes!"
- if("etherealjaunt")
- feedback_add_details("wizard_spell_learned","EJ") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(H)
- temp = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls."
- if("knock")
- feedback_add_details("wizard_spell_learned","KN") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/aoe_turf/knock(H)
- temp = "This spell opens nearby doors and does not require wizard garb."
- if("horseman")
- feedback_add_details("wizard_spell_learned","HH") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- H.spell_list += new /obj/effect/proc_holder/spell/targeted/horsemask(H)
- temp = "This spell will curse a person to wear an unremovable horse mask (it has glue on the inside) and speak like a horse. It does not require a wizard garb. Do note the curse will disintegrate the target's current mask if they are wearing one."
- if("summonguns")
- feedback_add_details("wizard_spell_learned","SG") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- rightandwrong()
- max_uses--
- temp = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill eachother. Just be careful not to get hit in the crossfire!"
- if("staffchange")
- feedback_add_details("wizard_spell_learned","ST") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/weapon/gun/energy/staff(get_turf(H))
- temp = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself"
- max_uses--
- if("mentalfocus")
- feedback_add_details("wizard_spell_learned","MF") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/weapon/gun/energy/staff/focus(get_turf(H))
- temp = "An artefact that channels the will of the user into destructive bolts of force."
- max_uses--
- if("soulstone")
- feedback_add_details("wizard_spell_learned","SS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/weapon/storage/belt/soulstone/full(get_turf(H))
- H.spell_list += new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(H)
- temp = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot."
- max_uses--
- if("armor")
- feedback_add_details("wizard_spell_learned","HS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/clothing/shoes/sandal(get_turf(H)) //In case they've lost them.
- new /obj/item/clothing/gloves/purple(get_turf(H))//To complete the outfit
- new /obj/item/clothing/suit/space/void/wizard(get_turf(H))
- new /obj/item/clothing/head/helmet/space/void/wizard(get_turf(H))
- temp = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space."
- max_uses--
- if("staffanimation")
- feedback_add_details("wizard_spell_learned","SA") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/weapon/gun/energy/staff/animate(get_turf(H))
- temp = "An artefact that spits bolts of life-force which causes objects which are hit by it to animate and come to life! This magic doesn't affect machines."
- max_uses--
- if("scrying")
- feedback_add_details("wizard_spell_learned","SO") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
- new /obj/item/weapon/scrying(get_turf(H))
- if (!(XRAY in H.mutations))
- H.mutations.Add(XRAY)
- H.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS)
- H.see_in_dark = 8
- H.see_invisible = SEE_INVISIBLE_LEVEL_TWO
- H << "The walls suddenly disappear."
- temp = "You have purchased a scrying orb, and gained x-ray vision."
- max_uses--
- else
- if(href_list["temp"])
- temp = null
- attack_self(H)
-
- return
+/obj/item/weapon/spellbook
+ name = "spell book"
+ desc = "The legendary book of spells of the wizard."
+ icon = 'icons/obj/library.dmi'
+ icon_state ="spellbook"
+ throw_speed = 1
+ throw_range = 5
+ w_class = 1.0
+ var/uses = 5
+ var/temp = null
+ var/max_uses = 5
+ var/op = 1
+
+/obj/item/weapon/spellbook/attack_self(mob/user = usr)
+ if(!user)
+ return
+ user.set_machine(src)
+ var/dat
+ if(temp)
+ dat = "[temp]
Clear"
+ else
+
+ // AUTOFIXED BY fix_string_idiocy.py
+ dat = {"The Book of Spells:
+ Spells left to memorize: [uses]
+
+ Memorize which spell:
+ The number after the spell name is the cooldown time.
+ Magic Missile (10)
+ This spell fires several, slow moving, magic projectiles at nearby targets. If they hit a target, it is paralyzed and takes minor damage.
+ Fireball (10)
+ This spell fires a fireball in the direction you're facing and does not require wizard garb. Be careful not to fire it at people that are standing next to you.
+ Disintegrate (60)
+ This spell instantly kills somebody adjacent to you with the vilest of magick. It has a long cooldown.
+ Disable Technology (60)
+ This spell disables all weapons, cameras and most other technology in range.
+ Smoke (10)
+ This spell spawns a cloud of choking smoke at your location and does not require wizard garb.
+ Blind (30)
+ This spell temporarly blinds a single person and does not require wizard garb.
+ Subjugation (30)
+ This spell temporarily subjugates a target's mind and does not require wizard garb.
+ Mind Transfer (60)
+ This spell allows the user to switch bodies with a target. Careful to not lose your memory in the process.
+ Forcewall (10)
+ This spell creates an unbreakable wall that lasts for 30 seconds and does not need wizard garb.
+ Blink (2)
+ This spell randomly teleports you a short distance. Useful for evasion or getting into areas if you have patience.
+ Teleport (60)
+ This spell teleports you to a type of area of your selection. Very useful if you are in danger, but has a decent cooldown, and is unpredictable.
+ Mutate (60)
+ This spell causes you to turn into a hulk and gain telekinesis for a short while.
+ Ethereal Jaunt (60)
+ This spell creates your ethereal form, temporarily making you invisible and able to pass through walls.
+ Knock (10)
+ This spell opens nearby doors and does not require wizard garb.
+ Curse of the Horseman (15)
+ This spell will curse a person to wear an unremovable horse mask (it has glue on the inside) and speak like a horse. It does not require wizard garb.
+ Flesh to Stone (60)
+ This spell will curse a person to immediately turn into an unmoving statue. The effect will eventually wear off if the statue is not destroyed.
+ Remove Clothes Requirement Warning: this takes away 2 spell choices.
+
+ Artefacts:
+ Powerful items imbued with eldritch magics. Summoning one will count towards your maximum number of spells.
+ It is recommended that only experienced wizards attempt to wield such artefacts.
+
+ Staff of Change
+ An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself.
+
+ Mental Focus
+ An artefact that channels the will of the user into destructive bolts of force.
+
+ Six Soul Stone Shards and the spell Artificer
+ Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot.
+
+ Mastercrafted Armor Set
+ An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space.
+
+ Staff of Animation
+ An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines.
+
+ Scrying Orb
+ An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision.
+
"}
+ // END AUTOFIX
+ if(op)
+ dat += "Re-memorize Spells
"
+ user << browse(dat, "window=radio")
+ onclose(user, "radio")
+ return
+
+/obj/item/weapon/spellbook/Topic(href, href_list)
+ ..()
+ var/mob/living/carbon/human/H = usr
+
+ if(H.stat || H.restrained())
+ return
+ if(!istype(H, /mob/living/carbon/human))
+ return 1
+
+ if(H.mind.special_role == "apprentice")
+ temp = "If you got caught sneaking a peak from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not."
+ return
+
+ if(loc == H || (in_range(src, H) && istype(loc, /turf)))
+ H.set_machine(src)
+ if(href_list["spell_choice"])
+ if(href_list["spell_choice"] == "rememorize")
+ var/area/wizard_station/A = locate()
+ if(usr in A.contents)
+ uses = max_uses
+ H.spellremove()
+ temp = "All spells have been removed. You may now memorize a new set of spells."
+ feedback_add_details("wizard_spell_learned","UM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ else
+ temp = "You may only re-memorize spells whilst located inside the wizard sanctuary."
+ else if(uses >= 1 && max_uses >=1)
+ if(href_list["spell_choice"] == "noclothes")
+ if(uses < 2)
+ return
+ uses--
+ /*
+ */
+ var/list/available_spells = list(magicmissile = "Magic Missile", fireball = "Fireball", disintegrate = "Disintegrate", disabletech = "Disable Tech", smoke = "Smoke", blind = "Blind", subjugation = "Subjugation", mindswap = "Mind Transfer", forcewall = "Forcewall", blink = "Blink", teleport = "Teleport", mutate = "Mutate", etherealjaunt = "Ethereal Jaunt", knock = "Knock", horseman = "Curse of the Horseman", staffchange = "Staff of Change", mentalfocus = "Mental Focus", soulstone = "Six Soul Stone Shards and the spell Artificer", armor = "Mastercrafted Armor Set", staffanimate = "Staff of Animation", noclothes = "No Clothes",fleshtostone = "Flesh to Stone")
+ var/already_knows = 0
+ for(var/spell/aspell in H.spell_list)
+ if(available_spells[href_list["spell_choice"]] == initial(aspell.name))
+ already_knows = 1
+ if(!aspell.can_improve())
+ temp = "This spell cannot be improved further."
+ uses++
+ break
+ else
+ if(aspell.can_improve("speed") && aspell.can_improve("power"))
+ switch(alert(src, "Do you want to upgrade this spell's speed or power?", "Select Upgrade", "Speed", "Power", "Cancel"))
+ if("Speed")
+ temp = aspell.quicken_spell()
+ if("Power")
+ temp = aspell.empower_spell()
+ else
+ uses++
+ break
+ else if (aspell.can_improve("speed"))
+ temp = aspell.quicken_spell()
+ else if (aspell.can_improve("power"))
+ temp = aspell.empower_spell()
+ /*
+ */
+ if(!already_knows)
+ switch(href_list["spell_choice"])
+ if("noclothes")
+ feedback_add_details("wizard_spell_learned","NC")
+ H.add_spell(new/spell/noclothes)
+ temp = "This teaches you how to use your spells without your magical garb, truely you are the wizardest."
+ uses--
+ if("magicmissile")
+ feedback_add_details("wizard_spell_learned","MM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/projectile/magic_missile)
+ temp = "You have learned magic missile."
+ if("fireball")
+ feedback_add_details("wizard_spell_learned","FB") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/projectile/dumbfire/fireball)
+ temp = "You have learned fireball."
+ if("disintegrate")
+ feedback_add_details("wizard_spell_learned","DG") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/disintegrate)
+ temp = "You have learned disintegrate."
+ if("disabletech")
+ feedback_add_details("wizard_spell_learned","DT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/aoe_turf/disable_tech)
+ temp = "You have learned disable technology."
+ if("smoke")
+ feedback_add_details("wizard_spell_learned","SM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/aoe_turf/smoke)
+ temp = "You have learned smoke."
+ if("blind")
+ feedback_add_details("wizard_spell_learned","BD") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/genetic/blind)
+ temp = "You have learned blind."
+ if("subjugation")
+ feedback_add_details("wizard_spell_learned","SJ") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/subjugation)
+ temp = "You have learned subjugate."
+ if("mindswap")
+ feedback_add_details("wizard_spell_learned","MT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/mind_transfer)
+ temp = "You have learned mindswap."
+ if("forcewall")
+ feedback_add_details("wizard_spell_learned","FW") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/aoe_turf/conjure/forcewall)
+ temp = "You have learned forcewall."
+ if("blink")
+ feedback_add_details("wizard_spell_learned","BL") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/aoe_turf/blink)
+ temp = "You have learned blink."
+ if("teleport")
+ feedback_add_details("wizard_spell_learned","TP") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/area_teleport)
+ temp = "You have learned teleport."
+ if("mutate")
+ feedback_add_details("wizard_spell_learned","MU") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/genetic/mutate)
+ temp = "You have learned mutate."
+ if("etherealjaunt")
+ feedback_add_details("wizard_spell_learned","EJ") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/ethereal_jaunt)
+ temp = "You have learned ethereal jaunt."
+ if("knock")
+ feedback_add_details("wizard_spell_learned","KN") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/aoe_turf/knock)
+ temp = "You have learned knock."
+ if("horseman")
+ feedback_add_details("wizard_spell_learned","HH") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/horsemask)
+ temp = "You have learned curse of the horseman."
+ if("fleshtostone")
+ feedback_add_details("wizard_spell_learned","FS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ H.add_spell(new/spell/targeted/flesh_to_stone)
+ temp = "You have learned flesh to stone."
+ if("staffchange")
+ feedback_add_details("wizard_spell_learned","ST") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/weapon/gun/energy/staff(get_turf(H))
+ temp = "You have purchased a staff of change."
+ max_uses--
+ if("mentalfocus")
+ feedback_add_details("wizard_spell_learned","MF") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/weapon/gun/energy/staff/focus(get_turf(H))
+ temp = "An artefact that channels the will of the user into destructive bolts of force."
+ max_uses--
+ if("soulstone")
+ feedback_add_details("wizard_spell_learned","SS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/weapon/storage/belt/soulstone/full(get_turf(H))
+ H.add_spell(new/spell/aoe_turf/conjure/construct)
+ temp = "You have purchased a belt full of soulstones and have learned the artificer spell."
+ max_uses--
+ if("armor")
+ feedback_add_details("wizard_spell_learned","HS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/clothing/shoes/sandal(get_turf(H)) //In case they've lost them.
+ new /obj/item/clothing/gloves/purple(get_turf(H))//To complete the outfit
+ new /obj/item/clothing/suit/space/void/wizard(get_turf(H))
+ new /obj/item/clothing/head/helmet/space/void/wizard(get_turf(H))
+ temp = "You have purchased a suit of wizard armor."
+ max_uses--
+ if("staffanimation")
+ feedback_add_details("wizard_spell_learned","SA") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/weapon/gun/energy/staff/animate(get_turf(H))
+ temp = "You have purchased a staff of animation."
+ max_uses--
+ if("scrying")
+ feedback_add_details("wizard_spell_learned","SO") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells
+ new /obj/item/weapon/scrying(get_turf(H))
+ if (!(XRAY in H.mutations))
+ H.mutations.Add(XRAY)
+ H.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS)
+ H.see_in_dark = 8
+ H.see_invisible = SEE_INVISIBLE_LEVEL_TWO
+ H << "\blue The walls suddenly disappear."
+ temp = "You have purchased a scrying orb, and gained x-ray vision."
+ max_uses--
+ else
+ if(href_list["temp"])
+ temp = null
+ attack_self()
+
+ return
+
+//Single Use Spellbooks//
+
+/obj/item/weapon/spellbook/oneuse
+ var/spell = /spell/targeted/projectile/magic_missile //just a placeholder to avoid runtimes if someone spawned the generic
+ var/spellname = "sandbox"
+ var/used = 0
+ name = "spellbook of "
+ uses = 1
+ max_uses = 1
+ desc = "This template spellbook was never meant for the eyes of man..."
+
+/obj/item/weapon/spellbook/oneuse/New()
+ ..()
+ name += spellname
+
+/obj/item/weapon/spellbook/oneuse/attack_self(mob/user as mob)
+ var/spell/S = new spell(user)
+ for(var/spell/knownspell in user.spell_list)
+ if(knownspell.type == S.type)
+ if(user.mind)
+ if(user.mind.special_role == "apprentice" || user.mind.special_role == "Wizard")
+ user <<"You're already far more versed in this spell than this flimsy how-to book can provide."
+ else
+ user <<"You've already read this one."
+ return
+ if(used)
+ recoil(user)
+ else
+ user.add_spell(S)
+ user <<"you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!"
+ user.attack_log += text("\[[time_stamp()]\] [user.real_name] ([user.ckey]) learned the spell [spellname] ([S]).")
+ onlearned(user)
+
+/obj/item/weapon/spellbook/oneuse/proc/recoil(mob/user as mob)
+ user.visible_message("[src] glows in a black light!")
+
+/obj/item/weapon/spellbook/oneuse/proc/onlearned(mob/user as mob)
+ used = 1
+ user.visible_message("[src] glows dark for a second!")
+
+/obj/item/weapon/spellbook/oneuse/attackby()
+ return
+
+/obj/item/weapon/spellbook/oneuse/fireball
+ spell = /spell/targeted/projectile/dumbfire/fireball
+ spellname = "fireball"
+ icon_state ="bookfireball"
+ desc = "This book feels warm to the touch."
+
+/obj/item/weapon/spellbook/oneuse/fireball/recoil(mob/user as mob)
+ ..()
+ explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2)
+ qdel(src)
+
+/obj/item/weapon/spellbook/oneuse/smoke
+ spell = /spell/aoe_turf/smoke
+ spellname = "smoke"
+ icon_state ="booksmoke"
+ desc = "This book is overflowing with the dank arts."
+
+/obj/item/weapon/spellbook/oneuse/smoke/recoil(mob/user as mob)
+ ..()
+ user <<"Your stomach rumbles..."
+ if(user.nutrition)
+ user.nutrition -= 200
+ if(user.nutrition <= 0)
+ user.nutrition = 0
+
+/obj/item/weapon/spellbook/oneuse/blind
+ spell = /spell/targeted/genetic/blind
+ spellname = "blind"
+ icon_state ="bookblind"
+ desc = "This book looks blurry, no matter how you look at it."
+
+/obj/item/weapon/spellbook/oneuse/blind/recoil(mob/user as mob)
+ ..()
+ user <<"You go blind!"
+ user.eye_blind = 10
+
+/obj/item/weapon/spellbook/oneuse/mindswap
+ spell = /spell/targeted/mind_transfer
+ spellname = "mindswap"
+ icon_state ="bookmindswap"
+ desc = "This book's cover is pristine, though its pages look ragged and torn."
+ var/mob/stored_swap = null //Used in used book recoils to store an identity for mindswaps
+
+/obj/item/weapon/spellbook/oneuse/mindswap/onlearned()
+ spellname = pick("fireball","smoke","blind","forcewall","knock","horses","charge")
+ icon_state = "book[spellname]"
+ name = "spellbook of [spellname]" //Note, desc doesn't change by design
+ ..()
+
+/obj/item/weapon/spellbook/oneuse/mindswap/recoil(mob/user as mob)
+ ..()
+ if(stored_swap in dead_mob_list)
+ stored_swap = null
+ if(!stored_swap)
+ stored_swap = user
+ user <<"For a moment you feel like you don't even know who you are anymore."
+ return
+ if(stored_swap == user)
+ user <<"You stare at the book some more, but there doesn't seem to be anything else to learn..."
+ return
+
+ if(user.mind.special_verbs.len)
+ for(var/V in user.mind.special_verbs)
+ user.verbs -= V
+
+ if(stored_swap.mind.special_verbs.len)
+ for(var/V in stored_swap.mind.special_verbs)
+ stored_swap.verbs -= V
+
+ var/mob/dead/observer/ghost = stored_swap.ghostize(0)
+ ghost.spell_list = stored_swap.spell_list
+
+ user.mind.transfer_to(stored_swap)
+ stored_swap.spell_list = user.spell_list
+
+ if(stored_swap.mind.special_verbs.len)
+ for(var/V in user.mind.special_verbs)
+ user.verbs += V
+
+ ghost.mind.transfer_to(user)
+ user.key = ghost.key
+ user.spell_list = ghost.spell_list
+
+ if(user.mind.special_verbs.len)
+ for(var/V in user.mind.special_verbs)
+ user.verbs += V
+
+ stored_swap <<"You're suddenly somewhere else... and someone else?!"
+ user <<"Suddenly you're staring at [src] again... where are you, who are you?!"
+ stored_swap = null
+
+/obj/item/weapon/spellbook/oneuse/forcewall
+ spell = /spell/aoe_turf/conjure/forcewall
+ spellname = "forcewall"
+ icon_state ="bookforcewall"
+ desc = "This book has a dedication to mimes everywhere inside the front cover."
+
+/obj/item/weapon/spellbook/oneuse/forcewall/recoil(mob/user as mob)
+ ..()
+ user <<"You suddenly feel very solid!"
+ var/obj/structure/closet/statue/S = new /obj/structure/closet/statue(user.loc, user)
+ S.timer = 30
+ user.drop_item()
+
+
+/obj/item/weapon/spellbook/oneuse/knock
+ spell = /spell/aoe_turf/knock
+ spellname = "knock"
+ icon_state ="bookknock"
+ desc = "This book is hard to hold closed properly."
+
+/obj/item/weapon/spellbook/oneuse/knock/recoil(mob/user as mob)
+ ..()
+ user <<"You're knocked down!"
+ user.Weaken(20)
+
+/obj/item/weapon/spellbook/oneuse/horsemask
+ spell = /spell/targeted/horsemask
+ spellname = "horses"
+ icon_state ="bookhorses"
+ desc = "This book is more horse than your mind has room for."
+
+/obj/item/weapon/spellbook/oneuse/horsemask/recoil(mob/living/carbon/user as mob)
+ if(istype(user, /mob/living/carbon/human))
+ user <<"HOR-SIE HAS RISEN"
+ var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
+ magichead.canremove = 0 //curses!
+ magichead.flags_inv = null //so you can still see their face
+ magichead.voicechange = 1 //NEEEEIIGHH
+ user.drop_from_inventory(user.wear_mask)
+ user.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1)
+ qdel(src)
+ else
+ user <<"I say thee neigh"
+
+/obj/item/weapon/spellbook/oneuse/charge
+ spell = /spell/aoe_turf/charge
+ spellname = "charging"
+ icon_state ="bookcharge"
+ desc = "This book is made of 100% post-consumer wizard."
+
+/obj/item/weapon/spellbook/oneuse/charge/recoil(mob/user as mob)
+ ..()
+ user <<"[src] suddenly feels very warm!"
+ empulse(src, 1, 1)
diff --git a/code/modules/spells/spells.dm b/code/modules/spells/spells.dm
new file mode 100644
index 0000000000..d20eea00dc
--- /dev/null
+++ b/code/modules/spells/spells.dm
@@ -0,0 +1,64 @@
+/mob/Life()
+ ..()
+ if(spell_masters && spell_masters.len)
+ for(var/obj/screen/movable/spell_master/spell_master in spell_masters)
+ spell_master.update_spells(0, src)
+
+/mob/Stat()
+ ..()
+ if(spell_list && spell_list.len && statpanel("Spells"))
+ for(var/spell/S in spell_list)
+ switch(S.charge_type)
+ if(Sp_RECHARGE)
+ statpanel("Spells","[S.charge_counter/10.0]/[S.charge_max/10]",S)
+ if(Sp_CHARGES)
+ statpanel("Spells","[S.charge_counter]/[S.charge_max]",S)
+ if(Sp_HOLDVAR)
+ statpanel("Spells","[S.holder_var_type] [S.holder_var_amount]",S)
+
+/mob/proc/add_spell(var/spell/spell_to_add, var/spell_base = "wiz_spell_ready", var/master_type = /obj/screen/movable/spell_master)
+ if(!spell_masters)
+ spell_masters = list()
+
+ if(spell_masters.len)
+ for(var/obj/screen/movable/spell_master/spell_master in spell_masters)
+ if(spell_master.type == master_type)
+ spell_list.Add(spell_to_add)
+ spell_master.add_spell(spell_to_add)
+ return 1
+
+ var/obj/screen/movable/spell_master/new_spell_master = new master_type //we're here because either we didn't find our type, or we have no spell masters to attach to
+ if(client)
+ src.client.screen += new_spell_master
+ new_spell_master.spell_holder = src
+ new_spell_master.add_spell(spell_to_add)
+ if(spell_base)
+ new_spell_master.icon_state = spell_base
+ spell_masters.Add(new_spell_master)
+ spell_list.Add(spell_to_add)
+ return 1
+
+/mob/proc/remove_spell(var/spell/spell_to_remove)
+ if(!spell_to_remove || !istype(spell_to_remove))
+ return
+
+ if(!(spell_to_remove in spell_list))
+ return
+
+ if(!spell_masters || !spell_masters.len)
+ return
+
+ spell_list.Remove(spell_to_remove)
+ for(var/obj/screen/movable/spell_master/spell_master in spell_masters)
+ spell_master.remove_spell(spell_to_remove)
+ return 1
+
+/mob/proc/silence_spells(var/amount = 0)
+ if(!(amount >= 0))
+ return
+
+ if(!spell_masters || !spell_masters.len)
+ return
+
+ for(var/obj/screen/movable/spell_master/spell_master in spell_masters)
+ spell_master.silence_spells(amount)
diff --git a/code/modules/spells/targeted/disintegrate.dm b/code/modules/spells/targeted/disintegrate.dm
new file mode 100644
index 0000000000..c3f0ee80e9
--- /dev/null
+++ b/code/modules/spells/targeted/disintegrate.dm
@@ -0,0 +1,27 @@
+/spell/targeted/disintegrate
+ name = "Disintegrate"
+ desc = "This spell instantly kills somebody adjacent to you with the vilest of magick."
+
+ school = "evocation"
+ charge_max = 600
+ spell_flags = NEEDSCLOTHES
+ invocation = "EI NATH"
+ invocation_type = SpI_SHOUT
+ range = 1
+ cooldown_min = 200 //100 deciseconds reduction per rank
+
+ sparks_spread = 1
+ sparks_amt = 4
+
+ hud_state = "wiz_disint"
+
+/spell/targeted/disintegrate/cast(var/list/targets)
+ ..()
+ for(var/mob/living/target in targets)
+ if(ishuman(target))
+ var/mob/living/carbon/C = target
+ if(!C.has_brain()) // Their brain is already taken out
+ var/obj/item/organ/brain/B = new(C.loc)
+ B.transfer_identity(C)
+ target.gib()
+ return
\ No newline at end of file
diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm
new file mode 100644
index 0000000000..2a0e52cc13
--- /dev/null
+++ b/code/modules/spells/targeted/ethereal_jaunt.dm
@@ -0,0 +1,98 @@
+/spell/targeted/ethereal_jaunt
+ name = "Ethereal Jaunt"
+ desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls."
+
+ school = "transmutation"
+ charge_max = 300
+ spell_flags = Z2NOCAST | NEEDSCLOTHES | INCLUDEUSER
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = -1
+ max_targets = 1
+ cooldown_min = 100 //50 deciseconds reduction per rank
+ duration = 50 //in deciseconds
+
+ hud_state = "wiz_jaunt"
+
+/spell/targeted/ethereal_jaunt/cast(list/targets) //magnets, so mostly hardcoded
+ for(var/mob/living/target in targets)
+ target.monkeyizing = 1 //protects the mob from being transformed (replaced) midjaunt and getting stuck in bluespace
+ if(target.buckled) target.buckled = null
+ spawn(0)
+ var/mobloc = get_turf(target.loc)
+ var/obj/effect/dummy/spell_jaunt/holder = new /obj/effect/dummy/spell_jaunt( mobloc )
+ var/atom/movable/overlay/animation = new /atom/movable/overlay( mobloc )
+ animation.name = "water"
+ animation.density = 0
+ animation.anchored = 1
+ animation.icon = 'icons/mob/mob.dmi'
+ animation.layer = 5
+ animation.master = holder
+ target.ExtinguishMob()
+ if(target.buckled)
+ target.buckled = null
+ jaunt_disappear(animation, target)
+ target.loc = holder
+ target.monkeyizing=0 //mob is safely inside holder now, no need for protection.
+ jaunt_steam(mobloc)
+ sleep(duration)
+ mobloc = get_turf(target.loc)
+ animation.loc = mobloc
+ jaunt_steam(mobloc)
+ target.canmove = 0
+ holder.reappearing = 1
+ sleep(20)
+ jaunt_reappear(animation, target)
+ sleep(5)
+ if(!target.forceMove(mobloc))
+ for(var/direction in list(1,2,4,8,5,6,9,10))
+ var/turf/T = get_step(mobloc, direction)
+ if(T)
+ if(target.forceMove(T))
+ break
+ target.canmove = 1
+ target.client.eye = target
+ del(animation)
+ del(holder)
+
+/spell/targeted/ethereal_jaunt/proc/jaunt_disappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ animation.icon_state = "liquify"
+ flick("liquify",animation)
+
+/spell/targeted/ethereal_jaunt/proc/jaunt_reappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ flick("reappear",animation)
+
+/spell/targeted/ethereal_jaunt/proc/jaunt_steam(var/mobloc)
+ var/datum/effect/effect/system/steam_spread/steam = new /datum/effect/effect/system/steam_spread()
+ steam.set_up(10, 0, mobloc)
+ steam.start()
+
+/obj/effect/dummy/spell_jaunt
+ name = "water"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "nothing"
+ var/canmove = 1
+ var/reappearing = 0
+ density = 0
+ anchored = 1
+
+/obj/effect/dummy/spell_jaunt/Destroy()
+ // Eject contents if deleted somehow
+ for(var/atom/movable/AM in src)
+ AM.loc = get_turf(src)
+ ..()
+
+/obj/effect/dummy/spell_jaunt/relaymove(var/mob/user, direction)
+ if (!src.canmove || reappearing) return
+ var/turf/newLoc = get_step(src,direction)
+ if(!(newLoc.flags & NOJAUNT))
+ loc = newLoc
+ else
+ user << "Some strange aura is blocking the way!"
+ src.canmove = 0
+ spawn(2) src.canmove = 1
+
+/obj/effect/dummy/spell_jaunt/ex_act(blah)
+ return
+/obj/effect/dummy/spell_jaunt/bullet_act(blah)
+ return
\ No newline at end of file
diff --git a/code/modules/spells/targeted/flesh_to_stone.dm b/code/modules/spells/targeted/flesh_to_stone.dm
new file mode 100644
index 0000000000..e640fba5c6
--- /dev/null
+++ b/code/modules/spells/targeted/flesh_to_stone.dm
@@ -0,0 +1,21 @@
+/spell/targeted/flesh_to_stone
+ name = "Flesh to Stone"
+ desc = "This spell turns a single person into an inert statue for a long period of time."
+
+ school = "transmutation"
+ charge_max = 600
+ spell_flags = NEEDSCLOTHES
+ range = 3
+ max_targets = 1
+ invocation = "STAUN EI"
+ invocation_type = SpI_SHOUT
+ amt_stunned = 5//just exists to make sure the statue "catches" them
+ cooldown_min = 200 //100 deciseconds reduction per rank
+
+ hud_state = "wiz_statue"
+
+/spell/targeted/flesh_to_stone/cast(var/list/targets, mob/user)
+ ..()
+ for(var/mob/living/target in targets)
+ new /obj/structure/closet/statue(target.loc, target) //makes the statue
+ return
\ No newline at end of file
diff --git a/code/modules/spells/targeted/genetic.dm b/code/modules/spells/targeted/genetic.dm
new file mode 100644
index 0000000000..028cdafba0
--- /dev/null
+++ b/code/modules/spells/targeted/genetic.dm
@@ -0,0 +1,68 @@
+/*
+Other mutation or disability spells can be found in
+code\game\dna\genes\vg_powers.dm //hulk is in this file
+code\game\dna\genes\goon_disabilities.dm
+code\game\dna\genes\goon_powers.dm
+*/
+/spell/targeted/genetic
+ name = "Genetic modifier"
+ desc = "This spell inflicts a set of mutations and disabilities upon the target."
+
+ var/disabilities = 0 //bits
+ var/list/mutations = list() //mutation strings
+ duration = 100 //deciseconds
+
+
+/spell/targeted/genetic/cast(list/targets)
+ ..()
+ for(var/mob/living/target in targets)
+ for(var/x in mutations)
+ target.mutations.Add(x)
+ target.disabilities |= disabilities
+ target.update_mutations() //update target's mutation overlays
+ spawn(duration)
+ for(var/x in mutations)
+ target.mutations.Remove(x)
+ target.disabilities &= ~disabilities
+ target.update_mutations()
+ return
+
+/spell/targeted/genetic/blind
+ name = "Blind"
+ disabilities = 1
+ duration = 300
+
+ charge_max = 300
+
+ spell_flags = 0
+ invocation = "STI KALY"
+ invocation_type = SpI_WHISPER
+ message = "Your eyes cry out in pain!"
+ cooldown_min = 50
+
+ range = 7
+ max_targets = 0
+
+ amt_eye_blind = 10
+ amt_eye_blurry = 20
+
+ hud_state = "wiz_blind"
+
+/spell/targeted/genetic/mutate
+ name = "Mutate"
+ desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
+
+ school = "transmutation"
+ charge_max = 400
+ spell_flags = Z2NOCAST | NEEDSCLOTHES | INCLUDEUSER
+ invocation = "BIRUZ BENNAR"
+ invocation_type = SpI_SHOUT
+ message = "\blue You feel strong! You feel a pressure building behind your eyes!"
+ range = 0
+ max_targets = 1
+
+ mutations = list(LASER, HULK)
+ duration = 300
+ cooldown_min = 300 //25 deciseconds reduction per rank
+
+ hud_state = "wiz_hulk"
diff --git a/code/modules/spells/targeted/harvest.dm b/code/modules/spells/targeted/harvest.dm
new file mode 100644
index 0000000000..844f223744
--- /dev/null
+++ b/code/modules/spells/targeted/harvest.dm
@@ -0,0 +1,36 @@
+/spell/targeted/harvest
+ name = "Harvest"
+ desc = "Back to where I come from, and you're coming with me."
+
+ school = "transmutation"
+ charge_max = 200
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK | INCLUDEUSER
+ invocation = ""
+ invocation_type = SpI_NONE
+ range = 0
+ max_targets = 0
+
+ overlay = 1
+ overlay_icon = 'icons/effects/effects.dmi'
+ overlay_icon_state = "rune_teleport"
+ overlay_lifespan = 0
+
+ hud_state = "const_harvest"
+
+/spell/targeted/harvest/cast(list/targets, mob/user)//because harvest is already a proc
+ ..()
+
+ var/destination = null
+ for(var/obj/machinery/singularity/narsie/large/N in narsie_list)
+ destination = N.loc
+ break
+ if(destination)
+ var/prey = 0
+ for(var/mob/living/M in targets)
+ if(!findNullRod(M))
+ M.forceMove(destination)
+ if(M != user)
+ prey = 1
+ user << "You warp back to Nar-Sie[prey ? " along with your prey":""]."
+ else
+ user << "...something's wrong!"//There shouldn't be an instance of Harvesters when Nar-Sie isn't in the world.
diff --git a/code/modules/spells/targeted/horsemask.dm b/code/modules/spells/targeted/horsemask.dm
new file mode 100644
index 0000000000..dad2032b72
--- /dev/null
+++ b/code/modules/spells/targeted/horsemask.dm
@@ -0,0 +1,35 @@
+/spell/targeted/horsemask
+ name = "Curse of the Horseman"
+ desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
+ school = "transmutation"
+ charge_type = Sp_RECHARGE
+ charge_max = 150
+ charge_counter = 0
+ spell_flags = 0
+ invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
+ invocation_type = SpI_SHOUT
+ range = 7
+ max_targets = 1
+ cooldown_min = 30 //30 deciseconds reduction per rank
+ selection_type = "range"
+
+ compatible_mobs = list(/mob/living/carbon/human)
+
+ hud_state = "wiz_horse"
+
+/spell/targeted/horsemask/cast(list/targets, mob/user = usr)
+ ..()
+ for(var/mob/living/target in targets)
+ var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
+ magichead.canremove = 0 //curses!
+ magichead.flags_inv = null //so you can still see their face
+ magichead.voicechange = 1 //NEEEEIIGHH
+ target.visible_message( "[target]'s face lights up in fire, and after the event a horse's head takes its place!", \
+ "Your face burns up, and shortly after the fire you realise you have the face of a horse!")
+ var/obj/old_mask = target.wear_mask
+ if(old_mask)
+ target.drop_from_inventory(old_mask)
+ qdel(old_mask) //get rid of this shit
+ target.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1)
+
+ flick("e_flash", target.flash)
\ No newline at end of file
diff --git a/code/modules/spells/targeted/mind_transfer.dm b/code/modules/spells/targeted/mind_transfer.dm
new file mode 100644
index 0000000000..6f4525b62c
--- /dev/null
+++ b/code/modules/spells/targeted/mind_transfer.dm
@@ -0,0 +1,73 @@
+/spell/targeted/mind_transfer
+ name = "Mind Transfer"
+ desc = "This spell allows the user to switch bodies with a target."
+
+ school = "transmutation"
+ charge_max = 600
+ spell_flags = 0
+ invocation = "GIN'YU CAPAN"
+ invocation_type = SpI_WHISPER
+ max_targets = 1
+ range = 1
+ cooldown_min = 200 //100 deciseconds reduction per rank
+ compatible_mobs = list(/mob/living/carbon/human) //which types of mobs are affected by the spell. NOTE: change at your own risk
+
+ var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell
+ var/msg_wait = 500 //how long in deciseconds it waits before telling that body doesn't feel right or mind swap robbed of a spell
+ amt_paralysis = 20 //how much the victim is paralysed for after the spell
+
+ hud_state = "wiz_mindswap"
+
+/spell/targeted/mind_transfer/cast(list/targets, mob/user)
+ ..()
+
+ for(var/mob/living/target in targets)
+ if(target.stat == DEAD)
+ user << "You didn't study necromancy back at the Space Wizard Federation academy."
+ continue
+
+ if(!target.key || !target.mind)
+ user << "They appear to be catatonic. Not even magic can affect their vacant mind."
+ continue
+
+ if(target.mind.special_role in protected_roles)
+ user << "Their mind is resisting your spell."
+ continue
+
+ var/mob/living/victim = target//The target of the spell whos body will be transferred to.
+ var/mob/caster = user//The wizard/whomever doing the body transferring.
+
+ //MIND TRANSFER BEGIN
+ if(caster.mind.special_verbs.len)//If the caster had any special verbs, remove them from the mob verb list.
+ for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot.
+ caster.verbs -= V//But a safety nontheless.
+
+ if(victim.mind.special_verbs.len)//Now remove all of the victim's verbs.
+ for(var/V in victim.mind.special_verbs)
+ victim.verbs -= V
+
+ var/mob/dead/observer/ghost = victim.ghostize(0)
+ ghost.spell_list = victim.spell_list//If they have spells, transfer them. Now we basically have a backup mob.
+
+ caster.mind.transfer_to(victim)
+ victim.spell_list = caster.spell_list//Now they are inside the victim's body.
+
+ if(victim.mind.special_verbs.len)//To add all the special verbs for the original caster.
+ for(var/V in caster.mind.special_verbs)//Not too important but could come into play.
+ caster.verbs += V
+
+ ghost.mind.transfer_to(caster)
+ caster.key = ghost.key //have to transfer the key since the mind was not active
+ caster.spell_list = ghost.spell_list
+
+ if(caster.mind.special_verbs.len)//If they had any special verbs, we add them here.
+ for(var/V in caster.mind.special_verbs)
+ caster.verbs += V
+ //MIND TRANSFER END
+
+ //Target is handled in ..(), so we handle the caster here
+ caster.Paralyse(amt_paralysis)
+
+ //After a certain amount of time the victim gets a message about being in a different body.
+ spawn(msg_wait)
+ caster << "\red You feel woozy and lightheaded. Your body doesn't seem like your own."
diff --git a/code/modules/spells/targeted/projectile/dumbfire.dm b/code/modules/spells/targeted/projectile/dumbfire.dm
new file mode 100644
index 0000000000..cd13f7169c
--- /dev/null
+++ b/code/modules/spells/targeted/projectile/dumbfire.dm
@@ -0,0 +1,13 @@
+/spell/targeted/projectile/dumbfire
+ name = "dumbfire spell"
+
+/spell/targeted/projectile/dumbfire/choose_targets(mob/user = usr)
+ var/list/targets = list()
+
+ var/starting_dir = user.dir //where are we facing at the time of casting?
+ var/turf/starting_turf = get_turf(user)
+ var/current_turf = starting_turf
+ for(var/i = 1; i <= src.range; i++)
+ current_turf = get_step(current_turf, starting_dir)
+ targets += current_turf
+ return targets
\ No newline at end of file
diff --git a/code/modules/spells/targeted/projectile/fireball.dm b/code/modules/spells/targeted/projectile/fireball.dm
new file mode 100644
index 0000000000..ec48c4ab49
--- /dev/null
+++ b/code/modules/spells/targeted/projectile/fireball.dm
@@ -0,0 +1,39 @@
+/spell/targeted/projectile/dumbfire/fireball
+ name = "Fireball"
+ desc = "This spell fires a fireball at a target and does not require wizard garb."
+
+ proj_type = /obj/item/projectile/spell_projectile/fireball
+
+ school = "evocation"
+ charge_max = 100
+ spell_flags = 0
+ invocation = "ONI SOMA"
+ invocation_type = SpI_SHOUT
+ range = 20
+ cooldown_min = 20 //10 deciseconds reduction per rank
+
+ spell_flags = 0
+
+ duration = 20
+ proj_step_delay = 1
+
+ amt_dam_brute = 20
+ amt_dam_fire = 25
+
+ var/ex_severe = -1
+ var/ex_heavy = 1
+ var/ex_light = 2
+ var/ex_flash = 5
+
+ hud_state = "wiz_fireball"
+
+/spell/targeted/projectile/dumbfire/fireball/prox_cast(var/list/targets, spell_holder)
+ for(var/mob/living/M in targets)
+ apply_spell_damage(M)
+ explosion(get_turf(spell_holder), ex_severe, ex_heavy, ex_light, ex_flash)
+
+//PROJECTILE
+
+/obj/item/projectile/spell_projectile/fireball
+ name = "fireball"
+ icon_state = "fireball"
\ No newline at end of file
diff --git a/code/modules/spells/targeted/projectile/magic_missile.dm b/code/modules/spells/targeted/projectile/magic_missile.dm
new file mode 100644
index 0000000000..c3589ce94b
--- /dev/null
+++ b/code/modules/spells/targeted/projectile/magic_missile.dm
@@ -0,0 +1,40 @@
+/spell/targeted/projectile/magic_missile
+ name = "Magic Missile"
+ desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
+
+ school = "evocation"
+ charge_max = 150
+ spell_flags = NEEDSCLOTHES
+ invocation = "FORTI GY AMA"
+ invocation_type = SpI_SHOUT
+ range = 7
+ cooldown_min = 90 //15 deciseconds reduction per rank
+
+ max_targets = 0
+
+ proj_type = /obj/item/projectile/spell_projectile/seeking/magic_missile
+ duration = 10
+ proj_step_delay = 5
+
+ hud_state = "wiz_mm"
+
+ amt_paralysis = 3
+ amt_stunned = 3
+
+ amt_dam_fire = 10
+
+/spell/targeted/projectile/magic_missile/prox_cast(var/list/targets, atom/spell_holder)
+ spell_holder.visible_message("\The [spell_holder] pops with a flash!")
+ for(var/mob/living/M in targets)
+ apply_spell_damage(M)
+ return
+
+//PROJECTILE
+
+/obj/item/projectile/spell_projectile/seeking/magic_missile
+ name = "magic missile"
+ icon_state = "magicm"
+
+ proj_trail = 1
+ proj_trail_lifespan = 5
+ proj_trail_icon_state = "magicmd"
diff --git a/code/modules/spells/targeted/projectile/projectile.dm b/code/modules/spells/targeted/projectile/projectile.dm
new file mode 100644
index 0000000000..a0c403b51a
--- /dev/null
+++ b/code/modules/spells/targeted/projectile/projectile.dm
@@ -0,0 +1,53 @@
+/*
+Projectile spells make special projectiles (obj/item/spell_projectile) and fire them at targets
+Dumbfire projectile spells fire directly ahead of the user
+spell_projectiles call their spell's (carried) prox_cast when they get in range of a target
+If the spell_projectile is seeking, it will update its target every process and follow them
+*/
+
+/spell/targeted/projectile
+ name = "projectile spell"
+
+ range = 7
+
+ var/proj_type = /obj/item/projectile/spell_projectile //use these. They are very nice
+
+ var/proj_step_delay = 1 //lower = faster
+ var/cast_prox_range = 1
+
+/spell/targeted/projectile/cast(list/targets, mob/user = usr)
+
+ if(istext(proj_type))
+ proj_type = text2path(proj_type) // sanity filters
+
+ for(var/atom/target in targets)
+ var/obj/item/projectile/projectile = new proj_type(user.loc, user.dir)
+
+ if(!projectile)
+ return
+
+ projectile.original = target
+ projectile.starting = get_turf(user)
+ projectile.shot_from = user //fired from the user
+ projectile.current = projectile.original
+ projectile.yo = target.y - user.y
+ projectile.xo = target.x - user.x
+ projectile.kill_count = src.duration
+ projectile.hitscan = !proj_step_delay
+ projectile.step_delay = proj_step_delay
+ if(istype(projectile, /obj/item/projectile/spell_projectile))
+ var/obj/item/projectile/spell_projectile/SP = projectile
+ SP.carried = src //casting is magical
+ spawn projectile.process()
+ return
+
+/spell/targeted/projectile/proc/choose_prox_targets(mob/user = usr, var/atom/movable/spell_holder)
+ var/list/targets = list()
+ for(var/mob/living/M in range(spell_holder, cast_prox_range))
+ if(M == user && !(spell_flags & INCLUDEUSER))
+ continue
+ targets += M
+ return targets
+
+/spell/targeted/projectile/proc/prox_cast(var/list/targets, var/atom/movable/spell_holder)
+ return targets
\ No newline at end of file
diff --git a/code/modules/spells/targeted/shift.dm b/code/modules/spells/targeted/shift.dm
new file mode 100644
index 0000000000..8f8ef57e51
--- /dev/null
+++ b/code/modules/spells/targeted/shift.dm
@@ -0,0 +1,24 @@
+/spell/targeted/ethereal_jaunt/shift
+ name = "Phase Shift"
+ desc = "This spell allows you to pass through walls"
+
+ charge_max = 200
+ spell_flags = Z2NOCAST | INCLUDEUSER | CONSTRUCT_CHECK
+ invocation_type = SpI_NONE
+ range = -1
+ duration = 50 //in deciseconds
+
+ hud_state = "const_shift"
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_disappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ animation.icon_state = "phase_shift"
+ animation.dir = target.dir
+ flick("phase_shift",animation)
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_reappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ animation.icon_state = "phase_shift2"
+ animation.dir = target.dir
+ flick("phase_shift2",animation)
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_steam(var/mobloc)
+ return
\ No newline at end of file
diff --git a/code/modules/spells/targeted/subjugate.dm b/code/modules/spells/targeted/subjugate.dm
new file mode 100644
index 0000000000..76cbcea777
--- /dev/null
+++ b/code/modules/spells/targeted/subjugate.dm
@@ -0,0 +1,20 @@
+/spell/targeted/subjugation
+ name = "Subjugation"
+ desc = "This spell temporarily subjugates a target's mind and does not require wizard garb."
+
+ school = "transmutation"
+ charge_max = 300
+ spell_flags = 0
+ invocation = "DII ODA BAJI"
+ invocation_type = SpI_WHISPER
+ message = "You suddenly feel completely overwhelmed!"
+
+ max_targets = 1
+
+ amt_dizziness = 300
+ amt_confused = 300
+ amt_stuttering = 300
+
+ compatible_mobs = list(/mob/living/carbon/human)
+
+ hud_state = "wiz_subj"
diff --git a/code/modules/spells/targeted/targeted.dm b/code/modules/spells/targeted/targeted.dm
new file mode 100644
index 0000000000..96bbab9905
--- /dev/null
+++ b/code/modules/spells/targeted/targeted.dm
@@ -0,0 +1,145 @@
+/*
+Targeted spells (with the exception of dumbfire) select from all the mobs in the defined range
+Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are explained in setup.dm
+*/
+
+
+/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
+ var/max_targets = 1 //leave 0 for unlimited targets in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
+ var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
+
+
+ var/amt_weakened = 0
+ var/amt_paralysis = 0
+ var/amt_stunned = 0
+
+ var/amt_dizziness = 0
+ var/amt_confused = 0
+ var/amt_stuttering = 0
+
+ //set to negatives for healing
+ var/amt_dam_fire = 0
+ var/amt_dam_brute = 0
+ var/amt_dam_oxy = 0
+ var/amt_dam_tox = 0
+
+ var/amt_eye_blind = 0
+ var/amt_eye_blurry = 0
+
+ var/list/compatible_mobs = list()
+
+
+/spell/targeted/choose_targets(mob/user = usr)
+ var/list/targets = list()
+
+ if(max_targets == 0) //unlimited
+ if(range == -2)
+ targets = living_mob_list
+ else
+ for(var/mob/living/target in view_or_range(range, user, selection_type))
+ targets += target
+
+ else if(max_targets == 1) //single target can be picked
+ if((range == 0 || range == -1) && spell_flags & INCLUDEUSER)
+ targets += user
+ else
+ var/list/possible_targets = list()
+ var/list/starting_targets
+ if(range == -2)
+ starting_targets = living_mob_list
+ else
+ starting_targets = view_or_range(range, user, selection_type)
+
+ for(var/mob/living/M in starting_targets)
+ if(!(spell_flags & INCLUDEUSER) && M == user)
+ continue
+ if(compatible_mobs && compatible_mobs.len)
+ if(!is_type_in_list(M, compatible_mobs)) continue
+ if(compatible_mobs && compatible_mobs.len && !is_type_in_list(M, compatible_mobs))
+ continue
+ possible_targets += M
+
+ if(possible_targets.len)
+ if(spell_flags & SELECTABLE) //if we are allowed to choose. see setup.dm for details
+ var/mob/temp_target = input(user, "Choose the target for the spell.", "Targeting") as null|mob in possible_targets
+ if(temp_target)
+ targets += temp_target
+ else
+ targets += pick(possible_targets)
+ //Adds a safety check post-input to make sure those targets are actually in range.
+
+
+ else
+ var/list/possible_targets = list()
+ var/list/starting_targets
+
+ if(range == -2)
+ starting_targets = living_mob_list
+ else
+ starting_targets = view_or_range(range, user, selection_type)
+
+ for(var/mob/living/target in starting_targets)
+ if(!(spell_flags & INCLUDEUSER) && target == user)
+ continue
+ if(compatible_mobs && !is_type_in_list(target, compatible_mobs))
+ continue
+ possible_targets += target
+
+ if(spell_flags & SELECTABLE)
+ for(var/i = 1; i<=max_targets, i++)
+ if(!possible_targets.len)
+ break
+ var/mob/M = input(user, "Choose the target for the spell.", "Targeting") as null|mob in possible_targets
+ if(!M)
+ break
+ if(range != -2)
+ if(!(M in view_or_range(range, user, selection_type)))
+ continue
+ targets += M
+ possible_targets -= M
+ else
+ for(var/i=1,i<=max_targets,i++)
+ if(!possible_targets.len)
+ break
+ if(target_ignore_prev)
+ var/target = pick(possible_targets)
+ possible_targets -= target
+ targets += target
+ else
+ targets += pick(possible_targets)
+
+ if(!(spell_flags & INCLUDEUSER) && (user in targets))
+ targets -= user
+
+ if(compatible_mobs && compatible_mobs.len)
+ for(var/mob/living/target in targets) //filters out all the non-compatible mobs
+ if(!is_type_in_list(target, compatible_mobs))
+ targets -= target
+
+ return targets
+
+/spell/targeted/cast(var/list/targets, mob/user)
+ for(var/mob/living/target in targets)
+ if(range >= 0)
+ if(!(target in view_or_range(range, user, selection_type))) //filter at time of casting
+ targets -= target
+ continue
+ apply_spell_damage(target)
+
+/spell/targeted/proc/apply_spell_damage(mob/living/target)
+ target.adjustBruteLoss(amt_dam_brute)
+ target.adjustFireLoss(amt_dam_fire)
+ target.adjustToxLoss(amt_dam_tox)
+ target.adjustOxyLoss(amt_dam_oxy)
+ //disabling
+ target.Weaken(amt_weakened)
+ target.Paralyse(amt_paralysis)
+ target.Stun(amt_stunned)
+ if(amt_weakened || amt_paralysis || amt_stunned)
+ if(target.buckled)
+ target.buckled = null
+ target.eye_blind += amt_eye_blind
+ target.eye_blurry += amt_eye_blurry
+ target.dizziness += amt_dizziness
+ target.confused += amt_confused
+ target.stuttering += amt_stuttering
\ No newline at end of file
diff --git a/code/modules/spells/trigger.dm b/code/modules/spells/trigger.dm
deleted file mode 100644
index 5e2ed8475f..0000000000
--- a/code/modules/spells/trigger.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/trigger
- name = "Trigger"
- desc = "This spell triggers another spell or a few."
-
- var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly
- var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks
-
-/obj/effect/proc_holder/spell/targeted/trigger/New()
- ..()
-
- for(var/spell in starting_spells)
- var/spell_to_add = text2path(spell)
- new spell_to_add(src) //should result in adding to contents, needs testing
-
-/obj/effect/proc_holder/spell/targeted/trigger/Del()
- for(var/spell in contents)
- del(spell)
-
- ..()
-
-/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets)
- for(var/mob/living/target in targets)
- for(var/obj/effect/proc_holder/spell/spell in contents)
- spell.perform(list(target),0)
- for(var/obj/effect/proc_holder/spell/spell in linked_spells)
- spell.perform(list(target),0)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/turf_teleport.dm b/code/modules/spells/turf_teleport.dm
deleted file mode 100644
index d59bff313a..0000000000
--- a/code/modules/spells/turf_teleport.dm
+++ /dev/null
@@ -1,34 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/turf_teleport
- name = "Turf Teleport"
- desc = "This spell teleports the target to the turf in range."
-
- var/inner_tele_radius = 1
- var/outer_tele_radius = 2
-
- var/include_space = 0 //whether it includes space tiles in possible teleport locations
- var/include_dense = 0 //whether it includes dense tiles in possible teleport locations
-
-/obj/effect/proc_holder/spell/targeted/turf_teleport/cast(list/targets)
- for(var/mob/living/target in targets)
- var/list/turfs = new/list()
- for(var/turf/T in range(target,outer_tele_radius))
- if(T in range(target,inner_tele_radius)) continue
- if(istype(T,/turf/space) && !include_space) continue
- if(T.density && !include_dense) continue
- if(T.x>world.maxx-outer_tele_radius || T.xworld.maxy-outer_tele_radius || T.y[src] hums with power as [usr] deals a blow to reality itself!")
- else
- user << "\red The unearthly energies that powered the blade are now dormant"
-
-// Scrying orb //
-
-/obj/item/weapon/scrying
- name = "scrying orb"
- desc = "An incandescent orb of otherworldly energy, staring into it gives you vision beyond mortal means."
- icon = 'icons/obj/projectiles.dmi'
- icon_state = "bluespace"
- throw_speed = 3
- throw_range = 7
- throwforce = 10
- damtype = BURN
- force = 10
- hitsound = 'sound/items/welder2.ogg'
-
-/obj/item/weapon/scrying/attack_self(mob/user as mob)
- user << "You can see... everything!"
- visible_message("[usr] stares into [src], their eyes glazing over.")
- announce_ghost_joinleave(user.ghostize(1), 1, "You feel that they used a powerful artifact to [pick("invade","disturb","disrupt","infest","taint","spoil","blight")] this place with their presence.")
- return
\ No newline at end of file
diff --git a/code/modules/spells/wizard_spells.dm b/code/modules/spells/wizard_spells.dm
deleted file mode 100644
index f5d7fe9a8e..0000000000
--- a/code/modules/spells/wizard_spells.dm
+++ /dev/null
@@ -1,327 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/projectile/magic_missile
- name = "Magic Missile"
- desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
-
- school = "evocation"
- charge_max = 150
- clothes_req = 1
- invocation = "FORTI GY AMA"
- invocation_type = "shout"
- range = 7
-
- max_targets = 0
-
- proj_icon_state = "magicm"
- proj_name = "a magic missile"
- proj_lingering = 1
- proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile"
-
- proj_lifespan = 20
- proj_step_delay = 5
-
- proj_trail = 1
- proj_trail_lifespan = 5
- proj_trail_icon_state = "magicmd"
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile
- amt_weakened = 5
- amt_dam_fire = 10
-
-/obj/effect/proc_holder/spell/targeted/genetic/mutate
- name = "Mutate"
- desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
-
- school = "transmutation"
- charge_max = 400
- clothes_req = 1
- invocation = "BIRUZ BENNAR"
- invocation_type = "shout"
- message = "\blue You feel strong! You feel a pressure building behind your eyes!"
- range = -1
- include_user = 1
-
- mutations = list(LASER, HULK)
- duration = 300
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/disintegrate
- name = "Disintegrate"
- desc = "This spell instantly kills somebody adjacent to you with the vilest of magick."
-
- school = "evocation"
- charge_max = 600
- clothes_req = 1
- invocation = "EI NATH"
- invocation_type = "shout"
- range = 1
-
- destroys = "gib_brain"
-
- sparks_spread = 1
- sparks_amt = 4
-
-/obj/effect/proc_holder/spell/targeted/smoke
- name = "Smoke"
- desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb."
-
- school = "conjuration"
- charge_max = 120
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = -1
- include_user = 1
-
- smoke_spread = 2
- smoke_amt = 10
-
-/obj/effect/proc_holder/spell/targeted/emplosion/disable_tech
- name = "Disable Tech"
- desc = "This spell disables all weapons, cameras and most other technology in range."
- charge_max = 400
- clothes_req = 1
- invocation = "NEC CANTIO"
- invocation_type = "shout"
- range = -1
- include_user = 1
-
- emp_heavy = 6
- emp_light = 10
-
-/obj/effect/proc_holder/spell/targeted/turf_teleport/blink
- name = "Blink"
- desc = "This spell randomly teleports you a short distance."
-
- school = "abjuration"
- charge_max = 20
- clothes_req = 1
- invocation = "none"
- invocation_type = "none"
- range = -1
- include_user = 1
-
- smoke_spread = 1
- smoke_amt = 10
-
- inner_tele_radius = 0
- outer_tele_radius = 6
-
- centcomm_cancast = 0 //prevent people from getting to centcomm
-
-/obj/effect/proc_holder/spell/targeted/area_teleport/teleport
- name = "Teleport"
- desc = "This spell teleports you to a type of area of your selection."
-
- school = "abjuration"
- charge_max = 600
- clothes_req = 1
- invocation = "SCYAR NILA"
- invocation_type = "shout"
- range = -1
- include_user = 1
-
- smoke_spread = 1
- smoke_amt = 5
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/forcewall
- name = "Forcewall"
- desc = "This spell creates an unbreakable wall that lasts for 30 seconds and does not need wizard garb."
-
- school = "transmutation"
- charge_max = 100
- clothes_req = 0
- invocation = "TARCOL MINTI ZHERI"
- invocation_type = "whisper"
- range = 0
-
- summon_type = list("/obj/effect/forcefield")
- summon_lifespan = 300
-
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/carp
- name = "Summon Carp"
- desc = "This spell conjures a simple carp."
-
- school = "conjuration"
- charge_max = 1200
- clothes_req = 1
- invocation = "NOUK FHUNMM SACP RISSKA"
- invocation_type = "shout"
- range = 1
-
- summon_type = list(/mob/living/simple_animal/hostile/carp)
-
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/construct
- name = "Artificer"
- desc = "This spell conjures a construct which may be controlled by Shades"
-
- school = "conjuration"
- charge_max = 600
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
-
- summon_type = list(/obj/structure/constructshell)
-
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/creature
- name = "Summon Creature Swarm"
- desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth"
-
- school = "conjuration"
- charge_max = 1200
- clothes_req = 0
- invocation = "IA IA"
- invocation_type = "shout"
- summon_amt = 10
- range = 3
-
- summon_type = list(/mob/living/simple_animal/hostile/creature)
-
-/obj/effect/proc_holder/spell/targeted/trigger/blind
- name = "Blind"
- desc = "This spell temporarily blinds a single person and does not require wizard garb."
-
- school = "transmutation"
- charge_max = 300
- clothes_req = 0
- invocation = "STI KALY"
- invocation_type = "whisper"
- message = "\blue Your eyes cry out in pain!"
-
- starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind")
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
- amt_eye_blind = 10
- amt_eye_blurry = 20
-
-/obj/effect/proc_holder/spell/targeted/genetic/blind
- disabilities = 1
- duration = 300
-
-/obj/effect/proc_holder/spell/dumbfire/fireball
- name = "Fireball"
- desc = "This spell fires a fireball at a target and does not require wizard garb."
-
- school = "evocation"
- charge_max = 100
- clothes_req = 0
- invocation = "ONI SOMA"
- invocation_type = "shout"
- range = 20
-
- proj_icon_state = "fireball"
- proj_name = "a fireball"
- proj_type = "/obj/effect/proc_holder/spell/turf/fireball"
-
- proj_lifespan = 200
- proj_step_delay = 1
-
-/obj/effect/proc_holder/spell/turf/fireball/cast(var/turf/T)
- explosion(T, -1, 1, 2, 3)
-
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/fireball
- amt_dam_brute = 20
- amt_dam_fire = 25
-
-/obj/effect/proc_holder/spell/targeted/explosion/fireball
- ex_severe = -1
- ex_heavy = -1
- ex_light = 2
- ex_flash = 5
-
-
-
-
-
-
-//////////////////////////////Construct Spells/////////////////////////
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser
- charge_max = 1800
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/floor
- name = "Floor Construction"
- desc = "This spell constructs a cult floor"
-
- school = "conjuration"
- charge_max = 20
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
- summon_type = list(/turf/simulated/floor/engine/cult)
- centcomm_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/wall
- name = "Leser Construction"
- desc = "This spell constructs a cult wall"
-
- school = "conjuration"
- charge_max = 100
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
- summon_type = list(/turf/simulated/wall/cult)
- centcomm_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/wall/reinforced
- name = "Greater Construction"
- desc = "This spell constructs a reinforced metal wall"
-
- school = "conjuration"
- charge_max = 300
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
- centcomm_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles
- delay = 50
-
- summon_type = list(/turf/simulated/wall/r_wall)
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone
- name = "Summon Soulstone"
- desc = "This spell reaches into Nar-Sie's realm, summoning one of the legendary fragments across time and space"
-
- school = "conjuration"
- charge_max = 3000
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
-
- summon_type = list(/obj/item/device/soulstone)
-
-
-/obj/effect/proc_holder/spell/aoe_turf/conjure/lesserforcewall
- name = "Shield"
- desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire"
-
- school = "transmutation"
- charge_max = 300
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = 0
- summon_type = list(/obj/effect/forcefield)
- summon_lifespan = 50
-
-
-/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift
- name = "Phase Shift"
- desc = "This spell allows you to pass through walls"
-
- school = "transmutation"
- charge_max = 200
- clothes_req = 0
- invocation = "none"
- invocation_type = "none"
- range = -1
- include_user = 1
- phaseshift = 1
- jaunt_duration = 50 //in deciseconds
- centcomm_cancast = 0 //Stop people from getting to centcomm
\ No newline at end of file
diff --git a/code/setup.dm b/code/setup.dm
index 127c33f89f..5fa1165798 100644
--- a/code/setup.dm
+++ b/code/setup.dm
@@ -871,3 +871,43 @@ var/list/be_special_flags = list(
#define OBFUSCATION_LAYER 14 //Where images covering the view for eyes are put
#define SCREEN_LAYER 17 //Mob HUD/effects layer
+
+/////////////////
+////WIZARD //////
+/////////////////
+
+/* WIZARD SPELL FLAGS */
+#define GHOSTCAST 1 //can a ghost cast it?
+#define NEEDSCLOTHES 2 //does it need the wizard garb to cast? Nonwizard spells should not have this
+#define NEEDSHUMAN 4 //does it require the caster to be human?
+#define Z2NOCAST 8 //if this is added, the spell can't be cast at centcomm
+#define STATALLOWED 16 //if set, the user doesn't have to be conscious to cast. Required for ghost spells
+#define IGNOREPREV 32 //if set, each new target does not overlap with the previous one
+//The following flags only affect different types of spell, and therefore overlap
+//Targeted spells
+#define INCLUDEUSER 64 //does the spell include the caster in its target selection?
+#define SELECTABLE 128 //can you select each target for the spell?
+//AOE spells
+#define IGNOREDENSE 64 //are dense turfs ignored in selection?
+#define IGNORESPACE 128 //are space turfs ignored in selection?
+//End split flags
+#define CONSTRUCT_CHECK 256 //used by construct spells - checks for nullrods
+#define NO_BUTTON 512 //spell won't show up in the HUD with this
+
+//invocation
+#define SpI_SHOUT "shout"
+#define SpI_WHISPER "whisper"
+#define SpI_EMOTE "emote"
+#define SpI_NONE "none"
+
+//upgrading
+#define Sp_SPEED "speed"
+#define Sp_POWER "power"
+#define Sp_TOTAL "total"
+
+//casting costs
+#define Sp_RECHARGE "recharge"
+#define Sp_CHARGES "charges"
+#define Sp_HOLDVAR "holdervar"
+
+///////WIZ END/////////
diff --git a/icons/mob/screen_spells.dmi b/icons/mob/screen_spells.dmi
new file mode 100644
index 0000000000..db693428d0
Binary files /dev/null and b/icons/mob/screen_spells.dmi differ
diff --git a/icons/obj/statue.dmi b/icons/obj/statue.dmi
new file mode 100644
index 0000000000..a577e8da3e
Binary files /dev/null and b/icons/obj/statue.dmi differ
diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi
index 1d6bad4c50..b7d095f87a 100644
Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ
diff --git a/sound/effects/teleport.ogg b/sound/effects/teleport.ogg
new file mode 100644
index 0000000000..650867782b
Binary files /dev/null and b/sound/effects/teleport.ogg differ