Files
ArrisFairburne e9061e8b19 Blacksmith Revamp (#5360)
## About The Pull Request

Makes a number of changes to blacksmithing to make the role more
appealing, convenient, and special:

<details>
<summary>New Additions</summary>

- [ ] Added a new blacksmith recipe that requires legendary skill and
the skillchip: parts for a classical single-action revolver. High
damage, but finnicky to use; no safety mechanism, can't accept speed
loaders, and the hammer has to be cocked before each shot. Cocking the
hammer is faster if both hands are free, allowing for fanning the
hammer.
- [ ] Smiths can now make new storage options: a cowboy holster that
stores a handgun and multiple ammo boxes, a charging belt that recharges
one energy gun, a botany bag with expanded storage, and multiple types
of scabbards.
- [ ] Added a new skillchip specific to blacksmithing; without the
skillchip, your options on what you can produce via smithing are
reduced. This is to make it so that the smithing job role (or at least,
whoever has been given the skillchip from the QM's locker) is more
special. Currently smithing is just sort of something anyone can do, the
only things unique about spawning in as the blacksmith is that you have
cargo access and smithing-specific bounties.
- [ ] Replaced the water barrel with a new structure: the reagent
quenching trough. This spawns in filled with a new reagent, quenching
oil. Quenching a smithing item in the trough with any reagent will imbue
it for zero reagent use, and heat the trough's reagent.
</details>

<details>
<summary>General direction changes</summary>

- [ ] Perfect hits are no longer probability-dependant; they're based on
the player's timing. Except if the player is max level at blacksmithing,
in which case all hits are perfects.
- [ ] Right-clicking with a hammer on the anvil will allow the player to
automatically forge the item they're working on, with a slower pace
compared to manual forging. This is mostly meant to allow talking to
someone while working on forges.
- [ ] Removed multi-anvil smithing; blacksmithing lacks a dedicated
space on most maps and so must occupy a hallway or something else
instead. "Playing optimally" with multi-anvil smithing means taking up
significantly more space, in addition to looking extremely silly and
bypassing part of the loop with blacksmithing (levelling up the smithing
skills to produce equipment faster); the smithing chip will reduce the
swings needed to finish a job to compensate.
- [ ] Reworked reagent imbued equipment; removed the bonus from leaving
a weapon unimbued. This bonus can now be regained specifically by
imbuing smithing oil (smithing oil is special; imbuing it doesn't
inflict it as a reagent for effects, but makes the armor/weapon/etc.
better by numbers). Also, reagent imbued equipment no longer has a big
damage multiplier but will merely stop applying reagents after a certain
integrity threshold is passed.
- [ ] Significantly reduced the income from smithing cargo bounties. It
honestly kind of sucks that blacksmiths' value to the station is that
they print money and not for the actual quality of their items.
- [ ] Blacksmiths now have weapon access by default. It's honestly kind
of weird that the guy whose whole schtick is making weapons, didn't have
weapon access to start.
- [ ] Moved the crusader belt from the general crafting menu to the
smithing crafting menu; it now requires lv7 smithing and the smithing
chip. This change is because it's highly contingent on blacksmithing
equipment for its main storage so it seems most sensible for a smith to
work it.
- [ ] The leather apron that blacksmiths roundstart with now has
dedicated storage for various smithing and glassblowing tools.
</details>
<details>
<summary>Interactivity changes</summary>

- [ ] Trying to pick up a heated blacksmith item will now burn your
hand.
- [ ] Various quenched smithing items now have associated force values
for dealing damage, but will also (usually) cut the user's hand for
doing so.
- [ ] You can now use water tiles or certain reagent containers (needs
at least 300 units) to quench smithing equipment.
- [ ] The forge now heats up it's surroundings and burns carbon dioxide
while doing so. The wall-mounted heater now auto charges it's cell to
compensate for this.
- [ ] You can now weapon-bash your blacksmithing shields, similar to how
you can slap your baton against your riot shields to intimidate.
</details>

<details>
<summary>Convenience and misc.</summary>

- [ ] Changed the Rapier sprites to match the other smithing weapon
sprites.
- [ ] Added more tutorial tooltips when inspecting blacksmith equipment.
- [ ] The blacksmithing noises are quieter, thank god.
- [ ] Using tongs on a stack now pulls only one item from the stack,
instead of pulling all items in the stack. You never actually want to
have more than one sheet in the tongs so there's no reason to include it
really.
- [ ] Completed blacksmith items can now be picked up with tongs and
placed into the crafting bench with them. This is meant to reduce the
tool-juggling for moving the item from the anvil to the basin, then
moving the item from the basin to the bench.
- [ ] The blacksmith ID trim now has an actual icon 
- [ ] Commented out the cortical borer cage recipe. We don't really need
this since we don't run cortical borers.
- [ ] Halved the number of necessary chains and plates for all recipes,
but doubled the time needed to hammer them; this is to reduce how much
item juggling is needed.
- [ ] Cargo guards and cargo secoffs now have blacksmithing access.
- [ ] Ensure that Ashwalkers, Primal Podpeople, and feral nekos have all
the same mechanics as a skillchipped crewie, except cannot make the
revolver
</details>

<details>
<summary>Code refactoring</summary>

- [ ] The reagent clothing and reagent weapon components now come from a
general reagent imbued parent component.
- [ ] Refactored how anvil smithing works; it now relies on an item
component that handles item heat, hammer strikes, and quenching, instead
of all the code being handled within the anvil itself.
- [ ] Removed the skyrat_obj_flags variable that only functioned for the
anvil_repair flag and nothing else. We have different ways to check for
the stuff it relied on so it's not really needed.
- [ ] Removed the in_use code from various forging objects. It's a
really cumbersome system and annoying to manage all the time. Doafters
with interaction_key stand as a replacement.
</details>

Overall, in the hands of blacksmiths, smithing-chipped characters, and
the tribal factions, this should feel like a general buff. For everyone
else, this should feel like a nerf, and they should go to the blacksmith
or get skillchipped so they can make smithing stuff. It should also feel
less 'shallow' and a bit more interactive with the rest of the game.

## Proof Of Testing
<details>
<summary>Screenshots/Videos</summary>

<img width="180" height="108" alt="image"
src="https://github.com/user-attachments/assets/fff5bf67-2f6b-42e6-b8a6-920b5ef8bf26"
/>

<img width="399" height="52" alt="image"
src="https://github.com/user-attachments/assets/f7a1f395-d060-49ad-848f-52d0e4303177"
/>

https://youtu.be/hcPZXzCOSIU

https://youtu.be/V7GeLetcRtk

https://youtu.be/YEyW9pKidTU

</details>

## Changelog
🆑
add: Way, way too many blacksmith job features & smithing system changes
to list. But here's the highlights:
balance: Forged blacksmith weapons can now be used nonlethally via
striking with pommels, spear shafts, etc. which knocks down victims and
stamina damages them like a weaker baton.
qol: Blacksmith tongs now only pull one item at a time, if used on a
stackable item.
qol: Blacksmiths can now auto-smith at an anvil by right clicking with a
hammer.
qol: Blacksmithing tools now have more tooltips to tutorialize their
usage.
qol: Blacksmithing crafting bench can now store item stacks, forging
plates, and forging chains. Right-click to view what's in it.
balance: Removed multi-anvil smithing. Individual smithing pieces can
now be made faster with the smithing chip and other means to compensate.
add: Blacksmiths can now build a single action revolver if they are
fully levelled.
add: Blacksmiths can build a variety of new equipment at the crafting
bench -- gun-charging holsters, knife belts, multiple types of
scabbards, and even a new expanded-storage botany bag.
add: Replaced the water barrel for smithing with a barrel of smithing
oil; smithing oil is now used to grant the bonuses that was previously
given by leaving the item unimbued. The new barrel can have its contents
changed for reagent imbuing.
add: A new reagent "smithing oil" which gives innate bonuses to any
blacksmith equipment imbued using it.
balance: Material types now affect the quality of resulting items and
how quickly they can be forged.
balance: The reagent smithing hammer now has increased toolspeed if
forged well.
balance: Reagent imbuing's durability penalty has been altered; it
reduces durability for each time an imbue effect procs, and then stops
functioning for such when below a certain durability percentage.
/🆑

---------

Co-authored-by: Mathilde <146444134+ibexgoetia@users.noreply.github.com>
Co-authored-by: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
2026-07-03 15:05:07 -04:00
..
2026-07-03 15:05:07 -04:00

UpdatePaths

How To Use:

Drag one of the scripts in the “Scripts” folder onto the .bat file “Update Paths” to open it with the .bat file (or use the Python script directly depending on your operating system). Let the script run to completion.

Use this tool before using MapMerge2 or opening the map in an map editor. This is because the map editor may discard any unknown paths not found in the /tg/station environment (or what it builds after parsing tgstation.dme).

Scriptmaking:

This tool updates paths in the game to new paths. For instance:

If you have a path labeled /obj/structure/door/airlock/science/closed/rd and wanted it to be /obj/structure/door/airlock/science/rd/closed, this tool would update it for you! This is extremely helpful if you want to be nice to people who have to resolve merge conflicts from the PRs that you make updating these areas.


How do I do it?

Simply create a .TXT file and type this on a line:

Tried and True - Part One

/obj/structure/door/airlock/science/closed/rd : /obj/structure/door/airlock/science/rd/closed{@OLD}

The path on the left is the old, the path on the right is the new. It is seperated by a ":" If you want to make multiple path changes in one script, simply add more changes on new lines.

Putting {@OLD} is important since otherwise, UpdatePaths will automatically discard the old variables attached to the old path. Adding {@OLD} to the right-hand side will ensure that every single variable from the old path will be applied to the new path.

You'll want to save your .TXT file with a name that is descriptive of what it does, as well as the associated PR Number to your PR. So, it would look like PRNUMBER_RD_AIRLOCK_REPATH.txt. Both of these are for book-keeping purposes, so that intent is clear to anyone who looks at the file. They can also easily reference the PR number that the script was made in to determine why it was made, and if it is still needed.


What does it look like?

Alright, so we've already made the script. So, let's say we have this example map key in the TGM Format.

Tried and True - Part Two

"a" = (
/obj/structure/door/airlock/science/closed/rd{
	dir = 4;
	name = "RD Airlock"
	},
/turf/open/floor/iron,
/area/science/rd),

Now, after you drag and drop your script onto the Update Paths.bat file, it will look like this:

"a" = (
/obj/structure/door/airlock/science/rd/closed{
	dir = 4;
	name = "RD Airlock"
	},
/turf/open/floor/iron,
/area/science/rd),

It worked! Great!


On Variable Editing

If you do not want any variable edits to carry over, you can simply skip adding the {@OLD} tag. This will make the script change the path, and discard all variables associated to the old path. So, continuing with the same example mentioned above, lets run the following script:

Discarding Old Variables

/obj/structure/door/airlock/science/closed/rd : /obj/structure/door/airlock/science/rd/closed

On this example map key:

"a" = (
/obj/structure/door/airlock/science/rd/closed{
	dir = 4;
	name = "RD Airlock"
	},
/turf/open/floor/iron,
/area/science/rd),

You will then result the following:

"a" = (
/obj/structure/door/airlock/science/closed/rd,
/turf/open/floor/iron,
/area/science/rd),

As expected, all variables were discarded. This is only really useful in certain edgecases, and you shouldn't do something like this trivially in case someone has lovably named a variable special since it'll just nuke it.

There are also a bunch of neat features you can use with UpdatePaths variable filtering, with it all documented here: https://github.com/tgstation/tgstation/blob/master/tools/UpdatePaths/__main__.py#L9. However, let's spin it all out for you here as well:

Deleting Entire Paths

Alright, you did a large refactor and you got rid of some shoddy paths. Great! So, let's make a script to delete that old path from all of our map files. Let's say we want to delete /mob/living/deez_nuts. We can do that by simply adding the following to our script:

/mob/living/deez_nuts : @DELETE

So, now when you have the following example map keys:

"a" = (
/turf/open/floor/carpet,
/area/meme_zone),
"b" = (
/mob/living/deez_nuts{
	goteem = 1;
	desc = "these jokes are still funny"
	},
/turf/open/floor/carpet,
/area/meme_zone),

And you run the script, you will get the following:

"a" = (
/turf/open/floor/carpet,
/area/meme_zone),

Presto, like it never existed. Note how both the "a" and "b" files were able to combine into the same dictionary key, since the "b" key was deleted entirely, and since "a" and 'b" now matched, UpdatePaths was able to just clean that up for you. It'll also update the map itself to reflect this as well. Now that is something your Search & Replace tool can't do!

Multiple Path Output

UpdatePaths has the powerful ability to output multiple paths from a single input path. Let's say that you have a snowflake turf (/turf/open/floor/iron/i_like_spawning_mobs) with some behavior that you atomize out into some spawner /obj/mob_spawner that can work on every single turf. So, let's script that out.

/turf/open/floor/iron/i_like_spawning_mobs : /obj/mob_spawner, /turf/open/floor/iron

So, now when you have the following example map keys:

"a" = (
/turf/open/floor/iron/i_like_spawning_mobs,
/area/station/kitchen),

Running the script will mutate this into:

"a" = (
/obj/mob_spawner,
/turf/open/floor/iron,
/area/station/kitchen),

Remember that this is a kind of silly example, but this is one of the things that UpdatePaths was built to do- help coders fix shitty code without having to bug out over how maps don't compile.

Subtype Handling

This is one of UpdatePaths' more recent features. It allows you to specify a generic base path that you've done a major refactor on, and then easily specify the gamut of subtypes you want to swap it to. Let's say you have a /obj/item/weapon/big_chungus base path that you want to refactor to /obj/item/big_chungus. However, you also have subtypes like /obj/item/weapon/big_chungus/funny, /obj/item/weapon/big_chungus/really_large, etc. You can do that by simply adding the following to your script:

/obj/item/weapon/big_chungus/@SUBTYPES : /obj/item/big_chungus/@SUBTYPES{@OLD}

So, let's assume we have the following map file:

"a" = (
/obj/item/weapon/big_chungus,
/obj/item/weapon/big_chungus/funny{
	name = "funny big chungus"
	},
/obj/item/weapon/big_chungus/really_large{
	name = "really large big chungus"
	},
/turf/open/floor/iron,
/area/station/maintenance/fore/greater),

Running the script will update this into:

"a" = (
/obj/item/big_chungus,
/obj/item/big_chungus/funny{
	name = "funny big chungus"
	},
/obj/item/big_chungus/really_large{
	name = "really large big chungus"
	},
/turf/open/floor/iron,
/area/station/maintenance/fore/greater),

Note how since you kept in {@OLD}, it was able to retain the re-named variables of the subtypes.

Old Path Variable Filtering

Alright, there's a few subsections here. This is how you are able to filter out old paths to ensure you target something precise. Let's just go through them one by one.

Property Filtration (feat. @SKIP)

Method: Open Mind To All Possibilities

Alright, you saw something cool in a map that you wanted to expand upon codeside. So, you make the new path /mob/living/basic/mouse/tom with all sorts of nice behavior. However, you don't want to just replace all of the old /mob/living/basic/mouse paths with the new one, you want to only replace the ones that have a name variable of "Tom". You can do that by simply adding the following to your script:

/mob/living/basic/mouse{name="Tom"} : /mob/living/basic/mouse/tom{@OLD;name=@SKIP}

In this test example, you already set the name of the Mob to "Tom", so you don't need to worry about that, so first you'll insert @OLD, because you want to retain all the other variables, and then add @SKIP in order to skip adding that variable to the new path. Its important that '@OLD' goes before '@SKIP', otherwise the script won't see the variables to skip and will just keep all of them anyway. So, let's assume we have the following map file:

"a" = (
/mob/living/basic/mouse{
	name = "Tom";
	desc = "A mouse named Tom";
	pixel_x = 12
	},
/mob/living/basic/mouse{
	name = "Tina";
	pixel_x = -12
	},
/turf/open/floor/iron,
/area/station/prison),

Running the script will update this into:

"a" = (
/mob/living/basic/mouse/tom{
	desc = "A mouse named Tom";
	pixel_x = 12
	},
/mob/living/basic/mouse{
	name = "Tina";
	pixel_x = -12
	},
/turf/open/floor/iron,
/area/station/prison),

Notice how since you @SKIP'd the name, it doesn't need to re-apply itself, and since you added (the global) @OLD, it was able to keep the desc and pixel_x variable. Also, cute little mouse named Tina goes unfazed through this change.


Method: I Don't Care About Soulful Var-Edits

That's cool, but let's say you have this same example, but let's say that you don't want to carry over the desc variable either (because you did that code-side). In fact, you don't want to carry over any variables beyond the pixel_x. You can choose to only copy over one variable with the following script entry:

/mob/living/basic/mouse{name="Tom"} : /mob/living/basic/mouse/tom{pixel_x = @OLD}

The following is also supported, but it's not recommended since it's harder to read because it doesn't really mesh with the TGM format:

/mob/living/basic/mouse{name="Tom"} : /mob/living/basic/mouse/tom{@OLD:pixel_x}

So, let's assume we have the following map file:

"a" = (
/mob/living/basic/mouse{
	name = "Tom";
	desc = "A mouse named Tom";
	pixel_x = -12
	},
/mob/living/basic/mouse{
	name = "Tina";
	pixel_x = 12
	},
/turf/open/floor/iron,
/area/station/prison),

You would then get the following output:

"a" = (
/mob/living/basic/mouse/tom{
	pixel_x = -12
	},
/mob/living/basic/mouse{
	name = "Tina";
	pixel_x = 12
	},
/turf/open/floor/iron,
/area/station/prison),

As you would have wished, only the pixel_x variable copied through. This is pretty constraining and might not match up to certain needs of the repository (or other repositories), so recommend using the first example when possible.

Method: Keep All The Soul!

Okay, let's say that you want to change all instances of /obj/structure/sink that have dir=2 to dir=1 for a laugh. However, there's an issue. You see, 2 is SOUTH in DM directions, (1 is NORTH), and code-side, /obj/structure/sink has dir = 2 by default and doesn't show up in the map editor. You would have to do something like this:

/obj/structure/sink{@UNSET} : /obj/structure/sink{dir=1}

@UNSET will only apply to the change to paths that do not have any variable edits. So, let's assume we have the following map file:

"a" = (
/obj/structure/sink,
/turf/open/floor/iron,
/area/station/bathroom),
"b" = (
/obj/structure/sink{
	dir = 8
	name = "Money Hole"
	},
/turf/open/floor/iron,
/area/station/bathroom),

You would then get the following output:

"a" = (
/obj/structure/sink{
	dir = 1
	},
/turf/open/floor/iron,
/area/station/bathroom),
"b" = (
/obj/structure/sink{
	dir = 8
	name = "Money Hole"
	},
/turf/open/floor/iron,
/area/station/bathroom),

Note how we keep the "Money Hole" intact, while still managing to extrapolate the dir variable to 1 on the sink that had absolutely no variables set on it. This is useful for when you want to change a variable that is not shown in the map editor, but you want to keep the rest of the variables intact.

Methods: Any Value Fits All and Naming Conventions

But what if you just want to rename the variable maxHealth to good_boy_points for all instances of /mob/living/github_user? Using the @ANY parameter after a variable name, you can capture any instance that has it edited in a map. While, to set the value of the newly named good_boy_points to that of the old maxHealth, we can use @OLD:maxHealth, put after the name of the new variable to achieve that. The result'll be something like this:

/mob/living/github_user{maxHealth=@ANY} : /mob/living/github_user{good_boy_points=@OLD:maxHealth}

Though, If you read about the previous methods, you'd know that without the @OLD parameter (the one without colon), every other variable edit will also be discarded, so it's important to add that BEFORE any other parament, as well as maxHealth=@SKIP following that since we're renaming that variable. So, take two:

/mob/living/github_user{maxHealth=@ANY} : /mob/living/github_user{@OLD; maxHealth=@SKIP; good_boy_points=@OLD:maxHealth}

Perfect, so now let's assume the following map:

"a" = (
/mob/living/basic/mouse{
	maxHealth = 15
	},
/turf/open/floor/iron,
/area/github),
"b" = (
/mob/living/github_user{
	name = "ShizCalev";
	desc= "Has more good boy points than a megafauna has health.";
	maxHealth = 2083
	},
/turf/open/floor/iron,
/area/github),

You would then get the following output:

"a" = (
/mob/living/basic/mouse{
	maxHealth = 15
	},
/turf/open/floor/iron,
/area/github),
"b" = (
/mob/living/github_user{
	name = "ShizCalev";
	desc= "Has more good boy points than a megafauna has health.";
	good_boy_points = 2083
	},
/turf/open/floor/iron,
/area/github),

As an addendum, you don't have to use both @ANY and @OLD:prop_name together. I'm merely providing a single example for the both of them and their most practical usage.

Blend it all together

All of the examples provided within are not mutually exclusive! They can be mixed-and-matched in several ways (old scripts might have a few good examples of these), and the only limit here is your imagination. You can do some very powerful things with UpdatePaths, with your scripts lasting for years to come.

Why should I care?

UpdatePaths is an incredible valuable tool to the following populations:

  • Mappers who have mapping PRs that take a long time to create, and that will need to be updated as progression goes on. Having an UpdatePaths file makes it much more simple to get them to compile their map properly, and not lose paths.
  • Downstreams who have additional maps to the ones we have. You obviously can't Search & Replace fix for a whole downstream, but you can give them the ammunition (UpdatePaths script) for them to quickly and easily resolve that problem.
  • You! As you've seen, you can do a lot of clever and powerful tools that respect the TGM format, from Old Path Filtering to Multiple Path Output- and you can do it all with a simple text file! Otherwise, you would be stuck in absolute RegEx hell, and still end up missing on several potential edge cases. UpdatePaths is built on the same framework that builds the TGM format, so it's incredibly reliable in finding and replacing paths.