Files
Bubberstation/code/game/objects/structures/spawner.dm
T
Joshua Kidder 7a3ad79506 All camelCase (Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss) procs now use snake_case. UNDERSCORES RULE! (#94111)
## 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!
/🆑
2025-11-27 15:50:23 -05:00

303 lines
11 KiB
Plaintext

/obj/structure/spawner
name = "monster nest"
icon = 'icons/mob/simple/animal.dmi'
icon_state = "hole"
max_integrity = 100
move_resist = MOVE_FORCE_EXTREMELY_STRONG
anchored = TRUE
density = TRUE
faction = list(FACTION_HOSTILE)
var/max_mobs = 5
var/spawn_time = 30 SECONDS
var/mob_types = list(/mob/living/basic/carp)
var/spawn_text = "emerges from"
var/spawner_type = /datum/component/spawner
/// Is this spawner taggable with something?
var/scanner_taggable = FALSE
/// If this spawner's taggable, what can we tag it with?
var/static/list/scanner_types = list(/obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner)
/// If this spawner's taggable, what's the text we use to describe what we can tag it with?
var/scanner_descriptor = "mining analyzer"
/// Has this spawner been tagged/analyzed by a mining scanner?
var/gps_tagged = FALSE
/// A short identifier for the mob it spawns. Keep around 3 characters or less?
var/mob_gps_id = "???"
/// A short identifier for what kind of spawner it is, for use in putting together its GPS tag.
var/spawner_gps_id = "Creature Nest"
/// A complete identifier. Generated on tag (if tagged), used for its examine.
var/assigned_tag
/obj/structure/spawner/examine(mob/user)
. = ..()
if(!scanner_taggable)
return
if(gps_tagged)
. += span_notice("A holotag's been attached, projecting \"<b>[assigned_tag]</b>\".")
else
. += span_notice("It looks like you could probably scan and tag it with a <b>[scanner_descriptor]</b>.")
/obj/structure/spawner/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers)
. = ..()
if(.)
return TRUE
if(scanner_taggable && is_type_in_list(item, scanner_types))
gps_tag(user)
return TRUE
/// Tag the spawner, prefixing its GPS entry with an identifier - or giving it one, if nonexistent.
/obj/structure/spawner/proc/gps_tag(mob/user)
if(gps_tagged)
to_chat(user, span_warning("[src] already has a holotag attached!"))
return
to_chat(user, span_notice("You affix a holotag to [src]."))
playsound(src, 'sound/machines/beep/twobeep.ogg', 100)
gps_tagged = TRUE
assigned_tag = "\[[mob_gps_id]-[rand(100,999)]\] " + spawner_gps_id
var/datum/component/gps/our_gps = GetComponent(/datum/component/gps)
if(our_gps)
our_gps.gpstag = assigned_tag
return
AddComponent(/datum/component/gps, assigned_tag)
/obj/structure/spawner/Initialize(mapload)
. = ..()
AddComponent(\
spawner_type, \
spawn_types = mob_types, \
spawn_time = spawn_time, \
max_spawned = max_mobs, \
faction = faction, \
spawn_text = spawn_text,\
spawn_callback = CALLBACK(src, PROC_REF(on_mob_spawn)), \
initial_spawn_delay = !mapload, \
)
/obj/structure/spawner/attack_animal(mob/living/simple_animal/user, list/modifiers)
if(faction_check(faction, user.faction, FALSE) && !user.client)
return
return ..()
/obj/structure/spawner/proc/on_mob_spawn(atom/created_atom)
return
/obj/structure/spawner/syndicate
name = "warp beacon"
icon = 'icons/obj/machines/beacon.dmi'
icon_state = "syndbeacon"
spawn_text = "warps in from"
mob_types = list(/mob/living/basic/trooper/syndicate/ranged)
faction = list(ROLE_SYNDICATE)
mob_gps_id = "SYN" // syndicate
spawner_gps_id = "Hostile Warp Beacon"
/obj/structure/spawner/skeleton
name = "bone pit"
desc = "A pit full of bones, and some still seem to be moving..."
icon_state = "hole"
icon = 'icons/mob/simple/lavaland/nest.dmi'
max_integrity = 150
max_mobs = 15
spawn_time = 15 SECONDS
mob_types = list(/mob/living/basic/skeleton)
spawn_text = "climbs out of"
faction = list(FACTION_SKELETON)
mob_gps_id = "SKL" // skeletons
spawner_gps_id = "Bone Pit"
/obj/structure/spawner/clown
name = "Laughing Larry"
desc = "A laughing, jovial figure. Something seems stuck in his throat."
icon_state = "clownbeacon"
icon = 'icons/obj/machines/beacon.dmi'
max_integrity = 200
max_mobs = 15
spawn_time = 15 SECONDS
mob_types = list(
/mob/living/basic/clown,
/mob/living/basic/clown/banana,
/mob/living/basic/clown/clownhulk,
/mob/living/basic/clown/clownhulk/chlown,
/mob/living/basic/clown/clownhulk/honkmunculus,
/mob/living/basic/clown/fleshclown,
/mob/living/basic/clown/mutant/glutton,
/mob/living/basic/clown/honkling,
/mob/living/basic/clown/longface,
/mob/living/basic/clown/lube,
)
spawn_text = "climbs out of"
faction = list(FACTION_CLOWN)
mob_gps_id = "???" // clowns
spawner_gps_id = "Clown Planet Distortion"
/obj/structure/spawner/mining
name = "monster den"
desc = "A hole dug into the ground, harboring all kinds of monsters found within most caves or mining asteroids."
icon_state = "hole"
max_integrity = 200
max_mobs = 3
icon = 'icons/mob/simple/lavaland/nest.dmi'
spawn_text = "crawls out of"
mob_types = list(
/mob/living/basic/mining/basilisk,
/mob/living/basic/mining/goldgrub,
/mob/living/basic/mining/goliath/ancient,
/mob/living/basic/mining/hivelord,
/mob/living/basic/wumborian_fugu,
)
faction = list(FACTION_MINING)
/obj/structure/spawner/mining/goldgrub
name = "goldgrub den"
desc = "A den housing a nest of goldgrubs, annoying but arguably much better than anything else you'll find in a nest."
mob_types = list(/mob/living/basic/mining/goldgrub)
mob_gps_id = "GG"
/obj/structure/spawner/mining/goliath
name = "goliath den"
desc = "A den housing a nest of goliaths, oh god why?"
mob_types = list(/mob/living/basic/mining/goliath/ancient)
mob_gps_id = "GL|A"
/obj/structure/spawner/mining/hivelord
name = "hivelord den"
desc = "A den housing a nest of hivelords."
mob_types = list(/mob/living/basic/mining/hivelord)
mob_gps_id = "HL"
/obj/structure/spawner/mining/basilisk
name = "basilisk den"
desc = "A den housing a nest of basilisks, bring a coat."
mob_types = list(/mob/living/basic/mining/basilisk)
mob_gps_id = "BK"
/obj/structure/spawner/mining/wumborian
name = "wumborian fugu den"
desc = "A den housing a nest of wumborian fugus, how do they all even fit in there?"
mob_types = list(/mob/living/basic/wumborian_fugu)
mob_gps_id = "WF"
/obj/structure/spawner/nether
name = "netherworld link"
desc = null //see examine()
icon_state = "nether"
max_integrity = 50
spawn_time = 60 SECONDS
max_mobs = 15
icon = 'icons/mob/simple/lavaland/nest.dmi'
spawn_text = "crawls through"
mob_types = list(
/mob/living/basic/blankbody,
/mob/living/basic/creature,
/mob/living/basic/migo,
)
faction = list(FACTION_NETHER)
scanner_taggable = TRUE
mob_gps_id = "?!?"
spawner_gps_id = "Netheric Distortion"
/obj/structure/spawner/nether/Initialize(mapload)
. = ..()
START_PROCESSING(SSprocessing, src)
/obj/structure/spawner/nether/examine(mob/user)
. = ..()
if(isskeleton(user) || iszombie(user))
. += "A direct link to another dimension full of creatures very happy to see you. [span_nicegreen("You can see your house from here!")]"
else
. += "A direct link to another dimension full of creatures not very happy to see you. [span_warning("Entering the link would be a very bad idea.")]"
/obj/structure/spawner/nether/attack_hand(mob/user, list/modifiers)
. = ..()
if(isskeleton(user) || iszombie(user))
to_chat(user, span_notice("You don't feel like going home yet..."))
else
user.visible_message(span_warning("[user] is violently pulled into the link!"), \
span_userdanger("Touching the portal, you are quickly pulled through into a world of unimaginable horror!"))
contents.Add(user)
/obj/structure/spawner/nether/process(seconds_per_tick)
for(var/mob/living/living_mob in contents)
playsound(src, 'sound/effects/magic/demon_consume.ogg', 50, TRUE)
living_mob.adjust_brute_loss(60 * seconds_per_tick)
new /obj/effect/gibspawner/generic(get_turf(living_mob), living_mob)
if(living_mob.stat == DEAD)
var/mob/living/basic/blankbody/newmob = new(loc)
newmob.name = "[living_mob]"
newmob.desc = "It's [living_mob], but [living_mob.p_their()] flesh has an ashy texture, and [living_mob.p_their()] face is featureless save an eerie smile."
src.visible_message(span_warning("[living_mob] reemerges from the link!"))
qdel(living_mob)
/obj/structure/spawner/sentient
var/role_name = "A sentient mob"
var/assumed_control_message = "You are a sentient mob from a badly coded spawner"
/obj/structure/spawner/sentient/Initialize(mapload)
. = ..()
notify_ghosts(
"A [name] has been created in \the [get_area(src)]!",
source = src,
header = "Sentient Spawner Created",
notify_flags = NOTIFY_CATEGORY_NOFLASH,
)
/obj/structure/spawner/sentient/on_mob_spawn(atom/created_atom)
created_atom.AddComponent(\
/datum/component/ghost_direct_control,\
role_name = src.role_name,\
assumed_control_message = src.assumed_control_message,\
after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\
)
/obj/structure/spawner/sentient/proc/became_player_controlled(mob/proteon)
return
/obj/structure/spawner/sentient/proteon_spawner
name = "eldritch gateway"
desc = "A dizzying structure that somehow links into Nar'Sie's own domain. The screams of the damned echo continously."
icon = 'icons/obj/antags/cult/structures.dmi'
icon_state = "hole"
light_power = 2
light_color = COLOR_CULT_RED
max_integrity = 50
density = FALSE
max_mobs = 2
spawn_time = 15 SECONDS
mob_types = list(/mob/living/basic/construct/proteon/hostile)
spawn_text = "arises from"
faction = list(FACTION_CULT)
role_name = "A proteon cult construct"
assumed_control_message = null
/obj/structure/spawner/sentient/proteon_spawner/examine_status(mob/user)
if(IS_CULTIST(user) || !isliving(user))
return span_cult("It's at <b>[round(atom_integrity * 100 / max_integrity)]%</b> stability.")
return ..()
/obj/structure/spawner/sentient/proteon_spawner/examine(mob/user)
. = ..()
if(!IS_CULTIST(user) && isliving(user))
var/mob/living/living_user = user
living_user.adjust_organ_loss(ORGAN_SLOT_BRAIN, 15)
. += span_danger("The voices of the damned echo relentlessly in your mind, continously rebounding on the walls of your self the more you focus on [src]. Your head pounds, better keep away...")
else
. += span_cult("The gateway will create one weak proteon construct every [spawn_time * 0.1] seconds, up to a total of [max_mobs], that may be controlled by the spirits of the dead.")
/obj/structure/spawner/sentient/proteon_spawner/became_player_controlled(mob/living/basic/construct/proteon/proteon)
proteon.mind.add_antag_datum(/datum/antagonist/cult)
proteon.add_filter("awoken_proteon", 3, list("type" = "outline", "color" = COLOR_CULT_RED, "size" = 2))
visible_message(span_cult_bold("[proteon] awakens, glowing an eerie red as it stirs from its stupor!"))
playsound(proteon, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE)
proteon.balloon_alert_to_viewers("awoken!")
addtimer(CALLBACK(src, PROC_REF(remove_wake_outline), proteon), 8 SECONDS)
/obj/structure/spawner/sentient/proteon_spawner/proc/remove_wake_outline(mob/proteon)
proteon.remove_filter("awoken_proteon")
proteon.add_filter("sentient_proteon", 3, list("type" = "outline", "color" = COLOR_CULT_RED, "size" = 2, "alpha" = 40))
/obj/structure/spawner/sentient/proteon_spawner/handle_deconstruct(disassembled)
playsound(src, 'sound/effects/hallucinations/veryfar_noise.ogg', 75)
visible_message(span_cult_bold("[src] completely falls apart, the screams of the damned reaching a feverous pitch before slowly fading away into nothing."))