mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 05:30:05 +01:00
## About The Pull Request It's just a partial cleanup of anti-[STYLE](https://github.com/tgstation/tgstation/blob/master/.github/guides/STYLE.md) code from /tg/'s ancient history. I compiled & tested with my helpful assistant and damage is still working. <img width="1920" height="1040" alt="image" src="https://github.com/user-attachments/assets/26dabc17-088f-4008-b299-3ff4c27142c3" /> I'll upload the .cs script I used to do it shortly. ## Why It's Good For The Game Just minor code cleanup. Script used is located at https://metek.tech/camelTo-Snake.7z EDIT 11/23/25: Updated the script to use multithreading and sequential scan so it works a hell of a lot faster ``` /* // Copyright 2025 Joshua 'Joan Metekillot' Kidder This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // */ using System.Text.RegularExpressions; class Program { static async Task Main(string[] args) { var readFile = new FileStreamOptions { Access = FileAccess.Read, Share = FileShare.ReadWrite, Options = FileOptions.Asynchronous | FileOptions.SequentialScan }; FileStreamOptions writeFile = new FileStreamOptions { Share = FileShare.ReadWrite, Access = FileAccess.ReadWrite, Mode = FileMode.Truncate, Options = FileOptions.Asynchronous }; RegexOptions regexOptions = RegexOptions.Multiline | RegexOptions.Compiled; Dictionary<string, int> changedProcs = new(); string regexPattern = @"(?<=\P{L})([a-z]+)([A-Z]{1,2}[a-z]+)*(Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss)([A-Z]{1,2}[a-z]+)*"; Regex camelCaseProcRegex = new(regexPattern, regexOptions); string snakeify(Match matchingRegex) { var vals = matchingRegex.Groups.Cast<Group>().SelectMany(_ => _.Captures).Select(_ => _.Value).ToArray(); var newVal = string.Join("_", vals.Skip(1).ToArray()).ToLower(); string logString = $"{vals[0]} => {newVal}"; if (changedProcs.TryGetValue(logString, out int value)) { changedProcs[logString] = value + 1; } else { changedProcs.Add(logString, 1); } return newVal; } var dmFiles = Directory.EnumerateFiles(".", "*.dm", SearchOption.AllDirectories).ToAsyncEnumerable<string>(); // uses default ParallelOptions // https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions?view=net-10.0#main await Parallel.ForEachAsync(dmFiles, async (filePath, UnusedCancellationToken) => { var reader = new StreamReader(filePath, readFile); string oldContent = await reader.ReadToEndAsync(); string newContent = camelCaseProcRegex.Replace(oldContent, new MatchEvaluator((Func<Match, string>)snakeify)); if (oldContent != newContent) { var writer = new StreamWriter(filePath, writeFile); await writer.WriteAsync(newContent); await writer.DisposeAsync(); } reader.Dispose(); }); var logToList = changedProcs.Cast<KeyValuePair<string, int>>().ToList(); foreach (var pair in logToList) { Console.WriteLine($"{pair.Key}: {pair.Value} locations"); } } } ``` ## Changelog 🆑 Bisar code: All (Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss) procs now use snake_case, in-line with the STYLE guide. Underscores rule! /🆑
246 lines
12 KiB
Plaintext
246 lines
12 KiB
Plaintext
/obj/item/autopsy_scanner
|
|
name = "autopsy scanner"
|
|
desc = "Used in surgery to extract information from a cadaver. Can also scan the health of cadavers like an advanced health analyzer!"
|
|
icon = 'icons/obj/devices/scanner.dmi'
|
|
icon_state = "autopsy_scanner"
|
|
inhand_icon_state = "autopsy_scanner"
|
|
worn_icon_state = "autopsy_scanner"
|
|
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
|
|
obj_flags = CONDUCTS_ELECTRICITY
|
|
item_flags = CRUEL_IMPLEMENT
|
|
slot_flags = ITEM_SLOT_BELT
|
|
w_class = WEIGHT_CLASS_NORMAL
|
|
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*2)
|
|
custom_price = PAYCHECK_COMMAND
|
|
sound_vary = TRUE
|
|
pickup_sound = SFX_GENERIC_DEVICE_PICKUP
|
|
drop_sound = SFX_GENERIC_DEVICE_DROP
|
|
|
|
/obj/item/autopsy_scanner/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/scanned = interacting_with
|
|
|
|
if(scanned.stat != DEAD && !HAS_TRAIT(scanned, TRAIT_FAKEDEATH)) // good job, you found a loophole
|
|
to_chat(user, span_deadsay("[icon2html(src, user)] ERROR! CANNOT SCAN LIVE CADAVERS. PROCURE HEALTH ANALYZER OR TERMINATE PATIENT."))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
. = ITEM_INTERACT_SUCCESS
|
|
|
|
// 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
|
|
|
|
user.visible_message(span_notice("[user] scans [scanned]'s cadaver."))
|
|
to_chat(user, span_deadsay("[icon2html(src, user)] ANALYZING CADAVER."))
|
|
|
|
healthscan(user, scanned, advanced = TRUE)
|
|
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/autopsy_scanner/proc/scan_cadaver(mob/living/carbon/human/user, mob/living/carbon/scanned)
|
|
if(scanned.stat != DEAD)
|
|
return
|
|
|
|
var/obj/item/paper/autopsy_report = new(get_turf(src))
|
|
autopsy_report.color = "#99ccff"
|
|
autopsy_report.name = "autopsy report of [scanned] - [station_time_timestamp()])"
|
|
var/final_report_text = "<center><b>Autopsy report. Time of Autopsy: [station_time_timestamp()]</b></center>"
|
|
|
|
//A lot of this is extremely similar to /proc/healthscan() - but with different formatting, no color, and some added/removed info
|
|
//Does not list quirks/exhaustion/how to repair wounds
|
|
//DOES list wound sources/
|
|
var/list/autopsy_information = list()
|
|
autopsy_information += "Autopsy Coroner - [user.name]<hr>"
|
|
|
|
autopsy_information += "Analyzing results for <b>[scanned.name]</b>:</br></br>"
|
|
autopsy_information += "Time of Death - <b>[scanned.station_timestamp_timeofdeath]</b></br>"
|
|
autopsy_information += "Subject has been dead for <b>[DisplayTimeText(round(world.time - scanned.timeofdeath))]</b>.<hr>"
|
|
|
|
var/oxy_loss = scanned.get_oxy_loss()
|
|
var/tox_loss = scanned.get_tox_loss()
|
|
var/fire_loss = scanned.get_fire_loss()
|
|
var/brute_loss = scanned.get_brute_loss()
|
|
/// "Body Data" portion of the autopsy - damage, wounds, and limbs
|
|
var/dmgreport = "<u><b>Body Data:</b></u>\
|
|
<table class='ml-2'>\
|
|
<tr>\
|
|
<td style='width:7em;'><b>Damage:</b></td>\
|
|
<td style='width:5em;'><b>Brute</b></td>\
|
|
<td style='width:4em;'><b>Burn</b></td>\
|
|
<td style='width:4em;'><b>Toxin</b></td>\
|
|
<td style='width:8em;'><b>Suffocation</b></td>\
|
|
</tr>"
|
|
for(var/zone in scanned.get_all_limbs()) //Same order every time for consistency across all humans
|
|
var/obj/item/bodypart/limb = scanned.get_bodypart(zone)
|
|
if(isnull(limb))
|
|
dmgreport += "<tr>"
|
|
dmgreport += "<td><b>[capitalize(parse_zone(zone))]:</b></td>"
|
|
dmgreport += "<td>-</td>"
|
|
dmgreport += "<td>-</td>"
|
|
dmgreport += "</tr>"
|
|
dmgreport += "<tr><td colspan=6>↳ Physical trauma: <u>Dismembered</u></td></tr>"
|
|
continue
|
|
var/has_any_embeds = length(limb.embedded_objects) >= 1
|
|
var/has_any_wounds = length(limb.wounds) >= 1
|
|
dmgreport += "<tr>"
|
|
dmgreport += "<td><b>[capitalize(limb.name)]:</b></td>"
|
|
dmgreport += "<td>[limb.brute_dam > 0 ? ceil(limb.brute_dam) : "0"]</td>"
|
|
dmgreport += "<td>[limb.burn_dam > 0 ? ceil(limb.burn_dam) : "0"]</td>"
|
|
if(zone == BODY_ZONE_CHEST) // tox/oxy is stored in the chest
|
|
dmgreport += "<td>[tox_loss > 0 ? ceil(tox_loss) : "0"]</td>"
|
|
dmgreport += "<td>[oxy_loss > 0 ? ceil(oxy_loss) : "0"]</td>"
|
|
dmgreport += "</tr>"
|
|
if(has_any_embeds)
|
|
var/list/embedded_names = list()
|
|
for(var/obj/item/embed as anything in limb.embedded_objects)
|
|
embedded_names[capitalize(embed.name)] += 1
|
|
for(var/embedded_name in embedded_names)
|
|
var/displayed = embedded_name
|
|
var/embedded_amt = embedded_names[embedded_name]
|
|
if(embedded_amt > 1)
|
|
displayed = "[embedded_amt]x [embedded_name]"
|
|
dmgreport += "<tr><td colspan=6><i>↳ Foreign object(s): [displayed].</i></td></tr>"
|
|
if(has_any_wounds)
|
|
for(var/datum/wound/wound as anything in limb.wounds)
|
|
dmgreport += "<tr><td colspan=6><i>↳ Physical trauma: [wound.name] ([wound.severity_text()]) - Caused by <u>[wound.wound_source].</u></i></td></tr>"
|
|
|
|
dmgreport += "<tr>\
|
|
<td><b>Overall:</b></td>\
|
|
<td><b>[ceil(brute_loss)] Brute</b></td>\
|
|
<td><b>[ceil(fire_loss)] Burn</b></td>\
|
|
<td><b>[ceil(tox_loss)] Toxin</b></td>\
|
|
<td><b>[ceil(oxy_loss)] Suffocation</b></td>\
|
|
</tr>"
|
|
dmgreport += "</table><hr>"
|
|
autopsy_information += dmgreport
|
|
|
|
// Only humanoids list organs, implants, gene stability, species and core-temp
|
|
if(ishuman(scanned))
|
|
var/mob/living/carbon/human/humantarget = scanned
|
|
/// "Organ Data" portion of the autopsy - damage, functional status (or if it's missing), and implants
|
|
var/organreport = "<u><b>Organ Data:</b></u>\
|
|
<table class='ml-2'>\
|
|
<tr>\
|
|
<td style='width:10em;'><b>Organ:</b></td>\
|
|
<td style='width:6em;'><b>Dmg</b></td>\
|
|
<td style='width:30em;'><b>Status</b></td>\
|
|
</tr>"
|
|
|
|
var/list/missing_organs = humantarget.get_missing_organs()
|
|
for(var/sorted_slot in GLOB.organ_process_order) //Same order every time for consistency across all humans
|
|
var/obj/item/organ/organ = humantarget.get_organ_slot(sorted_slot)
|
|
if(isnull(organ))
|
|
if(missing_organs[sorted_slot])
|
|
organreport += "<tr><td><b>[missing_organs[sorted_slot]]:</b></td>\
|
|
<td>-</td>\
|
|
<td><u>Missing</u></td></tr>"
|
|
continue
|
|
var/status = organ.get_status_text(advanced = TRUE, add_tooltips = FALSE, colored = FALSE)
|
|
var/appendix = organ.get_status_appendix(advanced = TRUE, add_tooltips = FALSE)
|
|
if(!status)
|
|
status ||= "OK" // otherwise flawless organs have no status reported by default
|
|
organreport += "<tr>\
|
|
<td><b>[capitalize(organ.name)]:</b></td>\
|
|
<td>[organ.damage > 0 ? ceil(organ.damage) : "0"]</td>\
|
|
<td>[status]</td>\
|
|
</tr>"
|
|
if(appendix)
|
|
organreport += "<tr><td colspan=4><i>↳ [appendix]</i></td></tr>"
|
|
organreport += "</table>" //<hr> comes after Cybernetics below
|
|
autopsy_information += organreport
|
|
|
|
var/mutant = HAS_TRAIT(humantarget, TRAIT_HULK) //Also applied by genetics infusions
|
|
// Cybernetics
|
|
var/list/cyberimps
|
|
for(var/obj/item/organ/target_organ as anything in humantarget.organs)
|
|
if(IS_ROBOTIC_ORGAN(target_organ) && !(target_organ.organ_flags & ORGAN_HIDDEN))
|
|
LAZYADD(cyberimps, target_organ.examine_title(user))
|
|
if(target_organ.organ_flags & ORGAN_MUTANT)
|
|
mutant = TRUE
|
|
if(LAZYLEN(cyberimps))
|
|
autopsy_information += "<b>Detected cybernetic modifications:</b></br>"
|
|
autopsy_information += "[english_list(cyberimps, and_text = ", and ")]</br>"
|
|
|
|
autopsy_information += "<hr>"
|
|
|
|
// Genetic Stability, Species, and Body Temperature
|
|
if(humantarget.has_dna() && humantarget.dna.stability != initial(humantarget.dna.stability))
|
|
autopsy_information += "<b>Genetic Stability:</b> [humantarget.dna.stability]%.</br>"
|
|
var/datum/species/targetspecies = humantarget.dna.species
|
|
var/disguised = !ishumanbasic(humantarget) && istype(humantarget.head, /obj/item/clothing/head/hooded/human_head) && istype(humantarget.wear_suit, /obj/item/clothing/suit/hooded/bloated_human)
|
|
var/species_name = "[disguised ? "\"[/datum/species/human::name]\"" : targetspecies.name][mutant ? "-derived mutant" : ""]"
|
|
autopsy_information += "<b>Species:</b> [species_name]</br>"
|
|
autopsy_information += "<b>Core temperature:</b> [round(humantarget.coretemperature-T0C, 0.1)] °C ([round(humantarget.coretemperature*1.8-459.67,0.1)] °F)</br>"
|
|
// (End of humanoid-only information)
|
|
autopsy_information += "<b>Body temperature:</b> [round(scanned.bodytemperature-T0C, 0.1)] °C ([round(scanned.bodytemperature*1.8-459.67,0.1)] °F)</br>"
|
|
|
|
// Blood Info
|
|
if(HAS_TRAIT(scanned, TRAIT_HUSK))
|
|
autopsy_information += "Subject is husked by: "
|
|
if(HAS_TRAIT_FROM(scanned, TRAIT_HUSK, CHANGELING_DRAIN))
|
|
autopsy_information += "Desiccation, commonly caused by Changelings.</br>"
|
|
else if(!HAS_TRAIT_FROM(scanned, TRAIT_HUSK, BURN)) // prioritize showing unknown causes over burns
|
|
autopsy_information += "Unknown causes.</br>"
|
|
else
|
|
autopsy_information += "Severe burns.</br>"
|
|
|
|
var/datum/blood_type/blood_type = scanned.get_bloodtype()
|
|
if(blood_type)
|
|
var/cached_blood_volume = scanned.get_blood_volume(apply_modifiers = TRUE)
|
|
var/blood_percent = round((cached_blood_volume / BLOOD_VOLUME_NORMAL) * 100)
|
|
var/blood_type_format
|
|
var/level_format
|
|
if(cached_blood_volume <= BLOOD_VOLUME_SAFE && cached_blood_volume > BLOOD_VOLUME_OKAY)
|
|
level_format = "LOW [blood_percent]%, [cached_blood_volume] cl"
|
|
else if(cached_blood_volume <= BLOOD_VOLUME_OKAY)
|
|
level_format = "<u>CRITICAL [blood_percent]%</u>, [cached_blood_volume] cl"
|
|
else
|
|
level_format = "[blood_percent]%, [cached_blood_volume] cl"
|
|
if(blood_type.get_type())
|
|
blood_type_format = "type: [blood_type.get_type()]"
|
|
autopsy_information += "<b>[blood_type.get_blood_name()] level:</b> [level_format], [blood_type_format]</br>"
|
|
var/blood_alcohol_content = scanned.get_blood_alcohol_content()
|
|
if(blood_alcohol_content > 0)
|
|
autopsy_information += "↳ [blood_type?.get_blood_name() || "Blood"] alcohol content: [blood_alcohol_content]%</br>"
|
|
autopsy_information += "<hr>"
|
|
|
|
autopsy_information += "<u>Chemical Data:</u></br>"
|
|
for(var/datum/reagent/scanned_reagents as anything in scanned.reagents.reagent_list)
|
|
if(scanned_reagents.chemical_flags & REAGENT_INVISIBLE)
|
|
continue
|
|
autopsy_information += "<b>[scanned_reagents.name]:</b> [round(scanned_reagents.volume, 0.1)] unit\s in bloodstream.</br>"
|
|
autopsy_information += "<i>↳ [scanned_reagents.description]</i></br>"
|
|
autopsy_information += "<hr>"
|
|
|
|
autopsy_information += "<u>Disease Data:</u></br>"
|
|
for(var/datum/disease/diseases as anything in scanned.diseases)
|
|
autopsy_information += "<b>Disease Name:</b> \"[diseases.name]\"</br>"
|
|
autopsy_information += "<b>Transmission Type:</b> [diseases.spread_text]</br>"
|
|
if(!istype(diseases, /datum/disease/advance))
|
|
continue
|
|
autopsy_information += "<b>Symptoms:</b></br>"
|
|
var/datum/disease/advance/advanced_disease = diseases
|
|
for(var/datum/symptom/symptom as anything in advanced_disease.symptoms)
|
|
autopsy_information += "↳ [symptom.name] - <i>[symptom.desc]</i></br>"
|
|
autopsy_information += "<hr>"
|
|
|
|
autopsy_information += "<b>Coroner's Notes:</b>" //Bottom of the page, anything past here is player-written
|
|
|
|
final_report_text += jointext(autopsy_information, "")
|
|
autopsy_report.add_raw_text(final_report_text)
|
|
autopsy_report.update_appearance()
|
|
user.put_in_hands(autopsy_report)
|
|
user.balloon_alert(user, "report printed")
|
|
return TRUE
|