@@ -0,0 +1,547 @@
|
||||
#define TARGET_CLOSEST 1
|
||||
#define TARGET_RANDOM 2
|
||||
|
||||
|
||||
/obj/effect/proc_holder
|
||||
var/panel = "Debug"//What panel the proc holder needs to go on.
|
||||
var/active = FALSE //Used by toggle based abilities.
|
||||
var/ranged_mousepointer
|
||||
var/mob/living/ranged_ability_user
|
||||
var/ranged_clickcd_override = -1
|
||||
var/has_action = TRUE
|
||||
var/datum/action/spell_action/action = null
|
||||
var/action_icon = 'icons/mob/actions/actions_spells.dmi'
|
||||
var/action_icon_state = "spell_default"
|
||||
var/action_background_icon_state = "bg_spell"
|
||||
var/base_action = /datum/action/spell_action
|
||||
|
||||
/obj/effect/proc_holder/Initialize()
|
||||
. = ..()
|
||||
if(has_action)
|
||||
action = new base_action(src)
|
||||
|
||||
/obj/effect/proc_holder/proc/on_gain(mob/living/user)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/proc/on_lose(mob/living/user)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/proc/fire(mob/living/user)
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/proc/get_panel_text()
|
||||
return ""
|
||||
|
||||
GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now
|
||||
|
||||
/obj/effect/proc_holder/Destroy()
|
||||
if(ranged_ability_user)
|
||||
remove_ranged_ability()
|
||||
return ..()
|
||||
|
||||
/obj/effect/proc_holder/singularity_act()
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/singularity_pull()
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/caller, params, atom/A)
|
||||
if(caller.ranged_ability != src || ranged_ability_user != caller) //I'm not actually sure how these would trigger, but, uh, safety, I guess?
|
||||
to_chat(caller, "<span class='warning'><b>[caller.ranged_ability.name]</b> has been disabled.</span>")
|
||||
caller.ranged_ability.remove_ranged_ability()
|
||||
return TRUE //TRUE for failed, FALSE for passed.
|
||||
if(ranged_clickcd_override >= 0)
|
||||
ranged_ability_user.next_click = world.time + ranged_clickcd_override
|
||||
else
|
||||
ranged_ability_user.next_click = world.time + CLICK_CD_CLICK_ABILITY
|
||||
ranged_ability_user.face_atom(A)
|
||||
return FALSE
|
||||
|
||||
/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, msg, forced)
|
||||
if(!user || !user.client)
|
||||
return
|
||||
if(user.ranged_ability && user.ranged_ability != src)
|
||||
if(forced)
|
||||
to_chat(user, "<span class='warning'><b>[user.ranged_ability.name]</b> has been replaced by <b>[name]</b>.</span>")
|
||||
user.ranged_ability.remove_ranged_ability()
|
||||
else
|
||||
return
|
||||
user.ranged_ability = src
|
||||
user.click_intercept = src
|
||||
user.update_mouse_pointer()
|
||||
ranged_ability_user = user
|
||||
if(msg)
|
||||
to_chat(ranged_ability_user, msg)
|
||||
active = TRUE
|
||||
update_icon()
|
||||
|
||||
/obj/effect/proc_holder/proc/remove_ranged_ability(msg)
|
||||
if(!ranged_ability_user || !ranged_ability_user.client || (ranged_ability_user.ranged_ability && ranged_ability_user.ranged_ability != src)) //To avoid removing the wrong ability
|
||||
return
|
||||
ranged_ability_user.ranged_ability = null
|
||||
ranged_ability_user.click_intercept = null
|
||||
ranged_ability_user.update_mouse_pointer()
|
||||
if(msg)
|
||||
to_chat(ranged_ability_user, msg)
|
||||
ranged_ability_user = null
|
||||
active = FALSE
|
||||
update_icon()
|
||||
|
||||
/obj/effect/proc_holder/spell
|
||||
name = "Spell"
|
||||
desc = "A wizard spell."
|
||||
panel = "Spells"
|
||||
var/sound = null //The sound the spell makes when it is cast
|
||||
anchored = TRUE // Crap like fireball projectiles are proc_holders, this is needed so fireballs don't get blown back into your face via atmos etc.
|
||||
pass_flags = PASSTABLE
|
||||
density = FALSE
|
||||
opacity = 0
|
||||
|
||||
var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
|
||||
|
||||
var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
|
||||
|
||||
var/charge_max = 100 //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges"
|
||||
var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges"
|
||||
var/still_recharging_msg = "<span class='notice'>The spell is still recharging.</span>"
|
||||
var/recharging = TRUE
|
||||
|
||||
var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var"
|
||||
var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used
|
||||
|
||||
var/clothes_req = 1 //see if it requires clothes
|
||||
var/cult_req = 0 //SPECIAL SNOWFLAKE clothes required for cult only spells
|
||||
var/human_req = 0 //spell can only be cast by humans
|
||||
var/nonabstract_req = 0 //spell can only be cast by mobs that are physical entities
|
||||
var/stat_allowed = 0 //see if it requires being conscious/alive, need to set to 1 for ghostpells
|
||||
var/phase_allowed = 0 // If true, the spell can be cast while phased, eg. blood crawling, ethereal jaunting
|
||||
var/antimagic_allowed = TRUE // If false, the spell cannot be cast while under the effect of antimagic
|
||||
var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell
|
||||
var/invocation_emote_self = null
|
||||
var/invocation_type = "none" //can be none, whisper, emote and shout
|
||||
var/range = 7 //the range of the spell; outer radius for aoe spells
|
||||
var/message = "" //whatever it says to the guy affected by it
|
||||
var/selection_type = "view" //can be "range" or "view"
|
||||
var/spell_level = 0 //if a spell can be taken multiple times, this raises
|
||||
var/level_max = 4 //The max possible level_max is 4
|
||||
var/cooldown_min = 0 //This defines what spell quickened four times has as a cooldown. Make sure to set this for every spell
|
||||
var/player_lock = 1 //If it can be used by simple mobs
|
||||
|
||||
var/overlay = 0
|
||||
var/overlay_icon = 'icons/obj/wizard.dmi'
|
||||
var/overlay_icon_state = "spell"
|
||||
var/overlay_lifespan = 0
|
||||
|
||||
var/sparks_spread = 0
|
||||
var/sparks_amt = 0 //cropped at 10
|
||||
var/smoke_spread = 0 //1 - harmless, 2 - harmful
|
||||
var/smoke_amt = 0 //cropped at 10
|
||||
|
||||
var/centcom_cancast = 1 //Whether or not the spell should be allowed on z2
|
||||
|
||||
action_icon = 'icons/mob/actions/actions_spells.dmi'
|
||||
action_icon_state = "spell_default"
|
||||
action_background_icon_state = "bg_spell"
|
||||
base_action = /datum/action/spell_action/spell
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0,mob/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
|
||||
if(player_lock)
|
||||
if(!user.mind || !(src in user.mind.spell_list) && !(src in user.mob_spell_list))
|
||||
to_chat(user, "<span class='warning'>You shouldn't have this spell! Something's wrong.</span>")
|
||||
return FALSE
|
||||
else
|
||||
if(!(src in user.mob_spell_list))
|
||||
return FALSE
|
||||
|
||||
var/turf/T = get_turf(user)
|
||||
if(is_centcom_level(T.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
|
||||
to_chat(user, "<span class='notice'>You can't cast this spell here.</span>")
|
||||
return FALSE
|
||||
|
||||
if(!skipcharge)
|
||||
if(!charge_check(user))
|
||||
return FALSE
|
||||
|
||||
if(user.stat && !stat_allowed)
|
||||
to_chat(user, "<span class='notice'>Not when you're incapacitated.</span>")
|
||||
return FALSE
|
||||
|
||||
if(!antimagic_allowed)
|
||||
var/antimagic = user.anti_magic_check(TRUE, FALSE, chargecost = 0, self = TRUE)
|
||||
if(antimagic)
|
||||
if(isitem(antimagic))
|
||||
to_chat(user, "<span class='notice'>[antimagic] is interfering with your magic.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>Magic seems to flee from you, you can't gather enough power to cast this spell.</span>")
|
||||
return FALSE
|
||||
|
||||
if(!phase_allowed && istype(user.loc, /obj/effect/dummy))
|
||||
to_chat(user, "<span class='notice'>[name] cannot be cast unless you are completely manifested in the material plane.</span>")
|
||||
return FALSE
|
||||
|
||||
if(ishuman(user))
|
||||
|
||||
var/mob/living/carbon/human/H = user
|
||||
|
||||
if((invocation_type == "whisper" || invocation_type == "shout") && !H.can_speak_vocal())
|
||||
to_chat(user, "<span class='notice'>You can't get the words out!</span>")
|
||||
return FALSE
|
||||
|
||||
var/list/casting_clothes = typecacheof(list(/obj/item/clothing/suit/wizrobe,
|
||||
/obj/item/clothing/suit/space/hardsuit/wizard,
|
||||
/obj/item/clothing/head/wizard,
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/wizard,
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/wizard,
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard))
|
||||
|
||||
if(clothes_req) //clothes check
|
||||
if(!is_type_in_typecache(H.wear_suit, casting_clothes))
|
||||
to_chat(H, "<span class='notice'>I don't feel strong enough without my robe.</span>")
|
||||
return FALSE
|
||||
if(!is_type_in_typecache(H.head, casting_clothes))
|
||||
to_chat(H, "<span class='notice'>I don't feel strong enough without my hat.</span>")
|
||||
return FALSE
|
||||
if(cult_req) //CULT_REQ CLOTHES CHECK
|
||||
if(!istype(H.wear_suit, /obj/item/clothing/suit/magusred) && !istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit/cult))
|
||||
to_chat(H, "<span class='notice'>I don't feel strong enough without my armor.</span>")
|
||||
return FALSE
|
||||
if(!istype(H.head, /obj/item/clothing/head/magus) && !istype(H.head, /obj/item/clothing/head/helmet/space/hardsuit/cult))
|
||||
to_chat(H, "<span class='notice'>I don't feel strong enough without my helmet.</span>")
|
||||
return FALSE
|
||||
else
|
||||
if(clothes_req || human_req)
|
||||
to_chat(user, "<span class='notice'>This spell can only be cast by humans!</span>")
|
||||
return FALSE
|
||||
if(nonabstract_req && (isbrain(user) || ispAI(user)))
|
||||
to_chat(user, "<span class='notice'>This spell can only be cast by physical beings!</span>")
|
||||
return FALSE
|
||||
|
||||
|
||||
if(!skipcharge)
|
||||
switch(charge_type)
|
||||
if("recharge")
|
||||
charge_counter = 0 //doesn't start recharging until the targets selecting ends
|
||||
if("charges")
|
||||
charge_counter-- //returns the charge if the targets selecting fails
|
||||
if("holdervar")
|
||||
adjust_var(user, holder_var_type, holder_var_amount)
|
||||
if(action)
|
||||
action.UpdateButtonIcon()
|
||||
return 1
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/charge_check(mob/user, silent = FALSE)
|
||||
switch(charge_type)
|
||||
if("recharge")
|
||||
if(charge_counter < charge_max)
|
||||
if(!silent)
|
||||
to_chat(user, still_recharging_msg)
|
||||
return FALSE
|
||||
if("charges")
|
||||
if(!charge_counter)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='notice'>[name] has no charges left.</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount
|
||||
switch(invocation_type)
|
||||
if("shout")
|
||||
if(prob(50))//Auto-mute? Fuck that noise
|
||||
user.say(invocation, forced = "spell")
|
||||
else
|
||||
user.say(replacetext(invocation," ","`"), forced = "spell")
|
||||
if("whisper")
|
||||
if(prob(50))
|
||||
user.whisper(invocation)
|
||||
else
|
||||
user.whisper(replacetext(invocation," ","`"))
|
||||
if("emote")
|
||||
user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/playMagSound()
|
||||
playsound(get_turf(usr), sound,50,1)
|
||||
|
||||
/obj/effect/proc_holder/spell/Initialize()
|
||||
. = ..()
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
still_recharging_msg = "<span class='notice'>[name] is still recharging.</span>"
|
||||
charge_counter = charge_max
|
||||
|
||||
/obj/effect/proc_holder/spell/Destroy()
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
qdel(action)
|
||||
return ..()
|
||||
|
||||
/obj/effect/proc_holder/spell/Click()
|
||||
if(cast_check())
|
||||
choose_targets()
|
||||
return 1
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/can_target(mob/living/target)
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/start_recharge()
|
||||
recharging = TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/process()
|
||||
if(recharging && charge_type == "recharge" && (charge_counter < charge_max))
|
||||
charge_counter += 2 //processes 5 times per second instead of 10.
|
||||
if(charge_counter >= charge_max)
|
||||
action.UpdateButtonIcon()
|
||||
charge_counter = charge_max
|
||||
recharging = FALSE
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells
|
||||
before_cast(targets)
|
||||
invocation(user)
|
||||
if(user && user.ckey)
|
||||
user.log_message("<span class='danger'>cast the spell [name].</span>", LOG_ATTACK)
|
||||
if(recharge)
|
||||
recharging = TRUE
|
||||
if(sound)
|
||||
playMagSound()
|
||||
cast(targets,user=user)
|
||||
after_cast(targets)
|
||||
if(action)
|
||||
action.UpdateButtonIcon()
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/before_cast(list/targets)
|
||||
if(overlay)
|
||||
for(var/atom/target in targets)
|
||||
var/location
|
||||
if(isliving(target))
|
||||
location = target.loc
|
||||
else if(isturf(target))
|
||||
location = target
|
||||
var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
|
||||
spell.icon = overlay_icon
|
||||
spell.icon_state = overlay_icon_state
|
||||
spell.anchored = TRUE
|
||||
spell.density = FALSE
|
||||
QDEL_IN(spell, overlay_lifespan)
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/after_cast(list/targets)
|
||||
for(var/atom/target in targets)
|
||||
var/location
|
||||
if(isliving(target))
|
||||
location = target.loc
|
||||
else if(isturf(target))
|
||||
location = target
|
||||
if(isliving(target) && message)
|
||||
to_chat(target, text("[message]"))
|
||||
if(sparks_spread)
|
||||
do_sparks(sparks_amt, FALSE, location)
|
||||
if(smoke_spread)
|
||||
if(smoke_spread == 1)
|
||||
var/datum/effect_system/smoke_spread/smoke = new
|
||||
smoke.set_up(smoke_amt, location)
|
||||
smoke.start()
|
||||
else if(smoke_spread == 2)
|
||||
var/datum/effect_system/smoke_spread/bad/smoke = new
|
||||
smoke.set_up(smoke_amt, location)
|
||||
smoke.start()
|
||||
else if(smoke_spread == 3)
|
||||
var/datum/effect_system/smoke_spread/sleeping/smoke = new
|
||||
smoke.set_up(smoke_amt, location)
|
||||
smoke.start()
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
|
||||
switch(charge_type)
|
||||
if("recharge")
|
||||
charge_counter = charge_max
|
||||
if("charges")
|
||||
charge_counter++
|
||||
if("holdervar")
|
||||
adjust_var(user, holder_var_type, -holder_var_amount)
|
||||
if(action)
|
||||
action.UpdateButtonIcon()
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
|
||||
if (!istype(target))
|
||||
return
|
||||
switch(type)
|
||||
if("bruteloss")
|
||||
target.adjustBruteLoss(amount)
|
||||
if("fireloss")
|
||||
target.adjustFireLoss(amount)
|
||||
if("toxloss")
|
||||
target.adjustToxLoss(amount)
|
||||
if("oxyloss")
|
||||
target.adjustOxyLoss(amount)
|
||||
if("stun")
|
||||
target.AdjustStun(amount)
|
||||
if("knockdown")
|
||||
target.AdjustKnockdown(amount)
|
||||
if("unconscious")
|
||||
target.AdjustUnconscious(amount)
|
||||
else
|
||||
target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existent vars
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
|
||||
var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
|
||||
var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
|
||||
var/include_user = 0 //if it includes usr in the target list
|
||||
var/random_target = 0 // chooses random viable target instead of asking the caster
|
||||
var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends)
|
||||
var/inner_radius = -1 //for all your ring spell needs
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr)
|
||||
var/list/targets = list()
|
||||
|
||||
switch(max_targets)
|
||||
if(0) //unlimited
|
||||
for(var/mob/living/target in view_or_range(range, user, selection_type))
|
||||
if(!can_target(target))
|
||||
continue
|
||||
targets += target
|
||||
if(1) //single target can be picked
|
||||
if(range < 0)
|
||||
targets += user
|
||||
else
|
||||
var/possible_targets = list()
|
||||
|
||||
for(var/mob/living/M in view_or_range(range, user, selection_type))
|
||||
if(!include_user && user == M)
|
||||
continue
|
||||
if(!can_target(M))
|
||||
continue
|
||||
possible_targets += M
|
||||
|
||||
//targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets
|
||||
//Adds a safety check post-input to make sure those targets are actually in range.
|
||||
var/mob/M
|
||||
if(!random_target)
|
||||
M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets
|
||||
else
|
||||
switch(random_target_priority)
|
||||
if(TARGET_RANDOM)
|
||||
M = pick(possible_targets)
|
||||
if(TARGET_CLOSEST)
|
||||
for(var/mob/living/L in possible_targets)
|
||||
if(M)
|
||||
if(get_dist(user,L) < get_dist(user,M))
|
||||
if(los_check(user,L))
|
||||
M = L
|
||||
else
|
||||
if(los_check(user,L))
|
||||
M = L
|
||||
if(M in view_or_range(range, user, selection_type))
|
||||
targets += M
|
||||
|
||||
else
|
||||
var/list/possible_targets = list()
|
||||
for(var/mob/living/target in view_or_range(range, user, selection_type))
|
||||
if(!can_target(target))
|
||||
continue
|
||||
possible_targets += target
|
||||
for(var/i=1,i<=max_targets,i++)
|
||||
if(!possible_targets.len)
|
||||
break
|
||||
if(target_ignore_prev)
|
||||
var/target = pick(possible_targets)
|
||||
possible_targets -= target
|
||||
targets += target
|
||||
else
|
||||
targets += pick(possible_targets)
|
||||
|
||||
if(!include_user && (user in targets))
|
||||
targets -= user
|
||||
|
||||
if(!targets.len) //doesn't waste the spell
|
||||
revert_cast(user)
|
||||
return
|
||||
|
||||
perform(targets,user=user)
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
|
||||
var/list/targets = list()
|
||||
|
||||
for(var/turf/target in view_or_range(range,user,selection_type))
|
||||
if(!can_target(target))
|
||||
continue
|
||||
if(!(target in view_or_range(inner_radius,user,selection_type)))
|
||||
targets += target
|
||||
|
||||
if(!targets.len) //doesn't waste the spell
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
perform(targets,user=user)
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/updateButtonIcon(status_only, force)
|
||||
action.UpdateButtonIcon(status_only, force)
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/can_be_cast_by(mob/caster)
|
||||
if((human_req || clothes_req) && !ishuman(caster))
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B)
|
||||
//Checks for obstacles from A to B
|
||||
var/obj/dummy = new(A.loc)
|
||||
dummy.pass_flags |= PASSTABLE
|
||||
for(var/turf/turf in getline(A,B))
|
||||
for(var/atom/movable/AM in turf)
|
||||
if(!AM.CanPass(dummy,turf,1))
|
||||
qdel(dummy)
|
||||
return 0
|
||||
qdel(dummy)
|
||||
return 1
|
||||
|
||||
/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr)
|
||||
if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list))
|
||||
return FALSE
|
||||
|
||||
if(!charge_check(user,TRUE))
|
||||
return FALSE
|
||||
|
||||
if(user.stat && !stat_allowed)
|
||||
return FALSE
|
||||
|
||||
if(!antimagic_allowed && user.anti_magic_check(TRUE, FALSE, chargecost = 0, self = TRUE))
|
||||
return FALSE
|
||||
|
||||
if(!ishuman(user))
|
||||
if(clothes_req || human_req)
|
||||
return FALSE
|
||||
if(nonabstract_req && (isbrain(user) || ispAI(user)))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/self //Targets only the caster. Good for buffs and heals, but probably not wise for fireballs (although they usually fireball themselves anyway, honke)
|
||||
range = -1 //Duh
|
||||
|
||||
/obj/effect/proc_holder/spell/self/choose_targets(mob/user = usr)
|
||||
if(!user)
|
||||
revert_cast()
|
||||
return
|
||||
perform(null,user=user)
|
||||
|
||||
/obj/effect/proc_holder/spell/self/basic_heal //This spell exists mainly for debugging purposes, and also to show how casting works
|
||||
name = "Lesser Heal"
|
||||
desc = "Heals a small amount of brute and burn damage."
|
||||
human_req = 1
|
||||
clothes_req = 0
|
||||
charge_max = 100
|
||||
cooldown_min = 50
|
||||
invocation = "Victus sano!"
|
||||
invocation_type = "whisper"
|
||||
school = "restoration"
|
||||
sound = 'sound/magic/staff_healing.ogg'
|
||||
|
||||
/obj/effect/proc_holder/spell/self/basic_heal/cast(mob/living/carbon/human/user) //Note the lack of "list/targets" here. Instead, use a "user" var depending on mob requirements.
|
||||
//Also, notice the lack of a "for()" statement that looks through the targets. This is, again, because the spell can only have a single target.
|
||||
user.visible_message("<span class='warning'>A wreath of gentle light passes over [user]!</span>", "<span class='notice'>You wreath yourself in healing light!</span>")
|
||||
user.adjustBruteLoss(-10)
|
||||
user.adjustFireLoss(-10)
|
||||
@@ -0,0 +1,92 @@
|
||||
/obj/effect/proc_holder/spell/targeted/area_teleport
|
||||
name = "Area teleport"
|
||||
desc = "This spell teleports you to a type of area of your selection."
|
||||
nonabstract_req = 1
|
||||
|
||||
var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list
|
||||
var/invocation_area = 1 //if the invocation appends the selected area
|
||||
var/sound1 = 'sound/weapons/zapbang.ogg'
|
||||
var/sound2 = 'sound/weapons/zapbang.ogg'
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1,mob/living/user = usr)
|
||||
var/thearea = before_cast(targets)
|
||||
if(!thearea || !cast_check(1))
|
||||
revert_cast()
|
||||
return
|
||||
invocation(thearea,user)
|
||||
if(charge_type == "recharge" && recharge)
|
||||
INVOKE_ASYNC(src, .proc/start_recharge)
|
||||
cast(targets,thearea,user)
|
||||
after_cast(targets)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets)
|
||||
var/area/U = get_area(usr)
|
||||
if(U.noteleport && !istype(U, /area/wizard_station)) // Wizard den special check for those complaining about being unable to tele on station.
|
||||
to_chat(usr, "<span class='warning'>Unseen forces prevent you from casting this spell in this area</span>")
|
||||
return
|
||||
var/A
|
||||
if(!randomise_selection)
|
||||
A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs
|
||||
else
|
||||
A = pick(GLOB.teleportlocs)
|
||||
if(!A)
|
||||
return
|
||||
var/area/thearea = GLOB.teleportlocs[A]
|
||||
|
||||
return thearea
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/user = usr)
|
||||
playsound(get_turf(user), sound1, 50,1)
|
||||
for(var/mob/living/target in targets)
|
||||
var/list/L = list()
|
||||
for(var/turf/T in get_area_turfs(thearea.type))
|
||||
if(!T.density)
|
||||
var/clear = 1
|
||||
for(var/obj/O in T)
|
||||
if(O.density)
|
||||
clear = 0
|
||||
break
|
||||
if(clear)
|
||||
L+=T
|
||||
|
||||
if(!L.len)
|
||||
to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.")
|
||||
return
|
||||
|
||||
if(target && target.buckled)
|
||||
target.buckled.unbuckle_mob(target, force=1)
|
||||
|
||||
var/forcecheck = istype(get_area(target), /area/wizard_station)
|
||||
var/list/tempL = L
|
||||
var/attempt = null
|
||||
var/success = 0
|
||||
while(tempL.len)
|
||||
attempt = pick(tempL)
|
||||
do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck)
|
||||
if(get_turf(target) == attempt)
|
||||
success = 1
|
||||
break
|
||||
else
|
||||
tempL.Remove(attempt)
|
||||
|
||||
if(!success)
|
||||
do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck)
|
||||
playsound(get_turf(user), sound2, 50,1)
|
||||
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/user = usr)
|
||||
if(!invocation_area || !chosenarea)
|
||||
..()
|
||||
else
|
||||
switch(invocation_type)
|
||||
if("shout")
|
||||
user.say("[invocation] [uppertext(chosenarea.name)]", forced = "spell")
|
||||
if(user.gender==MALE)
|
||||
playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
|
||||
else
|
||||
playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1)
|
||||
if("whisper")
|
||||
user.whisper("[invocation] [uppertext(chosenarea.name)]")
|
||||
|
||||
return
|
||||
@@ -0,0 +1,322 @@
|
||||
//////////////////////////////Construct Spells/////////////////////////
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser
|
||||
charge_max = 1800
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "artificer"
|
||||
action_background_icon_state = "bg_demon"
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser/cult
|
||||
cult_req = 1
|
||||
charge_max = 2500
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/area_conversion
|
||||
name = "Area Conversion"
|
||||
desc = "This spell instantly converts a small area around you."
|
||||
|
||||
school = "transmutation"
|
||||
charge_max = 50
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = 2
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "areaconvert"
|
||||
action_background_icon_state = "bg_cult"
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/area_conversion/cast(list/targets, mob/user = usr)
|
||||
playsound(get_turf(user), 'sound/items/welder.ogg', 75, 1)
|
||||
for(var/turf/T in targets)
|
||||
T.narsie_act(FALSE, TRUE, 100 - (get_dist(user, T) * 25))
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/floor
|
||||
name = "Summon Cult Floor"
|
||||
desc = "This spell constructs a cult floor."
|
||||
|
||||
school = "conjuration"
|
||||
charge_max = 20
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = 0
|
||||
summon_type = list(/turf/open/floor/engine/cult)
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "floorconstruct"
|
||||
action_background_icon_state = "bg_cult"
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/wall
|
||||
name = "Summon Cult Wall"
|
||||
desc = "This spell constructs a cult wall."
|
||||
|
||||
school = "conjuration"
|
||||
charge_max = 100
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = 0
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "lesserconstruct"
|
||||
action_background_icon_state = "bg_cult"
|
||||
|
||||
summon_type = list(/turf/closed/wall/mineral/cult/artificer) //we don't want artificer-based runed metal farms
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/wall/reinforced
|
||||
name = "Greater Construction"
|
||||
desc = "This spell constructs a reinforced metal wall."
|
||||
|
||||
school = "conjuration"
|
||||
charge_max = 300
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = 0
|
||||
|
||||
summon_type = list(/turf/closed/wall/r_wall)
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone
|
||||
name = "Summon Soulstone"
|
||||
desc = "This spell reaches into Nar'Sie's realm, summoning one of the legendary fragments across time and space."
|
||||
|
||||
school = "conjuration"
|
||||
charge_max = 2400
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = 0
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "summonsoulstone"
|
||||
action_background_icon_state = "bg_demon"
|
||||
|
||||
summon_type = list(/obj/item/soulstone)
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/cult
|
||||
cult_req = 1
|
||||
charge_max = 3600
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult
|
||||
summon_type = list(/obj/item/soulstone/anybody)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/forcewall/cult
|
||||
name = "Shield"
|
||||
desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire."
|
||||
school = "transmutation"
|
||||
charge_max = 400
|
||||
clothes_req = FALSE
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
wall_type = /obj/effect/forcefield/cult
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "cultforcewall"
|
||||
action_background_icon_state = "bg_demon"
|
||||
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift
|
||||
name = "Phase Shift"
|
||||
desc = "This spell allows you to pass through walls."
|
||||
|
||||
school = "transmutation"
|
||||
charge_max = 250
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = -1
|
||||
include_user = 1
|
||||
jaunt_duration = 50 //in deciseconds
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "phaseshift"
|
||||
action_background_icon_state = "bg_demon"
|
||||
jaunt_in_time = 12
|
||||
jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith
|
||||
jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/jaunt_steam(mobloc)
|
||||
return
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser
|
||||
name = "Lesser Magic Missile"
|
||||
desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
|
||||
|
||||
school = "evocation"
|
||||
charge_max = 400
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile/lesser"
|
||||
proj_lifespan = 10
|
||||
max_targets = 6
|
||||
action_icon_state = "magicm"
|
||||
action_background_icon_state = "bg_demon"
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile/lesser
|
||||
amt_knockdown = 84
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/smoke/disable
|
||||
name = "Paralysing Smoke"
|
||||
desc = "This spell spawns a cloud of paralysing smoke."
|
||||
|
||||
school = "conjuration"
|
||||
charge_max = 200
|
||||
clothes_req = 0
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
range = -1
|
||||
include_user = 1
|
||||
cooldown_min = 20 //25 deciseconds reduction per rank
|
||||
|
||||
smoke_spread = 3
|
||||
smoke_amt = 4
|
||||
action_icon_state = "smoke"
|
||||
action_background_icon_state = "bg_cult"
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/abyssal_gaze
|
||||
name = "Abyssal Gaze"
|
||||
desc = "This spell instills a deep terror in your target, temporarily chilling and blinding it."
|
||||
|
||||
charge_max = 750
|
||||
range = 5
|
||||
include_user = FALSE
|
||||
selection_type = "range"
|
||||
stat_allowed = FALSE
|
||||
|
||||
school = "evocation"
|
||||
clothes_req = FALSE
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_background_icon_state = "bg_demon"
|
||||
action_icon_state = "abyssal_gaze"
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/abyssal_gaze/cast(list/targets, mob/user = usr)
|
||||
if(!LAZYLEN(targets))
|
||||
to_chat(user, "<span class='notice'>No target found in range.</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
var/mob/living/carbon/target = targets[1]
|
||||
|
||||
if(!(target in oview(range)))
|
||||
to_chat(user, "<span class='notice'>[target] is too far away!</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
if(target.anti_magic_check(TRUE, TRUE))
|
||||
to_chat(target, "<span class='warning'>You feel a freezing darkness closing in on you, but it rapidly dissipates.</span>")
|
||||
return
|
||||
|
||||
to_chat(target, "<span class='userdanger'>A freezing darkness surrounds you...</span>")
|
||||
target.playsound_local(get_turf(target), 'sound/hallucinations/i_see_you1.ogg', 50, 1)
|
||||
user.playsound_local(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1)
|
||||
target.become_blind(ABYSSAL_GAZE_BLIND)
|
||||
addtimer(CALLBACK(src, .proc/cure_blindness, target), 40)
|
||||
target.adjust_bodytemperature(-200)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/target)
|
||||
if(isliving(target))
|
||||
var/mob/living/L = target
|
||||
L.cure_blind(ABYSSAL_GAZE_BLIND)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/dominate
|
||||
name = "Dominate"
|
||||
desc = "This spell dominates the mind of a lesser creature to the will of Nar'Sie, allying it only to her direct followers."
|
||||
|
||||
charge_max = 600
|
||||
range = 7
|
||||
include_user = FALSE
|
||||
selection_type = "range"
|
||||
stat_allowed = FALSE
|
||||
|
||||
school = "evocation"
|
||||
clothes_req = FALSE
|
||||
invocation = "none"
|
||||
invocation_type = "none"
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_background_icon_state = "bg_demon"
|
||||
action_icon_state = "dominate"
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/dominate/cast(list/targets, mob/user = usr)
|
||||
if(!LAZYLEN(targets))
|
||||
to_chat(user, "<span class='notice'>No target found in range.</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
var/mob/living/simple_animal/S = targets[1]
|
||||
|
||||
if(S.ckey)
|
||||
to_chat(user, "<span class='warning'>[S] is too intelligent to dominate!</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
if(S.stat)
|
||||
to_chat(user, "<span class='warning'>[S] is dead!</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
if(S.sentience_type != SENTIENCE_ORGANIC)
|
||||
to_chat(user, "<span class='warning'>[S] cannot be dominated!</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
if(!(S in oview(range)))
|
||||
to_chat(user, "<span class='notice'>[S] is too far away!</span>")
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
S.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY)
|
||||
S.faction = list("cult")
|
||||
playsound(get_turf(S), 'sound/effects/ghost.ogg', 100, 1)
|
||||
new /obj/effect/temp_visual/cult/sac(get_turf(S))
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/dominate/can_target(mob/living/target)
|
||||
if(!isanimal(target) || target.stat)
|
||||
return FALSE
|
||||
if("cult" in target.faction)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem
|
||||
charge_max = 800
|
||||
jaunt_in_type = /obj/effect/temp_visual/dir_setting/cult/phase
|
||||
jaunt_out_type = /obj/effect/temp_visual/dir_setting/cult/phase/out
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/dumbfire/juggernaut
|
||||
name = "Gauntlet Echo"
|
||||
desc = "Channels energy into your gauntlet - firing its essence forward in a slow moving, yet devastating, attack."
|
||||
proj_icon_state = "cultfist"
|
||||
proj_name = "gauntlet echo"
|
||||
proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/juggernaut" //IMPORTANT use only subtypes of this
|
||||
proj_lifespan = 15
|
||||
proj_step_delay = 7
|
||||
charge_max = 350
|
||||
clothes_req = FALSE
|
||||
action_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
action_icon_state = "cultfist"
|
||||
action_background_icon_state = "bg_demon"
|
||||
sound = 'sound/weapons/resonator_blast.ogg'
|
||||
proj_trigger_range = 0
|
||||
ignore_factions = list("cult")
|
||||
check_holy = TRUE
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/inflict_handler/juggernaut
|
||||
name = "Gauntlet Echo"
|
||||
alpha = 180
|
||||
amt_dam_brute = 30
|
||||
amt_knockdown = 84
|
||||
amt_dam_stam = 30
|
||||
sound = 'sound/weapons/punch3.ogg'
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/inflict_handler/juggernaut/cast(list/targets,mob/user = usr)
|
||||
var/turf/T = get_turf(src)
|
||||
playsound(T, 'sound/weapons/resonator_blast.ogg', 100, FALSE)
|
||||
new /obj/effect/temp_visual/cult/sac(T)
|
||||
for(var/obj/O in range(src,1))
|
||||
if(O.density && !istype(O, /obj/structure/destructible/cult))
|
||||
O.take_damage(90, BRUTE, "melee", 0)
|
||||
new /obj/effect/temp_visual/cult/turf/floor
|
||||
..()
|
||||
@@ -0,0 +1,40 @@
|
||||
/obj/effect/proc_holder/spell/targeted/forcewall
|
||||
name = "Forcewall"
|
||||
desc = "Create a magical barrier that only you can pass through. Does not require wizard garb."
|
||||
school = "transmutation"
|
||||
charge_max = 100
|
||||
clothes_req = 0
|
||||
invocation = "TARCOL MINTI ZHERI"
|
||||
invocation_type = "shout"
|
||||
sound = 'sound/magic/forcewall.ogg'
|
||||
action_icon_state = "shield"
|
||||
range = -1
|
||||
include_user = 1
|
||||
cooldown_min = 50 //12 deciseconds reduction per rank
|
||||
var/wall_type = /obj/effect/forcefield/wizard
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/forcewall/cast(list/targets,mob/user = usr)
|
||||
new wall_type(get_turf(user),user)
|
||||
if(user.dir == SOUTH || user.dir == NORTH)
|
||||
new wall_type(get_step(user, EAST),user)
|
||||
new wall_type(get_step(user, WEST),user)
|
||||
else
|
||||
new wall_type(get_step(user, NORTH),user)
|
||||
new wall_type(get_step(user, SOUTH),user)
|
||||
|
||||
|
||||
/obj/effect/forcefield/wizard
|
||||
var/mob/wizard
|
||||
|
||||
/obj/effect/forcefield/wizard/Initialize(mapload, mob/summoner)
|
||||
. = ..()
|
||||
wizard = summoner
|
||||
|
||||
/obj/effect/forcefield/wizard/CanPass(atom/movable/mover, turf/target)
|
||||
if(mover == wizard)
|
||||
return TRUE
|
||||
if(ismob(mover))
|
||||
var/mob/M = mover
|
||||
if(M.anti_magic_check(chargecost = 0))
|
||||
return TRUE
|
||||
return FALSE
|
||||
@@ -0,0 +1,57 @@
|
||||
/obj/effect/proc_holder/spell/targeted/inflict_handler
|
||||
name = "Inflict Handler"
|
||||
desc = "This spell blinds and/or destroys/damages/heals and/or knockdowns/stuns the target."
|
||||
|
||||
var/amt_knockdown = 0
|
||||
var/amt_hardstun
|
||||
var/amt_unconscious = 0
|
||||
var/amt_stun = 0
|
||||
|
||||
//set to negatives for healing
|
||||
var/amt_dam_stam
|
||||
var/amt_dam_fire = 0
|
||||
var/amt_dam_brute = 0
|
||||
var/amt_dam_oxy = 0
|
||||
var/amt_dam_tox = 0
|
||||
|
||||
var/amt_eye_blind = 0
|
||||
var/amt_eye_blurry = 0
|
||||
|
||||
var/destroys = "none" //can be "none", "gib" or "disintegrate"
|
||||
|
||||
var/summon_type = null //this will put an obj at the target's location
|
||||
|
||||
var/check_anti_magic = TRUE
|
||||
var/check_holy = FALSE
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr)
|
||||
for(var/mob/living/target in targets)
|
||||
playsound(target,sound, 50,1)
|
||||
if(target.anti_magic_check(check_anti_magic, check_holy))
|
||||
return
|
||||
switch(destroys)
|
||||
if("gib")
|
||||
target.gib()
|
||||
if("disintegrate")
|
||||
target.dust()
|
||||
|
||||
if(!target)
|
||||
continue
|
||||
//damage/healing
|
||||
target.adjustBruteLoss(amt_dam_brute)
|
||||
target.adjustFireLoss(amt_dam_fire)
|
||||
target.adjustToxLoss(amt_dam_tox)
|
||||
target.adjustOxyLoss(amt_dam_oxy)
|
||||
//disabling
|
||||
if(!amt_knockdown && amt_dam_stam)
|
||||
target.adjustStaminaLoss(amt_dam_stam)
|
||||
else
|
||||
target.Knockdown(amt_knockdown, override_hardstun = amt_hardstun, override_stamdmg = amt_dam_stam)
|
||||
target.Unconscious(amt_unconscious)
|
||||
target.Stun(amt_stun)
|
||||
|
||||
target.blind_eyes(amt_eye_blind)
|
||||
target.blur_eyes(amt_eye_blurry)
|
||||
//summoning
|
||||
if(summon_type)
|
||||
new summon_type(target.loc, target)
|
||||
@@ -0,0 +1,88 @@
|
||||
/obj/effect/proc_holder/spell/targeted/mind_transfer
|
||||
name = "Mind Transfer"
|
||||
desc = "This spell allows the user to switch bodies with a target."
|
||||
|
||||
school = "transmutation"
|
||||
charge_max = 600
|
||||
clothes_req = 0
|
||||
invocation = "GIN'YU CAPAN"
|
||||
invocation_type = "whisper"
|
||||
range = 1
|
||||
cooldown_min = 200 //100 deciseconds reduction per rank
|
||||
var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell
|
||||
var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell
|
||||
|
||||
action_icon_state = "mindswap"
|
||||
|
||||
/*
|
||||
Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
|
||||
Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring.
|
||||
Also, you never added distance checking after target is selected. I've went ahead and did that.
|
||||
*/
|
||||
/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE)
|
||||
if(!targets.len)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>No mind found!</span>")
|
||||
return
|
||||
|
||||
if(targets.len > 1)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>Too many minds! You're not a hive damnit!</span>")
|
||||
return
|
||||
|
||||
var/mob/living/target = targets[1]
|
||||
|
||||
var/t_He = target.p_they(TRUE)
|
||||
var/t_is = target.p_are()
|
||||
|
||||
if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>[t_He] [t_is] too far away!</span>")
|
||||
return
|
||||
|
||||
if(ismegafauna(target))
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>This creature is too powerful to control!</span>")
|
||||
return
|
||||
|
||||
if(target.stat == DEAD)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>You don't particularly want to be dead!</span>")
|
||||
return
|
||||
|
||||
if(!target.key || !target.mind)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.</span>")
|
||||
return
|
||||
|
||||
if(user.suiciding)
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>You're killing yourself! You can't concentrate enough to do this!</span>")
|
||||
return
|
||||
|
||||
var/datum/mind/TM = target.mind
|
||||
if((target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev)) || cmptext(copytext(target.key,1,2),"@"))
|
||||
if(!silent)
|
||||
to_chat(user, "<span class='warning'>[target.p_their(TRUE)] mind is resisting your spell!</span>")
|
||||
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.
|
||||
|
||||
//MIND TRANSFER BEGIN
|
||||
var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE)
|
||||
caster.mind.transfer_to(victim)
|
||||
|
||||
ghost.mind.transfer_to(caster)
|
||||
if(ghost.key)
|
||||
ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active
|
||||
qdel(ghost)
|
||||
|
||||
//MIND TRANSFER END
|
||||
|
||||
//Here we knock both mobs out for a time.
|
||||
caster.Unconscious(unconscious_amount_caster)
|
||||
victim.Unconscious(unconscious_amount_victim)
|
||||
SEND_SOUND(caster, sound('sound/magic/mandswap.ogg'))
|
||||
SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
|
||||
return TRUE
|
||||
@@ -0,0 +1,173 @@
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift
|
||||
name = "Shapechange"
|
||||
desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed."
|
||||
clothes_req = 0
|
||||
human_req = 0
|
||||
charge_max = 200
|
||||
cooldown_min = 50
|
||||
range = -1
|
||||
include_user = 1
|
||||
invocation = "RAC'WA NO!"
|
||||
invocation_type = "shout"
|
||||
action_icon_state = "shapeshift"
|
||||
|
||||
var/revert_on_death = TRUE
|
||||
var/die_with_shapeshifted_form = TRUE
|
||||
var/convert_damage = TRUE //If you want to convert the caster's health to the shift, and vice versa.
|
||||
var/convert_damage_type = BRUTE //Since simplemobs don't have advanced damagetypes, what to convert damage back into.
|
||||
var/shapeshift_type
|
||||
var/list/possible_shapes = list(/mob/living/simple_animal/mouse,\
|
||||
/mob/living/simple_animal/pet/dog/corgi,\
|
||||
/mob/living/simple_animal/hostile/carp/ranged/chaos,\
|
||||
/mob/living/simple_animal/bot/ed209,\
|
||||
/mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper,\
|
||||
/mob/living/simple_animal/hostile/construct/armored)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift/cast(list/targets,mob/user = usr)
|
||||
if(src in user.mob_spell_list)
|
||||
user.mob_spell_list.Remove(src)
|
||||
user.mind.AddSpell(src)
|
||||
if(user.buckled)
|
||||
user.buckled.unbuckle_mob(src,force=TRUE)
|
||||
for(var/mob/living/M in targets)
|
||||
if(!shapeshift_type)
|
||||
var/list/animal_list = list()
|
||||
for(var/path in possible_shapes)
|
||||
var/mob/living/simple_animal/A = path
|
||||
animal_list[initial(A.name)] = path
|
||||
var/new_shapeshift_type = input(M, "Choose Your Animal Form!", "It's Morphing Time!", null) as null|anything in animal_list
|
||||
if(shapeshift_type)
|
||||
return
|
||||
shapeshift_type = new_shapeshift_type
|
||||
if(!shapeshift_type) //If you aren't gonna decide I am!
|
||||
shapeshift_type = pick(animal_list)
|
||||
shapeshift_type = animal_list[shapeshift_type]
|
||||
|
||||
var/obj/shapeshift_holder/S = locate() in M
|
||||
if(S)
|
||||
Restore(M)
|
||||
else
|
||||
Shapeshift(M)
|
||||
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Shapeshift(mob/living/caster)
|
||||
var/obj/shapeshift_holder/H = locate() in caster
|
||||
if(H)
|
||||
to_chat(caster, "<span class='warning'>You're already shapeshifted!</span>")
|
||||
return
|
||||
|
||||
var/mob/living/shape = new shapeshift_type(caster.loc)
|
||||
H = new(shape,src,caster)
|
||||
|
||||
clothes_req = 0
|
||||
human_req = 0
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Restore(mob/living/shape)
|
||||
var/obj/shapeshift_holder/H = locate() in shape
|
||||
if(!H)
|
||||
return
|
||||
|
||||
H.restore()
|
||||
|
||||
clothes_req = initial(clothes_req)
|
||||
human_req = initial(human_req)
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/shapeshift/dragon
|
||||
name = "Dragon Form"
|
||||
desc = "Take on the shape a lesser ash drake."
|
||||
invocation = "RAAAAAAAAWR!"
|
||||
|
||||
shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser
|
||||
|
||||
|
||||
/obj/shapeshift_holder
|
||||
name = "Shapeshift holder"
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
|
||||
var/mob/living/stored
|
||||
var/mob/living/shape
|
||||
var/restoring = FALSE
|
||||
var/datum/soullink/shapeshift/slink
|
||||
var/obj/effect/proc_holder/spell/targeted/shapeshift/source
|
||||
|
||||
/obj/shapeshift_holder/Initialize(mapload,obj/effect/proc_holder/spell/targeted/shapeshift/source,mob/living/caster)
|
||||
. = ..()
|
||||
src.source = source
|
||||
shape = loc
|
||||
if(!istype(shape))
|
||||
CRASH("shapeshift holder created outside mob/living")
|
||||
stored = caster
|
||||
if(stored.mind)
|
||||
stored.mind.transfer_to(shape)
|
||||
stored.forceMove(src)
|
||||
stored.notransform = TRUE
|
||||
if(source.convert_damage)
|
||||
var/damage_percent = (stored.maxHealth - stored.health)/stored.maxHealth;
|
||||
var/damapply = damage_percent * shape.maxHealth;
|
||||
|
||||
shape.apply_damage(damapply, source.convert_damage_type, forced = TRUE);
|
||||
slink = soullink(/datum/soullink/shapeshift, stored , shape)
|
||||
slink.source = src
|
||||
|
||||
/obj/shapeshift_holder/Destroy()
|
||||
if(!restoring)
|
||||
restore()
|
||||
stored = null
|
||||
shape = null
|
||||
. = ..()
|
||||
|
||||
/obj/shapeshift_holder/Moved()
|
||||
. = ..()
|
||||
if(!restoring || QDELETED(src))
|
||||
restore()
|
||||
|
||||
/obj/shapeshift_holder/handle_atom_del(atom/A)
|
||||
if(A == stored && !restoring)
|
||||
restore()
|
||||
|
||||
/obj/shapeshift_holder/Exited(atom/movable/AM)
|
||||
if(AM == stored && !restoring)
|
||||
restore()
|
||||
|
||||
/obj/shapeshift_holder/proc/casterDeath()
|
||||
//Something kills the stored caster through direct damage.
|
||||
if(source.revert_on_death)
|
||||
restore(death=TRUE)
|
||||
else
|
||||
shape.death()
|
||||
|
||||
/obj/shapeshift_holder/proc/shapeDeath()
|
||||
//Shape dies.
|
||||
if(source.die_with_shapeshifted_form)
|
||||
if(source.revert_on_death)
|
||||
restore(death=TRUE)
|
||||
else
|
||||
restore()
|
||||
|
||||
/obj/shapeshift_holder/proc/restore(death=FALSE)
|
||||
restoring = TRUE
|
||||
qdel(slink)
|
||||
stored.forceMove(get_turf(src))
|
||||
stored.notransform = FALSE
|
||||
if(shape.mind)
|
||||
shape.mind.transfer_to(stored)
|
||||
if(death)
|
||||
stored.death()
|
||||
else if(source.convert_damage)
|
||||
stored.revive(full_heal = TRUE)
|
||||
var/damage_percent = (shape.maxHealth - shape.health)/shape.maxHealth;
|
||||
var/damapply = stored.maxHealth * damage_percent
|
||||
|
||||
stored.apply_damage(damapply, source.convert_damage_type, forced = TRUE)
|
||||
qdel(shape)
|
||||
qdel(src)
|
||||
|
||||
/datum/soullink/shapeshift
|
||||
var/obj/shapeshift_holder/source
|
||||
|
||||
/datum/soullink/shapeshift/ownerDies(gibbed, mob/living/owner)
|
||||
if(source)
|
||||
source.casterDeath(gibbed)
|
||||
|
||||
/datum/soullink/shapeshift/sharerDies(gibbed, mob/living/sharer)
|
||||
if(source)
|
||||
source.shapeDeath(gibbed)
|
||||
@@ -0,0 +1,123 @@
|
||||
/obj/effect/proc_holder/spell/spacetime_dist
|
||||
name = "Spacetime Distortion"
|
||||
desc = "Entangle the strings of spacetime to deny easy movement around you. The strings vibrate..."
|
||||
charge_max = 300
|
||||
var/duration = 150
|
||||
range = 7
|
||||
var/list/effects
|
||||
var/ready = TRUE
|
||||
centcom_cancast = FALSE
|
||||
sound = 'sound/effects/magic.ogg'
|
||||
cooldown_min = 300
|
||||
level_max = 0
|
||||
|
||||
/obj/effect/proc_holder/spell/spacetime_dist/can_cast(mob/user = usr)
|
||||
if(ready)
|
||||
return ..()
|
||||
return FALSE
|
||||
|
||||
/obj/effect/proc_holder/spell/spacetime_dist/choose_targets(mob/user = usr)
|
||||
var/list/turfs = spiral_range_turfs(range, user)
|
||||
if(!turfs.len)
|
||||
revert_cast()
|
||||
return
|
||||
|
||||
ready = FALSE
|
||||
var/list/turf_steps = list()
|
||||
var/length = round(turfs.len * 0.5)
|
||||
for(var/i in 1 to length)
|
||||
turf_steps[pick_n_take(turfs)] = pick_n_take(turfs)
|
||||
if(turfs.len > 0)
|
||||
var/turf/loner = pick(turfs)
|
||||
var/area/A = get_area(user)
|
||||
turf_steps[loner] = get_turf(pick(A.contents))
|
||||
|
||||
perform(turf_steps,user=user)
|
||||
|
||||
/obj/effect/proc_holder/spell/spacetime_dist/after_cast(list/targets)
|
||||
addtimer(CALLBACK(src, .proc/clean_turfs), duration)
|
||||
|
||||
/obj/effect/proc_holder/spell/spacetime_dist/cast(list/targets, mob/user = usr)
|
||||
effects = list()
|
||||
for(var/V in targets)
|
||||
var/turf/T0 = V
|
||||
var/turf/T1 = targets[V]
|
||||
var/obj/effect/cross_action/spacetime_dist/STD0 = new /obj/effect/cross_action/spacetime_dist(T0)
|
||||
var/obj/effect/cross_action/spacetime_dist/STD1 = new /obj/effect/cross_action/spacetime_dist(T1)
|
||||
STD0.linked_dist = STD1
|
||||
STD0.add_overlay(T1.photograph())
|
||||
STD1.linked_dist = STD0
|
||||
STD1.add_overlay(T0.photograph())
|
||||
STD1.set_light(4, 30, "#c9fff5")
|
||||
effects += STD0
|
||||
effects += STD1
|
||||
|
||||
/obj/effect/proc_holder/spell/spacetime_dist/proc/clean_turfs()
|
||||
for(var/effect in effects)
|
||||
qdel(effect)
|
||||
effects.Cut()
|
||||
effects = null
|
||||
ready = TRUE
|
||||
|
||||
/obj/effect/cross_action
|
||||
name = "cross me"
|
||||
desc = "for crossing"
|
||||
anchored = TRUE
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist
|
||||
name = "spacetime distortion"
|
||||
desc = "A distortion in spacetime. You can hear faint music..."
|
||||
icon_state = ""
|
||||
var/obj/effect/cross_action/spacetime_dist/linked_dist
|
||||
var/busy = FALSE
|
||||
var/sound
|
||||
var/walks_left = 50 //prevents the game from hanging in extreme cases (such as minigun fire)
|
||||
|
||||
/obj/effect/cross_action/singularity_act()
|
||||
return
|
||||
|
||||
/obj/effect/cross_action/singularity_pull()
|
||||
return
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/Initialize(mapload)
|
||||
. = ..()
|
||||
setDir(pick(GLOB.cardinals))
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM)
|
||||
if(ismob(AM))
|
||||
var/mob/M = AM
|
||||
if(M.anti_magic_check(chargecost = 0))
|
||||
return
|
||||
if(linked_dist && walks_left > 0)
|
||||
flick("purplesparkles", src)
|
||||
linked_dist.get_walker(AM)
|
||||
walks_left--
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM)
|
||||
busy = TRUE
|
||||
flick("purplesparkles", src)
|
||||
AM.forceMove(get_turf(src))
|
||||
playsound(get_turf(src),sound,70,0)
|
||||
busy = FALSE
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/Crossed(atom/movable/AM)
|
||||
if(!busy)
|
||||
walk_link(AM)
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/attackby(obj/item/W, mob/user, params)
|
||||
if(user.temporarilyRemoveItemFromInventory(W))
|
||||
walk_link(W)
|
||||
else
|
||||
walk_link(user)
|
||||
|
||||
//ATTACK HAND IGNORING PARENT RETURN VALUE
|
||||
/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user)
|
||||
walk_link(user)
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/attack_paw(mob/user)
|
||||
walk_link(user)
|
||||
|
||||
/obj/effect/cross_action/spacetime_dist/Destroy()
|
||||
busy = TRUE
|
||||
linked_dist = null
|
||||
return ..()
|
||||
@@ -0,0 +1,118 @@
|
||||
/obj/effect/proc_holder/spell/targeted/summonitem
|
||||
name = "Instant Summons"
|
||||
desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe."
|
||||
school = "transmutation"
|
||||
charge_max = 100
|
||||
clothes_req = 0
|
||||
invocation = "GAR YOK"
|
||||
invocation_type = "whisper"
|
||||
range = -1
|
||||
level_max = 0 //cannot be improved
|
||||
cooldown_min = 100
|
||||
include_user = 1
|
||||
|
||||
var/obj/marked_item
|
||||
|
||||
action_icon_state = "summons"
|
||||
|
||||
/obj/effect/proc_holder/spell/targeted/summonitem/cast(list/targets,mob/user = usr)
|
||||
for(var/mob/living/L in targets)
|
||||
var/list/hand_items = list(L.get_active_held_item(),L.get_inactive_held_item())
|
||||
var/message
|
||||
|
||||
if(!marked_item) //linking item to the spell
|
||||
message = "<span class='notice'>"
|
||||
for(var/obj/item/item in hand_items)
|
||||
if(item.item_flags & ABSTRACT)
|
||||
continue
|
||||
if(HAS_TRAIT(item, TRAIT_NODROP))
|
||||
message += "Though it feels redundant, "
|
||||
marked_item = item
|
||||
message += "You mark [item] for recall.</span>"
|
||||
name = "Recall [item]"
|
||||
break
|
||||
|
||||
if(!marked_item)
|
||||
if(hand_items)
|
||||
message = "<span class='caution'>You aren't holding anything that can be marked for recall.</span>"
|
||||
else
|
||||
message = "<span class='notice'>You must hold the desired item in your hands to mark it for recall.</span>"
|
||||
|
||||
else if(marked_item && marked_item in hand_items) //unlinking item to the spell
|
||||
message = "<span class='notice'>You remove the mark on [marked_item] to use elsewhere.</span>"
|
||||
name = "Instant Summons"
|
||||
marked_item = null
|
||||
|
||||
else if(marked_item && QDELETED(marked_item)) //the item was destroyed at some point
|
||||
message = "<span class='warning'>You sense your marked item has been destroyed!</span>"
|
||||
name = "Instant Summons"
|
||||
marked_item = null
|
||||
|
||||
else //Getting previously marked item
|
||||
var/obj/item_to_retrieve = marked_item
|
||||
var/infinite_recursion = 0 //I don't want to know how someone could put something inside itself but these are wizards so let's be safe
|
||||
|
||||
if(!item_to_retrieve.loc)
|
||||
if(isorgan(item_to_retrieve)) // Organs are usually stored in nullspace
|
||||
var/obj/item/organ/organ = item_to_retrieve
|
||||
if(organ.owner)
|
||||
// If this code ever runs I will be happy
|
||||
log_combat(L, organ.owner, "magically removed [organ.name] from", addition="INTENT: [uppertext(L.a_intent)]")
|
||||
organ.Remove(organ.owner)
|
||||
else
|
||||
while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) //if it's in something you get the whole thing.
|
||||
if(isitem(item_to_retrieve.loc))
|
||||
var/obj/item/I = item_to_retrieve.loc
|
||||
if(I.item_flags & ABSTRACT) //Being able to summon abstract things because your item happened to get placed there is a no-no
|
||||
break
|
||||
if(ismob(item_to_retrieve.loc)) //If its on someone, properly drop it
|
||||
var/mob/M = item_to_retrieve.loc
|
||||
|
||||
if(issilicon(M)) //Items in silicons warp the whole silicon
|
||||
M.loc.visible_message("<span class='warning'>[M] suddenly disappears!</span>")
|
||||
M.forceMove(L.loc)
|
||||
M.loc.visible_message("<span class='caution'>[M] suddenly appears!</span>")
|
||||
item_to_retrieve = null
|
||||
break
|
||||
M.dropItemToGround(item_to_retrieve)
|
||||
|
||||
if(iscarbon(M)) //Edge case housekeeping
|
||||
var/mob/living/carbon/C = M
|
||||
if(C.stomach_contents && item_to_retrieve in C.stomach_contents)
|
||||
C.stomach_contents -= item_to_retrieve
|
||||
for(var/X in C.bodyparts)
|
||||
var/obj/item/bodypart/part = X
|
||||
if(item_to_retrieve in part.embedded_objects)
|
||||
part.embedded_objects -= item_to_retrieve
|
||||
to_chat(C, "<span class='warning'>The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!</span>")
|
||||
if(!C.has_embedded_objects())
|
||||
C.clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
break
|
||||
|
||||
else
|
||||
if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery
|
||||
var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc
|
||||
P.disconnect()
|
||||
P.update_icon()
|
||||
|
||||
item_to_retrieve = item_to_retrieve.loc
|
||||
|
||||
infinite_recursion += 1
|
||||
|
||||
if(!item_to_retrieve)
|
||||
return
|
||||
|
||||
if(item_to_retrieve.loc)
|
||||
item_to_retrieve.loc.visible_message("<span class='warning'>The [item_to_retrieve.name] suddenly disappears!</span>")
|
||||
if(!L.put_in_hands(item_to_retrieve))
|
||||
item_to_retrieve.forceMove(L.drop_location())
|
||||
item_to_retrieve.loc.visible_message("<span class='caution'>The [item_to_retrieve.name] suddenly appears!</span>")
|
||||
playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1)
|
||||
else
|
||||
item_to_retrieve.loc.visible_message("<span class='caution'>The [item_to_retrieve.name] suddenly appears in [L]'s hand!</span>")
|
||||
playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1)
|
||||
|
||||
|
||||
if(message)
|
||||
to_chat(L, message)
|
||||
Reference in New Issue
Block a user