forked from tgstation/tgstation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_bodyparts.dm
1046 lines (879 loc) · 39.9 KB
/
_bodyparts.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
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/obj/item/bodypart
name = "limb"
desc = "Why is it detached..."
force = 3
throwforce = 3
w_class = WEIGHT_CLASS_SMALL
icon = 'icons/mob/human_parts.dmi'
icon_state = ""
/// The icon for Organic limbs using greyscale
var/icon_greyscale = DEFAULT_BODYPART_ICON_ORGANIC
/// The icon for Robotic limbs
var/icon_robotic = DEFAULT_BODYPART_ICON_ROBOTIC
layer = BELOW_MOB_LAYER //so it isn't hidden behind objects when on the floor
grind_results = list(/datum/reagent/bone_dust = 10, /datum/reagent/liquidgibs = 5) // robotic bodyparts and chests/heads cannot be ground
var/mob/living/carbon/owner
var/datum/weakref/original_owner
var/status = BODYPART_ORGANIC
var/needs_processing = FALSE
/// BODY_ZONE_CHEST, BODY_ZONE_L_ARM, etc , used for def_zone
var/body_zone
var/aux_zone // used for hands
var/aux_layer
/// bitflag used to check which clothes cover this bodypart
var/body_part
/// Used for alternate legs, useless elsewhere
var/use_digitigrade = NOT_DIGITIGRADE
var/list/embedded_objects = list()
/// are we a hand? if so, which one!
var/held_index = 0
/// For limbs that don't really exist, eg chainsaws
var/is_pseudopart = FALSE
///If disabled, limb is as good as missing.
var/bodypart_disabled = FALSE
///Multiplied by max_damage it returns the threshold which defines a limb being disabled or not. From 0 to 1. 0 means no disable thru damage
var/disable_threshold = 0
///Controls whether bodypart_disabled makes sense or not for this limb.
var/can_be_disabled = FALSE
///Multiplier of the limb's damage that gets applied to the mob
var/body_damage_coeff = 1
var/stam_damage_coeff = 0.75
var/brutestate = 0
var/burnstate = 0
var/brute_dam = 0
var/burn_dam = 0
var/stamina_dam = 0
var/max_stamina_damage = 0
var/max_damage = 0
///Gradually increases while burning when at full damage, destroys the limb when at 100
var/cremation_progress = 0
///Subtracted to brute damage taken
var/brute_reduction = 0
///Subtracted to burn damage taken
var/burn_reduction = 0
//Coloring and proper item icon update
var/skin_tone = ""
var/body_gender = ""
var/species_id = ""
var/should_draw_gender = FALSE
var/should_draw_greyscale = FALSE
var/species_color = ""
var/mutation_color = ""
var/no_update = 0
///for nonhuman bodypart (e.g. monkey)
var/animal_origin
//for all bodyparts
var/part_origin = HUMAN_BODY
///whether it can be dismembered with a weapon.
var/dismemberable = 1
var/px_x = 0
var/px_y = 0
var/species_flags_list = list()
///the type of damage overlay (if any) to use when this bodypart is bruised/burned.
var/dmg_overlay_type
//Damage messages used by help_shake_act()
var/light_brute_msg = "bruised"
var/medium_brute_msg = "battered"
var/heavy_brute_msg = "mangled"
var/light_burn_msg = "numb"
var/medium_burn_msg = "blistered"
var/heavy_burn_msg = "peeling away"
/// The wounds currently afflicting this body part
var/list/wounds
/// The scars currently afflicting this body part
var/list/scars
/// Our current stored wound damage multiplier
var/wound_damage_multiplier = 1
/// This number is subtracted from all wound rolls on this bodypart, higher numbers mean more defense, negative means easier to wound
var/wound_resistance = 0
/// When this bodypart hits max damage, this number is added to all wound rolls. Obviously only relevant for bodyparts that have damage caps.
var/disabled_wound_penalty = 15
/// A hat won't cover your face, but a shirt covering your chest will cover your... you know, chest
var/scars_covered_by_clothes = TRUE
/// So we know if we need to scream if this limb hits max damage
var/last_maxed
/// How much generic bleedstacks we have on this bodypart
var/generic_bleedstacks
/// If we have a gauze wrapping currently applied (not including splints)
var/obj/item/stack/current_gauze
/// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/self_grasp])
var/obj/item/self_grasp/grasped_by
///A list of all the external organs we've got stored to draw horns, wings and stuff with (special because we are actually in the limbs unlike normal organs :/ )
var/list/obj/item/organ/external/external_organs = list()
/obj/item/bodypart/Initialize(mapload)
. = ..()
if(can_be_disabled)
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS), .proc/on_paralysis_trait_gain)
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS), .proc/on_paralysis_trait_loss)
if(status != BODYPART_ORGANIC)
grind_results = null
/obj/item/bodypart/Destroy()
if(owner)
owner.remove_bodypart(src)
set_owner(null)
for(var/wound in wounds)
qdel(wound) // wounds is a lazylist, and each wound removes itself from it on deletion.
if(length(wounds))
stack_trace("[type] qdeleted with [length(wounds)] uncleared wounds")
wounds.Cut()
return ..()
/obj/item/bodypart/examine(mob/user)
. = ..()
if(brute_dam > DAMAGE_PRECISION)
. += span_warning("This limb has [brute_dam > 30 ? "severe" : "minor"] bruising.")
if(burn_dam > DAMAGE_PRECISION)
. += span_warning("This limb has [burn_dam > 30 ? "severe" : "minor"] burns.")
if(locate(/datum/wound/blunt) in wounds)
. += span_warning("The bones in this limb appear badly cracked.")
if(locate(/datum/wound/slash) in wounds)
. += span_warning("The flesh on this limb appears badly lacerated.")
if(locate(/datum/wound/pierce) in wounds)
. += span_warning("The flesh on this limb appears badly perforated.")
if(locate(/datum/wound/burn) in wounds)
. += span_warning("The flesh on this limb appears badly cooked.")
/obj/item/bodypart/blob_act()
take_damage(max_damage)
/obj/item/bodypart/attack(mob/living/carbon/victim, mob/user)
if(ishuman(victim))
var/mob/living/carbon/human/human_victim = victim
if(HAS_TRAIT(victim, TRAIT_LIMBATTACHMENT))
if(!human_victim.get_bodypart(body_zone) && !animal_origin)
user.temporarilyRemoveItemFromInventory(src, TRUE)
if(!attach_limb(victim))
to_chat(user, span_warning("[human_victim]'s body rejects [src]!"))
forceMove(human_victim.loc)
if(human_victim == user)
human_victim.visible_message(span_warning("[human_victim] jams [src] into [human_victim.p_their()] empty socket!"),\
span_notice("You force [src] into your empty socket, and it locks into place!"))
else
human_victim.visible_message(span_warning("[user] jams [src] into [human_victim]'s empty socket!"),\
span_notice("[user] forces [src] into your empty socket, and it locks into place!"))
return
..()
/obj/item/bodypart/attackby(obj/item/weapon, mob/user, params)
if(weapon.get_sharpness())
add_fingerprint(user)
if(!contents.len)
to_chat(user, span_warning("There is nothing left inside [src]!"))
return
playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1)
user.visible_message(span_warning("[user] begins to cut open [src]."),\
span_notice("You begin to cut open [src]..."))
if(do_after(user, 54, target = src))
drop_organs(user, TRUE)
else
return ..()
/obj/item/bodypart/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..()
if(status != BODYPART_ROBOTIC)
playsound(get_turf(src), 'sound/misc/splort.ogg', 50, TRUE, -1)
pixel_x = rand(-3, 3)
pixel_y = rand(-3, 3)
//empties the bodypart from its organs and other things inside it
/obj/item/bodypart/proc/drop_organs(mob/user, violent_removal)
var/turf/bodypart_turf = get_turf(src)
if(status != BODYPART_ROBOTIC)
playsound(bodypart_turf, 'sound/misc/splort.ogg', 50, TRUE, -1)
seep_gauze(9999) // destroy any existing gauze if any exists
for(var/obj/item/organ/bodypart_organ in get_organs())
bodypart_organ.transfer_to_limb(src, owner)
for(var/obj/item/item_in_bodypart in src)
item_in_bodypart.forceMove(bodypart_turf)
///since organs aren't actually stored in the bodypart themselves while attached to a person, we have to query the owner for what we should have
/obj/item/bodypart/proc/get_organs()
if(!owner)
return FALSE
var/list/bodypart_organs
for(var/obj/item/organ/organ_check as anything in owner.internal_organs) //internal organs inside the dismembered limb are dropped.
if(check_zone(organ_check.zone) == body_zone)
LAZYADD(bodypart_organs, organ_check) // this way if we don't have any, it'll just return null
return bodypart_organs
//Return TRUE to get whatever mob this is in to update health.
/obj/item/bodypart/proc/on_life(delta_time, times_fired, stam_regen)
if(stamina_dam > DAMAGE_PRECISION && stam_regen) //DO NOT update health here, it'll be done in the carbon's life.
heal_damage(0, 0, INFINITY, null, FALSE)
. |= BODYPART_LIFE_UPDATE_HEALTH
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
//Cannot apply negative damage
/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm
var/hit_percent = (100-blocked)/100
if((!brute && !burn && !stamina) || hit_percent <= 0)
return FALSE
if(owner && (owner.status_flags & GODMODE))
return FALSE //godmode
if(required_status && (status != required_status))
return FALSE
var/dmg_multi = CONFIG_GET(number/damage_multiplier) * hit_percent
brute = round(max(brute * dmg_multi, 0),DAMAGE_PRECISION)
burn = round(max(burn * dmg_multi, 0),DAMAGE_PRECISION)
stamina = round(max(stamina * dmg_multi, 0),DAMAGE_PRECISION)
brute = max(0, brute - brute_reduction)
burn = max(0, burn - burn_reduction)
//No stamina scaling.. for now..
if(!brute && !burn && !stamina)
return FALSE
brute *= wound_damage_multiplier
burn *= wound_damage_multiplier
switch(animal_origin)
if(ALIEN_BODYPART,LARVA_BODYPART) //aliens take double burn //nothing can burn with so much snowflake code around
burn *= 2
/*
// START WOUND HANDLING
*/
// what kind of wounds we're gonna roll for, take the greater between brute and burn, then if it's brute, we subdivide based on sharpness
var/wounding_type = (brute > burn ? WOUND_BLUNT : WOUND_BURN)
var/wounding_dmg = max(brute, burn)
var/mangled_state = get_mangled_state()
var/bio_state = owner.get_biological_state()
var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
if(wounding_type == WOUND_BLUNT && sharpness)
if(sharpness & SHARP_EDGED)
wounding_type = WOUND_SLASH
else if (sharpness & SHARP_POINTY)
wounding_type = WOUND_PIERCE
//Handling for bone only/flesh only(none right now)/flesh and bone targets
switch(bio_state)
// if we're bone only, all cutting attacks go straight to the bone
if(BIO_JUST_BONE)
if(wounding_type == WOUND_SLASH)
wounding_type = WOUND_BLUNT
wounding_dmg *= (easy_dismember ? 1 : 0.6)
else if(wounding_type == WOUND_PIERCE)
wounding_type = WOUND_BLUNT
wounding_dmg *= (easy_dismember ? 1 : 0.75)
if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
return
// note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
// standard humanoids
if(BIO_FLESH_BONE)
// if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
// So a big sharp weapon is still all you need to destroy a limb
if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
if(wounding_type == WOUND_SLASH && !easy_dismember)
wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
if(wounding_type == WOUND_PIERCE && !easy_dismember)
wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
wounding_type = WOUND_BLUNT
else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
return
// now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
if(owner && wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND)
check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
for(var/datum/wound/iter_wound as anything in wounds)
iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus)
/*
// END WOUND HANDLING
*/
//back to our regularly scheduled program, we now actually apply damage if there's room below limb damage cap
var/can_inflict = max_damage - get_damage()
var/total_damage = brute + burn
if(total_damage > can_inflict && total_damage > 0) // TODO: the second part of this check should be removed once disabling is all done
brute = round(brute * (can_inflict / total_damage),DAMAGE_PRECISION)
burn = round(burn * (can_inflict / total_damage),DAMAGE_PRECISION)
if(can_inflict <= 0)
return FALSE
if(brute)
set_brute_dam(brute_dam + brute)
if(burn)
set_burn_dam(burn_dam + burn)
//We've dealt the physical damages, if there's room lets apply the stamina damage.
if(stamina)
set_stamina_dam(stamina_dam + round(clamp(stamina, 0, max_stamina_damage - stamina_dam), DAMAGE_PRECISION))
if(owner)
if(can_be_disabled)
update_disabled()
if(updating_health)
owner.updatehealth()
if(stamina > DAMAGE_PRECISION)
owner.update_stamina()
owner.stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME
. = TRUE
return update_bodypart_damage_state() || .
/// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds
/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE)
if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND)
return
var/mangled_state = get_mangled_state()
var/bio_state = owner.get_biological_state()
var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
if(wounding_type == WOUND_BLUNT && sharpness)
if(sharpness & SHARP_EDGED)
wounding_type = WOUND_SLASH
else if (sharpness & SHARP_POINTY)
wounding_type = WOUND_PIERCE
//Handling for bone only/flesh only(none right now)/flesh and bone targets
switch(bio_state)
// if we're bone only, all cutting attacks go straight to the bone
if(BIO_JUST_BONE)
if(wounding_type == WOUND_SLASH)
wounding_type = WOUND_BLUNT
phantom_wounding_dmg *= (easy_dismember ? 1 : 0.6)
else if(wounding_type == WOUND_PIERCE)
wounding_type = WOUND_BLUNT
phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75)
if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
return
// note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
// standard humanoids
if(BIO_FLESH_BONE)
// if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
// So a big sharp weapon is still all you need to destroy a limb
if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
if(wounding_type == WOUND_SLASH && !easy_dismember)
phantom_wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
if(wounding_type == WOUND_PIERCE && !easy_dismember)
phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
wounding_type = WOUND_BLUNT
else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
return
check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)
/**
* check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria
*
* We generate a "score" for how woundable the attack was based on the damage and other factors discussed in [/obj/item/bodypart/proc/check_woundings_mods], then go down the list from most severe to least severe wounds in that category.
* We can promote a wound from a lesser to a higher severity this way, but we give up if we have a wound of the given type and fail to roll a higher severity, so no sidegrades/downgrades
*
* Arguments:
* * woundtype- Either WOUND_BLUNT, WOUND_SLASH, WOUND_PIERCE, or WOUND_BURN based on the attack type.
* * damage- How much damage is tied to this attack, since wounding potential scales with damage in an attack (see: WOUND_DAMAGE_EXPONENT)
* * wound_bonus- The wound_bonus of an attack
* * bare_wound_bonus- The bare_wound_bonus of an attack
*/
/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus)
if(HAS_TRAIT(owner, TRAIT_NEVER_WOUNDED))
return
// note that these are fed into an exponent, so these are magnified
if(HAS_TRAIT(owner, TRAIT_EASILY_WOUNDED))
damage *= 1.5
else
damage = min(damage, WOUND_MAX_CONSIDERED_DAMAGE)
if(HAS_TRAIT(owner,TRAIT_HARDLY_WOUNDED))
damage *= 0.85
if(HAS_TRAIT(owner, TRAIT_EASYDISMEMBER))
damage *= 1.1
var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT))
var/injury_roll = base_roll
injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
var/list/wounds_checking = GLOB.global_wound_types[woundtype]
if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100))
var/datum/wound/loss/dismembering = new
dismembering.apply_dismember(src, woundtype, outright=TRUE)
return
// quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
if(ishuman(owner))
var/mob/living/carbon/human/human_wearer = owner
var/list/clothing = human_wearer.clothingonpart(src)
for(var/obj/item/clothing/clothes_check as anything in clothing)
// unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
if(clothes_check.armor.getRating(WOUND))
bare_wound_bonus = 0
break
//cycle through the wounds of the relevant category from the most severe down
for(var/datum/wound/possible_wound as anything in wounds_checking)
var/datum/wound/replaced_wound
for(var/datum/wound/existing_wound as anything in wounds)
if(existing_wound.type in wounds_checking)
if(existing_wound.severity >= initial(possible_wound.severity))
return
else
replaced_wound = existing_wound
if(initial(possible_wound.threshold_minimum) < injury_roll)
var/datum/wound/new_wound
if(replaced_wound)
new_wound = replaced_wound.replace_wound(possible_wound)
else
new_wound = new possible_wound
new_wound.apply_wound(src)
log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
return new_wound
// try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart
/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE)
var/datum/wound/potential_wound = specific_woundtype
for(var/datum/wound/existing_wound as anything in wounds)
if(existing_wound.wound_type == initial(potential_wound.wound_type))
if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force
existing_wound.replace_wound(potential_wound, smited)
return
var/datum/wound/new_wound = new potential_wound
new_wound.apply_wound(src, smited = smited)
/**
* check_wounding_mods() is where we handle the various modifiers of a wound roll
*
* A short list of things we consider: any armor a human target may be wearing, and if they have no wound armor on the limb, if we have a bare_wound_bonus to apply, plus the plain wound_bonus
* We also flick through all of the wounds we currently have on this limb and add their threshold penalties, so that having lots of bad wounds makes you more liable to get hurt worse
* Lastly, we add the inherent wound_resistance variable the bodypart has (heads and chests are slightly harder to wound), and a small bonus if the limb is already disabled
*
* Arguments:
* * It's the same ones on [/obj/item/bodypart/proc/receive_damage]
*/
/obj/item/bodypart/proc/check_woundings_mods(wounding_type, damage, wound_bonus, bare_wound_bonus)
var/armor_ablation = 0
var/injury_mod = 0
if(owner && ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
var/list/clothing = human_owner.clothingonpart(src)
for(var/obj/item/clothing/clothes as anything in clothing)
// unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
armor_ablation += clothes.armor.getRating(WOUND)
if(wounding_type == WOUND_SLASH)
clothes.take_damage_zone(body_zone, damage, BRUTE)
else if(wounding_type == WOUND_BURN && damage >= 10) // lazy way to block freezing from shredding clothes without adding another var onto apply_damage()
clothes.take_damage_zone(body_zone, damage, BURN)
if(!armor_ablation)
injury_mod += bare_wound_bonus
injury_mod -= armor_ablation
injury_mod += wound_bonus
for(var/datum/wound/wound as anything in wounds)
injury_mod += wound.threshold_penalty
var/part_mod = -wound_resistance
if(get_damage(TRUE) >= max_damage)
part_mod += disabled_wound_penalty
injury_mod += part_mod
return injury_mod
//Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all.
//Damage cannot go below zero.
//Cannot remove negative damage (i.e. apply damage)
/obj/item/bodypart/proc/heal_damage(brute, burn, stamina, required_status, updating_health = TRUE)
if(required_status && status != required_status) //So we can only heal certain kinds of limbs, ie robotic vs organic.
return
if(brute)
set_brute_dam(round(max(brute_dam - brute, 0), DAMAGE_PRECISION))
if(burn)
set_burn_dam(round(max(burn_dam - burn, 0), DAMAGE_PRECISION))
if(stamina)
set_stamina_dam(round(max(stamina_dam - stamina, 0), DAMAGE_PRECISION))
if(owner)
if(can_be_disabled)
update_disabled()
if(updating_health)
owner.updatehealth()
cremation_progress = min(0, cremation_progress - ((brute_dam + burn_dam)*(100/max_damage)))
return update_bodypart_damage_state()
///Proc to hook behavior associated to the change of the brute_dam variable's value.
/obj/item/bodypart/proc/set_brute_dam(new_value)
if(brute_dam == new_value)
return
. = brute_dam
brute_dam = new_value
///Proc to hook behavior associated to the change of the burn_dam variable's value.
/obj/item/bodypart/proc/set_burn_dam(new_value)
if(burn_dam == new_value)
return
. = burn_dam
burn_dam = new_value
///Proc to hook behavior associated to the change of the stamina_dam variable's value.
/obj/item/bodypart/proc/set_stamina_dam(new_value)
if(stamina_dam == new_value)
return
. = stamina_dam
stamina_dam = new_value
if(stamina_dam > DAMAGE_PRECISION)
needs_processing = TRUE
else
needs_processing = FALSE
//Returns total damage.
/obj/item/bodypart/proc/get_damage(include_stamina = FALSE)
var/total = brute_dam + burn_dam
if(include_stamina)
total = max(total, stamina_dam)
return total
//Checks disabled status thresholds
/obj/item/bodypart/proc/update_disabled()
if(!owner)
return
if(!can_be_disabled)
set_disabled(FALSE)
CRASH("update_disabled called with can_be_disabled false")
if(HAS_TRAIT(src, TRAIT_PARALYSIS))
set_disabled(TRUE)
return
var/total_damage = max(brute_dam + burn_dam, stamina_dam)
// this block of checks is for limbs that can be disabled, but not through pure damage (AKA limbs that suffer wounds, human/monkey parts and such)
if(!disable_threshold)
if(total_damage < max_damage)
last_maxed = FALSE
else
if(!last_maxed && owner.stat < UNCONSCIOUS)
INVOKE_ASYNC(owner, /mob.proc/emote, "scream")
last_maxed = TRUE
set_disabled(FALSE) // we only care about the paralysis trait
return
// we're now dealing solely with limbs that can be disabled through pure damage, AKA robot parts
if(total_damage >= max_damage * disable_threshold)
if(!last_maxed)
if(owner.stat < UNCONSCIOUS)
INVOKE_ASYNC(owner, /mob.proc/emote, "scream")
last_maxed = TRUE
set_disabled(TRUE)
return
if(bodypart_disabled && total_damage <= max_damage * 0.5) // reenable the limb at 50% health
last_maxed = FALSE
set_disabled(FALSE)
///Proc to change the value of the `disabled` variable and react to the event of its change.
/obj/item/bodypart/proc/set_disabled(new_disabled)
if(bodypart_disabled == new_disabled)
return
. = bodypart_disabled
bodypart_disabled = new_disabled
if(!owner)
return
owner.update_health_hud() //update the healthdoll
owner.update_body()
///Proc to change the value of the `owner` variable and react to the event of its change.
/obj/item/bodypart/proc/set_owner(new_owner)
if(owner == new_owner)
return FALSE //`null` is a valid option, so we need to use a num var to make it clear no change was made.
. = owner
owner = new_owner
var/needs_update_disabled = FALSE //Only really relevant if there's an owner
if(.)
var/mob/living/carbon/old_owner = .
if(initial(can_be_disabled))
if(HAS_TRAIT(old_owner, TRAIT_NOLIMBDISABLE))
if(!owner || !HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE))
set_can_be_disabled(initial(can_be_disabled))
needs_update_disabled = TRUE
UnregisterSignal(old_owner, list(
SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE),
SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE),
))
if(owner)
if(initial(can_be_disabled))
if(HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE))
set_can_be_disabled(FALSE)
needs_update_disabled = FALSE
RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), .proc/on_owner_nolimbdisable_trait_loss)
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), .proc/on_owner_nolimbdisable_trait_gain)
if(needs_update_disabled)
update_disabled()
///Proc to change the value of the `can_be_disabled` variable and react to the event of its change.
/obj/item/bodypart/proc/set_can_be_disabled(new_can_be_disabled)
if(can_be_disabled == new_can_be_disabled)
return
. = can_be_disabled
can_be_disabled = new_can_be_disabled
if(can_be_disabled)
if(owner)
if(HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE))
CRASH("set_can_be_disabled to TRUE with for limb whose owner has TRAIT_NOLIMBDISABLE")
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS), .proc/on_paralysis_trait_gain)
RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS), .proc/on_paralysis_trait_loss)
update_disabled()
else if(.)
if(owner)
UnregisterSignal(owner, list(
SIGNAL_ADDTRAIT(TRAIT_PARALYSIS),
SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS),
))
set_disabled(FALSE)
///Called when TRAIT_PARALYSIS is added to the limb.
/obj/item/bodypart/proc/on_paralysis_trait_gain(obj/item/bodypart/source)
SIGNAL_HANDLER
if(can_be_disabled)
set_disabled(TRUE)
///Called when TRAIT_PARALYSIS is removed from the limb.
/obj/item/bodypart/proc/on_paralysis_trait_loss(obj/item/bodypart/source)
SIGNAL_HANDLER
if(can_be_disabled)
update_disabled()
///Called when TRAIT_NOLIMBDISABLE is added to the owner.
/obj/item/bodypart/proc/on_owner_nolimbdisable_trait_gain(mob/living/carbon/source)
SIGNAL_HANDLER
set_can_be_disabled(FALSE)
///Called when TRAIT_NOLIMBDISABLE is removed from the owner.
/obj/item/bodypart/proc/on_owner_nolimbdisable_trait_loss(mob/living/carbon/source)
SIGNAL_HANDLER
set_can_be_disabled(initial(can_be_disabled))
//Updates an organ's brute/burn states for use by update_damage_overlays()
//Returns 1 if we need to update overlays. 0 otherwise.
/obj/item/bodypart/proc/update_bodypart_damage_state()
var/tbrute = round( (brute_dam/max_damage)*3, 1 )
var/tburn = round( (burn_dam/max_damage)*3, 1 )
if((tbrute != brutestate) || (tburn != burnstate))
brutestate = tbrute
burnstate = tburn
return TRUE
return FALSE
//Change organ status
/obj/item/bodypart/proc/change_bodypart_status(new_limb_status, heal_limb, change_icon_to_default)
status = new_limb_status
if(heal_limb)
burn_dam = 0
brute_dam = 0
brutestate = 0
burnstate = 0
if(change_icon_to_default)
if(status == BODYPART_ORGANIC)
icon = icon_greyscale
else if(status == BODYPART_ROBOTIC)
icon = icon_robotic
if(owner)
owner.updatehealth()
owner.update_body() //if our head becomes robotic, we remove the lizard horns and human hair.
owner.update_hair()
owner.update_damage_overlays()
/obj/item/bodypart/proc/is_organic_limb()
return (status == BODYPART_ORGANIC)
//we inform the bodypart of the changes that happened to the owner, or give it the informations from a source mob.
/obj/item/bodypart/proc/update_limb(dropping_limb, mob/living/carbon/source)
var/mob/living/carbon/limb_owner
if(source)
limb_owner = source
if(!original_owner)
original_owner = WEAKREF(source)
else
limb_owner = owner
if(original_owner && !IS_WEAKREF_OF(owner, original_owner)) //Foreign limb
no_update = TRUE
else
no_update = FALSE
if(HAS_TRAIT(limb_owner, TRAIT_HUSK) && is_organic_limb())
species_id = "husk" //overrides species_id
dmg_overlay_type = "" //no damage overlay shown when husked
should_draw_gender = FALSE
should_draw_greyscale = FALSE
no_update = TRUE
if(HAS_TRAIT(src, TRAIT_PLASMABURNT) && is_organic_limb())
species_id = SPECIES_PLASMAMAN
dmg_overlay_type = ""
should_draw_gender = FALSE
should_draw_greyscale = FALSE
no_update = TRUE
if(HAS_TRAIT(limb_owner, TRAIT_INVISIBLE_MAN) && is_organic_limb())
species_id = "invisible" //overrides species_id
dmg_overlay_type = "" //no damage overlay shown when invisible since the wounds themselves are invisible.
should_draw_gender = FALSE
should_draw_greyscale = FALSE
no_update = TRUE
if(no_update)
return
if(!animal_origin)
var/mob/living/carbon/human/human_owner = limb_owner
should_draw_greyscale = FALSE
var/datum/species/owner_species = human_owner.dna.species
species_id = owner_species.limbs_id
species_flags_list = human_owner.dna.species.species_traits
if(owner_species.use_skintones)
skin_tone = human_owner.skin_tone
should_draw_greyscale = TRUE
else
skin_tone = ""
body_gender = human_owner.body_type
should_draw_gender = owner_species.sexes
if((MUTCOLORS in owner_species.species_traits) || (DYNCOLORS in owner_species.species_traits))
if(owner_species.fixed_mut_color)
species_color = owner_species.fixed_mut_color
else
species_color = human_owner.dna.features["mcolor"]
should_draw_greyscale = TRUE
else
species_color = ""
if(!dropping_limb && human_owner.dna.check_mutation(HULK))
mutation_color = "00aa00"
else
mutation_color = ""
dmg_overlay_type = owner_species.damage_overlay_type
else if(animal_origin == MONKEY_BODYPART) //currently monkeys are the only non human mob to have damage overlays.
dmg_overlay_type = animal_origin
if(status == BODYPART_ROBOTIC)
dmg_overlay_type = "robotic"
if(dropping_limb)
no_update = TRUE //when attached, the limb won't be affected by the appearance changes of its mob owner.
//to update the bodypart's icon when not attached to a mob
/obj/item/bodypart/proc/update_icon_dropped()
cut_overlays()
var/list/standing = get_limb_icon(1)
if(!standing.len)
icon_state = initial(icon_state)//no overlays found, we default back to initial icon.
return
for(var/image/img in standing)
img.pixel_x = px_x
img.pixel_y = px_y
add_overlay(standing)
//Gives you a proper icon appearance for the dismembered limb
/obj/item/bodypart/proc/get_limb_icon(dropped, draw_external_organs)
icon_state = "" //to erase the default sprite, we're building the visual aspects of the bodypart through overlays alone.
. = list()
var/image_dir = 0
if(dropped)
image_dir = SOUTH
if(dmg_overlay_type)
if(brutestate)
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir)
if(burnstate)
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER, image_dir)
var/image/limb = image(layer = -BODYPARTS_LAYER, dir = image_dir)
var/image/aux
. += limb
if(animal_origin)
if(is_organic_limb())
limb.icon = 'icons/mob/animal_parts.dmi'
if(species_id == "husk")
limb.icon_state = "[animal_origin]_husk_[body_zone]"
else
limb.icon_state = "[animal_origin]_[body_zone]"
else
limb.icon = 'icons/mob/augmentation/augments.dmi'
limb.icon_state = "[animal_origin]_[body_zone]"
if(blocks_emissive)
var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, alpha = limb.alpha)
limb_em_block.dir = image_dir
limb.overlays += limb_em_block
return
var/icon_gender = (body_gender == FEMALE) ? "f" : "m" //gender of the icon, if applicable
if((body_zone != BODY_ZONE_HEAD && body_zone != BODY_ZONE_CHEST))
should_draw_gender = FALSE
if(!is_organic_limb())
limb.icon = icon
limb.icon_state = "[body_zone]" //Inorganic limbs are agender
if(blocks_emissive)
var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, alpha = limb.alpha)
limb_em_block.dir = image_dir
limb.overlays += limb_em_block
if(aux_zone)
aux = image(limb.icon, "[aux_zone]", -aux_layer, image_dir)
. += aux
if(blocks_emissive)
var/mutable_appearance/aux_em_block = emissive_blocker(aux.icon, aux.icon_state, alpha = aux.alpha)
aux_em_block.dir = image_dir
aux.overlays += aux_em_block
return
if(should_draw_greyscale)
limb.icon = icon_greyscale
if(should_draw_gender)
limb.icon_state = "[species_id]_[body_zone]_[icon_gender]"
else if(use_digitigrade)
limb.icon_state = "digitigrade_[use_digitigrade]_[body_zone]"
else
limb.icon_state = "[species_id]_[body_zone]"
else
limb.icon = 'icons/mob/human_parts.dmi'
if(should_draw_gender)
limb.icon_state = "[species_id]_[body_zone]_[icon_gender]"
else
limb.icon_state = "[species_id]_[body_zone]"
if(aux_zone)
aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir)
. += aux
var/draw_color
if(should_draw_greyscale)
draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone))
if(draw_color)
limb.color = "#[draw_color]"
if(aux_zone)
aux.color = "#[draw_color]"
if(blocks_emissive)
var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, alpha = limb.alpha)
limb_em_block.dir = image_dir
limb.overlays += limb_em_block
if(aux_zone)
var/mutable_appearance/aux_em_block = emissive_blocker(aux.icon, aux.icon_state, alpha = aux.alpha)
aux_em_block.dir = image_dir
aux.overlays += aux_em_block
if(!draw_external_organs)
return
//Draw external organs like horns and frills
for(var/obj/item/organ/external/external_organ in external_organs)
if(!dropped && !external_organ.can_draw_on_bodypart(owner))
continue
//Some externals have multiple layers for background, foreground and between
for(var/external_layer in external_organ.all_layers)
if(external_organ.layers & external_layer)
external_organ.get_overlays(., image_dir, external_organ.bitflag_to_layer(external_layer), icon_gender, "#[draw_color]")
/obj/item/bodypart/deconstruct(disassembled = TRUE)
drop_organs()
qdel(src)
/// Get whatever wound of the given type is currently attached to this limb, if any
/obj/item/bodypart/proc/get_wound_type(checking_type)
if(isnull(wounds))
return
for(var/wound in wounds)
if(istype(wound, checking_type))
return wound
/**
* update_wounds() is called whenever a wound is gained or lost on this bodypart, as well as if there's a change of some kind on a bone wound possibly changing disabled status
*
* Covers tabulating the damage multipliers we have from wounds (burn specifically), as well as deleting our gauze wrapping if we don't have any wounds that can use bandaging
*
* Arguments:
* * replaced- If true, this is being called from the remove_wound() of a wound that's being replaced, so the bandage that already existed is still relevant, but the new wound hasn't been added yet
*/
/obj/item/bodypart/proc/update_wounds(replaced = FALSE)
var/dam_mul = 1 //initial(wound_damage_multiplier)
// we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
for(var/datum/wound/iter_wound as anything in wounds)
dam_mul *= iter_wound.damage_mulitplier_penalty
if(!LAZYLEN(wounds) && current_gauze && !replaced) // no more wounds = no need for the gauze anymore
owner.visible_message(span_notice("\The [current_gauze.name] on [owner]'s [name] falls away."), span_notice("The [current_gauze.name] on your [name] falls away."))
QDEL_NULL(current_gauze)
wound_damage_multiplier = dam_mul
/obj/item/bodypart/proc/get_bleed_rate()
if(HAS_TRAIT(owner, TRAIT_NOBLEED))
return
if(status != BODYPART_ORGANIC) // maybe in the future we can bleed oil from aug parts, but not now
return
var/bleed_rate = 0
if(generic_bleedstacks > 0)
bleed_rate += 0.5
//We want an accurate reading of .len
listclearnulls(embedded_objects)
for(var/obj/item/embeddies in embedded_objects)
if(!embeddies.isEmbedHarmless())
bleed_rate += 0.25
for(var/datum/wound/wound as anything in wounds)
bleed_rate += wound.blood_flow
if(owner.body_position == LYING_DOWN)
bleed_rate *= 0.75
if(grasped_by)
bleed_rate *= 0.7
if(!bleed_rate)
QDEL_NULL(grasped_by)
return bleed_rate
/**
* apply_gauze() is used to- well, apply gauze to a bodypart