From 990530d1189fc83a28c1446c9a07cfe3ba6c49f2 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Thu, 9 Apr 2015 14:36:46 +0200 Subject: [PATCH] Ports /vg/'s spell system for the sake of cult porting. Does offer a much more pleasant basis to work with in the future, if someone wishes to add more spell-like abilities. --- baystation12.dme | 53 +- code/__HELPERS/atom_pool.dm | 2 +- code/_onclick/hud/_defines.dm | 3 + code/_onclick/hud/spell_screen_objects.dm | 175 +++++ code/defines/obj.dm | 3 + code/defines/procs/admin.dm | 6 +- code/game/antagonist/outsider/wizard.dm | 36 +- code/game/gamemodes/cult/cult_structures.dm | 44 ++ code/game/gamemodes/cult/ritual.dm | 7 +- code/game/gamemodes/cult/talisman.dm | 2 +- code/game/objects/effects/overlays.dm | 4 + .../crates_lockers/closets/statue.dm | 133 ++++ code/game/turfs/turf.dm | 3 + code/game/turfs/turf_flick_animations.dm | 21 + code/global.dm | 3 + code/modules/admin/admin_verbs.dm | 11 + code/modules/clothing/shoes/miscellaneous.dm | 2 + .../clothing/spacesuits/void/wizard.dm | 4 +- code/modules/clothing/suits/wiz_robe.dm | 2 + code/modules/mob/living/silicon/pai/pai.dm | 4 - .../simple_animal/constructs/constructs.dm | 21 +- .../simple_animal/constructs/soulstone.dm | 9 +- code/modules/mob/mob.dm | 13 - code/modules/mob/mob_defines.dm | 5 +- code/modules/mob/mob_helpers.dm | 13 +- code/modules/power/singularity/narsie.dm | 133 ++++ code/modules/power/singularity/singularity.dm | 127 ---- code/modules/projectiles/projectile.dm | 40 +- code/modules/projectiles/projectile/change.dm | 4 +- .../reagents/reagent_containers/spray.dm | 2 +- code/modules/spells/aoe_turf/aoe_turf.dm | 25 + code/modules/spells/aoe_turf/blink.dm | 34 + code/modules/spells/aoe_turf/charge.dm | 69 ++ .../spells/aoe_turf/conjure/conjure.dm | 74 ++ .../spells/aoe_turf/conjure/construct.dm | 137 ++++ .../spells/aoe_turf/conjure/forcewall.dm | 52 ++ code/modules/spells/aoe_turf/disable_tech.dm | 23 + code/modules/spells/aoe_turf/knock.dm | 44 ++ code/modules/spells/aoe_turf/smoke.dm | 17 + code/modules/spells/aoe_turf/summons.dm | 41 ++ code/modules/spells/area_teleport.dm | 82 --- code/modules/spells/artifacts.dm | 19 + code/modules/spells/conjure.dm | 87 --- code/modules/spells/construct_spells.dm | 12 + code/modules/spells/dumbfire.dm | 86 --- code/modules/spells/emplosion.dm | 13 - code/modules/spells/ethereal_jaunt.dm | 106 --- code/modules/spells/explosion.dm | 15 - code/modules/spells/general/area_teleport.dm | 85 +++ code/modules/spells/general/rune_write.dm | 175 +++++ code/modules/spells/genetic.dm | 31 - code/modules/spells/horsemask.dm | 50 -- code/modules/spells/inflict_handler.dm | 59 -- code/modules/spells/knock.dm | 20 - code/modules/spells/mind_transfer.dm | 116 --- code/modules/spells/no_clothes.dm | 5 + code/modules/spells/projectile.dm | 83 --- code/modules/spells/spell.dm | 302 -------- code/modules/spells/spell_code.dm | 341 +++++++++ code/modules/spells/spell_projectile.dm | 62 ++ code/modules/spells/spellbook.dm | 665 ++++++++++++------ code/modules/spells/spells.dm | 64 ++ code/modules/spells/targeted/disintegrate.dm | 27 + .../modules/spells/targeted/ethereal_jaunt.dm | 98 +++ .../modules/spells/targeted/flesh_to_stone.dm | 21 + code/modules/spells/targeted/genetic.dm | 68 ++ code/modules/spells/targeted/harvest.dm | 36 + code/modules/spells/targeted/horsemask.dm | 35 + code/modules/spells/targeted/mind_transfer.dm | 73 ++ .../spells/targeted/projectile/dumbfire.dm | 13 + .../spells/targeted/projectile/fireball.dm | 39 + .../targeted/projectile/magic_missile.dm | 40 ++ .../spells/targeted/projectile/projectile.dm | 53 ++ code/modules/spells/targeted/shift.dm | 24 + code/modules/spells/targeted/subjugate.dm | 20 + code/modules/spells/targeted/targeted.dm | 145 ++++ code/modules/spells/trigger.dm | 28 - code/modules/spells/turf_teleport.dm | 34 - code/modules/spells/wizard_artifacts.dm | 59 -- code/modules/spells/wizard_spells.dm | 327 --------- code/setup.dm | 40 ++ icons/mob/screen_spells.dmi | Bin 0 -> 34589 bytes icons/obj/statue.dmi | Bin 0 -> 2677 bytes icons/obj/wizard.dmi | Bin 12565 -> 16793 bytes sound/effects/teleport.ogg | Bin 0 -> 12904 bytes 85 files changed, 3135 insertions(+), 1924 deletions(-) create mode 100644 code/_onclick/hud/spell_screen_objects.dm create mode 100644 code/game/objects/structures/crates_lockers/closets/statue.dm create mode 100644 code/game/turfs/turf_flick_animations.dm create mode 100644 code/modules/power/singularity/narsie.dm create mode 100644 code/modules/spells/aoe_turf/aoe_turf.dm create mode 100644 code/modules/spells/aoe_turf/blink.dm create mode 100644 code/modules/spells/aoe_turf/charge.dm create mode 100644 code/modules/spells/aoe_turf/conjure/conjure.dm create mode 100644 code/modules/spells/aoe_turf/conjure/construct.dm create mode 100644 code/modules/spells/aoe_turf/conjure/forcewall.dm create mode 100644 code/modules/spells/aoe_turf/disable_tech.dm create mode 100644 code/modules/spells/aoe_turf/knock.dm create mode 100644 code/modules/spells/aoe_turf/smoke.dm create mode 100644 code/modules/spells/aoe_turf/summons.dm delete mode 100644 code/modules/spells/area_teleport.dm create mode 100644 code/modules/spells/artifacts.dm delete mode 100644 code/modules/spells/conjure.dm create mode 100644 code/modules/spells/construct_spells.dm delete mode 100644 code/modules/spells/dumbfire.dm delete mode 100644 code/modules/spells/emplosion.dm delete mode 100644 code/modules/spells/ethereal_jaunt.dm delete mode 100644 code/modules/spells/explosion.dm create mode 100644 code/modules/spells/general/area_teleport.dm create mode 100644 code/modules/spells/general/rune_write.dm delete mode 100644 code/modules/spells/genetic.dm delete mode 100644 code/modules/spells/horsemask.dm delete mode 100644 code/modules/spells/inflict_handler.dm delete mode 100644 code/modules/spells/knock.dm delete mode 100644 code/modules/spells/mind_transfer.dm create mode 100644 code/modules/spells/no_clothes.dm delete mode 100644 code/modules/spells/projectile.dm delete mode 100644 code/modules/spells/spell.dm create mode 100644 code/modules/spells/spell_code.dm create mode 100644 code/modules/spells/spell_projectile.dm create mode 100644 code/modules/spells/spells.dm create mode 100644 code/modules/spells/targeted/disintegrate.dm create mode 100644 code/modules/spells/targeted/ethereal_jaunt.dm create mode 100644 code/modules/spells/targeted/flesh_to_stone.dm create mode 100644 code/modules/spells/targeted/genetic.dm create mode 100644 code/modules/spells/targeted/harvest.dm create mode 100644 code/modules/spells/targeted/horsemask.dm create mode 100644 code/modules/spells/targeted/mind_transfer.dm create mode 100644 code/modules/spells/targeted/projectile/dumbfire.dm create mode 100644 code/modules/spells/targeted/projectile/fireball.dm create mode 100644 code/modules/spells/targeted/projectile/magic_missile.dm create mode 100644 code/modules/spells/targeted/projectile/projectile.dm create mode 100644 code/modules/spells/targeted/shift.dm create mode 100644 code/modules/spells/targeted/subjugate.dm create mode 100644 code/modules/spells/targeted/targeted.dm delete mode 100644 code/modules/spells/trigger.dm delete mode 100644 code/modules/spells/turf_teleport.dm delete mode 100644 code/modules/spells/wizard_artifacts.dm delete mode 100644 code/modules/spells/wizard_spells.dm create mode 100644 icons/mob/screen_spells.dmi create mode 100644 icons/obj/statue.dmi create mode 100644 sound/effects/teleport.ogg 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= 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 0000000000000000000000000000000000000000..db693428d07ad43edac2e001d7b57231f09f2729 GIT binary patch literal 34589 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 literal 0 HcmV?d00001 diff --git a/icons/obj/statue.dmi b/icons/obj/statue.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a577e8da3edec140884c76c66c069ff2d30c8736 GIT binary patch literal 2677 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 literal 0 HcmV?d00001 diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi index 1d6bad4c507774f871c1d52ef47fbc4999fe2f32..b7d095f87ad3f9b3d36273ccca0acd0c8eaabb64 100644 GIT binary patch literal 16793 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 diff --git a/sound/effects/teleport.ogg b/sound/effects/teleport.ogg new file mode 100644 index 0000000000000000000000000000000000000000..650867782b7455435ea183596678c7ba27a81774 GIT binary patch literal 12904 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⩔8(;QOT=T(_23|q_WO6XDl=IjE