forked from Aurorastation/Aurora.3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
modifiers.dm
460 lines (354 loc) · 15.7 KB
/
modifiers.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
/*
//Temporary modifiers system, by Nanako
//This system is designed to allow making non-permanant, reversible changes to variables of any atom,
//Though the system will be primarily used for altering properties of mobs, it can work on anything.
//Intended uses are to allow equipment and items that modify a mob's various stats and attributes
//As well as to replace the badly designed chem effects system
This system works through a few main procs which should be overridden:
All overridden procs should contain a call to parent at the start, before any other code
Activate: This applies the effects. its here that you change any variables.
The author is also responsible here for storing any information necessary to later revert these changes
Deactivate: This proc removes the effects. The author is responsible for writing it to reverse the changes cleanly
If using a strength var or any other kind of dynamic determinor of effects
It is very important NOT to factor that in when deactivating, because it may be changed while active
Instead, factor it in while activating and save the delta of the changed values.
that is, how much you added/subtracted
Apply that saved value in reverse when deactivating.
Both activate and deactivate are stateful. Activate will not run if active is true.
Deactivate will not run if active is false
Process: Called once every second while the effect is active. Usually this will only see if its time
to recheck validity, but it can be overridden to add extra per-tick functionality.
When created, a status effect will take the following parameters in new
Mandatory:
1. Affected atom
2. Modifier type
3. Source atom (not mandatory if type is custom)
Optional:
4. Source data
5. Strength
6. Duration
7. Check interval
//The affected atom is mandatory, without something to affect the modifier cannot exist
//Modifier type is one of a selection of constants which determines the automated validity checking.
It does not enforce anything about the changes or other functionality. A valid option is mandatory
//Source object is the thing that this modifier is based on, or anchored to.
//It is used as a point of reference in validity checks. Usually mandatory but some types do not require it
//Source data provides additional information for validity, such as a maximum range from the source.
//Only required for certain types
//Strength can be passed in by the caller and used insetting or removing the variable changes.
//It is never required for anything and not incorporated in base behaviour
//Duration can be used for any type except custom. The modifier will cease to be valid
//this long after it is created
//Duration is decremented every proc until it falls below zero.
//This is used so that duration can be refreshed or increased before the modifier expires to prolong it
//Check interval is a time, in deciseconds, between validity checks. a >0 value is required here,
//the default is 300 (every 30 seconds),
//Check interval is used to generate a world time at which the next check will run
Please note that automated validity checking is primarily as a safety, to ensure things aren't left
when they shouldn't be. If you desire something removed in a timely manner, it's recommended to manually
call the effect's end proc from your code when you're done with it. For example, if a piece of equipment
applying a modifier is taken off.
Setting the check interval very low just to cause the effect to be removed quickly is bad practise
it should be avoided in favour of manual removal where possible
*/
//Modifier types
//These are needed globally and cannot be undefined
#define MODIFIER_EQUIPMENT 1
//The status effect remains valid as long as it is worn upon the affected mob.
//Worn here means it must be held in a valid equip slot, which does not include pockets, storage, or held in hands.
//The affected atom must be a mob
#define MODIFIER_ITEM 2
//The modifier remains valid as long as the item is in the target's contents,
//no matter how many layers deep, if it can be found by recursing up, it is valid
//This is essentially a more permissable version of equipment, and works when held, in backpacks, pockets, etc
//It can also be used on non-mob targets
#define MODIFIER_REAGENT 3
//The status effect remains valid as long as the dose of this chemical in a mob's reagents is above
//a specified dose value (specified in source data).
//The default of zero will keep it valid if the chemical is in them at all
//This checks for the reagent by type, in any of a mob's reagent holders - touching, blood, ingested
//Affected atom must be a mob
#define MODIFIER_AURA 4
//The modifier remains valid as long as the target's turf is within a range of the source's turf
//The range is defined in source data
//A range of zero is still valid if source and target are on the same turf. Sub-zero range is invalid
//Works on any affected atom
#define MODIFIER_TIMED 5
//The modifier remains valid as long as the duration has not expired.
//Note that a duration can be used on any time, this type is just one that does not
//check anything else but duration.
//Does not require or use a source atom
//Duration is mandatory for this type.
//Works on any atom
#define MODIFIER_CUSTOM 6
//The validity check will always return 1. The author is expected to override
//it with custom validity checking behaviour.
//Does not require or use a source atom
//Does not support duration
//Override Modes:
//An override parameter is passed in with New, which determines what to do if a modifier of
//the same type already exists on the target
#define MODIFIER_OVERRIDE_DENY 0
//The default. If a modifier of our type already exists, the new one is discarded. It will Qdel itself
//Without adding itself to any lists
#define MODIFIER_OVERRIDE_NEIGHBOR 1
//The new modifier ignores the existing one, and adds itself to the list alongside it
//This is not recommended but you may have a specific application
//Using the strength var and updating the effects is preferred if you want to stack multiples
//of the same type of modifier on one mob
#define MODIFIER_OVERRIDE_REPLACE 2
//Probably the most common nondefault and most useful. If an old modifier of the same type exists,
//Then the old one is first stopped without suspending, and deleted.
//Then the new one will add itself as normal
#define MODIFIER_OVERRIDE_REFRESH 3
//This mode will overwrite the variables of the old one with our new values
//It will also force it to remove and reapply its effects
//This is useful for applying a lingering modifier, by refreshing its duration
#define MODIFIER_OVERRIDE_STRENGTHEN 4
//Almost identical to refresh, but it will only apply if the new modifer has a higher strength value
//If the existing modifier's strength is higher than the new one, the new is discarded
#define MODIFIER_OVERRIDE_CUSTOM 5
//Calls a custom override function to be overwritten
//This is the main proc you should call to create a modifier on a target object
/datum/proc/add_modifier(var/typepath, var/_modifier_type, var/_source = null, var/_source_data = 0, var/_strength = 0, var/_duration = 0, var/_check_interval = 0, var/override = 0)
var/datum/modifier/D = new typepath(src, _modifier_type, _source, _source_data, _strength, _duration, _check_interval)
if (!QDELETED(D))
return D.handle_registration(override)
else
return null//The modifier must have failed creation and deleted itself
/datum/modifier
//Config
var/check_interval = 300//How often, in deciseconds, we will recheck the validity
var/atom/target = null
var/atom/source = null
var/modifier_type = 0
var/source_data = 0
var/strength = 0
var/duration = null
//A list of equip slots which are considered 'worn'.
//For equipment modifier type to be valid, the source object must be in a mob's contents
//and equipped to one of these whitelisted slots
//This list can be overridden if you want a custom slot whitelist
var/list/valid_equipment_slots = list(slot_back, slot_wear_mask, slot_handcuffed, slot_belt, \
slot_wear_id, slot_l_ear, slot_glasses, slot_gloves, slot_head, slot_shoes, slot_wear_suit, \
slot_w_uniform,slot_legcuffed, slot_r_ear, slot_legs, slot_tie)
//Operating Vars
var/active = 0//Whether or not the effects are currently applied
var/next_check = 0
var/last_tick = 0
//If creation of a modifier is successful, it will return a reference to itself
//If creation fails for any reason, it will return null as well as giving some debug output
/datum/modifier/New(var/atom/_target, var/_modifier_type, var/_source = null, var/_source_data = 0, var/_strength = 0, var/_duration = 0, var/_check_interval = 0)
..()
target = _target
modifier_type = _modifier_type
source = _source
source_data = _source_data
strength = _strength
last_tick = world.time
if (_duration)
duration = _duration
if (_check_interval)
check_interval = _check_interval
if (!target || !modifier_type)
return invalid_creation("No target and/or no modifier type was submitted")
switch (modifier_type)
if (MODIFIER_EQUIPMENT)
if (!istype(target, /mob))
return invalid_creation("Equipment type requires a mob target")
if (!source || !istype(source, /obj))
return invalid_creation("Equipment type requires an object source")
//TODO: Port equip slot var
if (MODIFIER_ITEM)
if (!source || !istype(source, /obj))
return invalid_creation("Item type requires a source")
if (MODIFIER_REAGENT)
if (!istype(target, /mob) || !istype(source, /datum/reagent))
return invalid_creation("Reagent type requires a mob target and a reagent source")
if (MODIFIER_AURA)
if (!source || !istype(source, /atom))
return invalid_creation("Aura type requires an atom source")
if (MODIFIER_TIMED)
if (!duration || duration <= 0)
return invalid_creation("Timed type requires a duration")
if (MODIFIER_CUSTOM)
//No code here, just to prevent else
else
return invalid_creation("Invalid or unrecognised modifier type")//Not a valid modifier type.
return 1
/datum/modifier/proc/handle_registration(var/override = 0)
var/datum/modifier/existing = null
for (var/datum/modifier/D in target.modifiers)
if (D.type == type)
existing = D
if (!existing)
START_PROCESSING(SSmodifiers, src)
LAZYADD(target.modifiers, src)
activate()
return src
else
return handle_override(override, existing)
/datum/modifier/proc/activate()
if (!QDELING(src) && !active && target)
active = 1
return 1
return 0
/datum/modifier/proc/deactivate()
active = 0
return 1
/datum/modifier/process()
if (!active)
last_tick = world.time
return 0
if (!isnull(duration))duration -= world.time - last_tick
if (world.time > next_check)
last_tick = world.time
return check_validity()
last_tick = world.time
return 1
/datum/modifier/proc/check_validity()
next_check = world.time + check_interval
if (QDELETED(target))
return validity_fail("Target is gone!")
if (modifier_type == MODIFIER_CUSTOM)
if (custom_validity())
return 1
else
return validity_fail("Custom failed")
if (!isnull(duration) && duration <= 0)
return validity_fail("Duration expired")
else if (modifier_type == MODIFIER_TIMED)
return 1
if (QDELETED(source))//If we're not timed or custom, then we need a source. If our source is gone, we are invalid
return validity_fail("Source is gone and we need one")
switch (modifier_type)
if (MODIFIER_EQUIPMENT)
if (source.loc != target)
return validity_fail("Not in contents of mob")
var/obj/item/I = source
if (!I.equip_slot || !(I.equip_slot in valid_equipment_slots))
return validity_fail("Not equipped in the correct place")
//TODO: Port equip slot var. this cant be done properly without it. This is a temporary implementation
if (MODIFIER_ITEM)
if (!source.find_up_hierarchy(target))//If source is somewhere inside target, this will be true
return validity_fail("Not found in parent hierarchy")
if (MODIFIER_REAGENT)
var/totaldose = 0
if (!istype(source, /datum/reagent))//this shouldnt happen
return validity_fail("Source is not a reagent!")
var/ourtype = source.type
for (var/datum/reagent/R in target.reagents.reagent_list)
if (istype(R, ourtype))
totaldose += R.dose
if (istype(target, /mob/living))
var/mob/living/L = target
for (var/datum/reagent/R in L.ingested.reagent_list)
if (istype(R, ourtype))
totaldose += R.dose
if (istype(target, /mob/living/carbon))
var/mob/living/carbon/C = target
for (var/datum/reagent/R in C.bloodstr.reagent_list)
if (istype(R, ourtype))
totaldose += R.dose
for (var/datum/reagent/R in C.touching.reagent_list)
if (istype(R, ourtype))
totaldose += R.dose
for (var/datum/reagent/R in C.breathing.reagent_list)
if (istype(R, ourtype))
totaldose += R.dose
if (totaldose < source_data)
return validity_fail("Dose is too low!")
if (MODIFIER_AURA)
if (!(get_turf(target) in range(source_data, get_turf(source))))
return validity_fail("Target not in range of source")
return 1
//Override this without a call to parent, for custom validity conditions
/datum/modifier/proc/custom_validity()
return 1
/datum/modifier/proc/validity_fail(var/reason)
//world << "MODIFIER VALIDITY FAIL: [reason]"
qdel(src)
return 0
/datum/modifier/proc/invalid_creation(var/reason)
log_debug("ERROR: [src] MODIFIER CREATION FAILED on [target]: [reason]")
qdel(src)
return 0
//called by any object to either pause or remove the proc.
/datum/modifier/proc/stop(var/instant = 0, var/suspend = 0)
//Instant var removes us from the lists immediately, instead of waiting til next frame when qdel goes through
if (instant)
if (target)
LAZYREMOVE(target.modifiers, src)
STOP_PROCESSING(SSmodifiers, src)
if (suspend)
deactivate()
else
qdel(src)
//Suspends and immediately restarts the proc, thus reapplying its effects
/datum/modifier/proc/refresh()
deactivate()
activate()
/datum/modifier/Destroy()
if (active)
deactivate()
if (target)
LAZYREMOVE(target.modifiers, src)
STOP_PROCESSING(SSmodifiers, src)
return ..()
//Handles overriding an existing modifier of the same type.
//This function should return either src or the existing, depending on whether or not src will be kept
/datum/modifier/proc/handle_override(var/override, var/datum/modifier/existing)
switch(override)
if (MODIFIER_OVERRIDE_DENY)
qdel(src)
return existing
if (MODIFIER_OVERRIDE_NEIGHBOR)
START_PROCESSING(SSmodifiers, src)
LAZYADD(target.modifiers, src)
activate()
return src
if (MODIFIER_OVERRIDE_REPLACE)
existing.stop()
START_PROCESSING(SSmodifiers, src)
LAZYADD(target.modifiers, src)
activate()
return src
if (MODIFIER_OVERRIDE_REFRESH)
existing.strength = strength
existing.duration = duration
existing.source = source
existing.source_data = source_data
if (existing.check_validity())
existing.refresh()
qdel(src)
return existing
else
qdel(src)
return null//this should only happen if you overwrote the existing with bad values.
//It will result in both existing and src being deleted
//The null return will allow the source to see this went wrong and remake the modifier
if (MODIFIER_OVERRIDE_STRENGTHEN)
if (strength > existing.strength)
existing.strength = strength
existing.duration = duration
existing.source = source
existing.source_data = source_data
if (existing.check_validity())
existing.refresh()
qdel(src)
return existing
qdel(src)
return null
qdel(src)
return existing
if (MODIFIER_OVERRIDE_CUSTOM)
return custom_override(existing)
else
qdel(src)
return existing
//This function should be completely overwritten, without a call to parent, to specify custom override
/datum/modifier/proc/custom_override(var/datum/modifier/existing)
qdel(src)
return existing
/atom
var/tmp/list/modifiers