mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
[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:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user