Files
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

226 lines
9.6 KiB
Plaintext

/**
* Common behaviour shared by things which are minions to a blob
*/
/datum/component/blob_minion
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// Overmind who is our boss
var/mob/eye/blob/overmind
/// Callback to run if overmind strain changes
var/datum/callback/on_strain_changed
/// Our strain we should not acess the overminds strain directly as we may not have one.
var/datum/blobstrain/our_strain
/// Used to determine the size of blob mob death clouds or equivlent strain dependant spore death effects
var/death_cloud_size = BLOBMOB_CLOUD_NONE
/datum/component/blob_minion/Initialize(mob/eye/blob/new_overmind, datum/callback/on_strain_changed, new_death_cloud_size, datum/blobstrain/new_strain)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
if(isnum(new_death_cloud_size))
death_cloud_size = new_death_cloud_size
src.on_strain_changed = on_strain_changed
//checking for a lack of overmind to avoid calling strain_properties changed twice.
if(new_strain && !new_overmind)
strain_properties_changed(null, new_strain)
if(new_overmind)
register_overlord(new_overmind)
/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/eye/blob/new_overmind, datum/callback/on_strain_changed, new_death_cloud_size, datum/blobstrain/new_strain)
if(isnum(new_death_cloud_size))
death_cloud_size = new_death_cloud_size
if (!isnull(on_strain_changed))
src.on_strain_changed = on_strain_changed
if(new_strain && !new_overmind)
strain_properties_changed(null, new_strain)
if(new_overmind)
register_overlord(new_overmind)
/datum/component/blob_minion/proc/register_overlord(mob/eye/blob/new_overmind)
overmind = new_overmind
overmind.register_new_minion(parent)
RegisterSignal(overmind, COMSIG_QDELETING, PROC_REF(overmind_deleted))
RegisterSignal(overmind, COMSIG_BLOB_SELECTED_STRAIN, PROC_REF(strain_properties_changed))
strain_properties_changed(overmind, overmind.blobstrain)
var/mob/living_parent = parent
living_parent.pass_flags |= PASSBLOB
/// Our overmind is gone, uh oh!
/datum/component/blob_minion/proc/overmind_deleted()
SIGNAL_HANDLER
overmind = null
strain_properties_changed()
/// Our strain has changed, perhaps because our blob overmind has changed strain, died, or because of a mutation.
/datum/component/blob_minion/proc/strain_properties_changed(mob/eye/blob/changed_overmind, datum/blobstrain/new_strain)
SIGNAL_HANDLER
var/mob/living/living_parent = parent
if(new_strain)
our_strain = new_strain
else
our_strain = null
living_parent.update_appearance(UPDATE_ICON)
on_strain_changed?.Invoke(changed_overmind, new_strain)
/datum/component/blob_minion/RegisterWithParent()
var/mob/living/living_parent = parent
living_parent.faction |= ROLE_BLOB
ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob
RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_appearance))
RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_update_status_tab))
RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(on_blob_touched))
RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned))
RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass))
RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move))
RegisterSignal(parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech))
RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed))
RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death))
RegisterSignal(parent, COMSIG_BASICMOB_MUTATED, PROC_REF(on_mutated))
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_minion_atom_interacted))
if(overmind || our_strain)
strain_properties_changed(overmind, our_strain)
GLOB.blob_telepathy_mobs |= parent
/datum/component/blob_minion/UnregisterFromParent()
if (!isnull(overmind))
overmind.blob_mobs -= parent
var/mob/living/living_parent = parent
living_parent.pass_flags &= ~PASSBLOB
living_parent.faction -= ROLE_BLOB
REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
add_verb(parent, /mob/living/verb/pulled)
UnregisterSignal(parent, list(
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_FIRE_ACT,
COMSIG_ATOM_TRIED_PASS,
COMSIG_ATOM_UPDATE_ICON,
COMSIG_MOB_TRY_SPEECH,
COMSIG_MOB_CHANGED_TYPE,
COMSIG_MOB_GET_STATUS_TAB_ITEMS,
COMSIG_MOB_MIND_INITIALIZED,
COMSIG_MOVABLE_SPACEMOVE,
COMSIG_LIVING_DEATH,
COMSIG_BASICMOB_MUTATED,
COMSIG_HOSTILE_PRE_ATTACKINGTARGET,
))
GLOB.blob_telepathy_mobs -= parent
living_parent.pass_flags &= ~PASSBLOB
/// Become blobpilled when we gain a mind
/datum/component/blob_minion/proc/on_mind_init(mob/living/minion, datum/mind/new_mind)
SIGNAL_HANDLER
if (isnull(overmind))
return
var/datum/antagonist/blob_minion/minion_motive = new(overmind)
new_mind.add_antag_datum(minion_motive)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion)
SIGNAL_HANDLER
if(our_strain?.color)
minion.add_atom_colour(our_strain.color, FIXED_COLOUR_PRIORITY)
else
minion.remove_atom_colour(FIXED_COLOUR_PRIORITY)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_status_tab(mob/living/minion, list/status_items)
SIGNAL_HANDLER
if (isnull(overmind))
return
status_items += "Blobs to Win: [length(overmind.blobs_legit)]/[overmind.blobwincount]"
/// If we feel the gentle caress of a blob, we feel better
/datum/component/blob_minion/proc/on_blob_touched(mob/living/minion)
SIGNAL_HANDLER
if(minion.stat == DEAD || minion.health >= minion.maxHealth)
return COMPONENT_CANCEL_BLOB_ACT // Don't hurt us in order to heal us
for(var/i in 1 to 2)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(parent)) // hello yes you are being healed
heal_effect.color = isnull(overmind) ? COLOR_BLACK : overmind.blobstrain.complementary_color
minion.heal_overall_damage(minion.maxHealth * BLOBMOB_HEALING_MULTIPLIER)
return COMPONENT_CANCEL_BLOB_ACT
/// If we feel the fearsome bite of open flame, we feel worse
/datum/component/blob_minion/proc/on_burned(mob/living/minion, exposed_temperature, exposed_volume)
SIGNAL_HANDLER
if(isnull(exposed_temperature))
minion.adjust_fire_loss(5)
return
minion.adjust_fire_loss(clamp(0.01 * exposed_temperature, 1, 5))
/// Someone is attempting to move through us, allow it if it is a blob tile
/datum/component/blob_minion/proc/on_attempted_pass(mob/living/minion, atom/movable/incoming)
SIGNAL_HANDLER
if(istype(incoming, /obj/structure/blob))
return COMSIG_COMPONENT_PERMIT_PASSAGE
/// If we're near a blob, stop drifting
/datum/component/blob_minion/proc/on_space_move(mob/living/minion)
SIGNAL_HANDLER
var/obj/structure/blob/blob_handhold = locate() in range(1, parent)
if (!isnull(blob_handhold))
return COMSIG_MOVABLE_STOP_SPACEMOVE
/// We only speak telepathically to blobs
/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced)
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(send_blob_telepathy), minion, message)
return COMPONENT_CANNOT_SPEAK
/datum/component/blob_minion/proc/send_blob_telepathy(mob/living/minion, message)
var/list/message_mods = list()
// Note: check_for_custom_say_emote can sleep.
var/adjusted_message = minion.check_for_custom_say_emote(message, message_mods)
minion.log_sayverb_talk(message, message_mods, tag = "blob hivemind telepathy")
var/spanned_message = minion.generate_messagepart(adjusted_message, message_mods = message_mods)
var/rendered = span_blob("<b>\[Blob Telepathy\] [minion.real_name]</b> [spanned_message]")
relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion, MESSAGE_TYPE_RADIO)
/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie
/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement)
SIGNAL_HANDLER
replacement.AddComponent(/datum/component/blob_minion, new_overmind = overmind, new_death_cloud_size = death_cloud_size, new_strain = our_strain)
/datum/component/blob_minion/proc/on_death(mob/living/minion)
SIGNAL_HANDLER
if(death_cloud_size <= BLOBMOB_CLOUD_NONE)
return
if(our_strain)
our_strain.on_sporedeath(minion, death_cloud_size)
else
do_chem_smoke(range = death_cloud_size, holder = minion, location = get_turf(minion), reagent_type = /datum/reagent/toxin/spore, reagent_volume = BLOBMOB_CLOUD_REAGENT_VOLUME, smoke_type = /datum/effect_system/fluid_spread/smoke/chem/medium)
playsound(minion, 'sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg', vol = 100)
///When am independent mob with this component mutates, like from a random cytology mutation, give them a strain and modify their name to let the players know they have something special.
/datum/component/blob_minion/proc/on_mutated(mob/living/minion)
SIGNAL_HANDLER
if(overmind || our_strain)
return
var/datum/blobstrain/mutant_strain = pick(GLOB.valid_blobstrains)
strain_properties_changed(changed_overmind = null, new_strain = new mutant_strain)
minion.name = "[LOWER_TEXT(our_strain.name)] [minion.name]"
//normally the overmind would handle this, but we have none.
minion.maxHealth *= our_strain.max_mob_health_multiplier
minion.health *= our_strain.max_mob_health_multiplier
return MUTATED_NO_FURTHER_MUTATIONS
///For when we want to trigger effects when a blobmob clicks something, such as clicking on items.
/datum/component/blob_minion/proc/on_minion_atom_interacted(mob/living/minion, atom/interacted_atom, adjacent, modifiers)
SIGNAL_HANDLER
return our_strain?.on_blobmob_atom_interacted(minion, interacted_atom, adjacent, modifiers)