Proposal: Map "macros" for control sector reuse
Please, bear with this, at least the stuff under the first heading: I know it's wordy, but it's hard to explain in fewer words.
Basic Problem and Proposal
I'm sure anyone making complicated maps has plenty of experience with the problem of control sector duplication. In the way of example, suppose you are making a new Mario-themed zone: you have these pipes that, when you go down them, they teleport you to another pipe. They have multiple half-light blocks to make it darker inside, and there is a FOF for the rim of the pipe. That's easily five control sectors with at least three tags and a thing, conservative estimate. And you want these sprinkled around the world. Each time, you'll have to copy all of those, re-tag them, and update floor/ceiling heights. Heck, even UDMF doesn't help you here. Good luck if you need to add a new control sector or linedef to every pipe. I hate this, so I've devoted a lot of thought to it.
Here's the proposal: In programming, to remove code duplication, we'd use functions or macros. A macro is a name with parameters, and each time you use it, it expands, incorporating the parameters. That's the basic idea, but with level geometry. You set up a template with level geometry (that would be your rim FOF, half-light blocks, and teleportation executor and thing), and each time you want to make a pipe, you draw the circles for the pipe and then place a "Expand Macro" linedef. At runtime, SRB2 builds a list of macros, and then every time it finds an "Expand Macro" linedef, it copies the template geometry and replaces the parameters with floor/ceiling height, generates tags, etc. Therefore, a complicated and error-prone copy-paste-change operation has been replaced with a place-a-single-linedef operation.
I'm not proposing this idly; I have a close to working implementation in https://git.do.srb2.org/v-rob/SRB2/-/tree/macros. I'm willing to do all the work for this feature, but I want a "concept approval" before I put any more work into this as a way of making sure I don't do work that will be refused or miss glaring holes in my idea.
Advanced, knitty-gritty detail
In the mostly-done implementation mentioned above, macros are implemented for binary maps. However, it's definitely more suited to UDMF. With the liberty of custom fields, I wouldn't have to shove this complex idea into a bunch of integer fields and 8-character texture names. However, I'll detail my binary implementation since it's what I have thought out in great detail.
Now for the user-facing features. There are three distinct parts of a macro:
- Body: The geometry that will be copied every time and the parameters applied to.
- Definition: Defines the tag of the macro, collects parameters, and generates tags.
- Expander: Tells the macro to be copied and provides the parameters.
An example should be the best way to explain macros. Consider the following map:
The wood is the body, the dirt is the definition, and the green lines above the cave rock are expanders.
Definition
The line that starts the body is "Define Macro". The tag of the macro is set to 1. All connected linedefs are part of the definition.
The second line, "Macro Parameters - Sidedef" gets the parameters. The front X and Y offsets are set to 30000 and 30001, respectively, so it will take the expander's front sidedef's X and Y offsets and stores them as the parameters 0 and 1. The back X offset is set to 30002, so it stores the index of the sidedef's sector into parameter 2.
The fourth line, "Macro Variables - New Tags" creates new tags. The front X offset is set to 30003, so a new tag will be generated and stored in parameter 3.
Define Macro | Macro Parameters - Sidedef | Macro Variables - New Tags |
---|---|---|
Body
When told to expand, the definition searches for all sectors with the macro tag. In our case, that's 1. The only sector with tag 1 is the FOF, so that sector and all its linedefs, sidedefs, and vertices will be copied.
In the sector, floor height and ceiling height are set to 30001 and 30000, respectively. That means that, when the geometry is copied, those fields will be replaced with the values of parameters 30001 and 30000.
On the control linedef, the tag is set to 30003, so it will tag to the automatically generated tag in parameter 3.
The last linedef is 96, an existing linedef special in SRB2: "Apply Tag to Other Sectors". It applies the tag in 30003 to sector 30002, i.e. applies the automatically generated tag to the expander's front sector.
Control Linedef | Apply Tag to Other Sectors |
---|---|
Expanders
Now that we have a macro all set up, we can see how extraordinarily fast it is to use it now.
Both expanders have a tag of 1, saying to expand our macro we just defined. The first expander has front X offsets 128 and 160, which are put into parameters 0 and 1, which are applied to the FOF as floor and ceiling height, respectively. The second expander has different offsets, 256 and 288, which will make another FOF with those for the heights.
End Result
I can't show you a screenshot in game since the implementation above is, again, not fully functional. However, this map now has two separate FOFs at two separate heights. Besides tag 1 for the macro, you don't even have to come up with new tags in this example because it's done in the macro! (You might need to work with tags directly in the expander in more complicated setups or with multitagging, but still.) A well-designed macro will allow our hypothetical Mario pipes to exist with almost no trouble.
My implementation has many more features, such as calculations, proxies for editing in-level geometry, and parameters of all types of geometry. However, that would take to long to explain here and aren't really relevant to the crux of the proposal.
Caveats
Well, there had to be some :) Firstly, the mechanism is as simple as stated: level geometry is copied at runtime. The main caveat to this modus operandi is that it happens after nodebuilding. Secondly, level geometry is copied in-place, which means that you have linedef, vertices, and sectors directly on top of each other. I have not done extensive testing on the effects of this; however, nodebuilding is only really important for rendering and collision. Since macros are entirely meant for control sectors, there is no need for them to be rendered or collided with, so if they simply aren't, it's not a problem. Ditto for them being on top of each other.
Additionally, this implementation isn't really simple. It would be simpler (and nicer to use!) for UDMF, but still not simple. So, it's a fair bit of code, plus the fact that it would be difficult for a map editor to fully support this (such as in FOF rendering if the FOF comes from a macro, and being able to edit the FOF's properties in-place).
Fin
That is the proposal. Please, feedback is totally welcome -- criticism, praise, mild interest or disgust, tearing it apart because it's terrible, whatever. I'm throwing this out here to see whether it's something I should work on, so please tell me.