About this page
I don't expect anybody to read this sub-page, but it goes into a little more detail about how things work in my projects. This page isn't yet finished because I deemed it far less important than the download page. It will be done when I feel like it.
Table Of Contents
On This Page
> New Invasion I Technical Background
> DukeMark Technical Background
> SWMD Technical Background
Download Page
> New Invasion I
> DukeMark
> SWMD
New Invasion I Technical Background
Nothing in this add-on uses new code. While CON scripts are included, these serve only to change the level titles in the game and alter one quote. As for technical details, I'll only cover them in brief as they're complex and really, require their own videos. Some tricks do have their own already, on my XeFF Plays YouTube channel.
Level 1 aimed to play like a fairly normal Duke Nukem 3D level and held back on what it could do. It does make an attempt to detect bad ports of the game when starting up, however, by launching a babe into a tripmine using a tea tray. If this doesn't work, the door of the starting room is locked and a message appears, prompting the player to use a switch to continue, but to expect things not to work properly in their port.
Level 2 is what I would call a "wanky level" which employed a different sloping technique to create a vaulted ceiling. For the most part this isn't a very practical thing to do because it eats sectors quickly. Essentially what you must do in the editor is create a long halfpipe and step it through regular rotations, overlap them and join them together without moving the angle of each wall. It is time consuming.
Level 3 sees the player aboard a subway train. The train doesn't actually
move and instead, the tunnel around it is comprised of sprites traveling on SE24
conveyor belts. Combined with sliding doors, these belts move over and leave the
sprites on extremely slow SE34 belts to simulate the train stopping and
starting. When moving, the belts overlap a second set moving in the opposite
direction and the sprites get pushed through white walls onto the other belts.
Moving sprites run a function called updatesector() frequently and this
is how they know they're in a belt sector. When they pass through the white wall
the other belt sector covers their x/y location and so their sectnum changes,
having them move in the other direction. Such things should be used carefully
because sprites going out of bounds causes the function to fail - initially it
tries the same sector, then nearby indexes and finally, the entire sector count.
It must iterate the walls of each sector it checks to determine the area and
whether it is within them, which can make the game run very slowly. It runs fine
here because the sprites are never out of bounds.
The only actual train in the level is one which intercepts the player's
partway through.
Level 4 has you board a plane. Upon jumping out, the player falls through the clouds and is ambushed by flaying liztroops. The clouds actually run on elevators with doors in front of them, timed by a set of targets running on belts, effectively similar to punch tape logic on old computers.
Level 5 introduces a few new things, but the star of the show is probably the Billy Boy Windows, named for the mapper that was first known to use them. Each red wall in Build makes reference to the nextwall, that being the sector on the other side of the wall. This is how the renderer knows what to draw beyond that wall. While the editor offers no trivial way to do it, multiple walls can point to the same nextwall, but each wall can only point to one nextwall. Conveniently this means you can have multiple windows looking out into the same space that cannot see into each other, while that space can only look into one of them, which prevents running afoul of the inability to draw overlapping areas in a single pass. The fact they do use a single rendering pass means that they generally have fast performance.
Level 6 takes what was learned from Level 5 and twists it around with what might be the first appearance of "Inline Billy Boy Windows" that cause a building to appear as though it changes in front of the player. Really there are just multiple instances of the building in the same place with some sectors being looked into from both, though they can only look out into one and so entering them can transport the player from one house to the other instantly.
Level 7 faces the player off against the boss. Like most levels, there are a number of targets and doors firing far off the map that track the boss's health and control its actions.
Level 8 is the secret level. This one is primarily an abstract, artsy level that uses the slope technique and inline windows extensively, the latter being a little disorientating. Be mindful that NI1 deliberately avoided taking many of these things as far as it could, meaning to introduce the player to what it could do to allow things to go further in NI2... now if I could only find the time to finish that some day.
DukeMark doesn't do very much to the game's internals. In fact it uses the existing logic for counting frames and simply stores these to a variable. It is then very easy to compare this to the lowest and highest recorded numbers and compute an average. It isn't very interesting and all of the code to do it will fit on one screen.
What is interesting is just how well the engine performs. When preparing to
draw a frame, Build looks at which sector the camera is currently in, known
internally as sectnum, though generally camerasect in this case (because the
player and camera aren't necessarily the same thing). The map format is
basically made out of structs and sectors actually know very little about
themselves. They know that they're at a certain index in that struct and that
struct has textures, heights and a firstwall associated with them. They do not
know where they are, but the wall does, to an extent.
The wall struct contains the x,y co-ordinates of a single vertex, those which
appear as 'points' in the editor and they also contain a member for point2,
which is the vertex at the other end of the wall. From this you can calculate
the line between them and the angle of that line.
If the wall is a whitewall, generally those at the edge of bounds, then no
reference is made to anything on the other side of the wall, but if the wall is
red, it is so because it makes reference to a nextwall, that being what appears
to be the other side of the same wall in the editor and likewise, there will be
another sector to which this wall belongs. Using this data, Build is able to
very quickly determine what is in the path of the camera's vision and what
isn't, add it to the rendering queue and being drawing it front to back.
Normally these columns are drawn to system memory and then copied to the VGA
framebuffer to increase performance. For further optimization, the textures are
stored in RAM rotated 90 degrees, as this way they too can be read as columns
without having to hammer row select and therefore increasing the performance
even further.
For what it's worth, sprites and maskwalls are drawn in the opposite order in a
separate function. Calculating translucency for masks that have the cstat bits
for it turned on may well be the slowest of the drawing features, due to all the
per-pixel calculations that have to be done.
Most of the processing time for running the game is spent in the engine's
drawing functions, DrawRooms() and DrawMasks(), but these
functions are indeed impressively quick and perhaps only LucasArt's Jedi engine
could do a comparable job faster.
This is a horribly oversimplified explanation and other people have explained it better elsewhere.
Shadow Warrior shares its audio library with several other games. In fact, the Apogee Sound System, henceforth referred to as ASS, was used in Rise of The Triad and Duke Nukem 3D, as well as Blood, Redneck Rampage and a few other things. Shadow Warrior had maintained its MIDI soundtrack until quite late in development when it was decided to only use it for the shareware episode. Luckily the ASS itself wasn't altered at all and the game was simply rigged to never call the PlaySong() function in the code. This was quite a simple affair, with numerous #ifdef SHAREWARE instances cutting out the calls for MIDI playback at compile time. As luck would have it, the game does still hold a bool in the gs struct, generally used for game settings, called gs.PlayCD from when the redbook was optional. Therefore, changing #ifdef SHAREWARE to if (gs.PlayCD) and adding an else for the redbook was generally enough. Altering the menu arrays and functions probably took more time than any of the actual gameplay stuff for this feature. The ASS always could play MIDI and it really is as simple a calling the PlaySong() function from ASS and feeding it a file name.
MIDI file names are held in a struct called LevelInfo. This struct does what you'd think it does; It holds several char values for each level, being LevelName, SongName, Description, BestTime, ParTime. The SongName member contains the name of the MIDI file to play for a given level. This list was incomplete, but adding the names of MIDI files that exist will have them play, because the game will refer to this struct to get that information. Right before loading a level the game does a strcpy(LevelSong,LevelInfo[LEVEL].SongName) which copies the SongName entry for LEVEL (int, the number of the level you're on) into the LevelSong variable (a char)... I'm not sure why they did it that way, as I'm quite sure PlaySong(LevelInfo[LEVEL].SongName) would have worked. In any case, calling PlaySong(LevelSong) gives us MIDI music.
Meanwhile, when SW released in the UK there was an ongoing censorship
campaign against ninjas and so shurikens were not allowed. The UK version of
Shadow Warrior, then, replaced its shurikens with darts (except those ones
embedded in the post in Monastery). Darts are functionally identical to the shurikens and
were only a visual replacement. In fact a lot of the code behind them only
replaces the tsprite in the game. tsprites are "temporary sprites"
or sprites that are in the rendering queue, but may or may not actually have a
counterpart inside the map.
When coding a thing to do a thing, like getting a monster to do all of the
"roar! shoot shoot" stuff or here, getting darts to do all the "whoosh hurty
hurty" shenanigans, you generally associate that code with a tile number, but
obviously other frames of these animations are on different tile numbers, so the
code would instantly stop running. The sprite in the map never actually changes
its picnum, then, and instead the tile number of the tsprite[].picnum is changed
before being drawn to the screen. You can change to any tile number. The darts,
then, are a very minimal use of this capability. Much like the music, most
instances of this were simply compile time options, behind #ifdef UK_VERSION
and a few if statements got them going. Adding gs.Darts member (bool) to
the game settings struct (gs.Darts) was needed to make them toggle from the menu and have
the value stick between sessions. This member being a bool is funny as in local
dialect, to bool something generally means to chuck it at something, albeit
usually more in regards to ball-like objects, though to say you booled a dart
would be understood by the general population without question.
The HUD version of the darts is a little more trouble, because it runs afoul of
a bug in 3D Realms' code. It seems somebody banked on the execution order for
STAR_REST to be overridden by tile 2510 by default and then later overriden
by 2518 if the bloody hands flag is on (and parental lock is off). Unfortunately
the code doesn't run in that order, so the bloody hand will never show up when
darts are selected - you can nab the UK EXE somewhere and try this yourself.
This code, then, had to be rewritten to only do tile 2510 (the non-bloody dart)
if !Bloody (not bloody) or if either of the PLock flags were on. This allows the
bloody dart hand to show up because STAR_REST cannot be overwritten with
2510 if the player's hands are bloody and PLock is off. Logically in that case
the other condition must be true and so 2518, the bloody dart, is displayed.
Shadow Warrior has no /g switch on its command line for loading custom GRPs
and overall, the game isn't very mod friendly. Twin Dragon and Wanton
Destruction both shipped with hacked EXE files to replace the level names. They
also use the same save game files as the vanilla game, so you either have to be
OK with backing those up beforehand or overwriting them.
As it happened, SWMD was already rigged to look for MIDI.GRP and load it
alongside the main SW.GRP at startup. The engine uses a function called
InitGroup() to load GRP files and will override any asset that has the same
name from previously loaded GRP files. There was, however, a problem. Duke Nukem
3D has its own function for checking the command line. It calls this immediately
after loading DUKE3D.GRP, so that any additional GRP specified can override
assets within the main one, but only those assets. If you loaded a GRP that
contained, say, a single file named E1L1, all of the regular DUKE3D.GRP assets
would be loaded, but anything within the game referring to E1L1.MAP would point
to that second GRP file as it was the last one loaded.
Shadow Warrior checks its command line parameters in a huge messy if/else within
main() right before calling Control() which calls InitGame()
which calls InitGroup("SW.GRP"). Any add-on GRP would thus have its
assets overridden by SW.GRP as it would be loaded last.
The workaround, then, was to set a global var, a short called PickMod to
a non-zero value if the add-on command line switch was detected, then have
conditional code act upon this. After loading SW.GRP, SWMD checks this variable
to see if it is non-zero and loads TWINDRAG.GRP if the value is 1, or
WT.GRP if
the value is 2. If the value were anything else, the game would continue as
normal. InitGroup() also returns a negative number if the GRP cannot be
loaded which will set PickMod back to 0. With the GRP loaded, new strings
are written into the LevelInfo struct to reflect the add-on being played.
The menu functions also react by skipping the episode selection menu, instead
going straight to the skill menu to prevent loading the shareware levels. With a
skill level selected, the game's LEVEL int is forced to 5 and the first
level of the add-on is loaded.
SWMD's PickMod variable has one more use. It is appended to the start of
save game file names. When zero, then, you are playing the vanilla game and file
names such a 0game2.sav are used. When playing Twin Dragon, this same
save slot would use 1game2.sav or for Wanton, 2game2.sav, ensuring
the same slots are never shared between either add-on or the vanilla game.
The nuke lines are hardly worth talking about. By default, when a nuke cloud spawns, the game runs a RANDOM_RANGE(1000) and then plays one of the lines based on an if/else. It starts by checking for a value over 990, else 980, else 970 for the three lines. I changed the range to 11 and checked for it being equal to 2, 5 or 8, giving just under a 30% chance of hearing a line when spawning a nuke cloud. It isn't like there are that many nukes in the game, so we don't fire them often and I'll bet a great many players have never heard these lines under normal play. Even if they knew the files were in the game, they likely thought they were unused.
Page authored and maintained by XeFF Systems, last revised in May 2026
Cookie Policy... my pages do not place cookies, though like all sites, the
server logs connecting IP addresses - this information is not shared with any
other party and these logs are routinely discarded
Please don't copy the contents of this page without permission