From 5caffe23c096adacdc11def6402b993e3a38fdde Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:18:56 +0200 Subject: [PATCH] [MIRROR] Allows for some locs to have spells cast while inside, such as PAI cards (for PAIs), AI cards (for AIs), and mechas [MDB IGNORE] (#22940) * Allows for some locs to have spells cast while inside, such as PAI cards (for PAIs), AI cards (for AIs), and mechas (#77418) ## About The Pull Request Spiritual successor to #76716 - Some spells can now be cast while the mob is within the contents of certain atoms. - PAIs can now cast if within a PAI card - AIs can now cast if within an AI card - People within vehicles (mechas and cars) can now cast their spells - Repulse and Knock now have unique interactions for being cast within a locker ## Why It's Good For The Game Carlac's PR gave me an idea for how to tackle this in a relatively clean way so I went ahead and adapted the suggestion to something that works for the cast chain. This isn't perfect, some spells will need to be updated, but they can be done piecemeal. This is something that, IN THEORY, should have already been wholesale supported by the spell refactor - any atom you pass into the cast chain should "just work :tm: ", either rejecting if it fails the valid target check or doing the spell effects as if the atom passed was the caster. ## Changelog :cl: Melbert add: PAIs can now cast wizard spells should they have any. add: AIs located in intellicards can now cast wizard spells should they have any. add: Some spells, such as AoE or conjure spells, are now castable from within Mechas or Clown Cars. To varying degrees of success. add: Knock will now unlock and open closets you are hiding within. add: Repulse will now throw open closets you are hiding within. /:cl: * Allows for some locs to have spells cast while inside, such as PAI cards (for PAIs), AI cards (for AIs), and mechas --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/__DEFINES/traits.dm | 5 ++ code/datums/mutations/cold.dm | 1 + code/game/machinery/doors/door.dm | 2 +- code/game/objects/items/devices/aicard.dm | 4 ++ .../structures/crates_lockers/closets.dm | 2 +- .../heretic/magic/aggressive_spread.dm | 6 +-- .../revenant/revenant_abilities.dm | 6 +-- .../wizard/equipment/enchanted_clown_suit.dm | 3 +- code/modules/pai/card.dm | 1 + code/modules/spells/spell.dm | 26 +++++++-- .../spell_types/aoe_spell/_aoe_spell.dm | 3 ++ .../spell_types/aoe_spell/area_conversion.dm | 6 +-- .../spells/spell_types/aoe_spell/knock.dm | 17 ++++++ .../spells/spell_types/aoe_spell/repulse.dm | 25 ++++++++- .../spells/spell_types/conjure/_conjure.dm | 3 ++ .../spell_types/conjure_item/_conjure_item.dm | 54 +++++++++++++++---- .../spell_types/conjure_item/infinite_guns.dm | 5 +- .../spell_types/conjure_item/invisible_box.dm | 2 +- .../conjure_item/lighting_packet.dm | 1 + .../spell_types/conjure_item/snowball.dm | 1 + .../spells/spell_types/pointed/_pointed.dm | 5 +- .../spells/spell_types/self/basic_heal.dm | 3 ++ code/modules/vehicles/_vehicle.dm | 1 + 23 files changed, 142 insertions(+), 40 deletions(-) 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)