/
health_analyzer.dm
694 lines (597 loc) · 33.3 KB
/
health_analyzer.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
// Describes the three modes of scanning available for health analyzers
#define SCANMODE_HEALTH 0
#define SCANMODE_WOUND 1
#define SCANMODE_COUNT 2 // Update this to be the number of scan modes if you add more
/obj/item/healthanalyzer
name = "health analyzer"
icon = 'icons/obj/devices/scanner.dmi'
icon_state = "health"
inhand_icon_state = "healthanalyzer"
worn_icon_state = "healthanalyzer"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
desc = "A hand-held body scanner capable of distinguishing vital signs of the subject. Has a side button to scan for chemicals, and can be toggled to scan wounds."
obj_flags = CONDUCTS_ELECTRICITY
item_flags = NOBLUDGEON
slot_flags = ITEM_SLOT_BELT
throwforce = 3
w_class = WEIGHT_CLASS_TINY
throw_speed = 3
throw_range = 7
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT *2)
interaction_flags_click = NEED_LITERACY|NEED_LIGHT|ALLOW_RESTING
/// Verbose/condensed
var/mode = SCANNER_VERBOSE
/// HEALTH/WOUND
var/scanmode = SCANMODE_HEALTH
/// Advanced health analyzer
var/advanced = FALSE
custom_price = PAYCHECK_COMMAND
/// If this analyzer will give a bonus to wound treatments apon woundscan.
var/give_wound_treatment_bonus = FALSE
/obj/item/healthanalyzer/Initialize(mapload)
. = ..()
register_item_context()
/obj/item/healthanalyzer/examine(mob/user)
. = ..()
if(src.mode != SCANNER_NO_MODE)
. += span_notice("Alt-click [src] to toggle the limb damage readout.")
/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!"))
return BRUTELOSS
/obj/item/healthanalyzer/attack_self(mob/user)
if(!user.can_read(src) || user.is_blind())
return
scanmode = (scanmode + 1) % SCANMODE_COUNT
switch(scanmode)
if(SCANMODE_HEALTH)
to_chat(user, span_notice("You switch the health analyzer to check physical health."))
if(SCANMODE_WOUND)
to_chat(user, span_notice("You switch the health analyzer to report extra info on wounds."))
/obj/item/healthanalyzer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!isliving(interacting_with))
return NONE
if(!user.can_read(src) || user.is_blind())
return ITEM_INTERACT_BLOCKING
var/mob/living/M = interacting_with
. = ITEM_INTERACT_SUCCESS
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning
// Clumsiness/brain damage check
if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
user.visible_message(span_warning("[user] analyzes the floor's vitals!"), \
span_notice("You stupidly try to analyze the floor's vitals!"))
to_chat(user, "[span_info("Analyzing results for The floor:\n\tOverall status: <b>Healthy</b>")]\
\n[span_info("Key: <font color='#00cccc'>Suffocation</font>/<font color='#00cc66'>Toxin</font>/<font color='#ffcc33'>Burn</font>/<font color='#ff3333'>Brute</font>")]\
\n[span_info("\tDamage specifics: <font color='#66cccc'>0</font>-<font color='#00cc66'>0</font>-<font color='#ff9933'>0</font>-<font color='#ff3333'>0</font>")]\
\n[span_info("Body temperature: ???")]")
return
if(ispodperson(M) && !advanced)
to_chat(user, "<span class='info'>[M]'s biological structure is too complex for the health analyzer.")
return
user.visible_message(span_notice("[user] analyzes [M]'s vitals."))
balloon_alert(user, "analyzing vitals")
playsound(user.loc, 'sound/items/healthanalyzer.ogg', 50)
switch (scanmode)
if (SCANMODE_HEALTH)
healthscan(user, M, mode, advanced)
if (SCANMODE_WOUND)
woundscan(user, M, src)
add_fingerprint(user)
/obj/item/healthanalyzer/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!isliving(interacting_with))
return NONE
if(!user.can_read(src) || user.is_blind())
return ITEM_INTERACT_BLOCKING
chemscan(user, interacting_with)
return ITEM_INTERACT_SUCCESS
/obj/item/healthanalyzer/add_item_context(
obj/item/source,
list/context,
atom/target,
)
if (!isliving(target))
return NONE
switch (scanmode)
if (SCANMODE_HEALTH)
context[SCREENTIP_CONTEXT_LMB] = "Scan health"
if (SCANMODE_WOUND)
context[SCREENTIP_CONTEXT_LMB] = "Scan wounds"
context[SCREENTIP_CONTEXT_RMB] = "Scan chemicals"
return CONTEXTUAL_SCREENTIP_SET
/**
* healthscan
* returns a list of everything a health scan should give to a player.
* Examples of where this is used is Health Analyzer and the Physical Scanner tablet app.
* Args:
* user - The person with the scanner
* target - The person being scanned
* mode - Uses SCANNER_CONDENSED or SCANNER_VERBOSE to decide whether to give a list of all individual limb damage
* advanced - Whether it will give more advanced details, such as husk source.
* tochat - Whether to immediately post the result into the chat of the user, otherwise it will return the results.
*/
/proc/healthscan(mob/user, mob/living/target, mode = SCANNER_VERBOSE, advanced = FALSE, tochat = TRUE)
if(user.incapacitated())
return
// the final list of strings to render
var/render_list = list()
// Damage specifics
var/oxy_loss = target.getOxyLoss()
var/tox_loss = target.getToxLoss()
var/fire_loss = target.getFireLoss()
var/brute_loss = target.getBruteLoss()
var/mob_status = (target.stat == DEAD ? span_alert("<b>Deceased</b>") : "<b>[round(target.health/target.maxHealth,0.01)*100]% healthy</b>")
if(HAS_TRAIT(target, TRAIT_FAKEDEATH) && !advanced)
mob_status = span_alert("<b>Deceased</b>")
oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss
render_list += "[span_info("Analyzing results for [target]:")]\n<span class='info ml-1'>Overall status: [mob_status]</span>\n"
if(ishuman(target))
var/mob/living/carbon/human/humantarget = target
if(humantarget.undergoing_cardiac_arrest() && humantarget.stat != DEAD)
render_list += "<span class='alert ml-1'><b>Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!</b></span>\n"
if(humantarget.has_reagent(/datum/reagent/inverse/technetium))
advanced = TRUE
SEND_SIGNAL(target, COMSIG_LIVING_HEALTHSCAN, render_list, advanced, user, mode)
// Husk detection
if(HAS_TRAIT(target, TRAIT_HUSK))
if(advanced)
if(HAS_TRAIT_FROM(target, TRAIT_HUSK, BURN))
render_list += "<span class='alert ml-1'>Subject has been husked by severe burns.</span>\n"
else if (HAS_TRAIT_FROM(target, TRAIT_HUSK, CHANGELING_DRAIN))
render_list += "<span class='alert ml-1'>Subject has been husked by dessication.</span>\n"
else
render_list += "<span class='alert ml-1'>Subject has been husked by mysterious causes.</span>\n"
else
render_list += "<span class='alert ml-1'>Subject has been husked.</span>\n"
if(target.getStaminaLoss())
if(advanced)
render_list += "<span class='alert ml-1'>Fatigue level: [target.getStaminaLoss()]%.</span>\n"
else
render_list += "<span class='alert ml-1'>Subject appears to be suffering from fatigue.</span>\n"
if (!target.get_organ_slot(ORGAN_SLOT_BRAIN)) // kept exclusively for soul purposes
render_list += "<span class='alert ml-1'>Subject lacks a brain.</span>\n"
if(iscarbon(target))
var/mob/living/carbon/carbontarget = target
if(LAZYLEN(carbontarget.get_traumas()))
var/list/trauma_text = list()
for(var/datum/brain_trauma/trauma in carbontarget.get_traumas())
var/trauma_desc = ""
switch(trauma.resilience)
if(TRAUMA_RESILIENCE_SURGERY)
trauma_desc += "severe "
if(TRAUMA_RESILIENCE_LOBOTOMY)
trauma_desc += "deep-rooted "
if(TRAUMA_RESILIENCE_WOUND)
trauma_desc += "fracture-derived "
if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE)
trauma_desc += "permanent "
trauma_desc += trauma.scan_desc
trauma_text += trauma_desc
render_list += "<span class='alert ml-1'>Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].</span>\n"
if(carbontarget.quirks.len)
render_list += "<span class='info ml-1'>Subject Major Disabilities: [carbontarget.get_quirk_string(FALSE, CAT_QUIRK_MAJOR_DISABILITY, from_scan = TRUE)].</span>\n"
if(advanced)
render_list += "<span class='info ml-1'>Subject Minor Disabilities: [carbontarget.get_quirk_string(FALSE, CAT_QUIRK_MINOR_DISABILITY, TRUE)].</span>\n"
if (HAS_TRAIT(target, TRAIT_IRRADIATED))
render_list += "<span class='alert ml-1'>Subject is irradiated. Supply toxin healing.</span>\n"
//Eyes and ears
if(advanced && iscarbon(target))
var/mob/living/carbon/carbontarget = target
// Ear status
var/obj/item/organ/internal/ears/ears = carbontarget.get_organ_slot(ORGAN_SLOT_EARS)
if(istype(ears))
if(HAS_TRAIT_FROM(carbontarget, TRAIT_DEAF, GENETIC_MUTATION))
render_list += "<span class='alert ml-2'>Subject is genetically deaf.\n</span>"
else if(HAS_TRAIT_FROM(carbontarget, TRAIT_DEAF, EAR_DAMAGE))
render_list += "<span class='alert ml-2'>Subject is deaf from ear damage.\n</span>"
else if(HAS_TRAIT(carbontarget, TRAIT_DEAF))
render_list += "<span class='alert ml-2'>Subject is deaf.\n</span>"
else
if(ears.damage)
render_list += "<span class='alert ml-2'>Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage.\n</span>"
if(ears.deaf)
render_list += "<span class='alert ml-2'>Subject is [ears.damage > ears.maxHealth ? "permanently": "temporarily"] deaf.\n</span>"
// Eye status
var/obj/item/organ/internal/eyes/eyes = carbontarget.get_organ_slot(ORGAN_SLOT_EYES)
if(istype(eyes))
if(carbontarget.is_blind())
render_list += "<span class='alert ml-2'>Subject is blind.\n</span>"
else if(carbontarget.is_nearsighted())
render_list += "<span class='alert ml-2'>Subject is nearsighted.\n</span>"
// Body part damage report
if(iscarbon(target))
var/mob/living/carbon/carbontarget = target
var/list/damaged = carbontarget.get_damaged_bodyparts(1,1)
if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0)
var/dmgreport = "<span class='info ml-1'>General status:</span>\
<table class='ml-2'><tr><font face='Verdana'>\
<td style='width:7em;'><font color='#ff0000'><b>Damage:</b></font></td>\
<td style='width:5em;'><font color='#ff3333'><b>Brute</b></font></td>\
<td style='width:4em;'><font color='#ff9933'><b>Burn</b></font></td>\
<td style='width:4em;'><font color='#00cc66'><b>Toxin</b></font></td>\
<td style='width:8em;'><font color='#00cccc'><b>Suffocation</b></font></td></tr>\
<tr><td><font color='#ff3333'><b>Overall:</b></font></td>\
<td><font color='#ff3333'><b>[CEILING(brute_loss,1)]</b></font></td>\
<td><font color='#ff9933'><b>[CEILING(fire_loss,1)]</b></font></td>\
<td><font color='#00cc66'><b>[CEILING(tox_loss,1)]</b></font></td>\
<td><font color='#33ccff'><b>[CEILING(oxy_loss,1)]</b></font></td></tr>"
if(mode == SCANNER_VERBOSE)
for(var/obj/item/bodypart/limb as anything in damaged)
if(limb.bodytype & BODYTYPE_ROBOTIC)
dmgreport += "<tr><td><font color='#cc3333'>[capitalize(limb.name)]:</font></td>"
else
dmgreport += "<tr><td><font color='#cc3333'>[capitalize(limb.plaintext_zone)]:</font></td>"
dmgreport += "<td><font color='#cc3333'>[(limb.brute_dam > 0) ? "[CEILING(limb.brute_dam,1)]" : "0"]</font></td>"
dmgreport += "<td><font color='#ff9933'>[(limb.burn_dam > 0) ? "[CEILING(limb.burn_dam,1)]" : "0"]</font></td></tr>"
dmgreport += "</font></table>"
render_list += dmgreport // tables do not need extra linebreak
for(var/obj/item/bodypart/limb as anything in carbontarget.bodyparts)
for(var/obj/item/embed as anything in limb.embedded_objects)
render_list += "<span class='alert ml-1'>Embedded object: [embed] located in \the [limb.plaintext_zone]</span>\n"
if(ishuman(target))
var/mob/living/carbon/human/humantarget = target
// Organ damage, missing organs
if(humantarget.organs && humantarget.organs.len)
var/render = FALSE
var/toReport = "<span class='info ml-1'>Organs:</span>\
<table class='ml-2'><tr>\
<td style='width:6em;'><font color='#ff0000'><b>Organ:</b></font></td>\
[advanced ? "<td style='width:3em;'><font color='#ff0000'><b>Dmg</b></font></td>" : ""]\
<td style='width:12em;'><font color='#ff0000'><b>Status</b></font></td>"
for(var/obj/item/organ/organ as anything in humantarget.organs)
var/status = organ.get_status_text()
if (status != "")
render = TRUE
toReport += "<tr><td><font color='#cc3333'>[organ.name]:</font></td>\
[advanced ? "<td><font color='#ff3333'>[CEILING(organ.damage,1)]</font></td>" : ""]\
<td>[status]</td></tr>"
var/missing_organs = list()
if(!humantarget.get_organ_slot(ORGAN_SLOT_BRAIN))
missing_organs += "brain"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_NOBLOOD, SPECIES_TRAIT) && !humantarget.get_organ_slot(ORGAN_SLOT_HEART))
missing_organs += "heart"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_NOBREATH, SPECIES_TRAIT) && !humantarget.get_organ_slot(ORGAN_SLOT_LUNGS))
missing_organs += "lungs"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_LIVERLESS_METABOLISM, SPECIES_TRAIT) && !humantarget.get_organ_slot(ORGAN_SLOT_LIVER))
missing_organs += "liver"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_NOHUNGER, SPECIES_TRAIT) && !humantarget.get_organ_slot(ORGAN_SLOT_STOMACH))
missing_organs += "stomach"
if(!humantarget.get_organ_slot(ORGAN_SLOT_TONGUE))
missing_organs += "tongue"
if(!humantarget.get_organ_slot(ORGAN_SLOT_EARS))
missing_organs += "ears"
if(!humantarget.get_organ_slot(ORGAN_SLOT_EYES))
missing_organs += "eyes"
if(length(missing_organs))
render = TRUE
for(var/organ in missing_organs)
toReport += "<tr><td><font color='#cc3333'>[organ]:</font></td>\
[advanced ? "<td><font color='#ff3333'>["-"]</font></td>" : ""]\
<td><font color='#cc3333'>["Missing"]</font></td></tr>"
if(render)
render_list += toReport + "</table>" // tables do not need extra linebreak
//Genetic stability
if(advanced && humantarget.has_dna())
render_list += "<span class='info ml-1'>Genetic Stability: [humantarget.dna.stability]%.</span>\n"
// Hulk and body temperature
var/datum/species/targetspecies = humantarget.dna.species
var/mutant = humantarget.dna.check_mutation(/datum/mutation/human/hulk)
render_list += "<span class='info ml-1'>Species: [targetspecies.name][mutant ? "-derived mutant" : ""]</span>\n"
var/core_temperature_message = "Core temperature: [round(humantarget.coretemperature-T0C, 0.1)] °C ([round(humantarget.coretemperature*1.8-459.67,0.1)] °F)"
if(humantarget.coretemperature >= humantarget.get_body_temp_heat_damage_limit())
render_list += "<span class='alert ml-1'>☼ [core_temperature_message] ☼</span>\n"
else if(humantarget.coretemperature <= humantarget.get_body_temp_cold_damage_limit())
render_list += "<span class='alert ml-1'>❄ [core_temperature_message] ❄</span>\n"
else
render_list += "<span class='info ml-1'>[core_temperature_message]</span>\n"
var/body_temperature_message = "Body temperature: [round(target.bodytemperature-T0C, 0.1)] °C ([round(target.bodytemperature*1.8-459.67,0.1)] °F)"
if(target.bodytemperature >= target.get_body_temp_heat_damage_limit())
render_list += "<span class='alert ml-1'>☼ [body_temperature_message] ☼</span>\n"
else if(target.bodytemperature <= target.get_body_temp_cold_damage_limit())
render_list += "<span class='alert ml-1'>❄ [body_temperature_message] ❄</span>\n"
else
render_list += "<span class='info ml-1'>[body_temperature_message]</span>\n"
// Time of death
if(target.station_timestamp_timeofdeath && (target.stat == DEAD || ((HAS_TRAIT(target, TRAIT_FAKEDEATH)) && !advanced)))
render_list += "<span class='info ml-1'>Time of Death: [target.station_timestamp_timeofdeath]</span>\n"
var/tdelta = round(world.time - target.timeofdeath)
render_list += "<span class='alert ml-1'><b>Subject died [DisplayTimeText(tdelta)] ago.</b></span>\n"
// Wounds
if(iscarbon(target))
var/mob/living/carbon/carbontarget = target
var/list/wounded_parts = carbontarget.get_wounded_bodyparts()
for(var/i in wounded_parts)
var/obj/item/bodypart/wounded_part = i
render_list += "<span class='alert ml-1'><b>Physical trauma[LAZYLEN(wounded_part.wounds) > 1 ? "s" : ""] detected in [wounded_part.name]</b>"
for(var/k in wounded_part.wounds)
var/datum/wound/W = k
render_list += "<div class='ml-2'>[W.name] ([W.severity_text()])\nRecommended treatment: [W.treat_text]</div>" // less lines than in woundscan() so we don't overload people trying to get basic med info
render_list += "</span>"
//Diseases
for(var/datum/disease/disease as anything in target.diseases)
if(!(disease.visibility_flags & HIDDEN_SCANNER))
render_list += "<span class='alert ml-1'><b>Warning: [disease.form] detected</b>\n\
<div class='ml-2'>Name: [disease.name].\nType: [disease.spread_text].\nStage: [disease.stage]/[disease.max_stages].\nPossible Cure: [disease.cure_text]</div>\
</span>" // divs do not need extra linebreak
// Blood Level
if(target.has_dna())
var/mob/living/carbon/carbontarget = target
var/blood_id = carbontarget.get_blood_id()
if(blood_id)
if(carbontarget.is_bleeding())
render_list += "<span class='alert ml-1'><b>Subject is bleeding!</b></span>\n"
var/blood_percent = round((carbontarget.blood_volume / BLOOD_VOLUME_NORMAL) * 100)
var/blood_type = carbontarget.dna.blood_type
if(blood_id != /datum/reagent/blood) // special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
blood_type = R ? R.name : blood_id
if(carbontarget.blood_volume <= BLOOD_VOLUME_SAFE && carbontarget.blood_volume > BLOOD_VOLUME_OKAY)
render_list += "<span class='alert ml-1'>Blood level: LOW [blood_percent]%, [carbontarget.blood_volume] cl,</span> [span_info("type: [blood_type]")]\n"
else if(carbontarget.blood_volume <= BLOOD_VOLUME_OKAY)
render_list += "<span class='alert ml-1'>Blood level: <b>CRITICAL [blood_percent]%</b>, [carbontarget.blood_volume] cl,</span> [span_info("type: [blood_type]")]\n"
else
render_list += "<span class='info ml-1'>Blood level: [blood_percent]%, [carbontarget.blood_volume] cl, type: [blood_type]</span>\n"
// Blood Alcohol Content
var/blood_alcohol_content = target.get_blood_alcohol_content()
if(blood_alcohol_content > 0)
if(blood_alcohol_content >= 0.24)
render_list += "<span class='alert ml-1'>Blood alcohol content: <b>CRITICAL [blood_alcohol_content]%</b></span>\n"
else
render_list += "<span class='info ml-1'>Blood alcohol content: [blood_alcohol_content]%</span>\n"
// Cybernetics
if(iscarbon(target))
var/mob/living/carbon/carbontarget = target
var/cyberimp_detect
for(var/obj/item/organ/internal/cyberimp/cyberimp in carbontarget.organs)
if(IS_ROBOTIC_ORGAN(cyberimp) && !(cyberimp.organ_flags & ORGAN_HIDDEN))
cyberimp_detect += "[!cyberimp_detect ? "[cyberimp.get_examine_string(user)]" : ", [cyberimp.get_examine_string(user)]"]"
if(cyberimp_detect)
render_list += "<span class='notice ml-1'>Detected cybernetic modifications:</span>\n"
render_list += "<span class='notice ml-2'>[cyberimp_detect]</span>\n"
// we handled the last <br> so we don't need handholding
if(tochat)
to_chat(user, examine_block(jointext(render_list, "")), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
else
return(jointext(render_list, ""))
/proc/chemscan(mob/living/user, mob/living/target)
if(user.incapacitated())
return
if(istype(target) && target.reagents)
var/list/render_list = list() //The master list of readouts, including reagents in the blood/stomach, addictions, quirks, etc.
var/list/render_block = list() //A second block of readout strings. If this ends up empty after checking stomach/blood contents, we give the "empty" header.
// Blood reagents
if(target.reagents.reagent_list.len)
for(var/r in target.reagents.reagent_list)
var/datum/reagent/reagent = r
if(reagent.chemical_flags & REAGENT_INVISIBLE) //Don't show hidden chems on scanners
continue
render_block += "<span class='notice ml-2'>[round(reagent.volume, 0.001)] units of [reagent.name][reagent.overdosed ? "</span> - [span_boldannounce("OVERDOSING")]" : ".</span>"]\n"
if(!length(render_block)) //If no VISIBLY DISPLAYED reagents are present, we report as if there is nothing.
render_list += "<span class='notice ml-1'>Subject contains no reagents in their blood.</span>\n"
else
render_list += "<span class='notice ml-1'>Subject contains the following reagents in their blood:</span>\n"
render_list += render_block //Otherwise, we add the header, reagent readouts, and clear the readout block for use on the stomach.
render_block.Cut()
// Stomach reagents
var/obj/item/organ/internal/stomach/belly = target.get_organ_slot(ORGAN_SLOT_STOMACH)
if(belly)
if(belly.reagents.reagent_list.len)
for(var/bile in belly.reagents.reagent_list)
var/datum/reagent/bit = bile
if(bit.chemical_flags & REAGENT_INVISIBLE)
continue
if(!belly.food_reagents[bit.type])
render_block += "<span class='notice ml-2'>[round(bit.volume, 0.001)] units of [bit.name][bit.overdosed ? "</span> - [span_boldannounce("OVERDOSING")]" : ".</span>"]\n"
else
var/bit_vol = bit.volume - belly.food_reagents[bit.type]
if(bit_vol > 0)
render_block += "<span class='notice ml-2'>[round(bit_vol, 0.001)] units of [bit.name][bit.overdosed ? "</span> - [span_boldannounce("OVERDOSING")]" : ".</span>"]\n"
if(!length(render_block))
render_list += "<span class='notice ml-1'>Subject contains no reagents in their stomach.</span>\n"
else
render_list += "<span class='notice ml-1'>Subject contains the following reagents in their stomach:</span>\n"
render_list += render_block
// Addictions
if(LAZYLEN(target.mind?.active_addictions))
render_list += "<span class='boldannounce ml-1'>Subject is addicted to the following types of drug:</span>\n"
for(var/datum/addiction/addiction_type as anything in target.mind.active_addictions)
render_list += "<span class='alert ml-2'>[initial(addiction_type.name)]</span>\n"
// Special eigenstasium addiction
if(target.has_status_effect(/datum/status_effect/eigenstasium))
render_list += "<span class='notice ml-1'>Subject is temporally unstable. Stabilising agent is recommended to reduce disturbances.</span>\n"
// Allergies
for(var/datum/quirk/quirky as anything in target.quirks)
if(istype(quirky, /datum/quirk/item_quirk/allergic))
var/datum/quirk/item_quirk/allergic/allergies_quirk = quirky
var/allergies = allergies_quirk.allergy_string
render_list += "<span class='alert ml-1'>Subject is extremely allergic to the following chemicals:</span>\n"
render_list += "<span class='alert ml-2'>[allergies]</span>\n"
// we handled the last <br> so we don't need handholding
to_chat(user, examine_block(jointext(render_list, "")), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
/obj/item/healthanalyzer/click_alt(mob/user)
if(mode == SCANNER_NO_MODE)
return CLICK_ACTION_BLOCKING
mode = !mode
to_chat(user, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "The scanner no longer shows limb damage.")
return CLICK_ACTION_SUCCESS
/obj/item/healthanalyzer/advanced
name = "advanced health analyzer"
icon_state = "health_adv"
desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy."
advanced = TRUE
#define AID_EMOTION_NEUTRAL "neutral"
#define AID_EMOTION_HAPPY "happy"
#define AID_EMOTION_WARN "cautious"
#define AID_EMOTION_ANGRY "angery"
#define AID_EMOTION_SAD "sad"
/// Displays wounds with extended information on their status vs medscanners
/proc/woundscan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/scanner, simple_scan = FALSE)
if(!istype(patient) || user.incapacitated())
return
var/render_list = ""
var/advised = FALSE
for(var/limb in patient.get_wounded_bodyparts())
var/obj/item/bodypart/wounded_part = limb
render_list += "<span class='alert ml-1'><b>Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]</b>"
for(var/limb_wound in wounded_part.wounds)
var/datum/wound/current_wound = limb_wound
render_list += "<div class='ml-2'>[simple_scan ? current_wound.get_simple_scanner_description() : current_wound.get_scanner_description()]</div>\n"
if (scanner.give_wound_treatment_bonus)
ADD_TRAIT(current_wound, TRAIT_WOUND_SCANNED, ANALYZER_TRAIT)
if(!advised)
to_chat(user, span_notice("You notice how bright holo-images appear over your [(length(wounded_part.wounds) || length(patient.get_wounded_bodyparts()) ) > 1 ? "various wounds" : "wound"]. They seem to be filled with helpful information, this should make treatment easier!"))
advised = TRUE
render_list += "</span>"
if(render_list == "")
if(simple_scan)
var/obj/item/healthanalyzer/simple/simple_scanner = scanner
// Only emit the cheerful scanner message if this scan came from a scanner
playsound(simple_scanner, 'sound/machines/ping.ogg', 50, FALSE)
to_chat(user, span_notice("\The [simple_scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!"))
simple_scanner.show_emotion(AID_EMOTION_HAPPY)
to_chat(user, "<span class='notice ml-1'>No wounds detected in subject.</span>")
else
to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
if(simple_scan)
var/obj/item/healthanalyzer/simple/simple_scanner = scanner
simple_scanner.show_emotion(AID_EMOTION_WARN)
playsound(simple_scanner, 'sound/machines/twobeep.ogg', 50, FALSE)
/obj/item/healthanalyzer/simple
name = "wound analyzer"
icon_state = "first_aid"
desc = "A helpful, child-proofed, and most importantly, extremely cheap MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds. While it might not sound very informative for it to be able to tell you if you have a gaping hole in your body or not, it applies a temporary holoimage near the wound with information that is guaranteed to double the efficacy and speed of treatment."
mode = SCANNER_NO_MODE
give_wound_treatment_bonus = TRUE
/// Cooldown for when the analyzer will allow you to ask it for encouragement. Don't get greedy!
var/next_encouragement
/// The analyzer's current emotion. Affects the sprite overlays and if it's going to prick you for being greedy or not.
var/emotion = AID_EMOTION_NEUTRAL
/// Encouragements to play when attack_selfing
var/list/encouragements = list("briefly displays a happy face, gazing emptily at you", "briefly displays a spinning cartoon heart", "displays an encouraging message about eating healthy and exercising", \
"reminds you that everyone is doing their best", "displays a message wishing you well", "displays a sincere thank-you for your interest in first-aid", "formally absolves you of all your sins")
/// How often one can ask for encouragement
var/patience = 10 SECONDS
/// What do we scan for, only used in descriptions
var/scan_for_what = "serious injuries"
/obj/item/healthanalyzer/simple/attack_self(mob/user)
if(next_encouragement < world.time)
playsound(src, 'sound/machines/ping.ogg', 50, FALSE)
to_chat(user, span_notice("[src] makes a happy ping and [pick(encouragements)]!"))
next_encouragement = world.time + 10 SECONDS
show_emotion(AID_EMOTION_HAPPY)
else if(emotion != AID_EMOTION_ANGRY)
greed_warning(user)
else
violence(user)
/obj/item/healthanalyzer/simple/proc/greed_warning(mob/user)
to_chat(user, span_warning("[src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement."))
show_emotion(AID_EMOTION_ANGRY)
/obj/item/healthanalyzer/simple/proc/violence(mob/user)
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
if(isliving(user))
var/mob/living/L = user
to_chat(L, span_warning("[src] makes a disappointed buzz and pricks your finger for being greedy. Ow!"))
flick(icon_state + "_pinprick", src)
violence_damage(user)
user.dropItemToGround(src)
show_emotion(AID_EMOTION_HAPPY)
/obj/item/healthanalyzer/simple/proc/violence_damage(mob/living/user)
user.adjustBruteLoss(4)
/obj/item/healthanalyzer/simple/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!isliving(interacting_with))
return NONE
if(!user.can_read(src) || user.is_blind())
return ITEM_INTERACT_BLOCKING
add_fingerprint(user)
user.visible_message(
span_notice("[user] scans [interacting_with] for [scan_for_what]."),
span_notice("You scan [interacting_with] for [scan_for_what]."),
)
if(!iscarbon(interacting_with))
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
to_chat(user, span_notice("[src] makes a sad buzz and briefly displays an unhappy face, indicating it can't scan [interacting_with]."))
show_emotion(AI_EMOTION_SAD)
return ITEM_INTERACT_BLOCKING
do_the_scan(interacting_with, user)
flick(icon_state + "_pinprick", src)
update_appearance(UPDATE_OVERLAYS)
return ITEM_INTERACT_SUCCESS
/obj/item/healthanalyzer/simple/proc/do_the_scan(mob/living/carbon/scanning, mob/living/user)
woundscan(user, scanning, src, simple_scan = TRUE)
/obj/item/healthanalyzer/simple/update_overlays()
. = ..()
switch(emotion)
if(AID_EMOTION_HAPPY)
. += mutable_appearance(icon, "+no_wounds")
if(AID_EMOTION_WARN)
. += mutable_appearance(icon, "+wound_warn")
if(AID_EMOTION_ANGRY)
. += mutable_appearance(icon, "+angry")
if(AID_EMOTION_SAD)
. += mutable_appearance(icon, "+fail_scan")
/// Sets a new emotion display on the scanner, and resets back to neutral in a moment
/obj/item/healthanalyzer/simple/proc/show_emotion(new_emotion)
emotion = new_emotion
update_appearance(UPDATE_OVERLAYS)
if (emotion != AID_EMOTION_NEUTRAL)
addtimer(CALLBACK(src, PROC_REF(reset_emotions), AID_EMOTION_NEUTRAL), 2 SECONDS)
// Resets visible emotion back to neutral
/obj/item/healthanalyzer/simple/proc/reset_emotions()
emotion = AID_EMOTION_NEUTRAL
update_appearance(UPDATE_OVERLAYS)
/obj/item/healthanalyzer/simple/miner
name = "mining wound analyzer"
icon_state = "miner_aid"
desc = "A helpful, child-proofed, and most importantly, extremely cheap MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds. While it might not sound very informative for it to be able to tell you if you have a gaping hole in your body or not, it applies a temporary holoimage near the wound with information that is guaranteed to double the efficacy and speed of treatment. This one has a cool aesthetic antenna that doesn't actually do anything!"
/obj/item/healthanalyzer/simple/disease
name = "disease state analyzer"
desc = "Another of MeLo-Tech's dubiously useful medsci scanners, the disease analyzer is a pretty rare find these days - NT found out that giving their hospitals the lowest-common-denominator pandemic equipment resulted in too much financial loss of life to be profitable. There's rumours that the inbuilt AI is jealous of the first aid analyzer's success."
icon_state = "disease_aid"
mode = SCANNER_NO_MODE
encouragements = list("encourages you to take your medication", "briefly displays a spinning cartoon heart", "reasures you about your condition", \
"reminds you that everyone is doing their best", "displays a message wishing you well", "displays a message saying how proud it is that you're taking care of yourself", "formally absolves you of all your sins")
patience = 20 SECONDS
scan_for_what = "diseases"
/obj/item/healthanalyzer/simple/disease/violence_damage(mob/living/user)
user.adjustBruteLoss(1)
user.reagents.add_reagent(/datum/reagent/toxin, rand(1, 3))
/obj/item/healthanalyzer/simple/disease/do_the_scan(mob/living/carbon/scanning, mob/living/user)
diseasescan(user, scanning, src)
/obj/item/healthanalyzer/simple/disease/update_overlays()
. = ..()
switch(emotion)
if(AID_EMOTION_HAPPY)
. += mutable_appearance(icon, "+not_infected")
if(AID_EMOTION_WARN)
. += mutable_appearance(icon, "+infected")
if(AID_EMOTION_ANGRY)
. += mutable_appearance(icon, "+rancurous")
if(AID_EMOTION_SAD)
. += mutable_appearance(icon, "+unknown_scan")
if(emotion != AID_EMOTION_NEUTRAL)
addtimer(CALLBACK(src, PROC_REF(reset_emotions)), 4 SECONDS) // longer on purpose
/// Checks the individual for any diseases that are visible to the scanner, and displays the diseases in the attacked to the attacker.
/proc/diseasescan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/simple/scanner)
if(!istype(patient) || user.incapacitated())
return
var/list/render = list()
for(var/datum/disease/disease as anything in patient.diseases)
if(!(disease.visibility_flags & HIDDEN_SCANNER))
render += "<span class='alert ml-1'><b>Warning: [disease.form] detected</b>\n\
<div class='ml-2'>Name: [disease.name].\nType: [disease.spread_text].\nStage: [disease.stage]/[disease.max_stages].\nPossible Cure: [disease.cure_text]</div>\
</span>"
if(!length(render))
playsound(scanner, 'sound/machines/ping.ogg', 50, FALSE)
to_chat(user, span_notice("\The [scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no diseases!"))
scanner.emotion = AID_EMOTION_HAPPY
else
to_chat(user, span_notice(render.Join("")))
scanner.emotion = AID_EMOTION_WARN
playsound(scanner, 'sound/machines/twobeep.ogg', 50, FALSE)
#undef SCANMODE_HEALTH
#undef SCANMODE_WOUND
#undef SCANMODE_COUNT
#undef AID_EMOTION_NEUTRAL
#undef AID_EMOTION_HAPPY
#undef AID_EMOTION_WARN
#undef AID_EMOTION_ANGRY
#undef AID_EMOTION_SAD