[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 ™️ ", either rejecting if it fails the valid target
check or doing the spell effects as if the atom passed was the caster.

## Changelog

🆑 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.
/🆑

* 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>
This commit is contained in:
SkyratBot
2023-08-08 08:18:56 -04:00
committed by GitHub
co-authored by MrMelbert
parent 583156705f
commit 5caffe23c0
23 changed files with 142 additions and 40 deletions
+5
View File
@@ -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"
+1
View File
@@ -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"
+1 -1
View File
@@ -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))
@@ -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)
@@ -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
@@ -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
@@ -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)
. = ..()
@@ -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 ..()
+1
View File
@@ -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!"))
+21 -5
View File
@@ -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.
@@ -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)
@@ -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)
@@ -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)
@@ -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"
@@ -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()
@@ -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
@@ -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"
@@ -30,7 +30,7 @@
. = ..()
invocation = span_notice("<b>[cast_on]</b> 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
@@ -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)
. = ..()
@@ -8,3 +8,4 @@
antimagic_flags = NONE
cooldown_time = 1.5 SECONDS
item_type = /obj/item/toy/snowball
requires_hands = TRUE
@@ -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)
@@ -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(
+1
View File
@@ -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)