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

291 lines
9.1 KiB
Plaintext

/// Shoots a random, fake projectile to the hallucinator
/datum/hallucination/stray_bullet
random_hallucination_weight = 7
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/datum/hallucination/stray_bullet/start()
var/list/turf/starting_locations = list()
for(var/turf/open/open_out_of_view in view(world.view + 1, hallucinator) - view(world.view, hallucinator))
starting_locations += open_out_of_view
if(!length(starting_locations))
return FALSE
var/turf/start = pick(starting_locations)
var/fake_type = pick(subtypesof(/obj/projectile/hallucination))
feedback_details += "Type: [fake_type], Source: ([start.x], [start.y], [start.z])"
var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src)
fake_projectile.aim_projectile(hallucinator, start)
fake_projectile.fire()
QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck.
return TRUE
/// Hallucinated projectiles.
/obj/projectile/hallucination
name = "bullet"
icon = null
icon_state = null
hitsound = ""
suppressed = SUPPRESSED_VERY
ricochets_max = 0
ricochet_chance = 0
damage = 0
log_override = TRUE
do_not_log = TRUE
/// Our parent hallucination that's created us
var/datum/hallucination/parent
/// The image that represents our projectile itself
var/image/fake_bullet
var/hal_icon = 'icons/obj/weapons/guns/projectiles.dmi'
var/hal_icon_state
var/hal_fire_sound
var/hal_hitsound
var/hal_hitsound_wall
var/hal_impact_effect
var/hal_impact_effect_wall
var/hit_duration
var/hit_duration_wall
/obj/projectile/hallucination/Initialize(mapload, datum/hallucination/parent)
. = ..()
if(!parent)
stack_trace("[type] was created without a parent hallucination.")
return INITIALIZE_HINT_QDEL
src.parent = parent
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(parent_deleting))
/obj/projectile/hallucination/Destroy()
if(!QDELETED(parent.hallucinator))
parent.hallucinator.client?.images -= fake_bullet
fake_bullet = null
UnregisterSignal(parent, COMSIG_QDELETING)
parent = null
return ..()
/// Signal proc for [COMSIG_QDELETING], if our associated hallucination deletes, we need to clean up
/obj/projectile/hallucination/proc/parent_deleting(datum/source)
SIGNAL_HANDLER
qdel(src)
/obj/projectile/hallucination/fire()
if(hal_fire_sound)
parent.hallucinator.playsound_local(get_turf(src), hal_fire_sound, 60, TRUE)
fake_bullet = image(hal_icon, src, hal_icon_state, ABOVE_MOB_LAYER)
parent.hallucinator.client?.images += fake_bullet
return ..()
/obj/projectile/hallucination/on_hit(atom/target, blocked, pierce_hit)
. = ..()
if(. != BULLET_ACT_HIT)
return
if(ismob(target))
if(hal_hitsound)
parent.hallucinator.playsound_local(target, hal_hitsound, 100, 1)
on_mob_hit(target)
else
if(hal_hitsound_wall)
parent.hallucinator.playsound_local(loc, hal_hitsound_wall, 40, 1)
if(hal_impact_effect_wall)
spawn_hit(target, TRUE)
qdel(src)
/// Called when a mob is hit by the fake projectile
/obj/projectile/hallucination/proc/on_mob_hit(mob/living/hit_mob)
if(hit_mob == parent.hallucinator)
to_chat(parent.hallucinator, span_userdanger("[hit_mob] is hit by \a [src] in the chest!"))
apply_effect_to_hallucinator(parent.hallucinator)
else if(hit_mob in view(parent.hallucinator))
to_chat(parent.hallucinator, span_danger("[hit_mob] is hit by \a [src] in the chest!"))
if(damage_type == BRUTE)
var/splatter_dir = dir
if(starting)
splatter_dir = get_dir(starting, get_turf(hit_mob))
spawn_blood(hit_mob, splatter_dir)
else if(hal_impact_effect)
spawn_hit(hit_mob, FALSE)
/// Called when the hallucinator themselves are hit by the fake projectile
/obj/projectile/hallucination/proc/apply_effect_to_hallucinator(mob/living/afflicted)
return
/// Called after a mob is hit by the fake projectile, and our fake projectile is of brute type, to create fake blood
/obj/projectile/hallucination/proc/spawn_blood(mob/living/bleeding, set_dir)
if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs
return
var/splatter_icon_state
if(ISDIAGONALDIR(set_dir))
splatter_icon_state = "splatter[pick(1, 2, 6)]"
else
splatter_icon_state = "splatter[pick(3, 4, 5)]"
var/image/blood = image('icons/effects/blood.dmi', bleeding, splatter_icon_state, ABOVE_MOB_LAYER)
var/target_pixel_x = 0
var/target_pixel_y = 0
switch(set_dir)
if(NORTH)
target_pixel_y = 16
if(SOUTH)
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
if(EAST)
target_pixel_x = 16
if(WEST)
target_pixel_x = -16
if(NORTHEAST)
target_pixel_x = 16
target_pixel_y = 16
if(NORTHWEST)
target_pixel_x = -16
target_pixel_y = 16
if(SOUTHEAST)
target_pixel_x = 16
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
if(SOUTHWEST)
target_pixel_x = -16
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
parent.hallucinator.client?.images |= blood
animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5)
addtimer(CALLBACK(src, PROC_REF(clean_up_blood), blood), 0.5 SECONDS)
/obj/projectile/hallucination/proc/clean_up_blood(image/blood)
parent.hallucinator.client?.images -= blood
/// Called with a non-mob atom was hit by our fake projectile, or a mob was hit and our damge type is not brute
/obj/projectile/hallucination/proc/spawn_hit(atom/hit_atom, is_wall)
if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs
return
var/image/hit_effect = image('icons/effects/blood.dmi', hit_atom, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER)
hit_effect.pixel_w = hit_atom.pixel_x + rand(-4,4)
hit_effect.pixel_z = hit_atom.pixel_y + rand(-4,4)
parent.hallucinator.client.images |= hit_effect
addtimer(CALLBACK(src, PROC_REF(clean_up_hit), hit_effect), is_wall ? hit_duration_wall : hit_duration)
/obj/projectile/hallucination/proc/clean_up_hit(image/hit_effect)
parent.hallucinator.client?.images -= hit_effect
/obj/projectile/hallucination/bullet
name = "bullet"
hal_icon_state = "bullet"
hal_fire_sound = "gunshot"
hal_hitsound = 'sound/items/weapons/pierce.ogg'
hal_hitsound_wall = SFX_RICOCHET
hal_impact_effect = "impact_bullet"
hal_impact_effect_wall = "impact_bullet"
hit_duration = 5
hit_duration_wall = 5
/obj/projectile/hallucination/bullet/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.adjust_stamina_loss(60)
/obj/projectile/hallucination/laser
name = "laser"
damage_type = BURN
hal_icon_state = "laser"
hal_fire_sound = 'sound/items/weapons/laser.ogg'
hal_hitsound = 'sound/items/weapons/sear.ogg'
hal_hitsound_wall = 'sound/items/weapons/effects/searwall.ogg'
hal_impact_effect = "impact_laser"
hal_impact_effect_wall = "impact_laser_wall"
hit_duration = 4
hit_duration_wall = 10
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
ricochets_max = 50
ricochet_chance = 80
reflectable = TRUE
/obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.adjust_stamina_loss(20)
afflicted.adjust_eye_blur(4 SECONDS)
/obj/projectile/hallucination/disabler
name = "disabler beam"
damage_type = STAMINA
hal_icon_state = "omnilaser"
hal_fire_sound = 'sound/items/weapons/taser2.ogg'
hal_hitsound = 'sound/items/weapons/tap.ogg'
hal_hitsound_wall = 'sound/items/weapons/effects/searwall.ogg'
hal_impact_effect = "impact_laser_blue"
hal_impact_effect_wall = null
hit_duration = 4
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
ricochets_max = 50
ricochet_chance = 80
reflectable = TRUE
/obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.adjust_stamina_loss(30)
/obj/projectile/hallucination/ebow
name = "bolt"
damage_type = TOX
hal_icon_state = "cbbolt"
hal_fire_sound = 'sound/items/weapons/genhit.ogg'
hal_hitsound = null
hal_hitsound_wall = null
hal_impact_effect = null
hal_impact_effect_wall = null
/obj/projectile/hallucination/ebow/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.adjust_slurring(10 SECONDS)
afflicted.Knockdown(1 SECONDS)
afflicted.adjust_stamina_loss(75) // 60 stam + 15 tox
afflicted.adjust_eye_blur(20 SECONDS)
/obj/projectile/hallucination/change
name = "bolt of change"
damage_type = BURN
hal_icon_state = "ice_1"
hal_fire_sound = 'sound/effects/magic/staff_change.ogg'
hal_hitsound = null
hal_hitsound_wall = null
hal_impact_effect = null
hal_impact_effect_wall = null
/obj/projectile/hallucination/change/apply_effect_to_hallucinator(mob/living/afflicted)
// Future idea: Make it so any other mob hit appear to be polymorphed to the hallucinator
afflicted.cause_hallucination( \
get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset), \
"fake [name]", \
duration = 30 SECONDS, \
affects_us = TRUE, \
affects_others = FALSE, \
skip_nearby = FALSE, \
play_wabbajack = TRUE, \
)
/obj/projectile/hallucination/death
name = "bolt of death"
damage_type = BURN
hal_icon_state = "pulse1_bl"
hal_fire_sound = 'sound/effects/magic/wandodeath.ogg'
hal_hitsound = null
hal_hitsound_wall = null
hal_impact_effect = null
hal_impact_effect_wall = null
/obj/projectile/hallucination/death/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.cause_hallucination(/datum/hallucination/death, "fake [name]")