Files
Joshua KidderandGitHub 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

129 lines
5.2 KiB
Plaintext

/datum/action/cooldown/mob_cooldown/fire_breath
name = "Fire Breath"
button_icon = 'icons/effects/magic.dmi'
button_icon_state = "fireball"
desc = "Breathe a line of flames towards the target."
cooldown_time = 3 SECONDS
/// The range of the fire
var/fire_range = 15
/// The sound played when you use this ability
var/fire_sound = 'sound/effects/magic/fireball.ogg'
/// Time to wait between spawning each fire turf
var/fire_delay = 1.5 DECISECONDS
/// How hot is our fire
var/fire_temperature = DRAKE_FIRE_TEMP
/// 'How much' fire do we expose the turf to?
var/fire_volume = DRAKE_FIRE_EXPOSURE
/// How much damage do you take when engulfed?
var/fire_damage = 20
/// How much damage to mechs take when engulfed?
var/mech_damage = 45
/datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom)
disable_cooldown_actions()
attack_sequence(target_atom)
StartCooldown()
enable_cooldown_actions()
return TRUE
/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes
/datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
fire_line(target)
/// Breathe fire in a line towards the target, optionally rotated at an offset from the target
/datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset)
if (isnull(target))
return
var/turf/target_turf = get_ranged_target_turf_direct(owner, target, fire_range, offset)
var/list/turfs = get_line(owner, target_turf) - get_turf(owner)
INVOKE_ASYNC(src, PROC_REF(progressive_fire_line), turfs)
/// Creates fire with a delay on the list of targeted turfs
/datum/action/cooldown/mob_cooldown/fire_breath/proc/progressive_fire_line(list/burn_turfs)
if (QDELETED(owner) || owner.stat == DEAD)
return
// Guys we have already hit, no double dipping
var/list/hit_list = list(owner) // also don't burn ourselves
for(var/turf/target_turf in burn_turfs)
if (target_turf.is_blocked_turf(exclude_mobs = TRUE))
return
burn_turf(target_turf, hit_list, owner)
sleep(fire_delay)
/// Finally spawn the actual fire, spawns the fire hotspot in case you want to recolour it or something
/datum/action/cooldown/mob_cooldown/fire_breath/proc/burn_turf(turf/fire_turf, list/hit_list, mob/living/source)
var/obj/effect/hotspot/fire_hotspot = new /obj/effect/hotspot(fire_turf, fire_volume, fire_temperature)
fire_turf.hotspot_expose(fire_temperature, fire_volume, TRUE)
for(var/mob/living/barbecued in fire_turf.contents)
if(barbecued in hit_list)
continue
hit_list |= barbecued
on_burn_mob(barbecued, source)
for(var/obj/vehicle/sealed/mecha/robotron in fire_turf.contents)
if(robotron in hit_list)
continue
hit_list |= robotron
robotron.take_damage(mech_damage, BURN, FIRE)
return fire_hotspot
/// Do something unpleasant to someone we set on fire
/datum/action/cooldown/mob_cooldown/fire_breath/proc/on_burn_mob(mob/living/barbecued, mob/living/source)
if(fire_temperature <= TCMB)
barbecued.apply_status_effect(/datum/status_effect/ice_block_talisman, 2 SECONDS)
to_chat(barbecued, span_userdanger("You're frozen solid by [source]'s icy breath!"))
else
to_chat(barbecued, span_userdanger("You are burned by [source]'s fire breath!"))
barbecued.adjust_fire_loss(fire_damage)
/// Shoot three lines of fire in a sort of fork pattern approximating a cone
/datum/action/cooldown/mob_cooldown/fire_breath/cone
name = "Fire Cone"
desc = "Breathe several lines of fire directed at a target."
/// The angles relative to the target that shoot lines of fire
var/list/angles = list(-40, 0, 40)
/datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
for(var/offset in angles)
fire_line(target, offset)
/// Shoot fire in a whole bunch of directions
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire
name = "Mass Fire"
button_icon = 'icons/effects/fire.dmi'
button_icon_state = "light"
desc = "Breathe flames in all directions."
cooldown_time = 10.5 SECONDS
click_to_activate = FALSE
/// How many fire lines do we produce to turn a full circle?
var/sectors = 12
/// How long do we wait between each spin?
var/breath_delay = 2.5 SECONDS
/// How many full circles do we perform?
var/total_spins = 3
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/Activate(atom/target_atom)
target_atom = get_step(owner, owner.dir) // Just shoot it forwards, we don't need to click on someone for this one
return ..()
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target)
var/queued_spins = 0
for (var/i in 1 to total_spins)
var/delay = queued_spins * breath_delay
queued_spins++
addtimer(CALLBACK(src, PROC_REF(fire_spin), target, queued_spins), delay)
/// Breathe fire in a circle, with a slight angle offset based on which of our several circles it is
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/fire_spin(target, spin_count)
if (QDELETED(owner) || owner.stat == DEAD)
return // Too dead to spin
playsound(owner.loc, fire_sound, 200, TRUE)
var/angle_increment = 360 / sectors
var/additional_offset = spin_count * angle_increment / 2
for (var/i in 1 to sectors)
fire_line(target, (angle_increment * i) + (additional_offset))