diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 5496b62277c..d043f2ecd86 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1248,3 +1248,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait given by /datum/component/germ_sensitive #define TRAIT_GERM_SENSITIVE "germ_sensitive" + +/// This atom can have spells cast from it if a mob is within it +/// This means the "caster" of the spell is changed to the mob's loc +/// Note this doesn't mean all spells are guaranteed to work or the mob is guaranteed to cast +#define TRAIT_CASTABLE_LOC "castable_loc" diff --git a/code/datums/mutations/cold.dm b/code/datums/mutations/cold.dm index f999eed421d..57c4f854fc7 100644 --- a/code/datums/mutations/cold.dm +++ b/code/datums/mutations/cold.dm @@ -18,6 +18,7 @@ item_type = /obj/item/stack/sheet/mineral/snow delete_old = FALSE + delete_on_failure = FALSE /datum/mutation/human/cryokinesis name = "Cryokinesis" diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index c5e1aa39f94..671e3efb197 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -535,7 +535,7 @@ . = ..() /// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Open up when someone casts knock. -/obj/machinery/door/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster) +/obj/machinery/door/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, atom/caster) SIGNAL_HANDLER INVOKE_ASYNC(src, PROC_REF(open)) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index 71b6080f6ce..c748d70e6fc 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -14,6 +14,10 @@ var/flush = FALSE var/mob/living/silicon/ai/AI +/obj/item/aicard/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_CASTABLE_LOC, INNATE_TRAIT) + /obj/item/aicard/Destroy(force) if(AI) AI.ghostize(can_reenter_corpse = FALSE) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index fc0f9e2937f..ee9bbf68f72 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1132,7 +1132,7 @@ return COMSIG_CARBON_SHOVE_HANDLED /// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted. -/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster) +/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, atom/caster) SIGNAL_HANDLER locked = FALSE diff --git a/code/modules/antagonists/heretic/magic/aggressive_spread.dm b/code/modules/antagonists/heretic/magic/aggressive_spread.dm index c15ae041a47..de1233382f6 100644 --- a/code/modules/antagonists/heretic/magic/aggressive_spread.dm +++ b/code/modules/antagonists/heretic/magic/aggressive_spread.dm @@ -17,11 +17,7 @@ aoe_radius = 3 /datum/action/cooldown/spell/aoe/rust_conversion/get_things_to_cast_on(atom/center) - var/list/things = list() - for(var/turf/nearby_turf in range(aoe_radius, center)) - things += nearby_turf - - return things + return RANGE_TURFS(aoe_radius, center) /datum/action/cooldown/spell/aoe/rust_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster) // We have less chance of rusting stuff that's further diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm index 18f81324c09..be59f22d313 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/antagonists/revenant/revenant_abilities.dm @@ -180,11 +180,7 @@ return TRUE /datum/action/cooldown/spell/aoe/revenant/get_things_to_cast_on(atom/center) - var/list/things = list() - for(var/turf/nearby_turf in range(aoe_radius, center)) - things += nearby_turf - - return things + return RANGE_TURFS(aoe_radius, center) /datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/simple_animal/revenant/cast_on) . = ..() diff --git a/code/modules/antagonists/wizard/equipment/enchanted_clown_suit.dm b/code/modules/antagonists/wizard/equipment/enchanted_clown_suit.dm index 3222deecd84..62397127693 100644 --- a/code/modules/antagonists/wizard/equipment/enchanted_clown_suit.dm +++ b/code/modules/antagonists/wizard/equipment/enchanted_clown_suit.dm @@ -9,6 +9,7 @@ cooldown_time = 30 SECONDS cooldown_reduction_per_rank = 2 SECONDS delete_old = FALSE + delete_on_failure = FALSE /// Amount of time it takes you to rummage around in there var/cast_time = 3 SECONDS /// True while currently casting the spell @@ -56,7 +57,7 @@ return . | SPELL_CANCEL_CAST casting = FALSE -/datum/action/cooldown/spell/conjure_item/clown_pockets/make_item() +/datum/action/cooldown/spell/conjure_item/clown_pockets/make_item(atom/caster) item_type = pick_weight(clown_items) return ..() diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm index a1b25312ecf..ef930cc2558 100644 --- a/code/modules/pai/card.dm +++ b/code/modules/pai/card.dm @@ -69,6 +69,7 @@ update_appearance() SSpai.pai_card_list += src + ADD_TRAIT(src, TRAIT_CASTABLE_LOC, INNATE_TRAIT) /obj/item/pai_card/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is staring sadly at [src]! [user.p_They()] can't keep living without real human intimacy!")) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 2ad318db529..0f4eba1f7f4 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -142,7 +142,9 @@ /datum/action/cooldown/spell/PreActivate(atom/target) if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) return FALSE - if(!is_valid_target(target)) + if(target == owner) + target = get_caster_from_target(target) + if(isnull(target) || !is_valid_target(target)) return FALSE return Activate(target) @@ -210,10 +212,6 @@ to_chat(owner, span_warning("[src] can't be cast in this state!")) return FALSE - // Being put into a card form breaks a lot of spells, so we'll just forbid them in these states - if(ispAI(owner) || (isAI(owner) && istype(owner.loc, /obj/item/aicard))) - return FALSE - return TRUE /** @@ -226,6 +224,24 @@ /datum/action/cooldown/spell/proc/is_valid_target(atom/cast_on) return TRUE +/** + * Used to get the cast_on atom if a self cast spell is being cast. + * + * Allows for some atoms to be used as casting sources if a spell caster is located within. + */ +/datum/action/cooldown/spell/proc/get_caster_from_target(atom/target) + var/atom/cast_loc = target.loc + if(isnull(cast_loc)) + return null // No magic in nullspace + + if(isturf(cast_loc)) + return target // They're just standing around, proceed as normal + + if(HAS_TRAIT(cast_loc, TRAIT_CASTABLE_LOC)) + return cast_loc // They're in an atom which allows casting, so redirect the caster to loc + + return null + // The actual cast chain occurs here, in Activate(). // You should generally not be overriding or extending Activate() for spells. // Defer to any of the cast chain procs instead. diff --git a/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm index 4d16ae37971..92442bd0e40 100644 --- a/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm +++ b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm @@ -12,6 +12,9 @@ /// The radius of the aoe. var/aoe_radius = 7 +/datum/action/cooldown/spell/aoe/is_valid_target(atom/cast_on) + return isturf(cast_on.loc) + // At this point, cast_on == owner. Either works. // Don't extend this for your spell! Look at cast_on_thing_in_aoe. /datum/action/cooldown/spell/aoe/cast(atom/cast_on) diff --git a/code/modules/spells/spell_types/aoe_spell/area_conversion.dm b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm index f75c3958685..a03b4c9ab21 100644 --- a/code/modules/spells/spell_types/aoe_spell/area_conversion.dm +++ b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm @@ -16,11 +16,7 @@ aoe_radius = 2 /datum/action/cooldown/spell/aoe/area_conversion/get_things_to_cast_on(atom/center) - var/list/things = list() - for(var/turf/nearby_turf in range(aoe_radius, center)) - things += nearby_turf - - return things + return RANGE_TURFS(aoe_radius, center) /datum/action/cooldown/spell/aoe/area_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster) playsound(victim, 'sound/items/welder.ogg', 75, TRUE) diff --git a/code/modules/spells/spell_types/aoe_spell/knock.dm b/code/modules/spells/spell_types/aoe_spell/knock.dm index fd9e4503de8..ede1462633b 100644 --- a/code/modules/spells/spell_types/aoe_spell/knock.dm +++ b/code/modules/spells/spell_types/aoe_spell/knock.dm @@ -13,6 +13,23 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC aoe_radius = 3 +/datum/action/cooldown/spell/aoe/knock/get_caster_from_target(atom/target) + if(istype(target.loc, /obj/structure/closet)) + return target + + return ..() + +/datum/action/cooldown/spell/aoe/knock/is_valid_target(atom/cast_on) + return ..() || istype(cast_on.loc, /obj/structure/closet) + +/datum/action/cooldown/spell/aoe/knock/cast(atom/cast_on) + if(istype(cast_on.loc, /obj/structure/closet)) + var/obj/structure/closet/open_closet = cast_on.loc + open_closet.locked = FALSE + open_closet.open() + + return ..() + /datum/action/cooldown/spell/aoe/knock/get_things_to_cast_on(atom/center) return RANGE_TURFS(aoe_radius, center) diff --git a/code/modules/spells/spell_types/aoe_spell/repulse.dm b/code/modules/spells/spell_types/aoe_spell/repulse.dm index d0074a9f386..259f20ee9ba 100644 --- a/code/modules/spells/spell_types/aoe_spell/repulse.dm +++ b/code/modules/spells/spell_types/aoe_spell/repulse.dm @@ -6,6 +6,23 @@ /// The moveforce of the throw done by the repulsion. var/repulse_force = MOVE_FORCE_EXTREMELY_STRONG +/datum/action/cooldown/spell/aoe/repulse/get_caster_from_target(atom/target) + if(istype(target.loc, /obj/structure/closet)) + return target + + return ..() + +/datum/action/cooldown/spell/aoe/repulse/is_valid_target(atom/cast_on) + return ..() || istype(cast_on.loc, /obj/structure/closet) + +/datum/action/cooldown/spell/aoe/repulse/cast(atom/cast_on) + if(istype(cast_on.loc, /obj/structure/closet)) + var/obj/structure/closet/open_closet = cast_on.loc + open_closet.open(force = TRUE) + open_closet.visible_message(span_warning("[open_closet] suddenly flies open!")) + + return ..() + /datum/action/cooldown/spell/aoe/repulse/get_things_to_cast_on(atom/center) var/list/things = list() for(var/atom/movable/nearby_movable in view(aoe_radius, center)) @@ -44,7 +61,13 @@ to_chat(victim, span_userdanger("You're thrown back by [caster]!")) // So stuff gets tossed around at the same time. - victim.safe_throw_at(throwtarget, ((clamp((max_throw - (clamp(dist_from_caster - 2, 0, dist_from_caster))), 3, max_throw))), 1, caster, force = repulse_force) + victim.safe_throw_at( + target = throwtarget, + range = clamp((max_throw - (clamp(dist_from_caster - 2, 0, dist_from_caster))), 3, max_throw), + speed = 1, + thrower = ismob(caster) ? caster : null, + force = repulse_force, + ) /datum/action/cooldown/spell/aoe/repulse/wizard name = "Repulse" diff --git a/code/modules/spells/spell_types/conjure/_conjure.dm b/code/modules/spells/spell_types/conjure/_conjure.dm index ffab0887111..3afe7c52557 100644 --- a/code/modules/spells/spell_types/conjure/_conjure.dm +++ b/code/modules/spells/spell_types/conjure/_conjure.dm @@ -16,6 +16,9 @@ /// If TRUE, no two summons can be spawned in the same turf. var/summon_respects_prev_spawn_points = TRUE +/datum/action/cooldown/spell/conjure/is_valid_target(atom/cast_on) + return isturf(cast_on.loc) + /datum/action/cooldown/spell/conjure/cast(atom/cast_on) . = ..() var/list/to_summon_in = list() diff --git a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm index ad460964923..7e39f778391 100644 --- a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -8,6 +8,10 @@ var/delete_old = TRUE /// List of weakrefs to items summoned var/list/datum/weakref/item_refs + /// If TRUE, deletes the item if no mob picks it up on cast + var/delete_on_failure = TRUE + /// If TRUE, requires the caster be able to pick it up afterwards + var/requires_hands = FALSE /datum/action/cooldown/spell/conjure_item/Destroy() // If we delete_old, clean up all of our items on delete @@ -20,28 +24,56 @@ return ..() -/datum/action/cooldown/spell/conjure_item/is_valid_target(atom/cast_on) - return iscarbon(cast_on) +/datum/action/cooldown/spell/conjure_item/can_cast_spell(feedback) + . = ..() + if(!.) + return FALSE -/datum/action/cooldown/spell/conjure_item/cast(mob/living/carbon/cast_on) + if(!requires_hands) + return TRUE + + if(!isliving(owner)) + return FALSE + + var/mob/living/living_owner = owner + if(living_owner.usable_hands < 1) + if(feedback) + owner.balloon_alert(owner, "no free hands!") + return FALSE + + return TRUE + +/datum/action/cooldown/spell/conjure_item/is_valid_target(atom/cast_on) + if(!requires_hands) + return TRUE + if(!isliving(cast_on)) + return FALSE + var/mob/living/living_cast_on = cast_on + return living_cast_on.usable_hands >= 1 + +/datum/action/cooldown/spell/conjure_item/cast(atom/cast_on) if(delete_old && LAZYLEN(item_refs)) QDEL_LAZYLIST(item_refs) - var/obj/item/existing_item = cast_on.get_active_held_item() - if(existing_item) - cast_on.dropItemToGround(existing_item) + var/mob/mob_caster = cast_on + if(istype(mob_caster)) + var/obj/item/existing_item = mob_caster.get_active_held_item() + if(existing_item) + mob_caster.dropItemToGround(existing_item) - var/obj/item/created = make_item() + var/obj/item/created = make_item(cast_on) if(QDELETED(created)) CRASH("[type] tried to create an item, but failed. It's item type is [item_type].") - cast_on.put_in_hands(created, del_on_fail = TRUE) + if(istype(mob_caster)) + mob_caster.put_in_hands(created, del_on_fail = delete_on_failure) + return ..() /// Instantiates the item we're conjuring and returns it. -/// Item is made in nullspace and moved out in cast(). -/datum/action/cooldown/spell/conjure_item/proc/make_item() - var/obj/item/made_item = new item_type() +/// Item is made in at the caster's. +/datum/action/cooldown/spell/conjure_item/proc/make_item(atom/caster) + var/obj/item/made_item = new item_type(caster.loc) LAZYADD(item_refs, WEAKREF(made_item)) return made_item diff --git a/code/modules/spells/spell_types/conjure_item/infinite_guns.dm b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm index a1e35c52c26..70ad834f33d 100644 --- a/code/modules/spells/spell_types/conjure_item/infinite_guns.dm +++ b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm @@ -8,6 +8,7 @@ item_type = /obj/item/gun/ballistic/rifle // Enchanted guns self delete / do wacky stuff, anyways delete_old = FALSE + requires_hands = TRUE /datum/action/cooldown/spell/conjure_item/infinite_guns/Remove(mob/living/remove_from) var/obj/item/existing = remove_from.is_holding_item_of_type(item_type) @@ -18,8 +19,8 @@ // Because enchanted guns self-delete and regenerate themselves, // override make_item here and let's not bother with tracking their weakrefs. -/datum/action/cooldown/spell/conjure_item/infinite_guns/make_item() - return new item_type() +/datum/action/cooldown/spell/conjure_item/infinite_guns/make_item(atom/caster) + return new item_type(caster.loc) /datum/action/cooldown/spell/conjure_item/infinite_guns/gun name = "Lesser Summon Guns" diff --git a/code/modules/spells/spell_types/conjure_item/invisible_box.dm b/code/modules/spells/spell_types/conjure_item/invisible_box.dm index af6d5586af2..42da02121d3 100644 --- a/code/modules/spells/spell_types/conjure_item/invisible_box.dm +++ b/code/modules/spells/spell_types/conjure_item/invisible_box.dm @@ -30,7 +30,7 @@ . = ..() invocation = span_notice("[cast_on] moves [cast_on.p_their()] hands in the shape of a cube, pressing a box out of the air.") -/datum/action/cooldown/spell/conjure_item/invisible_box/make_item() +/datum/action/cooldown/spell/conjure_item/invisible_box/make_item(atom/caster) . = ..() var/obj/item/made_box = . made_box.alpha = 255 diff --git a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm index 5901c268c2c..cffddf7e0ce 100644 --- a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm +++ b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm @@ -9,6 +9,7 @@ spell_max_level = 1 item_type = /obj/item/spellpacket/lightningbolt + requires_hands = TRUE /datum/action/cooldown/spell/conjure_item/spellpacket/cast(mob/living/carbon/cast_on) . = ..() diff --git a/code/modules/spells/spell_types/conjure_item/snowball.dm b/code/modules/spells/spell_types/conjure_item/snowball.dm index ffd6d8d5e54..80b4aad9361 100644 --- a/code/modules/spells/spell_types/conjure_item/snowball.dm +++ b/code/modules/spells/spell_types/conjure_item/snowball.dm @@ -8,3 +8,4 @@ antimagic_flags = NONE cooldown_time = 1.5 SECONDS item_type = /obj/item/toy/snowball + requires_hands = TRUE diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index b18669d6f9e..3d1c5b91b81 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -133,10 +133,11 @@ // cast_on is a turf, or atom target, that we clicked on to fire at. /datum/action/cooldown/spell/pointed/projectile/cast(atom/cast_on) . = ..() - if(!isturf(owner.loc)) + var/atom/caster = get_caster_from_target(owner) + if(!isturf(caster.loc)) return FALSE - var/turf/caster_turf = get_turf(owner) + var/turf/caster_turf = caster.loc // Get the tile infront of the caster, based on their direction var/turf/caster_front_turf = get_step(owner, owner.dir) diff --git a/code/modules/spells/spell_types/self/basic_heal.dm b/code/modules/spells/spell_types/self/basic_heal.dm index a4acba2d884..135b8094206 100644 --- a/code/modules/spells/spell_types/self/basic_heal.dm +++ b/code/modules/spells/spell_types/self/basic_heal.dm @@ -17,6 +17,9 @@ /// Amount of burn to heal to the spell caster on cast var/burn_to_heal = 10 +/datum/action/cooldown/spell/basic_heal/is_valid_target(atom/cast_on) + return isliving(cast_on) + /datum/action/cooldown/spell/basic_heal/cast(mob/living/cast_on) . = ..() cast_on.visible_message( diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm index c780a9b1e7d..fe79feff66b 100644 --- a/code/modules/vehicles/_vehicle.dm +++ b/code/modules/vehicles/_vehicle.dm @@ -51,6 +51,7 @@ autogrant_actions_controller = list() occupant_actions = list() generate_actions() + ADD_TRAIT(src, TRAIT_CASTABLE_LOC, INNATE_TRAIT) /obj/vehicle/Destroy(force) QDEL_NULL(trailer)