diff --git a/code/__DEFINES/dcs/atom_signals.dm b/code/__DEFINES/dcs/atom_signals.dm
index 294e5bd5aac..42745166c8d 100644
--- a/code/__DEFINES/dcs/atom_signals.dm
+++ b/code/__DEFINES/dcs/atom_signals.dm
@@ -134,5 +134,9 @@
#define COMPONENT_NO_MOUSEDROP (1<<0)
///from base of atom/MouseDrop_T: (/atom/from, /mob/user)
#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"
+
+/// Called on the atom being hit, from /datum/component/anti_magic/on_attack() : (obj/item/weapon, mob/user, antimagic_flags)
+#define COMSIG_ATOM_HOLY_ATTACK "atom_holyattacked"
/// On a ranged attack: base of mob/living/carbon/human/RangedAttack (/mob/living/carbon/human)
#define COMSIG_ATOM_RANGED_ATTACKED "atom_range_attacked"
+
diff --git a/code/__DEFINES/dcs/mob_signals.dm b/code/__DEFINES/dcs/mob_signals.dm
index 3d0406f583e..e9b71bdc58b 100644
--- a/code/__DEFINES/dcs/mob_signals.dm
+++ b/code/__DEFINES/dcs/mob_signals.dm
@@ -22,11 +22,14 @@
#define COMSIG_MOB_ALTCLICKON "mob_altclickon"
#define COMSIG_MOB_CANCEL_CLICKON (1<<0)
+///from base of mob/can_cast_magic(): (mob/user, magic_flags, charge_cost)
+#define COMSIG_MOB_RESTRICT_MAGIC "mob_cast_magic"
+///from base of mob/can_block_magic(): (mob/user, casted_magic_flags, charge_cost)
+#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic"
+ #define COMPONENT_MAGIC_BLOCKED (1<<0)
+
///from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
#define COMSIG_MOB_ALLOWED "mob_allowed"
-///from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
-#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic"
- #define COMPONENT_BLOCK_MAGIC (1<<0)
///from base of mob/create_mob_hud(): ()
#define COMSIG_MOB_HUD_CREATED "mob_hud_created"
///from base of atom/attack_hand(): (mob/user)
diff --git a/code/__DEFINES/magic_defines.dm b/code/__DEFINES/magic_defines.dm
new file mode 100644
index 00000000000..9d0ee8158cd
--- /dev/null
+++ b/code/__DEFINES/magic_defines.dm
@@ -0,0 +1,16 @@
+// Bitflags for magic resistance types
+/// Default magic resistance that blocks normal magic (wizard, spells, magical staff projectiles)
+#define MAGIC_RESISTANCE (1<<0)
+/// Tinfoil hat magic resistance that blocks mental magic (telepathy / mind links, mind curses, abductors)
+#define MAGIC_RESISTANCE_MIND (1<<1)
+/// Holy magic resistance that blocks unholy magic (revenant, vampire, voice of god)
+#define MAGIC_RESISTANCE_HOLY (1<<2)
+
+DEFINE_BITFIELD(antimagic_flags, list(
+ "MAGIC_RESISTANCE" = MAGIC_RESISTANCE,
+ "MAGIC_RESISTANCE_HOLY" = MAGIC_RESISTANCE_HOLY,
+ "MAGIC_RESISTANCE_MIND" = MAGIC_RESISTANCE_MIND,
+))
+
+/// Whether the spell can be cast while the user has antimagic on them that corresponds to the spell's own antimagic flags.
+#define SPELL_REQUIRES_NO_ANTIMAGIC (1 << 0)
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index 245fcc291bb..095e02a0fa8 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -244,6 +244,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_EMP_RESIST "emp_resist" //The mob will take less damage from EMPs
#define TRAIT_MINDFLAYER_NULLIFIED "flayer_nullified" //The mindflayer will not be able to activate their abilities, or drain swarms from people
#define TRAIT_FLYING "flying"
+/// This mob is antimagic, and immune to spells / cannot cast spells
+#define TRAIT_ANTIMAGIC "anti_magic"
+/// This allows a person who has antimagic to cast spells without getting blocked
+#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
+/// This mob recently blocked magic with some form of antimagic
+#define TRAIT_RECENTLY_BLOCKED_MAGIC "recently_blocked_magic"
#define TRAIT_UNKNOWN "unknown" // The person with this trait always appears as 'unknown'.
#define TRAIT_CRYO_DESPAWNING "cryo_despawning" // dont adminbus this please
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 525e2cddb76..e0be5bf3b90 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -106,6 +106,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_CANNOT_PULL" = TRAIT_CANNOT_PULL,
"TRAIT_BSG_IMMUNE" = TRAIT_BSG_IMMUNE,
"TRAIT_FLYING" = TRAIT_FLYING,
+ "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC,
+ "TRAIT_ANTIMAGIC_NO_SELFBLOCK" = TRAIT_ANTIMAGIC_NO_SELFBLOCK,
+ "TRAIT_RECENTLY_BLOCKED_MAGIC" = TRAIT_RECENTLY_BLOCKED_MAGIC,
"TRAIT_UNKNOWN" = TRAIT_UNKNOWN
),
diff --git a/code/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm
new file mode 100644
index 00000000000..645e579a709
--- /dev/null
+++ b/code/datums/components/anti_magic.dm
@@ -0,0 +1,166 @@
+/// This provides different types of magic resistance on an object
+/datum/component/anti_magic
+ /// A bitflag with the types of magic resistance on the object
+ var/antimagic_flags
+ /// The amount of times the object can protect the user from magic
+ /// Set to INFINITY to have, well, infinite charges.
+ var/charges
+ /// The inventory slot the object must be located at in order to activate
+ var/inventory_flags
+ /// The callback invoked when we have been drained a antimagic charge
+ var/datum/callback/drain_antimagic
+ /// The callback invoked when twe have been depleted of all charges
+ var/datum/callback/expiration
+ /// Whether we should, on equipping, alert the caster that this item can block any of their spells
+ /// This changes between true and false on equip and drop, don't set it outright to something
+ var/alert_caster_on_equip = TRUE
+
+/**
+ * Adds magic resistances to an object
+ *
+ * Magic resistance will prevent magic from affecting the user if it has the correct resistance
+ * against the type of magic being used
+ *
+ * args:
+ * * antimagic_flags (optional) A bitflag with the types of magic resistance on the object
+ * * charges (optional) The amount of times the object can protect the user from magic
+ * * inventory_flags (optional) The inventory slot the object must be located at in order to activate
+ * * drain_antimagic (optional) The proc that is triggered when an object has been drained a antimagic charge
+ * * expiration (optional) The proc that is triggered when the object is depleted of charges
+ * *
+ * antimagic bitflags: (see code/__DEFINES/magic.dm)
+ * * MAGIC_RESISTANCE - Default magic resistance that blocks normal magic (wizard, spells, staffs)
+ * * MAGIC_RESISTANCE_MIND - Tinfoil hat magic resistance that blocks mental magic (telepathy, abductors, jelly people)
+ * * MAGIC_RESISTANCE_HOLY - Holy magic resistance that blocks unholy magic (revenant, cult, vampire, voice of god)
+**/
+/datum/component/anti_magic/Initialize(
+ antimagic_flags = MAGIC_RESISTANCE,
+ charges = INFINITY,
+ inventory_flags = ~ITEM_SLOT_IN_BACKPACK, // items in a backpack won't activate, anywhere else is fine
+ datum/callback/drain_antimagic,
+ datum/callback/expiration,
+ )
+
+
+ var/atom/movable/movable = parent
+ if(!istype(movable))
+ return COMPONENT_INCOMPATIBLE
+
+ var/compatible = FALSE
+ if(isitem(movable))
+ RegisterSignal(movable, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
+ RegisterSignal(movable, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+ RegisterSignal(movable, COMSIG_ATTACK, PROC_REF(on_attack))
+ compatible = TRUE
+ else if(ismob(movable))
+ register_antimagic_signals(movable)
+ compatible = TRUE
+
+ if(movable.can_buckle)
+ RegisterSignal(movable, COMSIG_MOVABLE_BUCKLE, PROC_REF(on_buckle))
+ RegisterSignal(movable, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(on_unbuckle))
+ compatible = TRUE
+
+ if(!compatible)
+ return COMPONENT_INCOMPATIBLE
+
+ src.antimagic_flags = antimagic_flags
+ src.charges = charges
+ src.inventory_flags = inventory_flags
+ src.drain_antimagic = drain_antimagic
+ src.expiration = expiration
+
+/datum/component/anti_magic/Destroy(force)
+ drain_antimagic = null
+ expiration = null
+ return ..()
+
+/datum/component/anti_magic/proc/register_antimagic_signals(datum/on_what)
+ RegisterSignal(on_what, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(block_receiving_magic), override = TRUE)
+ RegisterSignal(on_what, COMSIG_MOB_RESTRICT_MAGIC, PROC_REF(restrict_casting_magic), override = TRUE)
+
+/datum/component/anti_magic/proc/unregister_antimagic_signals(datum/on_what)
+ UnregisterSignal(on_what, list(COMSIG_MOB_RECEIVE_MAGIC, COMSIG_MOB_RESTRICT_MAGIC))
+
+/datum/component/anti_magic/proc/on_buckle(atom/movable/source, mob/living/bucklee)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_BUCKLE
+ register_antimagic_signals(bucklee)
+
+/datum/component/anti_magic/proc/on_unbuckle(atom/movable/source, mob/living/bucklee)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_UNBUCKLE
+ unregister_antimagic_signals(bucklee)
+
+/datum/component/anti_magic/proc/on_equip(atom/movable/source, mob/equipper, slot)
+ SIGNAL_HANDLER // COMSIG_ITEM_EQUIPPED
+ addtimer(CALLBACK(src, PROC_REF(on_equip_part_2), source, equipper, slot), 0.1 SECONDS) //We wait a moment to see if the item grants antimagic flags
+
+/datum/component/anti_magic/proc/on_equip_part_2(atom/movable/source, mob/equipper, slot)
+ if(!(inventory_flags & slot)) //Check that the slot is valid for antimagic
+ unregister_antimagic_signals(equipper)
+ return
+
+ register_antimagic_signals(equipper)
+ if(HAS_TRAIT(equipper, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) //If they do not care about antimagic, don't warn them
+ return
+ if(!alert_caster_on_equip)
+ return
+
+ // Check to see if we have any spells that are blocked due to antimagic
+ if(!equipper.mind)
+ return
+ for(var/datum/spell/knownspell in equipper.mind.spell_list)
+ if(!(knownspell.spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC))
+ continue
+
+ if(!(antimagic_flags & knownspell.antimagic_flags))
+ continue
+
+ to_chat(equipper, "[parent] is interfering with your ability to cast magic!")
+ alert_caster_on_equip = FALSE
+ break
+
+/datum/component/anti_magic/proc/on_drop(atom/movable/source, mob/user)
+ SIGNAL_HANDLER //COMSIG_ITEM_DROPPED
+
+ // Reset alert
+ if(source.loc != user)
+ alert_caster_on_equip = TRUE
+ unregister_antimagic_signals(user)
+
+/datum/component/anti_magic/proc/block_receiving_magic(mob/living/carbon/source, casted_magic_flags, charge_cost, list/antimagic_sources)
+ SIGNAL_HANDLER // COMSIG_MOB_RECEIVE_MAGIC
+
+ // We do not block this type of magic, good day
+ if(!(casted_magic_flags & antimagic_flags))
+ return NONE
+
+ // We have already blocked this spell
+ if(parent in antimagic_sources)
+ return NONE
+
+ // Block success! Add this parent to the list of antimagic sources
+ antimagic_sources += parent
+
+ if((charges != INFINITY) && charge_cost > 0)
+ drain_antimagic?.Invoke(source, parent)
+ charges -= charge_cost
+ if(charges <= 0)
+ expiration?.Invoke(source, parent)
+ qdel(src) // no more antimagic
+
+ return COMPONENT_MAGIC_BLOCKED
+
+/// cannot cast magic with the same type of antimagic present
+/datum/component/anti_magic/proc/restrict_casting_magic(mob/user, magic_flags)
+ SIGNAL_HANDLER // COMSIG_MOB_RESTRICT_MAGIC
+
+ if(magic_flags & antimagic_flags)
+ if(HAS_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) // this trait bypasses magic casting restrictions
+ return NONE
+ return COMPONENT_MAGIC_BLOCKED
+
+ return NONE
+
+/datum/component/anti_magic/proc/on_attack(obj/item/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER //COMSIG_ATTACK
+ SEND_SIGNAL(target, COMSIG_ATOM_HOLY_ATTACK, source, user, antimagic_flags)
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index cd595686d61..3604140c819 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -134,6 +134,13 @@ GLOBAL_LIST_INIT(spells, typesof(/datum/spell))
var/static/list/spell_handlers = list()
/// handles a given spells cooldowns. tracks the time until its off cooldown,
var/datum/spell_cooldown/cooldown_handler
+ /// Flag for certain states that the spell requires the user be in to cast.
+ var/spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC
+ /// This determines what type of antimagic is needed to block the spell.
+ /// (MAGIC_RESISTANCE, MAGIC_RESISTANCE_MIND, MAGIC_RESISTANCE_HOLY)
+ /// If SPELL_REQUIRES_NO_ANTIMAGIC is set in Spell requirements,
+ /// The spell cannot be cast if the caster has any of the antimagic flags set.
+ var/antimagic_flags = MAGIC_RESISTANCE
/* Checks if the user can cast the spell
* @param charge_check If the proc should do the cooldown check
@@ -467,6 +474,12 @@ GLOBAL_LIST_INIT(spells, typesof(/datum/spell))
if(T && is_admin_level(T.z))
return FALSE
+ // If the spell requires the user has no antimagic equipped, and they're holding antimagic
+ // that corresponds with the spell's antimagic, then they can't actually cast the spell
+ if((spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC) && !user.can_cast_magic(antimagic_flags))
+ to_chat(user, "Some form of antimagic is preventing you from casting [src]!")
+ return FALSE
+
if(!holy_area_cancast && user.holy_check())
return FALSE
diff --git a/code/datums/spells/alien_spells/basetype_alien_spell.dm b/code/datums/spells/alien_spells/basetype_alien_spell.dm
index 12d98e8e489..02ac8ddab2a 100644
--- a/code/datums/spells/alien_spells/basetype_alien_spell.dm
+++ b/code/datums/spells/alien_spells/basetype_alien_spell.dm
@@ -24,6 +24,7 @@ Updates the spell's actions on use as well, so they know when they can or can't
create_attack_logs = FALSE
/// Every alien spell creates only logs, no attack messages on someone placing weeds, but you DO get attack messages on neurotoxin and corrosive acid
create_custom_logs = TRUE
+ antimagic_flags = NONE
/// How much plasma it costs to use this
var/plasma_cost = 0
diff --git a/code/datums/spells/alien_spells/basetype_alien_touch.dm b/code/datums/spells/alien_spells/basetype_alien_touch.dm
index c58ff15bed2..f979b2af6ba 100644
--- a/code/datums/spells/alien_spells/basetype_alien_touch.dm
+++ b/code/datums/spells/alien_spells/basetype_alien_touch.dm
@@ -6,6 +6,7 @@
base_cooldown = 1
action_background_icon_state = "bg_alien"
clothes_req = FALSE
+ antimagic_flags = NONE
create_attack_logs = FALSE
/// Every alien spell creates only logs, no attack messages on someone placing weeds, but you DO get attack messages on neurotoxin and corrosive acid
create_custom_logs = TRUE
diff --git a/code/datums/spells/banana_touch.dm b/code/datums/spells/banana_touch.dm
index b09b68d0639..9dc684f061a 100644
--- a/code/datums/spells/banana_touch.dm
+++ b/code/datums/spells/banana_touch.dm
@@ -27,10 +27,11 @@
/obj/item/melee/touch_attack/banana/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
+
if(is_apprentice_spell && iswizard(target) && target != user)
to_chat(user, "Seriously?! Honk THEM, not me!")
return
- if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
var/datum/effect_system/smoke_spread/s = new
diff --git a/code/datums/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm
index 007ff329932..9bc2864a656 100644
--- a/code/datums/spells/bloodcrawl.dm
+++ b/code/datums/spells/bloodcrawl.dm
@@ -8,6 +8,7 @@
overlay = null
action_icon_state = "bloodcrawl"
action_background_icon_state = "bg_demon"
+ antimagic_flags = NONE
var/allowed_type = /obj/effect/decal/cleanable
var/phased = FALSE
diff --git a/code/datums/spells/chaplain_bless.dm b/code/datums/spells/chaplain_bless.dm
index 970cebd638c..518f51207c8 100644
--- a/code/datums/spells/chaplain_bless.dm
+++ b/code/datums/spells/chaplain_bless.dm
@@ -7,6 +7,7 @@
clothes_req = FALSE
invocation = "none"
invocation_type = "none"
+ antimagic_flags = NONE
selection_activated_message = "You prepare a blessing. Click on a target to start blessing."
selection_deactivated_message = "The crew will be blessed another time."
diff --git a/code/datums/spells/chef.dm b/code/datums/spells/chef.dm
index a28438b397a..2e42e0b1be6 100644
--- a/code/datums/spells/chef.dm
+++ b/code/datums/spells/chef.dm
@@ -4,6 +4,7 @@
clothes_req = FALSE
base_cooldown = 5 SECONDS
human_req = TRUE
+ antimagic_flags = NONE
action_icon_state = "chef"
action_background_icon_state = "bg_default"
still_recharging_msg = "All this thinking makes your head hurt, wait a bit longer."
diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm
index f0614edddd5..32d5cab3dcb 100644
--- a/code/datums/spells/horsemask.dm
+++ b/code/datums/spells/horsemask.dm
@@ -27,6 +27,13 @@
var/mob/living/carbon/human/target = targets[1]
+ if(target.can_block_magic(antimagic_flags))
+ target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target.p_them()] unharmed!",
+ "Your face starts burning up, but the flames are repulsed by your anti-magic protection!",
+ )
+ to_chat(user, "The spell had no effect!")
+ return FALSE
+
var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
magichead.flags |= NODROP | DROPDEL //curses!
magichead.flags_inv = null //so you can still see their face
diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm
index d4a141c1ccb..c7e79e2e103 100644
--- a/code/datums/spells/lightning.dm
+++ b/code/datums/spells/lightning.dm
@@ -38,6 +38,12 @@
origin.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5)
/datum/spell/charge_up/bounce/lightning/apply_bounce_effect(mob/origin, mob/living/target, energy, mob/user)
+ if(target.can_block_magic(antimagic_flags))
+ target.visible_message(
+ "[target] absorbs the spell, remaining unharmed!",
+ "You absorb the spell, remaining unharmed!"
+ )
+ return
if(damaging)
target.electrocute_act(energy, "Lightning Bolt", flags = SHOCK_NOGLOVES)
else
diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm
index 2e139981d64..eda8b982241 100644
--- a/code/datums/spells/mime.dm
+++ b/code/datums/spells/mime.dm
@@ -9,6 +9,7 @@
clothes_req = FALSE
cast_sound = null
human_req = TRUE
+ antimagic_flags = NONE
action_icon_state = "mime"
action_background_icon_state = "bg_mime"
@@ -87,6 +88,7 @@
clothes_req = FALSE
base_cooldown = 30 SECONDS
human_req = TRUE
+ antimagic_flags = NONE
action_icon_state = "fingergun"
action_background_icon_state = "bg_mime"
diff --git a/code/datums/spells/mime_malaise.dm b/code/datums/spells/mime_malaise.dm
index f5e0011860c..67de6301334 100644
--- a/code/datums/spells/mime_malaise.dm
+++ b/code/datums/spells/mime_malaise.dm
@@ -20,7 +20,8 @@
/obj/item/melee/touch_attack/mime_malaise/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+
+ if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
var/datum/effect_system/smoke_spread/s = new
diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm
index 4690dec4972..2e3bad262cf 100644
--- a/code/datums/spells/mind_transfer.dm
+++ b/code/datums/spells/mind_transfer.dm
@@ -9,6 +9,7 @@
selection_activated_message = "You prepare to transfer your mind. Click on a target to cast the spell."
selection_deactivated_message = "You decide that your current form is good enough."
cooldown_min = 200 //100 deciseconds reduction per rank
+ antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
var/list/protected_roles = list(SPECIAL_ROLE_WIZARD, SPECIAL_ROLE_CHANGELING, SPECIAL_ROLE_CULTIST) //which roles are immune to the spell
var/paralysis_amount_caster = 40 SECONDS //how much the caster is paralysed for after the spell
var/paralysis_amount_victim = 40 SECONDS //how much the victim is paralysed for after the spell
@@ -47,6 +48,9 @@ Also, you never added distance checking after target is selected. I've went ahea
if(issilicon(target))
to_chat(user, "You feel this enslaved being is just as dead as its cold, hard exoskeleton.")
return
+ if(target.can_block_magic(antimagic_flags))
+ to_chat(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/living/caster = user//The wizard/whomever doing the body transferring.
diff --git a/code/datums/spells/night_vision.dm b/code/datums/spells/night_vision.dm
index de085eae199..ae5c94f9761 100644
--- a/code/datums/spells/night_vision.dm
+++ b/code/datums/spells/night_vision.dm
@@ -4,6 +4,7 @@
base_cooldown = 10
clothes_req = FALSE
+ antimagic_flags = NONE
message = "You toggle your night vision!"
diff --git a/code/datums/spells/rathens.dm b/code/datums/spells/rathens.dm
index 19510324444..22969027c46 100644
--- a/code/datums/spells/rathens.dm
+++ b/code/datums/spells/rathens.dm
@@ -15,6 +15,8 @@
/datum/spell/rathens/cast(list/targets, mob/user = usr)
for(var/mob/living/carbon/human/H in targets)
+ if(H.can_block_magic(antimagic_flags))
+ continue
var/datum/effect_system/smoke_spread/s = new
s.set_up(5, FALSE, H)
s.start()
diff --git a/code/datums/spells/spacetime_dist.dm b/code/datums/spells/spacetime_dist.dm
index 2e7a45c95ee..9d7b09c1629 100644
--- a/code/datums/spells/spacetime_dist.dm
+++ b/code/datums/spells/spacetime_dist.dm
@@ -82,6 +82,8 @@
desc = "A distortion in spacetime. You can hear faint music..."
icon_state = "nothing"
/// A flags which save people from being thrown about
+ var/antimagic_flags = MAGIC_RESISTANCE
+ /// A flags which save people from being thrown about
var/obj/effect/cross_action/spacetime_dist/linked_dist
/// Used to prevent an infinite loop in the space tiime continuum
var/cant_teleport = FALSE
@@ -101,6 +103,10 @@
AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM)
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.can_block_magic(antimagic_flags, charge_cost = 0))
+ return
if(linked_dist && walks_left > 0)
flick("purplesparkles", src)
linked_dist.get_walker(AM)
diff --git a/code/datums/spells/wizard_spells.dm b/code/datums/spells/wizard_spells.dm
index 5466ee845cf..c62f868f401 100644
--- a/code/datums/spells/wizard_spells.dm
+++ b/code/datums/spells/wizard_spells.dm
@@ -313,6 +313,10 @@
return
var/mob/living/target = targets[1]
+ if(target.can_block_magic(antimagic_flags))
+ to_chat(target, "Your eye itches, but it passes momentarily.")
+ to_chat(user, "The spell had no effect!")
+ return FALSE
target.EyeBlurry(40 SECONDS)
target.EyeBlind(30 SECONDS)
@@ -414,6 +418,10 @@
playMagSound()
for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously.
for(var/atom/movable/AM in T)
+ if(ismob(AM))
+ var/mob/victim_mob = AM
+ if(victim_mob.can_block_magic(antimagic_flags))
+ continue
thrownatoms += AM
for(var/am in thrownatoms)
@@ -457,6 +465,8 @@
/datum/spell/sacred_flame/cast(list/targets, mob/user = usr)
for(var/mob/living/L in targets)
+ if(L.can_block_magic(antimagic_flags))
+ continue
L.adjust_fire_stacks(20)
if(isliving(user))
var/mob/living/U = user
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index c4795b8d71a..aa8dfa6c23d 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -92,9 +92,12 @@
var/found_someone = FALSE
for(var/mob/living/L in oview(9, owner))
- found_someone = TRUE
playsound(owner, 'sound/magic/teleport_diss.ogg', 50, TRUE)
L.Beam(owner, "grabber_beam", time = 1 SECONDS, maxdistance = 9)
+ if(L.can_block_magic(MAGIC_RESISTANCE))
+ to_chat(L, "You shake off the tendrils that try to wrap around you!")
+ continue
+ found_someone = TRUE
L.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
if(found_someone)
owner.visible_message("The violet light around [owner] glows black... and shoots off to those around [owner.p_them()]!", "The tendrils around you cinch tightly... but then unwravel and fly at others!")
diff --git a/code/game/dna/mutations/mutation_powers.dm b/code/game/dna/mutations/mutation_powers.dm
index f9cb5574c82..078633409f4 100644
--- a/code/game/dna/mutations/mutation_powers.dm
+++ b/code/game/dna/mutations/mutation_powers.dm
@@ -301,6 +301,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
+ antimagic_flags = NONE
selection_activated_message = "Your mind grow cold. Click on a target to cast the spell."
selection_deactivated_message = "Your mind returns to normal."
@@ -372,6 +373,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = NONE
action_icon_state = "genetic_eat"
@@ -511,6 +513,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = NONE
action_icon_state = "genetic_jump"
var/leap_distance = 10
@@ -636,6 +639,7 @@
selection_deactivated_message = "Your body calms down again."
invocation_type = "none"
+ antimagic_flags = NONE
action_icon_state = "genetic_poly"
@@ -684,6 +688,7 @@
human_req = TRUE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = MAGIC_RESISTANCE_MIND
action_icon_state = "genetic_empath"
@@ -793,6 +798,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = NONE
action_icon_state = "genetic_morph"
@@ -983,6 +989,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = MAGIC_RESISTANCE_MIND
action_icon_state = "genetic_project"
@@ -1017,6 +1024,7 @@
base_cooldown = 0
clothes_req = FALSE
stat_allowed = CONSCIOUS
+ antimagic_flags = MAGIC_RESISTANCE_MIND
invocation_type = "none"
action_icon_state = "genetic_mindscan"
var/list/expanded_minds = list()
@@ -1105,6 +1113,7 @@
clothes_req = FALSE
stat_allowed = CONSCIOUS
invocation_type = "none"
+ antimagic_flags = MAGIC_RESISTANCE_MIND
action_icon_state = "genetic_view"
diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm
index 6f97e380e15..12b80aa8da5 100644
--- a/code/game/gamemodes/cult/blood_magic.dm
+++ b/code/game/gamemodes/cult/blood_magic.dm
@@ -385,6 +385,7 @@
var/uses = 1
var/health_cost = 0 //The amount of health taken from the user when invoking the spell
var/datum/action/innate/cult/blood_spell/source
+ var/antimagic_flags = MAGIC_RESISTANCE_HOLY
/obj/item/melee/blood_magic/Initialize(mapload, spell)
. = ..()
@@ -418,6 +419,11 @@
uses = 0
qdel(src)
return
+ if(M.can_block_magic(MAGIC_RESISTANCE_HOLY))
+ to_chat(user, "[M] absorbs your spell!")
+ uses = 0
+ qdel(src)
+ return
add_attack_logs(user, M, "used a cult spell ([src]) on")
M.lastattacker = user.real_name
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index ebe886ff10c..9592e8ecaf4 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -249,9 +249,8 @@ structure_check() searches for nearby cultist structures required for the invoca
qdel(src)
/mob/proc/null_rod_check() //The null rod, if equipped, will protect the holder from the effects of most runes
- var/obj/item/nullrod/N = locate() in src
- if(N)
- return N
+ if(can_block_magic(MAGIC_RESISTANCE_HOLY))
+ return TRUE
return FALSE
//Rite of Enlightenment: Converts a normal crewmember to the cult, or offer them as sacrifice if cant be converted.
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 87b88676d3c..90ba39865b7 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -13,6 +13,7 @@
base_cooldown = 0
var/uses //If we have multiple uses of the same power
var/auto_use_uses = TRUE //If we automatically use up uses on each activation
+ antimagic_flags = NONE
/datum/spell/ai_spell/create_new_targeting()
return new /datum/spell_targeting/self
diff --git a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
index df5df3aae74..d44adb00d17 100644
--- a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
+++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
@@ -212,6 +212,8 @@
return
hit = TRUE // to prevent double hits from the pull
. = ..()
+ if(!.)
+ return
for(var/atom/extinguish_target in range(2, src))
extinguish_target.extinguish_light(TRUE)
if(!isliving(target))
diff --git a/code/game/gamemodes/miniantags/guardian/host_actions.dm b/code/game/gamemodes/miniantags/guardian/host_actions.dm
index 4a4ff5144ba..e21f2509415 100644
--- a/code/game/gamemodes/miniantags/guardian/host_actions.dm
+++ b/code/game/gamemodes/miniantags/guardian/host_actions.dm
@@ -106,6 +106,7 @@
name = "Place Teleportation Beacon"
desc = "Mark a floor as your beacon point, allowing you to warp targets to it. Your beacon requires an anchor, will not work on space tiles."
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 300 SECONDS
action_icon_state = "no_state"
action_background_icon = 'icons/mob/guardian.dmi'
@@ -129,6 +130,7 @@
name = "Set Surveillance Snare"
desc = "Places an invisible Surveillance Snare on the ground, if someone walks over it you'll be alerted. Max of 6 snares active at a time"
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 3 SECONDS
action_icon_state = "no_state"
action_background_icon = 'icons/mob/guardian.dmi'
@@ -160,6 +162,7 @@
name = "Change battlecry"
desc = "Changes your battlecry."
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 1 SECONDS
action_icon_state = "no_state"
action_background_icon = 'icons/mob/guardian.dmi'
diff --git a/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm b/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm
index b8f2f1623bc..847ee5cce00 100644
--- a/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm
+++ b/code/game/gamemodes/miniantags/morph/spells/pass_airlock.dm
@@ -5,6 +5,7 @@
action_background_icon_state = "bg_morph"
action_icon_state = "morph_airlock"
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 10 SECONDS
selection_activated_message = "Click on an airlock to try pass it."
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
index d248e460ba2..97c86faf060 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
@@ -10,6 +10,7 @@
/datum/spell/pulse_demon
clothes_req = FALSE
+ antimagic_flags = NONE
action_background_icon_state = "bg_pulsedemon"
var/locked = TRUE
var/unlock_cost = 1 KJ
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index 0a167ec7724..ad6da8561d8 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -134,6 +134,7 @@
clothes_req = FALSE
action_icon_state = "r_transmit"
action_background_icon_state = "bg_revenant"
+ antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND
/datum/spell/revenant_transmit/create_new_targeting()
var/datum/spell_targeting/targeted/T = new()
@@ -165,6 +166,7 @@
var/unlock_amount = 100
/// How much essence it costs to use
var/cast_amount = 50
+ antimagic_flags = MAGIC_RESISTANCE_HOLY
/datum/spell/aoe/revenant/New()
..()
diff --git a/code/game/gamemodes/wizard/godhand.dm b/code/game/gamemodes/wizard/godhand.dm
index c9ffe3584e3..f75c5a692b8 100644
--- a/code/game/gamemodes/wizard/godhand.dm
+++ b/code/game/gamemodes/wizard/godhand.dm
@@ -16,6 +16,8 @@
throw_range = 0
throw_speed = 0
new_attack_chain = TRUE
+ /// Has it been blocked by antimagic? If so, abort.
+ var/blocked_by_antimagic = FALSE
/obj/item/melee/touch_attack/New(spell)
attached_spell = spell
@@ -37,6 +39,17 @@
to_chat(user, "You can't reach out!")
return FINISH_ATTACK
+/obj/item/melee/touch_attack/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ var/mob/mob_victim = target
+ if(istype(mob_victim) && mob_victim.can_block_magic(attached_spell.antimagic_flags))
+ to_chat(user, "[mob_victim] absorbs your spell!")
+ blocked_by_antimagic = TRUE
+ if(attached_spell && attached_spell.cooldown_handler)
+ attached_spell.cooldown_handler.start_recharge(attached_spell.cooldown_handler.recharge_duration * 0.5)
+ qdel(src)
+ return
+
/obj/item/melee/touch_attack/proc/handle_delete(mob/user)
if(catchphrase)
user.say(catchphrase)
@@ -53,9 +66,10 @@
icon_state = "disintegrate"
item_state = "disintegrate"
+
/obj/item/melee/touch_attack/disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //exploding after touching yourself would be bad
+ if(!proximity_flag || target == user || blocked_by_antimagic || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //exploding after touching yourself would be bad
return
var/mob/M = target
do_sparks(4, 0, M.loc) //no idea what the 0 is
@@ -72,7 +86,8 @@
/obj/item/melee/touch_attack/fleshtostone/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //getting hard after touching yourself would also be bad
+
+ if(!proximity_flag || target == user || blocked_by_antimagic || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //getting hard after touching yourself would also be bad
return
var/mob/living/L = target
L.Stun(4 SECONDS)
@@ -90,12 +105,14 @@
/obj/item/melee/touch_attack/plushify/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //There are better ways to get a good nights sleep in a bed.
+
+ if(!proximity_flag || target == user || blocked_by_antimagic || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //There are better ways to get a good nights sleep in a bed.
return
var/mob/living/L = target
L.plushify()
handle_delete(user)
+
/obj/item/melee/touch_attack/fake_disintegrate
name = "toy plastic hand"
desc = "This hand of mine glows with an awesome power! Ok, maybe just batteries."
@@ -107,7 +124,8 @@
/obj/item/melee/touch_attack/fake_disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //not exploding after touching yourself would be bad
+
+ if(!proximity_flag || target == user || blocked_by_antimagic || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //not exploding after touching yourself would be bad
return
do_sparks(4, 0, target.loc)
playsound(target.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
@@ -123,7 +141,8 @@
/obj/item/melee/touch_attack/cluwne/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //clowning around after touching yourself would unsurprisingly, be bad
+
+ if(!proximity_flag || target == user || blocked_by_antimagic || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //clowning around after touching yourself would unsurprisingly, be bad
return
if(iswizard(target))
diff --git a/code/game/gamemodes/wizard/magic_tarot.dm b/code/game/gamemodes/wizard/magic_tarot.dm
index 5f7eed073e9..b6d2cbc6140 100644
--- a/code/game/gamemodes/wizard/magic_tarot.dm
+++ b/code/game/gamemodes/wizard/magic_tarot.dm
@@ -232,6 +232,11 @@
qdel(src)
/obj/item/magic_tarot_card/proc/pre_activate(mob/user, atom/movable/thrower)
+ if(user != thrower) //Ignore antimagic stuff if the user is the thrower (aka self activation)
+ if(user.can_block_magic(our_tarot.antimagic_flags, 1))
+ visible_message("[src] burns up in a flash on contact with [user]!")
+ qdel(src)
+ return
has_been_activated = TRUE
forceMove(user)
var/obj/effect/temp_visual/card_preview/tarot/draft = new(user, "tarot_[our_tarot.card_icon]")
@@ -288,6 +293,8 @@
var/card_icon = "the_unknown"
/// Are we reversed? Used for the card back.
var/reversed = FALSE
+ /// What antimagic flags do we have?
+ var/antimagic_flags = MAGIC_RESISTANCE
/datum/tarot/proc/activate(mob/living/target)
stack_trace("A bugged tarot card was spawned and used. Please make an issue report! Type was [src.type]")
@@ -688,8 +695,11 @@
var/sparkle_path = /obj/effect/temp_visual/gravpush
for(var/turf/T in range(5, target)) //Done this way so things don't get thrown all around hilariously.
for(var/atom/movable/AM in T)
+ if(ismob(AM))
+ var/mob/victim_mob = AM
+ if(victim_mob.can_block_magic(antimagic_flags))
+ continue
thrown_atoms += AM
-
for(var/atom/movable/AM as anything in thrown_atoms)
if(AM == target || AM.anchored || (ismob(AM) && !isliving(AM)))
continue
@@ -729,6 +739,9 @@
/datum/tarot/reversed/the_empress/activate(mob/living/target)
for(var/mob/living/L in oview(9, target))
+ if(L.can_block_magic(antimagic_flags))
+ to_chat(L, "You feel calm for a second, but it quickly passes.")
+ continue
L.apply_status_effect(STATUS_EFFECT_PACIFIED)
/datum/tarot/reversed/the_emperor
@@ -763,6 +776,8 @@
for(var/mob/living/M in shuffle(orange(7, target)))
if(M.stat == DEAD) //Let us not have dead mobs be used to make a disco inferno.
continue
+ if(M.can_block_magic(antimagic_flags)) //Be spared!
+ continue
if(active_chasers >= 2)
return
var/obj/effect/temp_visual/hierophant/chaser/C = new(get_turf(target), target, M, 1, FALSE)
@@ -959,6 +974,7 @@
desc = "May you remember lost memories."
extended_desc = "will reveal the memories of everyone in range to the user."
card_icon = "the_moon?"
+ antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
/datum/tarot/reversed/the_moon/activate(mob/living/target)
for(var/mob/living/L in view(5, target)) //Shorter range as this kinda can give away antagonists, though that is also funny.
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index ab14142c562..22e844b1917 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -499,7 +499,9 @@
//Weapons and Armors
/datum/spellbook_entry/item/battlemage
name = "Battlemage Armor"
- desc = "An ensorceled spaceproof suit of protective yet light armor, protected by a powerful shield. The shield can completely negate 15 attacks before permanently failing."
+ desc = "An ensorceled spaceproof suit of protective yet light armor, protected by a powerful shield. The shield can completely negate 15 attacks before permanently failing. \
+ This armor grants you full protection from magical attacks, and allows you to cast magic despite that. However, this means it will also block wands or staffs of \
+ healing from working on you, and should be removed before application."
item_path = /obj/item/storage/box/wizard/hardsuit
limit = 1
category = "Weapons and Armors"
diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm
index 7c59c10efe1..b86ae670a39 100644
--- a/code/game/objects/effects/forcefields.dm
+++ b/code/game/objects/effects/forcefields.dm
@@ -17,6 +17,8 @@
/obj/effect/forcefield/wizard
var/mob/wizard
+ /// Flags for what antimagic can just ignore our forcefields
+ var/antimagic_flags = MAGIC_RESISTANCE
/obj/effect/forcefield/wizard/Initialize(mapload, mob/summoner)
. = ..()
@@ -25,6 +27,10 @@
/obj/effect/forcefield/wizard/CanPass(atom/movable/mover, border_dir)
if(mover == wizard)
return TRUE
+ if(isliving(mover))
+ var/mob/living/living_mover = mover
+ if(living_mover.can_block_magic(antimagic_flags, charge_cost = 0))
+ return TRUE
return FALSE
///////////Mimewalls///////////
diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm
index a1179c68167..0b567a426af 100644
--- a/code/game/objects/items/weapons/holy_weapons.dm
+++ b/code/game/objects/items/weapons/holy_weapons.dm
@@ -21,9 +21,12 @@
var/list/fluff_transformations = list()
/// Extra 'Holy' burn damage for ERT null rods
var/sanctify_force = 0
+ /// The antimagic type the nullrod has.
+ var/antimagic_type = MAGIC_RESISTANCE_HOLY
/obj/item/nullrod/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/anti_magic, antimagic_type)
if(!length(variant_names))
for(var/I in typesof(/obj/item/nullrod))
var/obj/item/nullrod/rod = I
@@ -37,11 +40,6 @@
/obj/item/nullrod/attack__legacy__attackchain(mob/M, mob/living/carbon/user)
..()
- var/datum/antagonist/vampire/V = M.mind?.has_antag_datum(/datum/antagonist/vampire)
- if(ishuman(M) && V && HAS_MIND_TRAIT(M, TRAIT_HOLY))
- if(!V.get_ability(/datum/vampire_passive/full))
- to_chat(M, "The nullrod's power interferes with your own!")
- V.adjust_nullification(30 + sanctify_force, 15 + sanctify_force)
if(!sanctify_force)
return
if(isliving(M))
@@ -593,6 +591,16 @@
to_chat(user, "Your prayer to [SSticker.Bible_deity_name] was interrupted.")
praying = FALSE
+
+/obj/item/nullrod/nazar
+ name = "nazar"
+ icon_state = "nazar"
+ item_state = null
+ desc = "A set of glass beads and amulet, which has been forged to provide powerful magic protection to the wielder."
+ force = 0
+ throwforce = 0
+ antimagic_type = ALL
+
/obj/item/nullrod/salt
name = "Holy Salt"
icon = 'icons/obj/food/containers.dmi'
diff --git a/code/modules/antagonists/mind_flayer/flayer_power.dm b/code/modules/antagonists/mind_flayer/flayer_power.dm
index b48c9c25ca9..481c3d0a78a 100644
--- a/code/modules/antagonists/mind_flayer/flayer_power.dm
+++ b/code/modules/antagonists/mind_flayer/flayer_power.dm
@@ -3,6 +3,7 @@
desc = "This spell needs a description!"
human_req = TRUE
clothes_req = FALSE
+ antimagic_flags = NONE
/// A reference to the owner mindflayer's antag datum.
var/datum/antagonist/mindflayer/flayer
diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm
index 3fe4dcc7660..b6e97c0c671 100644
--- a/code/modules/antagonists/vampire/vamp_datum.dm
+++ b/code/modules/antagonists/vampire/vamp_datum.dm
@@ -93,6 +93,7 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
mob_override.dna?.species.hunger_icon = initial(mob_override.dna.species.hunger_icon)
owner.current.alpha = 255
REMOVE_TRAITS_IN(owner.current, "vampire")
+ UnregisterSignal(owner, COMSIG_ATOM_HOLY_ATTACK)
#define BLOOD_GAINED_MODIFIER 0.5
@@ -362,8 +363,21 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
mob_override.dna?.species.hunger_icon = 'icons/mob/screen_hunger_vampire.dmi'
check_vampire_upgrade(FALSE)
+ RegisterSignal(mob_override, COMSIG_ATOM_HOLY_ATTACK, PROC_REF(holy_attack_reaction))
-
+/datum/antagonist/vampire/proc/holy_attack_reaction(mob/target, obj/item/source, mob/user, antimagic_flags)
+ SIGNAL_HANDLER // COMSIG_ATOM_HOLY_ATTACK
+ if(!HAS_MIND_TRAIT(user, TRAIT_HOLY)) // Sec officer with a nullrod, or miner with a talisman, does not get to do this
+ return
+ if(!source.force) // Needs force to work.
+ return
+ var/bonus_force = 0
+ if(istype(source, /obj/item/nullrod))
+ var/obj/item/nullrod/N = source
+ bonus_force = N.sanctify_force
+ if(!get_ability(/datum/vampire_passive/full))
+ to_chat(owner.current, "[source]'s power interferes with your own!")
+ adjust_nullification(30 + bonus_force, 15 + bonus_force)
/datum/antagonist/vampire/custom_blurb()
return "On the date [GLOB.current_date_string], at [station_time_timestamp()],\n in the [station_name()], [get_area_name(owner.current, TRUE)]...\nThe hunt begins again..."
diff --git a/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm b/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm
index d749bfccee3..c38b8523e3d 100644
--- a/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm
@@ -161,6 +161,10 @@
/datum/spell/vampire/switch_places/cast(list/targets, mob/user)
var/mob/living/target = targets[1]
+ if(target.can_block_magic(antimagic_flags))
+ to_chat(user, "The spell had no effect!")
+ to_chat(target, "You feel space bending, but it rapidly dissipates.")
+ return FALSE
var/turf/user_turf = get_turf(user)
var/turf/target_turf = get_turf(target)
if(!(SEND_SIGNAL(target, COMSIG_MOVABLE_TELEPORTING, user_turf) & COMPONENT_BLOCK_TELEPORT))
diff --git a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
index 20071ab2ebf..5207ccf7d97 100644
--- a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
@@ -157,6 +157,8 @@
/obj/item/projectile/magic/demonic_grasp/on_hit(atom/target, blocked, hit_zone)
. = ..()
+ if(!.)
+ return
if(!isliving(target))
return
var/mob/living/L = target
diff --git a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
index ff227c1774b..21e6b97f5fa 100644
--- a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
@@ -3,6 +3,9 @@
//Other vampires and thralls aren't affected
if(mind?.has_antag_datum(/datum/antagonist/vampire) || mind?.has_antag_datum(/datum/antagonist/mindslave/thrall))
return FALSE
+ /// Chaplains with their nullrod can block a full power vampire, but a chaplain by themselfs or a crew with a null rod can not.
+ if(can_block_magic(MAGIC_RESISTANCE_HOLY) && HAS_MIND_TRAIT(src, TRAIT_HOLY))
+ return FALSE
//Vampires who have reached their full potential can affect nearly everything
var/datum/antagonist/vampire/V = user?.mind.has_antag_datum(/datum/antagonist/vampire)
if(V?.get_ability(/datum/vampire_passive/full))
@@ -10,12 +13,15 @@
//Holy characters are resistant to vampire powers
if(HAS_MIND_TRAIT(src, TRAIT_HOLY))
return FALSE
+ if(can_block_magic(MAGIC_RESISTANCE_HOLY))
+ return FALSE
return TRUE
/datum/spell/vampire
action_background_icon_state = "bg_vampire"
human_req = TRUE
clothes_req = FALSE
+ antimagic_flags = MAGIC_RESISTANCE_HOLY
/// How much blood this ability costs to use
var/required_blood
var/deduct_blood_on_cast = TRUE
@@ -52,6 +58,7 @@
action_icon_state = "vampire_rejuvinate"
base_cooldown = 20 SECONDS
stat_allowed = UNCONSCIOUS
+ antimagic_flags = NONE // So. If you have a null rod on your person, you can't cast vampire spells. I would rather not have officers abuse this by putting a nullrod in their pocket or something to block rejuvinate.
/datum/spell/vampire/self/rejuvenate/cast(list/targets, mob/user = usr)
var/mob/living/U = user
diff --git a/code/modules/antagonists/zombie/zombie_spells.dm b/code/modules/antagonists/zombie/zombie_spells.dm
index 3739a953438..39d02fa9982 100644
--- a/code/modules/antagonists/zombie/zombie_spells.dm
+++ b/code/modules/antagonists/zombie/zombie_spells.dm
@@ -5,6 +5,7 @@
action_background_icon_state = "bg_vampire"
human_req = TRUE
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 0 SECONDS
var/list/our_claws = list()
var/infection_stage = 1 // mostly for adminbus and testing
diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm
index 6a4511534f1..7314f898497 100644
--- a/code/modules/clothing/suits/wiz_robe.dm
+++ b/code/modules/clothing/suits/wiz_robe.dm
@@ -200,6 +200,17 @@
/obj/item/clothing/suit/space/hardsuit/wizard/setup_shielding()
AddComponent(/datum/component/shielded, max_charges = 15, recharge_start_delay = 0 SECONDS)
+/obj/item/clothing/suit/space/hardsuit/wizard/equipped(mob/user, slot)
+ . = ..()
+ ADD_TRAIT(user, TRAIT_ANTIMAGIC, "[UID(src)]")
+ ADD_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, "[UID(src)]")
+
+/obj/item/clothing/suit/space/hardsuit/wizard/dropped(mob/user)
+ . = ..()
+ REMOVE_TRAIT(user, TRAIT_ANTIMAGIC, "[UID(src)]")
+ REMOVE_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, "[UID(src)]")
+
+
/obj/item/clothing/suit/space/hardsuit/wizard/arch
desc = "For the arch wizard in need of additional protection."
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
diff --git a/code/modules/mining/lavaland/loot/tendril_loot.dm b/code/modules/mining/lavaland/loot/tendril_loot.dm
index d0329bf4fcb..4cf0d24f8ce 100644
--- a/code/modules/mining/lavaland/loot/tendril_loot.dm
+++ b/code/modules/mining/lavaland/loot/tendril_loot.dm
@@ -338,6 +338,7 @@
max_charges = 1
flags = NOBLUDGEON
force = 18
+ antimagic_flags = NONE
/obj/item/ammo_casing/magic/hook
name = "hook"
@@ -384,7 +385,7 @@
//Immortality Talisman
/obj/item/immortality_talisman
- name = "Immortality Talisman"
+ name = "\improper Immortality Talisman"
desc = "A dread talisman that can render you completely invulnerable."
icon = 'icons/obj/lavaland/artefacts.dmi'
icon_state = "talisman"
@@ -393,6 +394,21 @@
actions_types = list(/datum/action/item_action/immortality)
var/cooldown = 0
+/obj/item/immortality_talisman/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/anti_magic, ALL)
+
+/obj/item/immortality_talisman/equipped(mob/user, slot)
+ ..()
+ if(slot != ITEM_SLOT_IN_BACKPACK)
+ var/user_UID = user.UID()
+ ADD_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, user_UID)
+
+/obj/item/immortality_talisman/dropped(mob/user, silent)
+ . = ..()
+ var/user_UID = user.UID()
+ REMOVE_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, user_UID)
+
/datum/action/item_action/immortality
name = "Immortality"
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index e4b1f52529d..89e0de5217f 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -184,6 +184,10 @@
)
hide_tail_by_species = list("Unathi", "Tajaran", "Vox", "Vulpkanin")
+/obj/item/clothing/suit/hooded/berserker/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/anti_magic, ALL, inventory_flags = ITEM_SLOT_OUTER_SUIT)
+
/obj/item/clothing/head/hooded/berserker
name = "berserker helmet"
desc = "Peering into the eyes of the helmet is enough to seal damnation."
diff --git a/code/modules/mob/living/carbon/superheroes.dm b/code/modules/mob/living/carbon/superheroes.dm
index a7f63bd6e07..cb497112049 100644
--- a/code/modules/mob/living/carbon/superheroes.dm
+++ b/code/modules/mob/living/carbon/superheroes.dm
@@ -143,6 +143,7 @@
desc = "Allows you to recruit a conscious, non-braindead, non-catatonic human to be part of the Greyshirts, your personal henchmen. This works on Assistants only and you can recruit a maximum of 3!."
base_cooldown = 450
clothes_req = FALSE
+ antimagic_flags = NONE
action_icon_state = "spell_greytide"
var/recruiting = 0
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index dd136add437..653a6c7cdb3 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -242,6 +242,7 @@
name = "Unfold/Fold Chassis"
desc = "Allows you to fold in/out of your mobile form."
clothes_req = FALSE
+ antimagic_flags = NONE
base_cooldown = 20 SECONDS
action_icon_state = "repairbot"
action_background_icon_state = "bg_tech_blue"
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm
index 05f9e655b73..cd32ea73e96 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm
@@ -63,6 +63,7 @@
gain_desc = "You can now charge at a target on screen, dealing massive damage and destroying structures."
base_cooldown = 30 SECONDS
clothes_req = FALSE
+ antimagic_flags = NONE
action_icon_state = "terror_prince"
/datum/spell/princely_charge/create_new_targeting()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 10f65af02b1..7583e3bf47e 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1428,3 +1428,90 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list(
if(user.incapacitated())
return
return relaydrive(user, direction)
+
+
+/**
+ * Checks to see if the mob can cast normal magic spells.
+ *
+ * args:
+ * * magic_flags (optional) A bitfield with the type of magic being cast (see flags at: /datum/component/anti_magic)
+**/
+/mob/proc/can_cast_magic(magic_flags = MAGIC_RESISTANCE)
+ if(magic_flags == NONE) // magic with the NONE flag can always be cast
+ return TRUE
+
+ var/restrict_magic_flags = SEND_SIGNAL(src, COMSIG_MOB_RESTRICT_MAGIC, magic_flags)
+ return restrict_magic_flags == NONE
+
+/**
+ * Checks to see if the mob can block magic
+ *
+ * args:
+ * * casted_magic_flags (optional) A bitfield with the types of magic resistance being checked (see flags at: /datum/component/anti_magic)
+ * * charge_cost (optional) The cost of charge to block a spell that will be subtracted from the protection used
+**/
+/mob/proc/can_block_magic(casted_magic_flags = MAGIC_RESISTANCE, charge_cost = 1)
+ if(casted_magic_flags == NONE) // magic with the NONE flag is immune to blocking
+ return FALSE
+
+ // A list of all things which are providing anti-magic to us
+ var/list/antimagic_sources = list()
+ var/is_magic_blocked = FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, casted_magic_flags, charge_cost, antimagic_sources) & COMPONENT_MAGIC_BLOCKED)
+ is_magic_blocked = TRUE
+ if(HAS_TRAIT(src, TRAIT_ANTIMAGIC))
+ is_magic_blocked = TRUE
+ if((casted_magic_flags & MAGIC_RESISTANCE_HOLY) && HAS_TRAIT(src, TRAIT_HOLY))
+ is_magic_blocked = TRUE
+
+ if(is_magic_blocked && charge_cost > 0 && !HAS_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC))
+ on_block_magic_effects(casted_magic_flags, antimagic_sources)
+
+ return is_magic_blocked
+
+/// Called whenever a magic effect with a charge cost is blocked and we haven't recently blocked magic.
+/mob/proc/on_block_magic_effects(magic_flags, list/antimagic_sources)
+ return
+
+/mob/living/on_block_magic_effects(magic_flags, list/antimagic_sources)
+ ADD_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC, MAGIC_TRAIT)
+ addtimer(CALLBACK(src, PROC_REF(remove_recent_magic_block)), 6 SECONDS)
+
+ var/mutable_appearance/antimagic_effect
+ var/antimagic_color
+ var/atom/antimagic_source = length(antimagic_sources) ? pick(antimagic_sources) : src
+
+ if(magic_flags & MAGIC_RESISTANCE)
+ visible_message(
+ "[src] pulses red as [ismob(antimagic_source) ? p_they() : antimagic_source] absorbs magic energy!",
+ "An intense magical aura pulses around [ismob(antimagic_source) ? "you" : antimagic_source] as it dissipates into the air!",
+ )
+ antimagic_effect = mutable_appearance('icons/effects/effects.dmi', "shield-red", ABOVE_MOB_LAYER)
+ antimagic_color = LIGHT_COLOR_BLOOD_MAGIC
+ playsound(src, 'sound/magic/magic_block.ogg', 50, TRUE)
+
+ else if(magic_flags & MAGIC_RESISTANCE_HOLY)
+ visible_message(
+ "[src] starts to glow as [ismob(antimagic_source) ? p_they() : antimagic_source] emits a halo of light!",
+ "A feeling of warmth washes over [ismob(antimagic_source) ? "you" : antimagic_source] as rays of light surround your body and protect you!",
+ )
+ antimagic_effect = mutable_appearance('icons/mob/genetics.dmi', "servitude", ABOVE_MOB_LAYER)
+ antimagic_color = LIGHT_COLOR_HOLY_MAGIC
+ playsound(src, 'sound/magic/magic_block_holy.ogg', 50, TRUE)
+
+ else if(magic_flags & MAGIC_RESISTANCE_MIND)
+ visible_message(
+ "[src] forehead shines as [ismob(antimagic_source) ? p_they() : antimagic_source] repulses magic from their mind!",
+ "A feeling of cold splashes on [ismob(antimagic_source) ? "you" : antimagic_source] as your forehead reflects magic usering your mind!",
+ )
+ antimagic_effect = mutable_appearance('icons/mob/genetics.dmi', "telekinesishead", ABOVE_MOB_LAYER)
+ antimagic_color = LIGHT_COLOR_DARK_BLUE
+ playsound(src, 'sound/magic/magic_block_mind.ogg', 50, TRUE)
+
+ mob_light(_color = antimagic_color, _range = 2, _power = 2, _duration = 5 SECONDS)
+ add_overlay(antimagic_effect)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, cut_overlay), antimagic_effect), 5 SECONDS)
+
+/mob/living/proc/remove_recent_magic_block()
+ REMOVE_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC, MAGIC_TRAIT)
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
index 74176c1a164..af6292a6284 100644
--- a/code/modules/mod/mod_types.dm
+++ b/code/modules/mod/mod_types.dm
@@ -366,12 +366,17 @@
var/insignia_type = /obj/item/mod/module/insignia
/// Additional module we add, as a treat.
var/additional_module
+ /// Inquisitorial module, as we have reached that point.
+ var/inquisitorial_module
/obj/item/mod/control/pre_equipped/responsory/Initialize(mapload, new_theme, new_skin, new_core)
applied_modules.Insert(1, insignia_type)
if(additional_module)
applied_modules += additional_module
default_pins += additional_module
+ if(inquisitorial_module)
+ applied_modules += inquisitorial_module
+
return ..()
/obj/item/mod/control/pre_equipped/responsory/commander
@@ -410,9 +415,10 @@
insignia_type = /obj/item/mod/module/insignia/chaplain
additional_module = /obj/item/mod/module/injector
-/// Diffrent look, as well as magic proof on TG. We don't have the magic proof stuff here, but it's perfect for inqusitors. Or if you want to give your ERT a fancy look.
+/// Diffrent look, as well as magic proof. It's perfect for inqusitors. Or if you want to give your ERT a fancy look. At this time, the other ones are unused, and frankly I don't like the idea of antimagic gamma.
/obj/item/mod/control/pre_equipped/responsory/inquisitory
applied_skin = "inquisitory"
+ inquisitorial_module = /obj/item/mod/module/anti_magic
/obj/item/mod/control/pre_equipped/responsory/inquisitory/commander
insignia_type = /obj/item/mod/module/insignia/commander
diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm
index f33db48aa27..002de3f3b30 100644
--- a/code/modules/mod/modules/modules_antag.dm
+++ b/code/modules/mod/modules/modules_antag.dm
@@ -527,6 +527,24 @@
/obj/item/mod/module/energy_shield/gamma
shield_icon = "shield-old"
+///Magic Nullifier - Protects you from magic.
+/obj/item/mod/module/anti_magic
+ name = "MOD magic nullifier module"
+ desc = "A series of obsidian rods installed into critical points around the suit, \
+ vibrated at a certain low frequency to enable them to resonate. \
+ This creates a low-range, yet strong, magic nullification field around the user, \
+ aided by a full replacement of the suit's normal coolant with holy water. \
+ Spells will spall right off this field, though it'll do nothing to help others believe you about all this."
+ icon_state = "magic_nullifier"
+ removable = FALSE
+ incompatible_modules = list(/obj/item/mod/module/anti_magic)
+
+/obj/item/mod/module/anti_magic/on_suit_activation()
+ ADD_TRAIT(mod.wearer, TRAIT_ANTIMAGIC, "[UID(src)]")
+
+/obj/item/mod/module/anti_magic/on_suit_deactivation(deleting = FALSE)
+ REMOVE_TRAIT(mod.wearer, TRAIT_ANTIMAGIC, "[UID(src)]")
+
/obj/item/mod/module/anomaly_locked/teslawall
name = "MOD arc-shield module" // temp
desc = "A module that uses a flux core to project an unstable protective shield." //change
diff --git a/code/modules/projectiles/guns/chaos_bolt.dm b/code/modules/projectiles/guns/chaos_bolt.dm
index fab8d9955f4..cd197bd8275 100644
--- a/code/modules/projectiles/guns/chaos_bolt.dm
+++ b/code/modules/projectiles/guns/chaos_bolt.dm
@@ -18,7 +18,8 @@
/obj/item/projectile/magic/chaos/on_hit(atom/target, blocked = 0)
. = ..()
-
+ if(!.)
+ return
if(iswallturf(target) || isobj(target))
target.color = pick(GLOB.random_color_list)
return
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index c542ffa0041..795e3176086 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -15,6 +15,7 @@
var/can_charge = TRUE
var/ammo_type
var/no_den_usage
+ var/antimagic_flags = MAGIC_RESISTANCE
origin_tech = null
clumsy_check = FALSE
trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses magic instead
@@ -31,6 +32,9 @@
return
else
no_den_usage = 0
+ if(!user.can_cast_magic(antimagic_flags))
+ to_chat(user, "[src] whizzles quietly.")
+ return FALSE
..()
/obj/item/gun/magic/can_shoot()
diff --git a/code/modules/projectiles/projectile/magic_projectiles.dm b/code/modules/projectiles/projectile/magic_projectiles.dm
index 99c40d5929c..7f06491c7e9 100644
--- a/code/modules/projectiles/projectile/magic_projectiles.dm
+++ b/code/modules/projectiles/projectile/magic_projectiles.dm
@@ -6,6 +6,28 @@
nodamage = 1
armour_penetration_percentage = 100
flag = MAGIC
+ /// determines what type of antimagic can block the spell projectile
+ var/antimagic_flags = MAGIC_RESISTANCE
+ /// determines the drain cost on the antimagic item
+ var/antimagic_charge_cost = 1
+
+/obj/item/projectile/magic/prehit(atom/target)
+ if(isliving(target))
+ var/mob/living/victim = target
+ if(victim.can_block_magic(antimagic_flags, antimagic_charge_cost))
+ visible_message("[src] fizzles on contact with [victim]!")
+ damage = 0
+ nodamage = 1
+ return FALSE
+ return ..()
+
+/obj/item/projectile/magic/on_hit(atom/target, blocked, hit_zone)
+ if(isliving(target))
+ var/mob/living/victim = target
+ if(victim.can_block_magic(antimagic_flags, antimagic_charge_cost)) // Yes we have to check this twice welcome to bullet hell code
+ return FALSE
+ return ..()
+
/obj/item/projectile/magic/death
name = "bolt of death"
@@ -21,7 +43,7 @@
muzzle_flash_range = 2
muzzle_flash_color_override = LIGHT_COLOR_PURPLE
impact_light_intensity = 7
- impact_light_range = 2.5
+ impact_light_range = 2.5
impact_light_color_override = LIGHT_COLOR_PURPLE
/obj/item/projectile/magic/fireball
@@ -41,6 +63,8 @@
/obj/item/projectile/magic/death/on_hit(mob/living/carbon/target)
. = ..()
+ if(!.)
+ return .
if(isliving(target))
if(target.mob_biotypes & MOB_UNDEAD) //negative energy heals the undead
if(target.revive())
@@ -75,6 +99,8 @@
/obj/item/projectile/magic/fireball/on_hit(target)
. = ..()
+ if(!.)
+ return .
var/turf/T = get_turf(target)
explosion(T, exp_devastate, exp_heavy, exp_light, exp_flash, 0, flame_range = exp_fire)
if(ismob(target)) //multiple flavors of pain
@@ -95,6 +121,8 @@
/obj/item/projectile/magic/resurrection/on_hit(mob/living/carbon/target)
. = ..()
+ if(!.)
+ return .
if(ismob(target))
if(target.mob_biotypes & MOB_UNDEAD) //positive energy harms the undead
target.death(FALSE)
@@ -121,6 +149,8 @@
/obj/item/projectile/magic/teleport/on_hit(mob/target)
. = ..()
+ if(!.)
+ return .
var/teleammount = 0
var/teleloc = target
if(!isturf(target))
@@ -142,6 +172,8 @@
/obj/item/projectile/magic/door/on_hit(atom/target)
. = ..()
+ if(!.)
+ return .
var/atom/T = target.loc
if(isturf(target) && target.density)
if(!(istype(target, /turf/simulated/wall/indestructible)))
@@ -179,6 +211,8 @@
/obj/item/projectile/magic/change/on_hit(atom/change)
. = ..()
+ if(!.)
+ return .
wabbajack(change)
GLOBAL_LIST_INIT(wabbajack_hostile_animals, list(
@@ -380,6 +414,9 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list(
SpinAnimation()
/obj/item/projectile/magic/slipping/on_hit(atom/target, blocked = 0)
+ . = ..()
+ if(!.)
+ return .
if(ishuman(target))
var/mob/living/carbon/human/H = target
H.slip(src, slip_weaken, 0, FALSE, TRUE) //Slips even with noslips/magboots on. NO ESCAPE!
@@ -394,7 +431,6 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list(
to_chat(target, "You get splatted by [src].")
L.Weaken(slip_weaken)
L.Stun(slip_stun)
- . = ..()
/obj/item/projectile/magic/arcane_barrage
name = "arcane bolt"
diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm
index b9084c82162..a96307d18a7 100644
--- a/code/modules/surgery/organs/augments_internal.dm
+++ b/code/modules/surgery/organs/augments_internal.dm
@@ -506,6 +506,7 @@
clothes_req = FALSE
invocation = "none"
invocation_type = "none"
+ antimagic_flags = NONE
selection_activated_message = "You warm up your Binyat deck, there's an idle buzzing at the back of your mind as it awaits a target."
selection_deactivated_message = "Your hacking deck makes an almost disappointed sounding buzz at the back of your mind as it powers down."
action_icon_state = "hackerman"
diff --git a/icons/mob/genetics.dmi b/icons/mob/genetics.dmi
new file mode 100644
index 00000000000..cb5a3df029c
Binary files /dev/null and b/icons/mob/genetics.dmi differ
diff --git a/icons/obj/weapons/magical_weapons.dmi b/icons/obj/weapons/magical_weapons.dmi
index f8be67a8c08..154cd879015 100644
Binary files a/icons/obj/weapons/magical_weapons.dmi and b/icons/obj/weapons/magical_weapons.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 7db88875bf9..a508f9c8cb5 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -83,6 +83,7 @@
#include "code\__DEFINES\lighting_defines.dm"
#include "code\__DEFINES\logs.dm"
#include "code\__DEFINES\machines.dm"
+#include "code\__DEFINES\magic_defines.dm"
#include "code\__DEFINES\mappers.dm"
#include "code\__DEFINES\martial_arts.dm"
#include "code\__DEFINES\MC.dm"
@@ -428,6 +429,7 @@
#include "code\datums\cache\crew.dm"
#include "code\datums\cache\powermonitor.dm"
#include "code\datums\components\_component.dm"
+#include "code\datums\components\anti_magic.dm"
#include "code\datums\components\boomerang.dm"
#include "code\datums\components\boss_music.dm"
#include "code\datums\components\caltrop.dm"
@@ -930,8 +932,8 @@
#include "code\game\machinery\computer\prisoner.dm"
#include "code\game\machinery\computer\robot_control.dm"
#include "code\game\machinery\computer\security_records.dm"
-#include "code\game\machinery\computer\sm_monitor.dm"
#include "code\game\machinery\computer\singulo_monitor.dm"
+#include "code\game\machinery\computer\sm_monitor.dm"
#include "code\game\machinery\computer\station_alert.dm"
#include "code\game\machinery\computer\arcade_games\recruiter.dm"
#include "code\game\machinery\doors\airlock.dm"
diff --git a/sound/magic/magic_block.ogg b/sound/magic/magic_block.ogg
new file mode 100644
index 00000000000..e9c4ffdca94
Binary files /dev/null and b/sound/magic/magic_block.ogg differ
diff --git a/sound/magic/magic_block_holy.ogg b/sound/magic/magic_block_holy.ogg
new file mode 100644
index 00000000000..eb34f01a896
Binary files /dev/null and b/sound/magic/magic_block_holy.ogg differ
diff --git a/sound/magic/magic_block_mind.ogg b/sound/magic/magic_block_mind.ogg
new file mode 100644
index 00000000000..3b824da3e4c
Binary files /dev/null and b/sound/magic/magic_block_mind.ogg differ