diff --git a/baystation12.dme b/baystation12.dme
index 48b8f0f5092..f119f0486fc 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -58,6 +58,7 @@
#include "code\__defines\lighting.dm"
#include "code\__defines\lists.dm"
#include "code\__defines\machinery.dm"
+#include "code\__defines\magic.dm"
#include "code\__defines\mapping.dm"
#include "code\__defines\materials.dm"
#include "code\__defines\math_physics.dm"
diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm
index fc0ee04b548..17620fc05c0 100644
--- a/code/__defines/dcs/signals.dm
+++ b/code/__defines/dcs/signals.dm
@@ -34,8 +34,10 @@
// /atom signals
// /atom/movable signals
-/// When an atom's Dispell() proc is called
+/// When an atom's Dispell() proc is called; Passes dispell strength as argument.
#define COMSIG_ATOM_MOVABLE_DISPELL "atom_dispell"
+// Return value of a signal handler if dispell should be blocked
+#define COMPONENT_DISPELL_BLOCKED (1 << 0)
// /area signals
diff --git a/code/__defines/magic.dm b/code/__defines/magic.dm
new file mode 100644
index 00000000000..4f0d6971f4d
--- /dev/null
+++ b/code/__defines/magic.dm
@@ -0,0 +1,5 @@
+// Defines for dispell strengths
+#define DISPELL_WEAK 1
+#define DISPELL_MEDIUM 2
+#define DISPELL_STRONG 3
+#define DISPELL_UNSTOPPABLE 4
diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm
index 0bf44029cbe..96f28259567 100644
--- a/code/game/antagonist/outsider/wizard.dm
+++ b/code/game/antagonist/outsider/wizard.dm
@@ -18,28 +18,28 @@ GLOBAL_DATUM_INIT(wizards, /datum/antagonist/wizard, new)
faction = "wizard"
base_to_load = /datum/map_template/ruin/antag_spawn/wizard
-/datum/antagonist/wizard/create_objectives(var/datum/mind/wizard)
+/datum/antagonist/wizard/create_objectives(datum/mind/wizard)
if(!..())
return
- var/kill
- var/escape
- var/steal
- var/hijack
+ var/kill = FALSE
+ var/escape = FALSE
+ var/steal = FALSE
+ var/hijack = FALSE
switch(rand(1,100))
if(1 to 30)
- escape = 1
- kill = 1
+ escape = TRUE
+ kill = TRUE
if(31 to 60)
- escape = 1
- steal = 1
+ escape = TRUE
+ steal = TRUE
if(61 to 99)
- kill = 1
- steal = 1
+ kill = TRUE
+ steal = TRUE
else
- hijack = 1
+ hijack = TRUE
if(kill)
var/datum/objective/assassinate/kill_objective = new
@@ -61,16 +61,16 @@ GLOBAL_DATUM_INIT(wizards, /datum/antagonist/wizard, new)
wizard.objectives |= hijack_objective
return
-/datum/antagonist/wizard/update_antag_mob(var/datum/mind/wizard)
+/datum/antagonist/wizard/update_antag_mob(datum/mind/wizard)
..()
wizard.StoreMemory("Remember: do not forget to prepare your spells.", /decl/memory_options/system)
wizard.current.real_name = "[pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)]"
wizard.current.SetName(wizard.current.real_name)
-/datum/antagonist/wizard/equip(var/mob/living/carbon/human/wizard_mob)
+/datum/antagonist/wizard/equip(mob/living/carbon/human/wizard_mob)
if(!..())
- return 0
+ return FALSE
var/outfit_type = pick(subtypesof(/decl/hierarchy/outfit/wizard))
var/decl/hierarchy/outfit/wizard_outfit = outfit_by_type(outfit_type)
@@ -82,7 +82,7 @@ GLOBAL_DATUM_INIT(wizards, /datum/antagonist/wizard, new)
wizard_mob.mind.mana.mana_recharge_speed = 2
wizard_mob.mind.mana.spell_points = 15 // Should allow wizard to buy 2-3 dangerous spells, or a bunch of small stuff
- return 1
+ return TRUE
/datum/antagonist/wizard/print_player_summary()
..()
@@ -107,28 +107,24 @@ GLOBAL_DATUM_INIT(wizards, /datum/antagonist/wizard, new)
for(var/datum/spell/spell_to_remove in mind.learned_spells)
remove_spell(spell_to_remove)
-obj/item/clothing
+/obj/item/clothing
var/wizard_garb = FALSE
-// Does this clothing slot count as wizard garb? (Combines a few checks)
-/proc/is_wiz_garb(var/obj/item/clothing/C)
+// Does this clothing slot count as wizard garb?
+/proc/is_wiz_garb(obj/item/clothing/C)
return istype(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.*/
+// Checks if the wizard is wearing the proper attire.
+// Made a proc so this is not repeated 14 (or more) times.
/mob/proc/wearing_wiz_garb()
to_chat(src, "Silly creature, you're not a human. Only humans can cast this spell.")
- return 0
+ return FALSE
-// Humans can wear clothes.
/mob/living/carbon/human/wearing_wiz_garb()
- if(!is_wiz_garb(src.wear_suit) && (!src.species.hud || (slot_wear_suit in src.species.hud.equip_slots)))
- to_chat(src, "I don't feel strong enough without my robe.")
- return 0
- if(!is_wiz_garb(src.shoes) && (!species.hud || (slot_shoes in src.species.hud.equip_slots)))
- to_chat(src, "I don't feel strong enough without my sandals.")
- return 0
- if(!is_wiz_garb(src.head) && (!species.hud || (slot_head in src.species.hud.equip_slots)))
- to_chat(src, "I don't feel strong enough without my hat.")
- return 0
- return 1
\ No newline at end of file
+ if(!is_wiz_garb(wear_suit) && (!species.hud || (slot_wear_suit in species.hud.equip_slots)))
+ to_chat(src, SPAN_WARNING("I don't feel strong enough without my robe."))
+ return FALSE
+ if(!is_wiz_garb(head) && (!species.hud || (slot_head in species.hud.equip_slots)))
+ to_chat(src, SPAN_WARNING("I don't feel strong enough without my hat."))
+ return FALSE
+ return TRUE
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index a623c78115e..5c831fbc8ed 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -350,6 +350,7 @@
return
/// The effect of being affected by dispells, either a projectile or AOE effects
-/atom/movable/proc/Dispell()
- SEND_SIGNAL(src, COMSIG_ATOM_MOVABLE_DISPELL)
- return
+/atom/movable/proc/Dispell(dispell_strength = DISPELL_WEAK)
+ if(SEND_SIGNAL(src, COMSIG_ATOM_MOVABLE_DISPELL, dispell_strength) & COMPONENT_DISPELL_BLOCKED)
+ return FALSE
+ return TRUE
diff --git a/code/modules/mana/mana.dm b/code/modules/mana/mana.dm
index 7a144f65e7a..72db7ccd368 100644
--- a/code/modules/mana/mana.dm
+++ b/code/modules/mana/mana.dm
@@ -1,4 +1,4 @@
-// Stores a lot of things related to magic, not just mana
+// Stores mana-related things and spell points
/datum/mana
var/mana_level = 10
var/mana_level_max = 10
@@ -18,6 +18,10 @@
StartRecharge()
return TRUE
+/datum/mana/proc/AddMana(amount = 0)
+ mana_level = clamp(mana_level + amount, 0, mana_level_max)
+ return TRUE
+
// Starts a "process" of recharging if we should and can
/datum/mana/proc/StartRecharge()
if(recharging)
@@ -34,6 +38,6 @@
if(mana_level >= mana_level_max)
recharging = FALSE
return FALSE
- mana_level = clamp(mana_level + mana_recharge_speed * 0.5, 0, mana_level_max)
+ AddMana(mana_recharge_speed * 0.5)
addtimer(CALLBACK(src, .proc/RechargeMana), (0.5 SECONDS))
return TRUE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 74006e3b125..62a132a326f 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -934,3 +934,28 @@ default behaviour is:
/mob/living/proc/jump_layer_shift_end()
jumping = FALSE
reset_layer()
+
+// If mob has an aimed spell prepared to cast - deactivates it.
+// Additionally, puts random spells on cooldown.
+/mob/living/Dispell(dispell_strength = DISPELL_WEAK)
+ . = ..()
+ if(!.)
+ return
+ if(!mind || !LAZYLEN(mind.learned_spells))
+ return
+ var/play_sound = FALSE
+ // It could also be non-aimed, but we should add the deactivation part to other spells then
+ if(istype(ranged_ability, /datum/spell/aimed))
+ var/datum/spell/aimed/AS = ranged_ability
+ AS.remove_ranged_ability(SPAN_DANGER("[ranged_ability] has been dispelled!"))
+ AS.on_deactivation(src)
+ play_sound = TRUE
+ for(var/datum/spell/S in mind.learned_spells)
+ if(!prob(dispell_strength * 25))
+ continue
+ S.charge_counter = S.charge_max * (rand(3, 10) * 0.1)
+ S.process()
+ to_chat(src, SPAN_WARNING("[S] has been dispelled and put on cooldown!"))
+ play_sound = TRUE
+ if(play_sound)
+ playsound(get_turf(src), 'sound/magic/blind.ogg', 50, TRUE)
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
index 7df464f8405..e6fa25aa7fe 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm
@@ -805,3 +805,18 @@
/datum/reagent/vaccine/mix_data(list/newdata, newamount)
if(istype(newdata))
src.data |= newdata.Copy()
+
+/datum/reagent/concentrated_mana
+ name = "Concentrated Mana"
+ description = "A mysterious liquid used by magic-fluent people to restore their internal mana reserves. \
+ Can also be used in certain tools that utilize magic phenomenon."
+ taste_description = "cool air"
+ reagent_state = LIQUID
+ color = "#47f0ff"
+
+/datum/reagent/concentrated_mana/affect_blood(mob/living/carbon/human/H, alien, removed)
+ if(!ishuman(H))
+ return
+ if(!H.mind || !H.mind?.mana)
+ return
+ H.mind.mana.AddMana(2)
diff --git a/code/modules/research/part_replacer.dm b/code/modules/research/part_replacer.dm
index 6fc0242e44b..9bb723862ad 100644
--- a/code/modules/research/part_replacer.dm
+++ b/code/modules/research/part_replacer.dm
@@ -29,7 +29,7 @@
if(istype(target, /obj/machinery))
var/obj/machinery/machine = target
if(machine.component_attackby(src, user))
- user.Beam(machine, icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 5)
+ user.Beam(machine, icon_state = "rped_upgrade", time = 5)
/obj/item/storage/part_replacer/bluespace
name = "bluespace rapid part exchange device"
diff --git a/code/modules/spellbook/_spellbook.dm b/code/modules/spellbook/_spellbook.dm
index c70142fef9d..f74b07eebe0 100644
--- a/code/modules/spellbook/_spellbook.dm
+++ b/code/modules/spellbook/_spellbook.dm
@@ -24,6 +24,8 @@ GLOBAL_LIST_EMPTY(spells_by_categories)
var/list/allowed_spells = list()
/// Currently applied spell categories that will be shown; If none - all spells are shown.
var/list/spell_categories = list()
+ /// Defines how strong the dispell must be to successfuly remove the restrictions.
+ var/dispell_resistance = 0
/obj/item/spellbook/Initialize()
. = ..()
@@ -152,10 +154,13 @@ GLOBAL_LIST_EMPTY(spells_by_categories)
interact(user)
// Being hit with any source of dispell releases any locks
-/obj/item/spellbook/Dispell()
+/obj/item/spellbook/Dispell(dispell_strength = DISPELL_WEAK)
. = ..()
if(!istype(owner) && !(book_flags & WIZARD_ONLY) && !(book_flags & APPRENTICE_ONLY))
return
+ if(dispell_resistance > dispell_strength)
+ visible_message(SPAN_WARNING("\The [src] repels the surrounding dispelling magic!"))
+ return
visible_message(SPAN_NOTICE("\The [src] fizzles and sparks!"))
RemoveOwner()
owner = null
diff --git a/code/modules/spells/_spell.dm b/code/modules/spells/_spell.dm
index 02e59ae2318..b215be80c89 100644
--- a/code/modules/spells/_spell.dm
+++ b/code/modules/spells/_spell.dm
@@ -155,7 +155,7 @@ GLOBAL_LIST_INIT(spell_categories, list(
/datum/spell/proc/Click(mob/user = usr, skipcharge = 0) // When action button is pressed
if(cast_check(skipcharge, user))
choose_targets(user)
- return 1
+ return TRUE
/datum/spell/proc/choose_targets(mob/user = usr) //depends on subtype - see targeted.dm, aoe_turf.dm, dumbfire.dm, or code in general folder
return
@@ -334,12 +334,12 @@ GLOBAL_LIST_INIT(spell_categories, list(
if(SPELL_RECHARGE)
if(charge_counter < charge_max)
to_chat(user, still_recharging_msg)
- return 0
+ return FALSE
if(SPELL_CHARGES)
if(!charge_counter)
to_chat(user, "[name] has no charges left.")
- return 0
- return 1
+ return FALSE
+ return TRUE
/datum/spell/proc/take_charge(mob/user = user, var/skipcharge)
if(!skipcharge)
@@ -347,15 +347,15 @@ GLOBAL_LIST_INIT(spell_categories, list(
if(SPELL_RECHARGE)
charge_counter = 0 //doesn't start recharging until the targets selecting ends
src.process()
- return 1
+ return TRUE
if(SPELL_CHARGES)
charge_counter-- //returns the charge if the targets selecting fails
- return 1
+ return TRUE
if(SPELL_HOLDVAR)
adjust_var(user, holder_var_type, holder_var_amount)
- return 1
- return 0
- return 1
+ return TRUE
+ return FALSE
+ return TRUE
/datum/spell/proc/TakeMana(mob/user = user)
if(!user.mind)
@@ -386,25 +386,25 @@ GLOBAL_LIST_INIT(spell_categories, list(
/datum/spell/proc/can_improve(upgrade_type)
if(level_max[UPGRADE_TOTAL] <= ( spell_levels[UPGRADE_SPEED] + spell_levels[UPGRADE_POWER] )) //too many levels, can't do it
- return 0
+ return FALSE
//if(upgrade_type && spell_levels[upgrade_type] && level_max[upgrade_type])
if(upgrade_type && spell_levels[upgrade_type] >= level_max[upgrade_type])
- return 0
+ return FALSE
- return 1
+ return TRUE
/datum/spell/proc/empower_spell()
if(!can_improve(UPGRADE_POWER))
- return 0
+ return FALSE
spell_levels[UPGRADE_POWER]++
- return 1
+ return TRUE
/datum/spell/proc/quicken_spell()
if(!can_improve(UPGRADE_SPEED))
- return 0
+ return FALSE
spell_levels[UPGRADE_SPEED]++
@@ -441,7 +441,7 @@ GLOBAL_LIST_INIT(spell_categories, list(
/datum/spell/proc/spell_do_after(var/mob/user as mob, delay as num, var/numticks = 5)
if(!user || isnull(user))
- return 0
+ return FALSE
var/incap_flags = INCAPACITATION_STUNNED|INCAPACITATION_RESTRAINED|INCAPACITATION_BUCKLED_FULLY|INCAPACITATION_FORCELYING
if(!(spell_flags & (GHOSTCAST)))
diff --git a/code/modules/spells/_spell_procs.dm b/code/modules/spells/_spell_procs.dm
index 8a15f67ee6f..01054176993 100644
--- a/code/modules/spells/_spell_procs.dm
+++ b/code/modules/spells/_spell_procs.dm
@@ -61,8 +61,8 @@
ability_master.remove_ability(ability_master.get_ability_by_spell(spell_to_remove))
return 1
-/mob/proc/silence_spells(var/amount = 0)
- if(amount < 0)
+/mob/proc/silence_spells(amount = 0)
+ if(!amount)
return
if(!ability_master)
diff --git a/code/modules/spells/aimed/dispell.dm b/code/modules/spells/aimed/dispell.dm
index 72205068b72..13d38e54b80 100644
--- a/code/modules/spells/aimed/dispell.dm
+++ b/code/modules/spells/aimed/dispell.dm
@@ -9,7 +9,6 @@
level_max = list(UPGRADE_TOTAL = 2, UPGRADE_SPEED = 0, UPGRADE_POWER = 2)
duration = 15
projectile_type = /obj/item/projectile/spell_projectile/dispell
- var/amt_range = 0
active_msg = "You prepare to cast the bolt of dispell!"
deactive_msg = "You decide against using the bolt of dispell."
@@ -21,6 +20,9 @@
spell_cost = 2
mana_cost = 15
+ var/amt_range = 0
+ var/strength = DISPELL_WEAK
+
/datum/spell/aimed/dispell_projectile/prox_cast(list/targets, atom/spell_holder)
var/atom/movable/A = targets[1]
if(amt_range > 0)
diff --git a/code/modules/spells/aimed/flamethrower.dm b/code/modules/spells/aimed/flamethrower.dm
index 63b76d24d7d..7a3e546cbaa 100644
--- a/code/modules/spells/aimed/flamethrower.dm
+++ b/code/modules/spells/aimed/flamethrower.dm
@@ -56,4 +56,4 @@
flame_power += 20
flame_color = flame_power >= 60 ? COLOR_PURPLE : COLOR_RED
- return "The [src] spell is now much more powerful."
+ return "The [src] spell is now [flame_power >= 60 ? "much " : ""]more powerful."
diff --git a/code/modules/spells/aimed/spark_bolt.dm b/code/modules/spells/aimed/spark_bolt.dm
index 79a9ea4d3ee..f25aa70f9f3 100644
--- a/code/modules/spells/aimed/spark_bolt.dm
+++ b/code/modules/spells/aimed/spark_bolt.dm
@@ -14,17 +14,27 @@
active_msg = "You prepare to cast spark bolt!"
deactive_msg = "You dissipate the spark bolt."
- level_max = list(UPGRADE_TOTAL = 2, UPGRADE_SPEED = 2, UPGRADE_POWER = 0)
+ level_max = list(UPGRADE_TOTAL = 4, UPGRADE_SPEED = 2, UPGRADE_POWER = 2)
categories = list()
spell_cost = 2
mana_cost = 0.5 // Per projectile, mind you
+/datum/spell/aimed/spark_bolt/empower_spell()
+ if(!..())
+ return FALSE
+
+ projectile_amount += 3
+
+ return "The spell [src] now has more projectiles stored per cast."
+
/datum/spell/aimed/spark_bolt/quicken_spell()
if(!..())
return FALSE
- return "The spell [src] now has lower cooldown."
+ ranged_clickcd = max(0.5, ranged_clickcd - 1.5)
+
+ return "The spell [src] now has lower cooldown and attack delay."
// Projectile
/obj/item/projectile/spark_bolt
diff --git a/code/modules/spells/hand/sunwrath.dm b/code/modules/spells/hand/sunwrath.dm
index e9f7b55731f..6d0e5e08e6e 100644
--- a/code/modules/spells/hand/sunwrath.dm
+++ b/code/modules/spells/hand/sunwrath.dm
@@ -10,6 +10,8 @@
spell_delay = 30
range = 4
+ categories = list(SPELL_CATEGORY_FIRE)
+
hud_state = "wiz_immolate"
spell_cost = 5
@@ -26,10 +28,10 @@
return 1
/obj/effect/fake_fire/sunwrath
- firelevel = 2
+ firelevel = 5
last_temperature = 0
- pressure = 3000
+ pressure = 5000
/obj/effect/fake_fire/sunwrath/Process() //Override, so we burn mobs only
for(var/mob/living/L in loc)
- L.FireBurn(firelevel,last_temperature,pressure)
+ L.FireBurn(firelevel, last_temperature, pressure)
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 264205694a8..2362c6743ea 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index dd4d1336d1a..74e8848f0e2 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/test/check-paths.sh b/test/check-paths.sh
index 7b4e60b1a41..e3101af5dd1 100755
--- a/test/check-paths.sh
+++ b/test/check-paths.sh
@@ -34,7 +34,7 @@ exactly 117 "to_world uses" '\sto_world\('
exactly 60 "to_world_log uses" '\sto_world_log\('
exactly 0 "world<< uses" 'world<<|world[[:space:]]<<'
exactly 0 "world.log<< uses" 'world.log<<|world.log[[:space:]]<<'
-exactly 143 "<< uses" '(?