Files
+37 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

438 lines
14 KiB
Plaintext

#define SCANGATE_NONE "Off"
#define SCANGATE_MINDSHIELD "Mindshield"
#define SCANGATE_DISEASE "Disease"
#define SCANGATE_GUNS "Guns"
#define SCANGATE_WANTED "Wanted"
#define SCANGATE_SPECIES "Species"
#define SCANGATE_NUTRITION "Nutrition"
#define SCANGATE_GENDER "Gender" // BUBBER EDIT ADDITION
#define SCANGATE_NANITES "Nanites" // BUBBER EDIT ADDITION
/obj/machinery/scanner_gate
name = "scanner gate"
desc = "A gate able to perform mid-depth scans on any organisms who pass under it."
icon = 'icons/obj/machines/scangate.dmi'
icon_state = "scangate"
base_icon_state = "scangate"
layer = ABOVE_MOB_LAYER
circuit = /obj/item/circuitboard/machine/scanner_gate
COOLDOWN_DECLARE(next_beep)
var/scanline_timer
///Bool to check if the scanner's controls are locked by an ID.
var/locked = FALSE
///Which setting is the scanner checking for? See defines in scanner_gate.dm for the list.
var/scangate_mode = SCANGATE_NONE
///Is searching for a disease, what severity is enough to trigger the gate?
var/disease_threshold = DISEASE_SEVERITY_MINOR
///If scanning for a specific species, what species is it looking for?
var/detect_species_id = SPECIES_HUMAN
///Flips all scan results for inverse scanning. Signals if scan returns false.
var/reverse = FALSE
///If scanning for nutrition, what level of nutrition will trigger the scanner?
var/detect_nutrition = NUTRITION_LEVEL_FAT
///Will the assembly on the pass wire activate if the scanner resolves green (Pass) on crossing?
var/light_pass = FALSE
///Will the assembly on the pass wire activate if the scanner resolves red (fail) on crossing?
var/light_fail = FALSE
///Does the scanner ignore light_pass and light_fail for sending signals?
var/ignore_signals = FALSE
var/detect_gender = "male" //SKYRAT EDIT ADDITION - MORE SCANNER GATE OPTIONS
var/nanite_cloud = 1 // BUBBER EDIT ADDITION - NANITES
///Modifier to the chance of scanner being false positive/negative
var/minus_false_beep = 0
///Base false positive/negative chance
var/base_false_beep = 5
///List of species that can be scanned by the gate. Supports adding more species' IDs during in-game.
var/static/list/available_species = list(
SPECIES_HUMAN,
SPECIES_LIZARD,
SPECIES_FLYPERSON,
SPECIES_FELINE,
SPECIES_PLASMAMAN,
SPECIES_MOTH,
SPECIES_JELLYPERSON,
SPECIES_PODPERSON,
SPECIES_GOLEM,
SPECIES_ZOMBIE,
)
/// All scan modes available to the scanner
var/static/list/all_modes = list(
SCANGATE_NONE,
SCANGATE_MINDSHIELD,
SCANGATE_DISEASE,
SCANGATE_GUNS,
SCANGATE_WANTED,
SCANGATE_SPECIES,
SCANGATE_NUTRITION,
SCANGATE_GENDER, // BUBBER EDIT ADDITION
SCANGATE_NANITES, // BUBBER EDIT ADDITION
)
/// All disease severity thresholds available to the scanner
var/static/list/all_disease_thresholds = list(
DISEASE_SEVERITY_POSITIVE,
DISEASE_SEVERITY_NONTHREAT,
DISEASE_SEVERITY_MINOR,
DISEASE_SEVERITY_MEDIUM,
DISEASE_SEVERITY_HARMFUL,
DISEASE_SEVERITY_DANGEROUS,
DISEASE_SEVERITY_BIOHAZARD,
)
/// All nutrition levels available to the scanner
var/static/list/nutrition_modes = list(
"Starving",
"Obese",
)
/// Overlay object we're using for scanlines
var/obj/effect/overlay/scanline = null
/obj/machinery/scanner_gate/Initialize(mapload)
. = ..()
set_wires(new /datum/wires/scanner_gate(src))
set_scanline("passive")
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
AddElement(/datum/element/simple_rotation, ROTATION_IGNORE_ANCHORED|ROTATION_REQUIRE_WRENCH|ROTATION_NEEDS_UNBLOCKED)
register_context()
/obj/machinery/scanner_gate/Destroy(force)
QDEL_NULL(scanline)
return ..()
/obj/machinery/scanner_gate/RefreshParts()
. = ..()
for(var/datum/stock_part/scanning_module/scanning_module in component_parts)
minus_false_beep = scanning_module.tier //The better are scanninning modules - the lower is chance of False Positives
/obj/machinery/scanner_gate/setDir(newdir)
. = ..()
scanline?.setDir(newdir)
/obj/machinery/scanner_gate/examine(mob/user)
. = ..()
. += span_notice("It's set to scan for [span_boldnotice(scangate_mode)].")
if(locked)
. += span_notice("The control panel is ID-locked. Swipe a valid ID to unlock it.")
else
. += span_notice("The control panel is unlocked. Swipe an ID to lock it.")
/obj/machinery/scanner_gate/proc/on_entered(datum/source, atom/movable/thing)
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(auto_scan), thing)
/obj/machinery/scanner_gate/proc/auto_scan(atom/movable/thing)
if(!(machine_stat & (BROKEN|NOPOWER)) && anchored && !panel_open)
perform_scan(thing)
/obj/machinery/scanner_gate/proc/set_scanline(scanline_type, duration)
if (!isnull(scanline))
vis_contents -= scanline
else
scanline = new(src)
scanline.icon = icon
scanline.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
scanline.layer = layer
deltimer(scanline_timer)
if (isnull(scanline_type))
if(duration)
scanline_timer = addtimer(CALLBACK(src, PROC_REF(set_scanline), "passive"), duration, TIMER_STOPPABLE)
return
scanline.icon_state = scanline_type
vis_contents += scanline
if(duration)
scanline_timer = addtimer(CALLBACK(src, PROC_REF(set_scanline), "passive"), duration, TIMER_STOPPABLE)
/obj/machinery/scanner_gate/power_change()
. = ..()
if (machine_stat & (NOPOWER | BROKEN))
set_scanline(null)
return
set_scanline("passive")
/obj/machinery/scanner_gate/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(panel_open && is_wire_tool(tool))
wires.interact(user)
return ITEM_INTERACT_SUCCESS
var/obj/item/card/id/card = tool.GetID()
if(isnull(card))
return NONE
if(locked)
if(!allowed(user))
return ITEM_INTERACT_BLOCKING
locked = FALSE
req_access = list()
balloon_alert(user, "unlocked")
return ITEM_INTERACT_SUCCESS
if(obj_flags & EMAGGED)
balloon_alert(user, "nothing happens!")
return ITEM_INTERACT_BLOCKING
balloon_alert(user, "locked")
req_access = tool.GetAccess() // returns a copy so this is chill
locked = TRUE
return ITEM_INTERACT_SUCCESS
/obj/machinery/scanner_gate/screwdriver_act(mob/living/user, obj/item/tool)
return locked ? NONE : default_deconstruction_screwdriver(user, tool)
/obj/machinery/scanner_gate/update_icon_state()
. = ..()
icon_state = panel_open ? "[base_icon_state]_open" : base_icon_state
/obj/machinery/scanner_gate/emag_act(mob/user, obj/item/card/emag/emag_card)
if(obj_flags & EMAGGED)
return FALSE
locked = FALSE
req_access = list()
obj_flags |= EMAGGED
balloon_alert(user, "id checker disabled")
return TRUE
/obj/machinery/scanner_gate/proc/perform_scan(atom/movable/thing)
var/beep = FALSE
var/color = null
var/detected_thing = null
var/bypassed = FALSE
playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE)
switch(scangate_mode)
if(SCANGATE_NONE)
return
if(SCANGATE_WANTED)
if(ishuman(thing))
detected_thing = "Warrant"
var/mob/living/carbon/human/scanned_human = thing
var/perpname = scanned_human.get_face_name(scanned_human.get_id_name())
var/datum/record/crew/target = find_record(perpname)
if(!target || (target.wanted_status == WANTED_ARREST))
beep = TRUE
if(SCANGATE_MINDSHIELD)
detected_thing = "Mindshield"
if(ishuman(thing))
var/mob/living/carbon/human/scanned_human = thing
if(HAS_TRAIT(scanned_human, TRAIT_MINDSHIELD))
beep = TRUE
if(SCANGATE_DISEASE)
detected_thing = "[disease_threshold] infection"
if(iscarbon(thing))
var/mob/living/carbon/scanned_carbon = thing
if(get_disease_severity_value(scanned_carbon.check_virus()) >= get_disease_severity_value(disease_threshold))
beep = TRUE
if(SCANGATE_SPECIES)
if(ishuman(thing))
var/mob/living/carbon/human/scanned_human = thing
var/datum/species/scan_species = /datum/species/human
if(detect_species_id && (detect_species_id in available_species))
scan_species = GLOB.species_list[detect_species_id]
detected_thing = scan_species.name
if(is_species(scanned_human, scan_species))
beep = TRUE
if(detect_species_id == SPECIES_ZOMBIE) //Can detect dormant zombies
detected_thing = "Romerol infection"
if(scanned_human.get_organ_slot(ORGAN_SLOT_ZOMBIE))
beep = TRUE
if(SCANGATE_GUNS)
detected_thing = "Weapons"
if(isgun(thing))
beep = TRUE
else if(ishuman(thing))
var/mob/living/carbon/human/scanned_human = thing
var/obj/item/card/id/idcard = scanned_human.get_idcard(hand_first = FALSE)
for(var/obj/item/scanned_item in scanned_human.get_all_contents_skipping_traits(TRAIT_CONTRABAND_BLOCKER))
if(isgun(scanned_item))
if((!HAS_TRAIT(scanned_human, TRAIT_MINDSHIELD)) && (isnull(idcard) || !(ACCESS_WEAPONS in idcard.access))) // mindshield or ID card with weapons access, like bartender
beep = TRUE
break
bypassed = TRUE
break
else
for(var/obj/item/content in thing.get_all_contents_skipping_traits(TRAIT_CONTRABAND_BLOCKER))
if(isgun(content))
beep = TRUE
break
if(SCANGATE_NUTRITION)
if(ishuman(thing))
var/mob/living/carbon/human/scanned_human = thing
if(scanned_human.nutrition <= detect_nutrition && detect_nutrition == NUTRITION_LEVEL_STARVING)
beep = TRUE
detected_thing = "Starvation"
if(scanned_human.nutrition >= detect_nutrition && detect_nutrition == NUTRITION_LEVEL_FAT)
beep = TRUE
detected_thing = "Obesity"
//SKYRAT EDIT BEGIN - MORE SCANNER GATE OPTIONS
if(SCANGATE_GENDER)
detected_thing = detect_gender
if(ishuman(thing))
var/mob/living/carbon/human/scanned_human = thing
if((scanned_human.gender in list("male", "female"))) //funny thing: nb people will always get by the scan B)
if(scanned_human.gender == detect_gender)
beep = TRUE
//SKYRAT EDIT END - MORE SCANNER GATE OPTIONS
// BUBBER EDIT ADDITION BEGIN - NANITES
if(SCANGATE_NANITES)
detected_thing = "Nanite cloud #[nanite_cloud]"
if(isliving(thing))
var/detected_cloud_id = SEND_SIGNAL(thing, COMSIG_NANITE_GET_CLOUD)
if(detected_cloud_id == nanite_cloud)
beep = TRUE
// BUBBER EDIT ADDITION END - NANITES
if(reverse)
beep = !beep
if(prob(base_false_beep - minus_false_beep)) //False positive/negative
beep = prob(50)
if(beep)
alarm_beep(detected_thing)
SEND_SIGNAL(src, COMSIG_SCANGATE_PASS_TRIGGER, thing)
if(!ignore_signals)
color = wires.get_color_of_wire(WIRE_ACCEPT)
var/obj/item/assembly/assembly = wires.get_attached(color)
assembly?.activate()
else
SEND_SIGNAL(src, COMSIG_SCANGATE_PASS_NO_TRIGGER, thing)
if(bypassed)
say("[detected_thing] detection bypassed.")
if(!ignore_signals)
color = wires.get_color_of_wire(WIRE_DENY)
var/obj/item/assembly/assembly = wires.get_attached(color)
assembly?.activate()
set_scanline("scanning", 1 SECONDS)
use_energy(active_power_usage)
/obj/machinery/scanner_gate/proc/alarm_beep(detected_thing)
if(!COOLDOWN_FINISHED(src, next_beep))
return
if(detected_thing)
say("[detected_thing][reverse ? " not " : " "]detected!!")
COOLDOWN_START(src, next_beep, 2 SECONDS)
playsound(source = src, soundin = 'sound/machines/scanner/scanbuzz.ogg', vol = 30, vary = FALSE, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE, falloff_distance = 4)
set_scanline("alarm", 2 SECONDS)
/obj/machinery/scanner_gate/can_interact(mob/user)
if(locked)
return FALSE
return ..()
/obj/machinery/scanner_gate/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ScannerGate", name)
ui.open()
/obj/machinery/scanner_gate/ui_static_data(mob/user)
. = ..()
for(var/species_id in available_species)
var/datum/species/specie = GLOB.species_list[species_id]
.["available_species"] += list(list(
"specie_name" = capitalize(format_text(specie.name)),
"specie_id" = species_id,
))
// BUBBER EDIT ADDITION BEGIN - NANITES
.["min_cloud_id"] = NANITE_MIN_CLOUD_ID
.["max_cloud_id"] = NANITE_MAX_CLOUD_ID
// BUBBER EDIT ADDITION END - NANITES
/obj/machinery/scanner_gate/ui_data()
var/list/data = list()
data["locked"] = locked
data["scan_mode"] = scangate_mode
data["reverse"] = reverse
data["disease_threshold"] = disease_threshold
data["target_species_id"] = detect_species_id
data["target_nutrition"] = detect_nutrition
data["target_gender"] = detect_gender //SKYRAT EDIT - MORE SCANNER GATE OPTIONS
data["nanite_cloud"] = nanite_cloud // BUBBER EDIT ADDITION - NANITES
data["target_zombie"] = (detect_species_id == SPECIES_ZOMBIE)
return data
/obj/machinery/scanner_gate/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("set_mode")
var/new_mode = params["new_mode"]
if(!new_mode || !(new_mode in all_modes))
return
scangate_mode = new_mode
. = TRUE
if("toggle_reverse")
reverse = !reverse
. = TRUE
if("toggle_lock")
if(allowed(usr))
locked = !locked
. = TRUE
if("set_disease_threshold")
var/new_threshold = params["new_threshold"]
if(!new_threshold || !(new_threshold in all_disease_thresholds))
return
disease_threshold = new_threshold
. = TRUE
if("set_target_species")
var/new_specie_id = params["new_species_id"]
if(!new_specie_id || !(new_specie_id in available_species))
return
detect_species_id = new_specie_id
. = TRUE
if("set_target_nutrition")
var/new_nutrition = params["new_nutrition"]
if(!new_nutrition || !(new_nutrition in nutrition_modes))
return
switch(new_nutrition)
if("Starving")
detect_nutrition = NUTRITION_LEVEL_STARVING
if("Obese")
detect_nutrition = NUTRITION_LEVEL_FAT
. = TRUE
//SKYRAT EDIT BEGIN - MORE SCANNER GATE OPTIONS
if("set_target_gender")
var/new_gender = params["new_gender"]
var/gender_list = list(
"Male",
"Female"
)
if(new_gender && (new_gender in gender_list))
switch(new_gender)
if("Male")
detect_gender = "male"
if("Female")
detect_gender = "female"
. = TRUE
//SKYRAT EDIT END - MORE SCANNER GATE OPTIONS
// BUBBER EDIT ADDITION BEGIN - NANITES
if("set_nanite_cloud")
var/new_cloud_id = params["new_cloud"]
if(!isnum(new_cloud_id))
return
nanite_cloud = clamp(new_cloud_id, NANITE_MIN_CLOUD_ID+1, NANITE_MAX_CLOUD_ID)
. = TRUE
// BUBBER EDIT ADDITION END - NANITES
/obj/machinery/scanner_gate/preset_guns
locked = TRUE
req_access = list(ACCESS_SECURITY)
scangate_mode = SCANGATE_GUNS
#undef SCANGATE_NONE
#undef SCANGATE_MINDSHIELD
#undef SCANGATE_DISEASE
#undef SCANGATE_GUNS
#undef SCANGATE_WANTED
#undef SCANGATE_SPECIES
#undef SCANGATE_NUTRITION
#undef SCANGATE_GENDER //BUBBER EDIT
#undef SCANGATE_NANITES //BUBBER EDIT