forked from ParadiseSS13/Paradise
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathspell.dm
499 lines (426 loc) · 17.2 KB
/
spell.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#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
/obj/effect/proc_holder/singularity_act()
return
/obj/effect/proc_holder/singularity_pull()
return
GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
/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
ranged_ability_user = user
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
ranged_ability_user = 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/starts_charged = TRUE //Does this spell start ready to go?
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)
if(action)
action.UpdateButtonIcon()
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>"
if(starts_charged)
charge_counter = charge_max
else
start_recharge()
/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()
START_PROCESSING(SSfastprocess, src)
/obj/effect/proc_holder/spell/process()
charge_counter += 2
if(charge_counter < charge_max)
return
STOP_PROCESSING(SSfastprocess, src)
charge_counter = charge_max
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)
add_attack_logs(user, null, "cast the spell [name]", ATKLOG_ALL)
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)
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(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)
if(action)
action.UpdateButtonIcon()
/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