/
spacevine.dm
762 lines (652 loc) · 31.2 KB
/
spacevine.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
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
/// Determines brightness of the light emitted by kudzu with the light mutation
#define LIGHT_MUTATION_BRIGHTNESS 4
/// Determines the probability that the toxicity mutation will harm someone who passes through it
#define TOXICITY_MUTATION_PROB 10
/// Determines the impact radius of kudzu's explosive mutation
#define EXPLOSION_MUTATION_IMPACT_RADIUS 2
/// Determines the scale factor for the amount of gas removed by kudzu with a gas removal mutation, which is this scale factor * the kudzu's energy level
#define GAS_MUTATION_REMOVAL_MULTIPLIER 3
/// Determines the probability that the thorn mutation will harm someone who passes through or attacks it
#define THORN_MUTATION_CUT_PROB 10
/// Determines the probability that a kudzu plant with the flowering mutation will spawn a venus flower bud
#define FLOWERING_MUTATION_SPAWN_PROB 10
/// Maximum energy used per atmos tick that the temperature stabilisation mutation will use to bring the temperature to T20C
#define TEMP_STABILISATION_MUTATION_MAXIMUM_ENERGY 40000
/// Temperature below which the kudzu can't spread
#define VINE_FREEZING_POINT 100
/// Kudzu severity values for traits, based on severity in terms of how severely it impacts the game, the lower the severity, the more likely it is to appear
#define SEVERITY_TRIVIAL 1
#define SEVERITY_MINOR 2
#define SEVERITY_AVERAGE 4
#define SEVERITY_ABOVE_AVERAGE 7
#define SEVERITY_MAJOR 10
/// Kudzu mutativeness is based on a scale factor * potency
#define MUTATIVENESS_SCALE_FACTOR 0.2
/// Kudzu maximum mutation severity is a linear function of potency
#define MAX_SEVERITY_LINEAR_COEFF 0.15
#define MAX_SEVERITY_CONSTANT_TERM 10
/// Additional maximum mutation severity given to kudzu spawned by a random event
#define MAX_SEVERITY_EVENT_BONUS 10
/// The maximum possible productivity value of a (normal) kudzu plant, used for calculating a plant's spread cap and multiplier
#define MAX_POSSIBLE_PRODUCTIVITY_VALUE 10
/// Kudzu spread cap is a scaled version of production speed, such that the better the production speed, ie. the lower the speed value is, the faster is spreads
#define SPREAD_CAP_LINEAR_COEFF 4
#define SPREAD_CAP_CONSTANT_TERM 20
/// Kudzu spread multiplier is a reciporal function of production speed, such that the better the production speed, ie. the lower the speed value is, the faster it spreads
#define SPREAD_MULTIPLIER_MAX 50
/datum/round_event_control/spacevine
name = "Space Vines"
typepath = /datum/round_event/spacevine
weight = 15
max_occurrences = 3
min_players = 10
category = EVENT_CATEGORY_ENTITIES
description = "Kudzu begins to overtake the station. Might spawn man-traps."
/datum/round_event/spacevine
fakeable = FALSE
/datum/round_event/spacevine/start()
var/list/turfs = list() //list of all the empty floor turfs in the hallway areas
var/obj/structure/spacevine/vine = new()
for(var/area/station/hallway/area in world)
for(var/turf/floor in area)
if(floor.Enter(vine))
turfs += floor
qdel(vine)
if(length(turfs)) //Pick a turf to spawn at if we can
var/turf/floor = pick(turfs)
new /datum/spacevine_controller(floor, list(pick(subtypesof(/datum/spacevine_mutation))), rand(50,100), rand(1,4), src) //spawn a controller at turf with randomized stats and a single random mutation
/datum/spacevine_mutation
/// Displayed name of mutation
var/name = ""
/// Severity of mutation in terms of gameplay, affects appearance chance and how many mutations can be on the same vine
var/severity = 1
var/hue
var/quality
/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder)
holder.mutations |= src
holder.add_atom_colour(hue, FIXED_COLOUR_PRIORITY)
/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder)
return
/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder)
return
/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder)
return
/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder)
return
/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/item, expected_damage)
. = expected_damage
/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser)
return
/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/chem)
return
/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater)
return
/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target)
return
/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled)
return
/datum/spacevine_mutation/proc/on_explosion(severity, target, obj/structure/spacevine/holder)
return
/datum/spacevine_mutation/proc/additional_atmos_processes(obj/structure/spacevine/holder, datum/gas_mixture/air)
return
/datum/spacevine_mutation/aggressive_spread/proc/aggrospread_act(obj/structure/spacevine/vine, mob/living/M)
return
/datum/spacevine_mutation/light
name = "Light"
hue = "#B2EA70"
quality = POSITIVE
severity = SEVERITY_TRIVIAL
/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder)
if(holder.energy)
holder.set_light(LIGHT_MUTATION_BRIGHTNESS, 0.3)
/datum/spacevine_mutation/toxicity
name = "Toxic"
hue = "#9B3675"
severity = SEVERITY_AVERAGE
quality = NEGATIVE
/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser)
if(issilicon(crosser))
return
if(prob(TOXICITY_MUTATION_PROB) && istype(crosser) && !isvineimmune(crosser))
to_chat(crosser, span_alert("You accidentally touch the vine and feel a strange sensation."))
crosser.adjustToxLoss(20)
/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater)
if(!isvineimmune(eater))
eater.adjustToxLoss(20)
/datum/spacevine_mutation/explosive // JC IT'S A BOMB
name = "Explosive"
hue = "#D83A56"
quality = NEGATIVE
severity = SEVERITY_MAJOR
/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, target, obj/structure/spacevine/holder)
if(explosion_severity < 3)
qdel(holder)
else
. = 1
QDEL_IN(holder, 5)
/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/item)
explosion(holder, light_impact_range = EXPLOSION_MUTATION_IMPACT_RADIUS, adminlog = FALSE)
/datum/spacevine_mutation/fire_proof
name = "Fire proof"
hue = "#FF616D"
quality = MINOR_NEGATIVE
severity = SEVERITY_ABOVE_AVERAGE
/datum/spacevine_mutation/fire_proof/add_mutation_to_vinepiece(obj/structure/spacevine/holder)
. = ..()
holder.trait_flags |= SPACEVINE_HEAT_RESISTANT
/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/item, expected_damage)
if(item && item.damtype == BURN)
. = 0
else
. = expected_damage
/datum/spacevine_mutation/cold_proof
name = "Cold proof"
hue = "#0BD5D9"
quality = MINOR_NEGATIVE
severity = SEVERITY_AVERAGE
/datum/spacevine_mutation/cold_proof/add_mutation_to_vinepiece(obj/structure/spacevine/holder)
. = ..()
holder.trait_flags |= SPACEVINE_COLD_RESISTANT
/datum/spacevine_mutation/temp_stabilisation
name = "Temperature stabilisation"
hue = "#B09856"
quality = POSITIVE
severity = SEVERITY_AVERAGE
/datum/spacevine_mutation/temp_stabilisation/add_mutation_to_vinepiece(obj/structure/spacevine/holder)
. = ..()
holder.always_atmos_process = TRUE
/datum/spacevine_mutation/temp_stabilisation/additional_atmos_processes(obj/structure/spacevine/holder, datum/gas_mixture/air)
var/heat_capacity = air.heat_capacity()
if(!heat_capacity) // No heating up space or vacuums
return
var/energy_used = min(abs(air.temperature - T20C) * heat_capacity, TEMP_STABILISATION_MUTATION_MAXIMUM_ENERGY)
var/delta_temperature = energy_used / heat_capacity
if(delta_temperature < 0.1)
return
if(air.temperature > T20C)
delta_temperature *= -1
air.temperature += delta_temperature
holder.air_update_turf(FALSE, FALSE)
/datum/spacevine_mutation/vine_eating
name = "Vine eating"
hue = "#F4A442"
quality = MINOR_NEGATIVE
severity = SEVERITY_MINOR
/// Destroys any vine on spread-target's tile. The checks for if this should be done are in the spread() proc.
/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target)
for(var/obj/structure/spacevine/prey in target)
qdel(prey)
/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently
name = "Aggressive spreading"
hue = "#316b2f"
severity = SEVERITY_MAJOR
quality = NEGATIVE
/// Checks mobs on spread-target's turf to see if they should be hit by a damaging proc or not.
/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/turf, mob/living)
for(var/mob/living/victim in turf)
if(!isvineimmune(victim) && victim.stat != DEAD) // Don't kill immune creatures. Dead check to prevent log spam when a corpse is trapped between vine eaters.
aggrospread_act(holder, victim)
/// What happens if an aggr spreading vine buckles a mob.
/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled)
aggrospread_act(holder, buckled)
/// Hurts mobs. To be used when a vine with aggressive spread mutation spreads into the mob's tile or buckles them.
/datum/spacevine_mutation/aggressive_spread/aggrospread_act(obj/structure/spacevine/vine, mob/living/living_mob)
var/mob/living/carbon/victim = living_mob //If the mob is carbon then it now also exists as a victim, and not just an living mob.
if(istype(victim)) //If the mob (M) is a carbon subtype (C) we move on to pick a more complex damage proc, with damage zones, wounds and armor mitigation.
var/obj/item/bodypart/limb = pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_HEAD, BODY_ZONE_CHEST) //Picks a random bodypart. Does not runtime even if it's missing.
var/armor = victim.run_armor_check(limb, MELEE, null, null) //armor = the armor value of that randomly chosen bodypart. Nulls to not print a message, because it would still print on pierce.
var/datum/spacevine_mutation/thorns/thorn = locate() in vine.mutations //Searches for the thorns mutation in the "mutations"-list inside obj/structure/spacevine, and defines T if it finds it.
if(thorn && (prob(40))) //If we found the thorns mutation there is now a chance to get stung instead of lashed or smashed.
victim.apply_damage(50, BRUTE, def_zone = limb, wound_bonus = rand(-20,10), sharpness = SHARP_POINTY) //This one gets a bit lower damage because it ignores armor.
victim.Stun(1 SECONDS) //Stopped in place for a moment.
playsound(living_mob, 'sound/weapons/pierce.ogg', 50, TRUE, -1)
living_mob.visible_message(span_danger("[living_mob] is nailed by a sharp thorn!"), \
span_userdanger("You are nailed by a sharp thorn!"))
log_combat(vine, living_mob, "aggressively pierced") //"Aggressively" for easy ctrl+F'ing in the attack logs.
else
if(prob(80))
victim.apply_damage(60, BRUTE, def_zone = limb, blocked = armor, wound_bonus = rand(-20,10), sharpness = SHARP_EDGED)
victim.Knockdown(2 SECONDS)
playsound(victim, 'sound/weapons/whip.ogg', 50, TRUE, -1)
living_mob.visible_message(span_danger("[living_mob] is lacerated by an outburst of vines!"), \
span_userdanger("You are lacerated by an outburst of vines!"))
log_combat(vine, living_mob, "aggressively lacerated")
else
victim.apply_damage(60, BRUTE, def_zone = limb, blocked = armor, wound_bonus = rand(-20,10), sharpness = NONE)
victim.Knockdown(3 SECONDS)
var/atom/throw_target = get_edge_target_turf(living_mob, get_dir(vine, get_step_away(living_mob, vine)))
victim.throw_at(throw_target, 3, 6)
playsound(victim, 'sound/effects/hit_kick.ogg', 50, TRUE, -1)
living_mob.visible_message(span_danger("[living_mob] is smashed by a large vine!"), \
span_userdanger("You are smashed by a large vine!"))
log_combat(vine, living_mob, "aggressively smashed")
else //Living but not a carbon? Maybe a silicon? Can't be wounded so have a big chunk of simple bruteloss with no special effects. They can be entangled.
living_mob.adjustBruteLoss(75)
playsound(living_mob, 'sound/weapons/whip.ogg', 50, TRUE, -1)
living_mob.visible_message(span_danger("[living_mob] is brutally threshed by [vine]!"), \
span_userdanger("You are brutally threshed by [vine]!"))
log_combat(vine, living_mob, "aggressively spread into") //You aren't being attacked by the vines. You just happen to stand in their way.
/datum/spacevine_mutation/transparency
name = "transparent"
hue = ""
quality = POSITIVE
severity = SEVERITY_TRIVIAL
/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder)
holder.set_opacity(0)
holder.alpha = 125
/datum/spacevine_mutation/oxy_eater
name = "Oxygen consuming"
hue = "#28B5B5"
severity = SEVERITY_AVERAGE
quality = NEGATIVE
/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder)
var/turf/open/floor/turf = holder.loc
if(istype(turf))
var/datum/gas_mixture/gas_mix = turf.air
if(!gas_mix.gases[/datum/gas/oxygen])
return
gas_mix.gases[/datum/gas/oxygen][MOLES] = max(gas_mix.gases[/datum/gas/oxygen][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.energy, 0)
gas_mix.garbage_collect()
/datum/spacevine_mutation/nitro_eater
name = "Nitrogen consuming"
hue = "#FF7B54"
severity = SEVERITY_AVERAGE
quality = NEGATIVE
/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder)
var/turf/open/floor/turf = holder.loc
if(istype(turf))
var/datum/gas_mixture/gas_mix = turf.air
if(!gas_mix.gases[/datum/gas/nitrogen])
return
gas_mix.gases[/datum/gas/nitrogen][MOLES] = max(gas_mix.gases[/datum/gas/nitrogen][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.energy, 0)
gas_mix.garbage_collect()
/datum/spacevine_mutation/carbondioxide_eater
name = "CO2 consuming"
hue = "#798777"
severity = SEVERITY_MINOR
quality = POSITIVE
/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder)
var/turf/open/floor/turf = holder.loc
if(istype(turf))
var/datum/gas_mixture/gas_mix = turf.air
if(!gas_mix.gases[/datum/gas/carbon_dioxide])
return
gas_mix.gases[/datum/gas/carbon_dioxide][MOLES] = max(gas_mix.gases[/datum/gas/carbon_dioxide][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.energy, 0)
gas_mix.garbage_collect()
/datum/spacevine_mutation/plasma_eater
name = "Plasma consuming"
hue = "#9074b6"
severity = SEVERITY_AVERAGE
quality = POSITIVE
/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder)
var/turf/open/floor/turf = holder.loc
if(istype(turf))
var/datum/gas_mixture/gas_mix = turf.air
if(!gas_mix.gases[/datum/gas/plasma])
return
gas_mix.gases[/datum/gas/plasma][MOLES] = max(gas_mix.gases[/datum/gas/plasma][MOLES] - GAS_MUTATION_REMOVAL_MULTIPLIER * holder.energy, 0)
gas_mix.garbage_collect()
/datum/spacevine_mutation/thorns
name = "Thorny"
hue = "#9ECCA4"
severity = SEVERITY_AVERAGE
quality = NEGATIVE
/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser)
if(prob(THORN_MUTATION_CUT_PROB) && istype(crosser) && !isvineimmune(crosser))
var/mob/living/victim = crosser
victim.adjustBruteLoss(15)
to_chat(victim, span_danger("You cut yourself on the thorny vines."))
/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/item, expected_damage)
if(prob(THORN_MUTATION_CUT_PROB) && istype(hitter) && !isvineimmune(hitter))
var/mob/living/victim = hitter
victim.adjustBruteLoss(15)
to_chat(victim, span_danger("You cut yourself on the thorny vines."))
. = expected_damage
/datum/spacevine_mutation/woodening
name = "Hardened"
hue = "#997700"
quality = NEGATIVE
severity = SEVERITY_ABOVE_AVERAGE
/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder)
if(holder.energy)
holder.set_density(TRUE)
holder.modify_max_integrity(100)
/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/item, expected_damage)
if(item?.get_sharpness())
. = expected_damage * 0.5
else
. = expected_damage
/datum/spacevine_mutation/flowering
name = "Flowering"
hue = "#66DE93"
quality = NEGATIVE
severity = SEVERITY_MAJOR
/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder)
if(holder.energy == 2 && prob(FLOWERING_MUTATION_SPAWN_PROB) && !locate(/obj/structure/alien/resin/flower_bud) in range(5,holder))
var/obj/structure/alien/resin/flower_bud/spawned_flower_bud = new/obj/structure/alien/resin/flower_bud(get_turf(holder))
spawned_flower_bud.trait_flags = holder.trait_flags
/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser)
if(prob(25))
holder.entangle(crosser)
// SPACE VINES (Note that this code is very similar to Biomass code)
/obj/structure/spacevine
name = "space vine"
desc = "An extremely expansionistic species of vine."
icon = 'icons/effects/spacevines.dmi'
icon_state = "Light1"
anchored = TRUE
density = FALSE
layer = SPACEVINE_LAYER
plane = GAME_PLANE_UPPER_FOV_HIDDEN
mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough
pass_flags = PASSTABLE | PASSGRILLE
max_integrity = 50
var/energy = 0
var/can_spread = TRUE //Can this kudzu spread?
var/datum/spacevine_controller/master = null
/// List of mutations for a specific vine
var/list/mutations = list()
var/trait_flags = 0
/// Should atmos always process this tile
var/always_atmos_process = FALSE
/obj/structure/spacevine/Initialize(mapload)
. = ..()
add_atom_colour("#ffffff", FIXED_COLOUR_PRIORITY)
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
AddElement(/datum/element/connect_loc, loc_connections)
AddElement(/datum/element/atmos_sensitive, mapload)
/obj/structure/spacevine/examine(mob/user)
. = ..()
if(!length(mutations))
. += "This vine has no mutations."
return
var/text = "This vine has the following mutations:\n"
for(var/datum/spacevine_mutation/mutation as anything in mutations)
if(mutation.name == "transparent") /// Transparent has no hue
text += "<font color='#346751'>Transparent</font> "
else
text += "<font color='[mutation.hue]'>[mutation.name]</font> "
. += text
/obj/structure/spacevine/Destroy()
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_death(src)
if(master)
master.VineDestroyed(src)
mutations = list()
set_opacity(0)
if(has_buckled_mobs())
unbuckle_all_mobs(force=1)
return ..()
/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/chem)
var/override = 0
for(var/datum/spacevine_mutation/mutation in mutations)
override += mutation.on_chem(src, chem)
if(!override && istype(chem, /datum/reagent/toxin/plantbgone))
if(prob(50))
qdel(src)
/obj/structure/spacevine/proc/eat(mob/eater)
var/override = 0
for(var/datum/spacevine_mutation/mutation in mutations)
override += mutation.on_eat(src, eater)
if(!override)
qdel(src)
/obj/structure/spacevine/attacked_by(obj/item/item, mob/living/user)
var/damage_dealt = item.force
if(item.get_sharpness())
damage_dealt *= 4
if(item.damtype == BURN)
damage_dealt *= 4
for(var/datum/spacevine_mutation/mutation in mutations)
damage_dealt = mutation.on_hit(src, user, item, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further
take_damage(damage_dealt, item.damtype, MELEE, 1)
/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
if(BRUTE)
if(damage_amount)
playsound(src, 'sound/weapons/slash.ogg', 50, TRUE)
else
playsound(src, 'sound/weapons/tap.ogg', 50, TRUE)
if(BURN)
playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE)
/obj/structure/spacevine/proc/on_entered(datum/source, atom/movable/movable)
SIGNAL_HANDLER
if(!isliving(movable))
return
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_cross(src, movable)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/structure/spacevine/attack_hand(mob/user, list/modifiers)
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_hit(src, user)
user_unbuckle_mob(user, user)
. = ..()
/obj/structure/spacevine/attack_paw(mob/living/user, list/modifiers)
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_hit(src, user)
user_unbuckle_mob(user,user)
/obj/structure/spacevine/attack_alien(mob/living/user, list/modifiers)
eat(user)
/datum/spacevine_controller
///Canonical list of all the vines we "own"
var/list/obj/structure/spacevine/vines
///Queue of vines to process
var/list/growth_queue
//List of currently processed vines, on this level to prevent runtime tomfoolery
var/list/obj/structure/spacevine/queue_end
///Spread multiplier, depends on productivity, affects how often kudzu spreads
var/spread_multiplier = 5 // corresponds to artifical kudzu with production speed of 1, approaches 10% of total vines will spread per second
///Maximum spreading limit (ie. how many kudzu can there be) for this controller
var/spread_cap = 30 // corresponds to artifical kudzu with production speed of 3.5
var/list/vine_mutations_list
var/mutativeness = 1
///Maximum sum of mutation severities
var/max_mutation_severity = 20
///Minimum spread rate per second
var/minimum_spread_rate = 1
/datum/spacevine_controller/New(turf/location, list/muts, potency, production, datum/round_event/event = null)
vines = list()
growth_queue = list()
queue_end = list()
var/obj/structure/spacevine/vine = spawn_spacevine_piece(location, null, muts)
if(event)
event.announce_to_ghosts(vine)
START_PROCESSING(SSobj, src)
vine_mutations_list = list()
init_subtypes(/datum/spacevine_mutation/, vine_mutations_list)
for(var/datum/spacevine_mutation/mutation as anything in vine_mutations_list)
vine_mutations_list[mutation] = max_mutation_severity - mutation.severity // this is intended to be before the potency check as the ideal maximum potency is used for weighting
if(potency != null)
mutativeness = potency * MUTATIVENESS_SCALE_FACTOR // If potency is 100, 20 mutativeness; if 1: 0.2 mutativeness
max_mutation_severity = round(potency * MAX_SEVERITY_LINEAR_COEFF + MAX_SEVERITY_CONSTANT_TERM) // If potency is 100, 25 max mutation severity; if 1, 10 max mutation severity
if(production != null && production <= MAX_POSSIBLE_PRODUCTIVITY_VALUE) //Prevents runtime in case production is set to 11.
spread_cap = SPREAD_CAP_LINEAR_COEFF * (MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - production) + SPREAD_CAP_CONSTANT_TERM //Best production speed of 1 increases spread_cap to 60, worst production speed of 10 lowers it to 24, even distribution
spread_multiplier = SPREAD_MULTIPLIER_MAX / (MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - production) // Best production speed of 1: 10% of total vines will spread per second, worst production speed of 10: 1% of total vines (with minimum of 1) will spread per second
if(event != null) // spawned by space vine event
max_mutation_severity += MAX_SEVERITY_EVENT_BONUS
minimum_spread_rate = 3
/datum/spacevine_controller/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION(VV_HK_SPACEVINE_PURGE, "Delete Vines")
/datum/spacevine_controller/vv_do_topic(href_list)
. = ..()
if(href_list[VV_HK_SPACEVINE_PURGE])
if(tgui_alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", list("Yes", "No")) == "Yes")
DeleteVines()
/datum/spacevine_controller/proc/DeleteVines() //this is kill
QDEL_LIST(vines) //this will also qdel us
/datum/spacevine_controller/Destroy()
STOP_PROCESSING(SSobj, src)
vines.Cut()
growth_queue.Cut()
queue_end.Cut()
return ..()
/datum/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts)
var/obj/structure/spacevine/vine = new(location)
growth_queue += vine
vines += vine
vine.master = src
for(var/datum/spacevine_mutation/mutation in muts)
mutation.add_mutation_to_vinepiece(vine)
if(parent)
vine.mutations |= parent.mutations
vine.trait_flags |= parent.trait_flags
var/parentcolor = parent.atom_colours[FIXED_COLOUR_PRIORITY]
vine.add_atom_colour(parentcolor, FIXED_COLOUR_PRIORITY)
if(prob(mutativeness))
var/datum/spacevine_mutation/random_mutate = pick_weight(vine_mutations_list - vine.mutations)
var/total_severity = random_mutate.severity
for(var/datum/spacevine_mutation/mutation as anything in vine.mutations)
total_severity += mutation.severity
if(total_severity <= max_mutation_severity)
random_mutate.add_mutation_to_vinepiece(vine)
for(var/datum/spacevine_mutation/mutation in vine.mutations)
mutation.on_birth(vine)
location.Entered(vine, null)
return vine
/datum/spacevine_controller/proc/VineDestroyed(obj/structure/spacevine/vine)
vine.master = null
vines -= vine
growth_queue -= vine
queue_end -= vine
if(length(vines))
return
var/obj/item/seeds/kudzu/seed = new(vine.loc)
seed.mutations |= vine.mutations
seed.set_potency(mutativeness / MUTATIVENESS_SCALE_FACTOR)
// Mathematical notes:
// The formula for spread_multiplier is SPREAD_MULTIPLIER_MAX / (MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - production)
// So (MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - production) = SPREAD_MULTIPLIER_MAX / spread_multiplier
// ie. production = MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - SPREAD_MULTIPLIER_MAX / spread_multiplier
seed.set_production(MAX_POSSIBLE_PRODUCTIVITY_VALUE + 1 - (SPREAD_MULTIPLIER_MAX / spread_multiplier)) //Reverts spread_multiplier formula so resulting seed gets original production stat or equivalent back.
qdel(src)
/// Life cycle of a space vine
/datum/spacevine_controller/process(delta_time)
var/vine_count = length(vines)
if(!vine_count)
qdel(src) //space vines exterminated. Remove the controller
return
/// Bonus spread for kudzu that has just started out (ie. with low vine count)
var/start_spread_bonus = max(5 - spread_multiplier * (vine_count ** 2) / 400, 0)
/// Base spread rate, depends solely on spread multiplier and vine count
var/spread_base = 0.5 * vine_count / spread_multiplier
/// Actual maximum spread rate for this process tick
var/spread_max = round(clamp(delta_time * (spread_base + start_spread_bonus), max(delta_time * minimum_spread_rate, 1), spread_cap))
var/amount_processed = 0
for(var/obj/structure/spacevine/vine as anything in growth_queue)
if(!vine.can_spread)
continue
growth_queue -= vine
queue_end += vine
for(var/datum/spacevine_mutation/mutation in vine.mutations)
mutation.process_mutation(vine)
if(vine.energy >= 2) //If tile is fully grown
vine.entangle_mob()
else if(DT_PROB(10, delta_time)) //If tile isn't fully grown
vine.grow()
vine.spread()
amount_processed++
if(amount_processed >= spread_max)
break
//We can only do so much work per process, but we still want to process everything at some point
//So we shift the queue a bit
growth_queue += queue_end
queue_end = list()
/// Updates the icon as the space vine grows
/obj/structure/spacevine/proc/grow()
if(!energy)
src.icon_state = pick("Med1", "Med2", "Med3")
energy = 1
set_opacity(1)
else
src.icon_state = pick("Hvy1", "Hvy2", "Hvy3")
energy = 2
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_grow(src)
/// Buckles mobs trying to pass through it
/obj/structure/spacevine/proc/entangle_mob()
if(!has_buckled_mobs() && prob(25))
for(var/mob/living/victim in src.loc)
entangle(victim)
if(has_buckled_mobs())
break //only capture one mob at a time
/obj/structure/spacevine/proc/entangle(mob/living/victim)
if(!victim || isvineimmune(victim))
return
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_buckle(src, victim)
if((victim.stat != DEAD) && (victim.buckled != src)) //not dead or captured
to_chat(victim, span_userdanger("The vines [pick("wind", "tangle", "tighten")] around you!"))
buckle_mob(victim, 1)
/// Finds a target tile to spread to. If checks pass it will spread to it and also proc on_spread on target.
/obj/structure/spacevine/proc/spread()
var/direction = pick(GLOB.cardinals)
var/turf/stepturf = get_step(src, direction)
if(!isspaceturf(stepturf) && stepturf.Enter(src))
var/obj/structure/spacevine/spot_taken = locate() in stepturf //Locates any vine on target turf. Calls that vine "spot_taken".
var/datum/spacevine_mutation/vine_eating/eating = locate() in mutations //Locates the vine eating trait in our own seed and calls it E.
if(!spot_taken || (eating && (spot_taken && !spot_taken.mutations?.Find(eating)))) //Proceed if there isn't a vine on the target turf, OR we have vine eater AND target vine is from our seed and doesn't. Vines from other seeds are eaten regardless.
if(master)
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.on_spread(src, stepturf) //Only do the on_spread proc if it actually spreads.
stepturf = get_step(src,direction) //in case turf changes, to make sure no runtimes happen
var/obj/structure/spacevine/spawning_vine = master.spawn_spacevine_piece(stepturf, src) //Let's do a cool little animate
if(NSCOMPONENT(direction))
spawning_vine.pixel_y = direction == NORTH ? -32 : 32
animate(spawning_vine, pixel_y = 0, time = 1 SECONDS)
else
spawning_vine.pixel_x = direction == EAST ? -32 : 32
animate(spawning_vine, pixel_x = 0, time = 1 SECONDS)
/// Destroying an explosive vine sets off a chain reaction
/obj/structure/spacevine/ex_act(severity, target)
var/index
for(var/datum/spacevine_mutation/mutation in mutations)
index += mutation.on_explosion(severity, target, src)
if(!index && prob(34 * severity))
qdel(src)
/obj/structure/spacevine/should_atmos_process(datum/gas_mixture/air, exposed_temperature)
return (always_atmos_process || exposed_temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD || exposed_temperature < VINE_FREEZING_POINT || !can_spread)//if you're room temperature you're safe
/obj/structure/spacevine/atmos_expose(datum/gas_mixture/air, exposed_temperature)
for(var/datum/spacevine_mutation/mutation in mutations)
mutation.additional_atmos_processes(src, air)
if(!can_spread && (exposed_temperature >= VINE_FREEZING_POINT || (trait_flags & SPACEVINE_COLD_RESISTANT)))
can_spread = TRUE // not returning here just in case its now a plasmafire and the kudzu should be deleted
if(exposed_temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD && !(trait_flags & SPACEVINE_HEAT_RESISTANT))
qdel(src)
else if (exposed_temperature < VINE_FREEZING_POINT && !(trait_flags & SPACEVINE_COLD_RESISTANT))
can_spread = FALSE
/obj/structure/spacevine/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(isvineimmune(mover))
return TRUE
/**
* Used to determine whether the mob is immune to actions by the vine.
* Use cases: Stops vine from attacking itself, other plants.
*/
/proc/isvineimmune(atom/target)
if(isliving(target))
var/mob/living/victim = target
if(("vines" in victim.faction) || ("plants" in victim.faction))
return TRUE
return FALSE
#undef LIGHT_MUTATION_BRIGHTNESS
#undef TOXICITY_MUTATION_PROB
#undef EXPLOSION_MUTATION_IMPACT_RADIUS
#undef GAS_MUTATION_REMOVAL_MULTIPLIER
#undef THORN_MUTATION_CUT_PROB
#undef FLOWERING_MUTATION_SPAWN_PROB
#undef VINE_FREEZING_POINT
#undef SEVERITY_TRIVIAL
#undef SEVERITY_MINOR
#undef SEVERITY_AVERAGE
#undef SEVERITY_ABOVE_AVERAGE
#undef SEVERITY_MAJOR
#undef MUTATIVENESS_SCALE_FACTOR
#undef MAX_SEVERITY_LINEAR_COEFF
#undef MAX_SEVERITY_CONSTANT_TERM
#undef MAX_SEVERITY_EVENT_BONUS
#undef MAX_POSSIBLE_PRODUCTIVITY_VALUE
#undef SPREAD_CAP_LINEAR_COEFF
#undef SPREAD_CAP_CONSTANT_TERM
#undef SPREAD_MULTIPLIER_MAX