Lunaran: the term "clever way" would probably be too generous. This is an ugly problem, and there doesn't seem to be a silver bullet.
My goal has ended up being to create a single QC file,
compat.qc, which could (in theory) be added to any existing mod, and which provides the tools to implement an extremely light "compatibility mode" as painlessly as possible.
Because my target is "any existing mod", I can't assume that the mod's mapping interface can be changed, so an opt-in worldspawn key is a no-go.
I should stress that I
do not assume this module is something you will necessarily want to add to Copper. I have simply used Copper as a test case while implementing it. I'm presenting it as something of possible interest.
How it works:
ENABLING / DISABLING "COMPATIBILITY MODE"
By default,
compat.qc will automagically enable the compatibility mode for the stock id1 maps, but not for any other maps. This default behaviour can also be overridden via console commands, allowing a player to play non-stock vanilla maps (which aren't auto-detected) in compatibility mode if they really want to.
It turns out that it's really easy to detect whether one of the stock id1 maps is being played. They all have known file names, so compat.qc checks "world.model" once, on map start, and enables compatibility mode if the file name is one of the stock maps. It's common for mods to provide a "start.bsp" that shadows the stock one (and UDOB does this with "end.bsp" too), but that can be distinguished by checking some other field as well (e.g. "world.message").
This alone makes it feasible to address compatibility with the stock maps, which seems like a big win.
But we've also 20+ years of custom vanilla maps that a player might want to play with whatever mod we're talking about. They can't all be auto-detected.
As a better-than-nothing workaround, compat.qc comes with a config file,
compat.cfg, which a mod can load from its quake.rc file. This defines console commands allowing the player to override the default behaviour:
compat_auto: restores the default behaviour.
compat_always: always enables compatibility mode on map start, allowing custom vanilla maps to be played. (This will break maps that are actually made for the mod, and the command warns the player about that, loudly.)
compat_never: always disables compatibility mode on map start. Not really useful for the player, just here for debugging.
Given my self-imposed restriction of "should work with any mod", I think this is the best that can be done, though I'd love to hear of anything better.
MAKING "COMPATIBILITY MODE" DO THINGS
compat.qc manages the compatibility mode, but by default, the mode doesn't actually
do anything. To make it do things, functions are provided that can be called to sanitize fields (and spawnflags) only if compatibility mode is in effect.
So to fix the func_train problem that affects e3m3 (and e4m4, apparently), only a single line needs to be added near the top of the func_train spawn function:
COMPAT_SanitizeFloat(wait, "wait");
That's it. If compatibility mode is in effect, the "wait" field will be cleared, and a developer message logged. If compatibility mode is not in effect, this is a no-op.
Screenshot:
http://www.quaketastic.com/files/iw_copper_patch_20190702.png
There are equivalent
COMPAT_SanitizeVector and
COMPAT_SanitizeString functions, and also
COMPAT_SanitizeSpawnflag (some entities have rogue spawnflags set).
A mod using this doesn't need to foul up its code with "if/else" wickedness.
THE CODE
Lunaran: as I said above, I
do not assume this is something you would definitely want to add to Copper. But if you're curious, a patch implementing this is here:
http://www.quaketastic.com/files/iw_copper_patch_20190702.zip
Changes to existing files are marked with "// iw" comments.
The following things are sanitized when in compatibility mode:
- func_train: "wait" field (affects e3m3, e4m4)
- path_corner: "speed" field (affects "Contract Revoked" maps)
- All non-boss monsters: all new fields and spawnflags (just to demonstrate how easy this becomes)
- In general: the "COOPONLY" and "NOTCOOP" spawnflags (ditto)
In principle, one
could go through every spawn function of every stock entity and sanitize every new field and spawnflag that the mod adds, to pre-emptively fix any as-yet-undiscovered problems. I started doing that, but then I realised
how many extensions you've added to the stock entities, so I stopped. =)