## About The Pull Request After months of preparation, and further months of work, I am finally done. Please bear with me, as this is a massive refactor, but I have already atomized everything I could. This is now ready for review. General - The hilbert hotel slimes are now a subtype instead of a varedit. - The `use_mob_ability` subtree now also accepts non cooldown abilities. If set_behaviours is set up properly, mobs won't keep continously triggering it as if it were a 0 second cooldown action. The alternative would have been turning the slime abilities into cooldown abilities. - Wrestling off a slime now signs up to the `COMSIG_ATOM_ATTACK_HAND` signal, instead of being part of attack_hand. - Adds datum/ai_controller/controller as a fourth, optional argument to `/datum/ai_behavior/find_hunt_target/valid_dinner()` to make it possible to access blackboard keys. - Slimes no longer attack windows if they would accidentally move into them (when the conditions are met), since random walk behaviour ignores tiles they can't go in. It was also not worth to keep. Did you know this was the sole override of `ObjBump()`? - Examine was made less snowflaky/bespoke. Also added a new element: `/datum/element/basic_health_examine`, which is a simple bespoke element that prints out a custom message based on how damaged the basic mob it is attached to is. - Slimes only perform knockdown instead of paralysis, as they can attack more often now, and paralysis is not that fun. - LAssailant has been removed due being archaic code. To befriend a slime, you have to spawn a monkey with the slime console, or feed them a sheet of plasma. Simple grabbing the monkey or stuffing them in disposals do not work anymore. Slime console spawned monkeys will have a visible status effect, with pheromones coming off them to make this clearer. Actions - Feeding, reproduction and evolution is no longer a verb. - Slime feeding is no longer an action button. You have to use right click, or as previously, mousedrop. Slimes can always unbuckle from mobs they are attached to. Hunger - Instead randomly changing the starvation and max nutrition values while growing up, evolution costs 200 nutrion. This makes the code more readable, and behaviour more predictable, while still giving the intended time between evolving and splitting. As a result, I could also turn these into defines. - Added a component that handles doing an effect over time while buckled to a mob, until the mob dies or you get unbuckled. - Slimes gained nutrition is no longer randomly multiplied by the damage config value, but rather gain nutrition equal to twice the damage dealt. You'll have to eat one monkey to evolve, just as before. - Slimes do not heal passively. They only heal from eating. It was a rather miniscule value that did not have much effect. - Slimes generate electricity from hunger threshold, instead of the random amount of hunger threshold + 100. Environment - Slimes take 15 damage from cold every second, instead of using a complex formula (that also decreased the damage up to a point?). - Slimes still heal from burn damage, but this is now set on the damage coefficient list. - Slimes instead of getting stunned by the cold, freeze in an ice cube. BZ instead of setting them unconscious, calls the stasis status effect, allowing you to safely stash your hungry slimes for later. They also no longer slow down from the cold, as they are already slowed down by the damage they get. Conversely they no longer get a speed up from a random amount of temperature. I could be convinced to readd this either as part of the basic sensitive component, or a similar one. AI - Removed the attacked_stacks system. Slimes will just perform regular retaliation if you hit them in a harmful manner. - Slimes now use the pet orders component. They will interrupt their feeding when given a command by their master. - Slimes have their own subtrees. I tried to replicate as much as I could from the old code, dividing ancient code artifacts and intentional stuff, so there might be some weirdness. - Slime speech has been almost fully reduced to basic blorbing, as you can not even understand them anymore, and most of them require the slime to loop through all of their surroundings. - Discipline does not have stacks either. Disciplined baby slimes have a chance to clear their attack and hunt blackboard keys. All slimes will stop feeding on the target otherwise. - Since discipline is not a stack, rabidity instead gets removed at a 10% chance per disciplining. - Slimes faces are a bit more randomly picked now. ## Why It's Good For The Game - We want to convert all simple animals to basic mobs. Old slime code was also very strange, and had some systems that have been replicated by components. - Slimes fully paralyzing you is not fun at all. Knockdown should give you a fighting chance when a slime would like to eat you. - Slimes slow down from the heavy damage they get from the cold, so I don't think they need extra slowdown, nor do they need to speed up from warmth, as they are already fast. - Slimes turning into an icecube instead of becoming paralyzed from the cold is more fun for the slimes, as they can break out for a few moments. It is also funny. - Slimes entering proper stasis from BZ is not just a visual indicator of a slime that is safe to approach, but also keeps the slimes's hunger value in check, allowing it to not starve while stopped. They can also look around and blorble, instead of staring at a black screen, if player controlled. - The attack_stack and discipline_stack behaviours were rather overcomplicated, and the xenobio mains I talked with didn't even know it was a thing, so I argue it needed simplification. - The bespoke friendship system of slimes was also too complicated. Slimes slowly gained levels of trust, and at certain levels commands costed friendship, and other levels, they did not. The binary friend/not friend system that everything else in the game uses is much more sensible. - Using right click for feeding is much more sensible than using an action, and then picking someone from a dropdown. - Slime speech was very soulful but not only did it loop through everything in sight, you couldn't even understand it unless you spoke slime. Maybe it can be readded later in a different form. - Slime's passive healing was miniscule, and having them rely on feeding is more interesting. also fixes #81463 ## Changelog 🆑 refactor: Slimes are now basic mobs. Please report any strange behaviours! balance: Slimes only stun you for two seconds when they shock you, the rest of the duration is a knockdown. balance: Slimes are not stunned from the cold, but rather, get frozen in a freon icecube. BZ also puts them in complete stasis, instead of making them unconscious. Their speed is likewise unchanged by temperatures. balance: Slimes do not passively heal, they instead rely on feeding. fix: Slimes can use the buckling screen alert to unbuckle and stop feeding, along with clicking on the mob they are riding /🆑
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/maintainence/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/maintainence/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.