Files
Paradise/code/datums/spell.dm
Mike Long 29c9aca1a8 Devil game mode
Fixes more compile errors.  Down to 65 now.

updates << into to_chat

Down to 60 errors, also starts to port the codex gigas and law 666 for cyborg devils.

Fixes more compile errors.  Down to 41 now.

Replaces timers with spawns, and <<s with to_chats
40 compile errors.

Down to 34 compile errors.

whoops, actually down to 34 now.

Down to 25 compile errors.

Down to 15 compile errors, I'llprobably need some help at this point.

Woo!  Down to 7 compile errors.

Ported over devil hud.  Number of errors up to 19.

WOO!  It compiles.  It's completely untested, but it compiles.

Adds devils to traitor panel

Implements iron, silver and salt banes.

implements flashing lights bane

Selling your soul prevents cloning, and some other methods of revival.

Implements harvest bane

Merged and sorted icons/obj/bureaucracy.dmi

Adds toy codex gigas

Fixes compile errors, adds codex gigas sprite.

Lots of bug fixes.  Contracts work, devil revival is more consistant, etc

Adds missing icons for flaming contracts, summon pitchfork, summon wealth, employment cabinet, and sintouch.

Converts DEEP LORE explanations from hell to inferno incorporated.

Banishes the compile errors.

Devils come from hell again.

replaces offer drink obligation with a much more lore appropriate devil's fiddle reference
Also fixes contract bashing brain damage chance.

Undoes some changes I accidentally did to example config files.

Fixes up a few remaining bugs.

Puts in the codex gigas and employment contract cabinets.
 -- Lemon - I kinda skipped this one, I'll patch it back in later because
 map conflicts are suffering incarnate

Solves the devil law problem in a REALLY hacky snowflake way.

Fixes a few methods in which a hellbound can be revived.

Devils respawn with a limited number of appropriate items, instead of COMPLETELY naked upon corpse destruction.  Also adds lines to example config.

Updates devil laws to be less hacky.

Objective to sintouch x mortals now greentexts correctly.

Contracts no longer cause brain damage.  I didn't realize it was lethal on this codebase.  Oops.

Splits dust(visual_only) into dust() and dust_animation() procs

Fixes some defines.
Adds undef statements to improve compile times.

Fixes race changes from demonic form changes.

Fixes small runtime error. (Which somehow didn't break anything?)

Implements lots of small changes/corrections suggested by CrazyLemon64

I still need to test these changes, along with other potential issues he brought up.

Fixes harvest bane and power contracts.  Also adds a few </span> tag enders.

Corrects some edge cases with revival contracts.

Fixes compile error.

Reverts unneccecary change to item/weapon/reagent_containers

Cleans up the code for readability.

Prevents cloning of hellbound individuals.

Latejoins now properly have employment contracts added to employment cabinets (provided they still exist)

Infernal contracts are no longer rendered unreadable by fire and alcohol.

All fireproof paper remains readable after being fireballed, not just infernal contracts.  (Though infernal contracts are the only fireproof paper atm)

Fixes an edge case problem with cloning.

Adds is_revivable proc to mind.

Removes snowflake code involving preventing soulseller resurrection.

Indulges in the sin of sloth, and copies tg's lazy list macros

Proc calls that transform the user no longer go to a null target

Fixes devil UI, human regression will keep appearance, and adds danceoff

Devil's base forms no longer suffocate inside the devil

Fixes runtimes, gets stuff working

The arch devil can now blast down walls with their pitchfork

EXTERMINATE ALL SPIRITS

Activates devil clause in voice of god

Fawks Mcclood

Feature P A R I T Y

Fixes devil bugs from testing

- Does a death refactor to make sure that diabolical resurrection works

- Walls no longer leave girders when blasted by the devil

- Getting a new body gives you a rudimentary amount of equipment to work
with to get out of maintenance or whereever

Does all the icons in a single commit on their own because icon

conflicts suck

Starting point of extra devil rebalance/fixes

Ports devil friends

Also oops tramples all over Fethas' corpse PR that's still up I need
to take care of that one

Styling fixes
2019-01-13 12:00:49 +01:00

479 lines
17 KiB
Plaintext

#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/list/spells = typesof(/obj/effect/proc_holder/spell) //needed for the badmin verb for now
/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/user, params, atom/A)
if(user.ranged_ability != src)
to_chat(user, "<span class='warning'><b>[user.ranged_ability.name]</b> has been disabled.")
user.ranged_ability.remove_ranged_ability(user)
return TRUE //TRUE for failed, FALSE for passed.
user.changeNext_click(CLICK_CD_CLICK_ABILITY)
user.face_atom(A)
return FALSE
/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg)
if(!user || !user.client)
return
if(user.ranged_ability && user.ranged_ability != src)
to_chat(user, "<span class='warning'><b>[user.ranged_ability.name]</b> has been replaced by <b>[name]</b>.")
user.ranged_ability.remove_ranged_ability(user)
user.ranged_ability = src
user.client.click_intercept = user.ranged_ability
add_mousepointer(user.client)
active = TRUE
if(msg)
to_chat(user, msg)
update_icon()
/obj/effect/proc_holder/proc/add_mousepointer(client/C)
if(C && ranged_mousepointer && C.mouse_pointer_icon == initial(C.mouse_pointer_icon))
C.mouse_pointer_icon = ranged_mousepointer
/obj/effect/proc_holder/proc/remove_mousepointer(client/C)
if(C && ranged_mousepointer && C.mouse_pointer_icon == ranged_mousepointer)
C.mouse_pointer_icon = initial(C.mouse_pointer_icon)
/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg)
if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
return
user.ranged_ability = null
user.client.click_intercept = null
remove_mousepointer(user.client)
active = FALSE
if(msg)
to_chat(user, msg)
update_icon()
/obj/effect/proc_holder/spell
name = "Spell"
desc = "A wizard spell"
panel = "Spells"//What panel the proc holder needs to go on.
density = 0
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/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/ghost = 0 // Skip life check.
var/clothes_req = 1 //see if it requires clothes
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/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 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 timeshas as a cooldown. Make sure to set this for every spell
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/critfailchance = 0
var/centcom_cancast = 1 //Whether or not the spell should be allowed on z2
var/datum/action/spell_action/action = null
var/action_icon = 'icons/mob/actions/actions.dmi'
var/action_icon_state = "spell_default"
var/action_background_icon_state = "bg_spell"
var/special_availability_check = 0//Whether the spell needs to bypass the action button's IsAvailable()
var/sound = null //The sound the spell makes when it is cast
/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/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(((!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 0
if(ishuman(user))
var/mob/living/carbon/human/caster = user
if(caster.remoteview_target)
caster.remoteview_target = null
caster.reset_perspective(0)
return 0
if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
return 0
if(!skipcharge)
switch(charge_type)
if("recharge")
if(charge_counter < charge_max)
to_chat(user, still_recharging_msg)
return 0
if("charges")
if(!charge_counter)
to_chat(user, "<span class='notice'>[name] has no charges left.</span>")
return 0
if(!ghost)
if(user.stat && !stat_allowed)
to_chat(user, "<span class='notice'>You can't cast this spell while incapacitated.</span>")
return 0
if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled())
to_chat(user, "Mmmf mrrfff!")
return 0
var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list()))
if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check
var/mob/living/carbon/human/H = user
var/obj/item/clothing/robe = H.wear_suit
var/obj/item/clothing/hat = H.head
var/obj/item/clothing/shoes = H.shoes
if(!robe || !hat || !shoes)
to_chat(user, "<span class='notice'>Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.</span>")
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
to_chat(user, "<span class='notice'>Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.</span>")
return 0
else if(!ishuman(user))
if(clothes_req || human_req)
to_chat(user, "<span class='notice'>This spell can only be cast by humans!</span>")
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
to_chat(user, "<span class='notice'>This spell can only be cast by physical beings!</span>")
return 0
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)
return 1
/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(!user.IsVocal())
user.custom_emote(1, "makes frantic gestures!")
else
if(prob(50))//Auto-mute? Fuck that noise
user.say(invocation)
else
user.say(replacetext(invocation," ","`"))
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/New()
..()
action = new(src)
still_recharging_msg = "<span class='notice'>[name] is still recharging.</span>"
charge_counter = charge_max
/obj/effect/proc_holder/spell/Destroy()
QDEL_NULL(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/start_recharge()
if(action)
action.UpdateButtonIcon()
while(charge_counter < charge_max)
sleep(1)
charge_counter++
if(action)
action.UpdateButtonIcon()
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1, mob/user = usr) //if recharge is started is important for the trigger spells
before_cast(targets)
invocation()
if(user && user.ckey)
user.create_attack_log("<font color='red'>[key_name(user)] cast the spell [name].</font>")
spawn(0)
if(charge_type == "recharge" && recharge)
start_recharge()
if(sound)
playMagSound()
if(prob(critfailchance))
critfail(targets)
else
cast(targets, user = user)
after_cast(targets)
/obj/effect/proc_holder/spell/proc/before_cast(list/targets)
if(overlay)
for(var/atom/target in targets)
var/location
if(istype(target,/mob/living))
location = target.loc
else if(istype(target,/turf))
location = target
var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
spell.icon = overlay_icon
spell.icon_state = overlay_icon_state
spell.anchored = 1
spell.density = 0
spawn(overlay_lifespan)
qdel(spell)
/obj/effect/proc_holder/spell/proc/after_cast(list/targets)
for(var/atom/target in targets)
var/location
if(istype(target,/mob/living))
location = target.loc
else if(istype(target,/turf))
location = target
if(istype(target,/mob/living) && message)
to_chat(target, text("[message]"))
if(sparks_spread)
do_sparks(sparks_amt, 0, location)
if(smoke_spread)
if(smoke_spread == 1)
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
smoke.start()
else if(smoke_spread == 2)
var/datum/effect_system/smoke_spread/bad/smoke = new
smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
smoke.start()
else if(smoke_spread == 3)
var/datum/effect_system/smoke_spread/sleeping/smoke = new
smoke.set_up(smoke_amt, 0, location) // same here
smoke.start()
/obj/effect/proc_holder/spell/proc/cast(list/targets, mob/user = usr)
return
/obj/effect/proc_holder/spell/proc/critfail(list/targets)
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)
return
/obj/effect/proc_holder/spell/proc/updateButtonIcon()
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
switch(type)
if("bruteloss")
target.adjustBruteLoss(amount)
if("fireloss")
target.adjustFireLoss(amount)
if("toxloss")
target.adjustToxLoss(amount)
if("oxyloss")
target.adjustOxyLoss(amount)
if("stunned")
target.AdjustStunned(amount)
if("weakened")
target.AdjustWeakened(amount)
if("paralysis")
target.AdjustParalysis(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-existant vars
return
/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
var/humans_only = 0 //for avoiding simple animals and only doing "human" mobs, 0 = all mobs, 1 = humans only
/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
if(!humans_only)
for(var/mob/living/target in view_or_range(range, user, selection_type))
targets += target
else
for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type))
targets += target
if(1) //single target can be picked
if(range < 0)
targets += user
else
var/possible_targets = list()
if(!humans_only)
for(var/mob/living/M in view_or_range(range, user, selection_type))
if(!include_user && user == M)
continue
possible_targets += M
else
for(var/mob/living/carbon/human/M in view_or_range(range, user, selection_type))
if(!include_user && user == 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 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()
if(!humans_only)
for(var/mob/living/target in view_or_range(range, user, selection_type))
possible_targets += target
else
for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type))
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)
return
/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(!(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)
return
/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 0
if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
return 0
switch(charge_type)
if("recharge")
if(charge_counter < charge_max)
return 0
if("charges")
if(!charge_counter)
return 0
if(user.stat && !stat_allowed)
return 0
if(ishuman(user))
var/mob/living/carbon/human/H = user
if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled())
return 0
var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list
var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list)
if(clothes_req && !clothcheck && !clothcheck2) //clothes check
var/obj/item/clothing/robe = H.wear_suit
var/obj/item/clothing/hat = H.head
var/obj/item/clothing/shoes = H.shoes
if(!robe || !hat || !shoes)
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
return 0
else
if(clothes_req || human_req)
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
return 0
return 1