summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Morgan <sjm@sjm.io>2018-02-23 21:12:47 +0000
committerSimon Morgan <sjm@sjm.io>2018-02-23 21:12:47 +0000
commitc11a67fbd5d10604c89a8ec3b1b206ac64779105 (patch)
treeb54f586df81a588289a7931f45e4dc07aa08e7c8
downloadblood-c11a67fbd5d10604c89a8ec3b1b206ac64779105.tar.gz
blood-c11a67fbd5d10604c89a8ec3b1b206ac64779105.tar.bz2
blood-c11a67fbd5d10604c89a8ec3b1b206ac64779105.zip
Initial importHEADmaster
-rw-r--r--ARRIVE.BTM7
-rw-r--r--ARTEDIT.TXT109
-rw-r--r--ARTPATCH.BTM3
-rw-r--r--BLOOD.RFS15
-rw-r--r--BLOOD.TXT1072
-rw-r--r--DEBUG.TXT424
-rw-r--r--GUI.RFS6
-rw-r--r--LEAVE.BTM10
-rw-r--r--LEVELS.TXT154
-rw-r--r--MAKEBARF.BTM1
-rw-r--r--MAKEFILE481
-rw-r--r--MAPEDIT.TXT1461
-rw-r--r--MERGE.BTM47
-rw-r--r--NAMES.H372
-rw-r--r--NEWINI.TXT3
-rw-r--r--QAV.RFS225
-rw-r--r--QAVEDIT.TXT86
-rw-r--r--QAVS.RFS2
-rw-r--r--RAW.RFS43
-rw-r--r--SEQEDIT.TXT154
-rw-r--r--SEQS.RFS342
-rw-r--r--SRC/ACTOR.CPP3054
-rw-r--r--SRC/ACTOR.H187
-rw-r--r--SRC/AI.CPP761
-rw-r--r--SRC/AI.H86
-rw-r--r--SRC/AIBURN.CPP330
-rw-r--r--SRC/AIBURN.H24
-rw-r--r--SRC/AICULT.CPP301
-rw-r--r--SRC/AICULT.H18
-rw-r--r--SRC/AIGARG.CPP246
-rw-r--r--SRC/AIGARG.H18
-rw-r--r--SRC/AIHAND.CPP170
-rw-r--r--SRC/AIHAND.H16
-rw-r--r--SRC/AIHOUND.CPP216
-rw-r--r--SRC/AIHOUND.H16
-rw-r--r--SRC/AIRAT.CPP192
-rw-r--r--SRC/AIRAT.H16
-rw-r--r--SRC/AISPID.CPP209
-rw-r--r--SRC/AISPID.H16
-rw-r--r--SRC/AIZOMBA.CPP426
-rw-r--r--SRC/AIZOMBA.H22
-rw-r--r--SRC/AIZOMBF.CPP249
-rw-r--r--SRC/AIZOMBF.H17
-rw-r--r--SRC/APE.CPP440
-rw-r--r--SRC/ARTEDIT.CPP1290
-rw-r--r--SRC/ASSTIMER.CPP112
-rw-r--r--SRC/BLOOD.CPP1450
-rw-r--r--SRC/BSTUB.CPP1924
-rw-r--r--SRC/BSTUB.H60
-rw-r--r--SRC/BSTUBIO.CPP245
-rw-r--r--SRC/BUILD.H188
-rw-r--r--SRC/C.TAG0
-rw-r--r--SRC/CONTROLS.CPP358
-rw-r--r--SRC/CONTROLS.H80
-rw-r--r--SRC/CONVDB3.CPP628
-rw-r--r--SRC/CONVDB4.CPP727
-rw-r--r--SRC/CONVDB5.CPP796
-rw-r--r--SRC/CONVDB6.CPP890
-rw-r--r--SRC/CREDITS.CPP304
-rw-r--r--SRC/CREDITS.H9
-rw-r--r--SRC/DB.CPP1014
-rw-r--r--SRC/DB.H722
-rw-r--r--SRC/DB2.H788
-rw-r--r--SRC/DUDE.CPP1669
-rw-r--r--SRC/DUDE.H125
-rw-r--r--SRC/EDGAR.CPP166
-rw-r--r--SRC/EDGAR.H16
-rw-r--r--SRC/EDIT2D.CPP1835
-rw-r--r--SRC/EDIT2D.H12
-rw-r--r--SRC/EDIT3D.CPP4501
-rw-r--r--SRC/ENGINE.H1523
-rw-r--r--SRC/EVENTQ.CPP344
-rw-r--r--SRC/EVENTQ.H82
-rw-r--r--SRC/FFRAME.ASM159
-rw-r--r--SRC/FIRE.CPP147
-rw-r--r--SRC/FIRE.H11
-rw-r--r--SRC/FIXCRC.CPP151
-rw-r--r--SRC/FONT.CPP235
-rw-r--r--SRC/FONT.H58
-rw-r--r--SRC/FONTEDIT.CPP630
-rw-r--r--SRC/FONTFIX.C90
-rw-r--r--SRC/GAMEMENU.CPP304
-rw-r--r--SRC/GAMEUTIL.CPP597
-rw-r--r--SRC/GAMEUTIL.H100
-rw-r--r--SRC/GIB.CPP123
-rw-r--r--SRC/GLOBALS.CPP52
-rw-r--r--SRC/GLOBALS.H67
-rw-r--r--SRC/GUI.CPP998
-rw-r--r--SRC/GUI.H281
-rw-r--r--SRC/JOYTEST.CPP58
-rw-r--r--SRC/LEVELS.CPP101
-rw-r--r--SRC/LEVELS.H58
-rw-r--r--SRC/MAIN.C499
-rw-r--r--SRC/MAP2D.CPP411
-rw-r--r--SRC/MAP2D.H6
-rw-r--r--SRC/MAPINFO.CPP715
-rw-r--r--SRC/MIRRORS.CPP165
-rw-r--r--SRC/MIRRORS.H7
-rw-r--r--SRC/MULTI.H94
-rw-r--r--SRC/OPTIONS.CPP130
-rw-r--r--SRC/OPTIONS.H42
-rw-r--r--SRC/PALTOOL.CPP473
-rw-r--r--SRC/PLAYER.CPP1292
-rw-r--r--SRC/PLAYER.H242
-rw-r--r--SRC/QAV.CPP92
-rw-r--r--SRC/QAV.H87
-rw-r--r--SRC/QAVEDIT.CPP1304
-rw-r--r--SRC/REMAP.CPP623
-rw-r--r--SRC/REPLACE.CPP258
-rw-r--r--SRC/SCREEN.CPP437
-rw-r--r--SRC/SCREEN.H65
-rw-r--r--SRC/SECTORFX.CPP348
-rw-r--r--SRC/SECTORFX.H26
-rw-r--r--SRC/SEQ.CPP480
-rw-r--r--SRC/SEQ.H70
-rw-r--r--SRC/SEQEDIT.CPP943
-rw-r--r--SRC/SETUP.CPP662
-rw-r--r--SRC/SFX.CPP836
-rw-r--r--SRC/SFX.H522
-rw-r--r--SRC/SHELL.CPP95
-rw-r--r--SRC/SHPLAY.CPP1256
-rw-r--r--SRC/SLUT.CPP473
-rw-r--r--SRC/SOUND.CPP287
-rw-r--r--SRC/SOUND.H36
-rw-r--r--SRC/SPRITEST.CPP47
-rw-r--r--SRC/TABLES.CPP85
-rw-r--r--SRC/TILE.CPP946
-rw-r--r--SRC/TILE.H40
-rw-r--r--SRC/TRICKS.CPP70
-rw-r--r--SRC/TRICKS.H22
-rw-r--r--SRC/TRIGGERS.CPP2244
-rw-r--r--SRC/TRIGGERS.H33
-rw-r--r--SRC/USRHOOKS.CPP58
-rw-r--r--SRC/VIEW.CPP1575
-rw-r--r--SRC/VIEW.H109
-rw-r--r--SRC/VOC.CPP299
-rw-r--r--SRC/WARP.CPP116
-rw-r--r--SRC/WARP.H34
-rw-r--r--SRC/WEAPON.CPP1871
-rw-r--r--SRC/WEAPON.H10
-rw-r--r--TASKS.TXT387
-rw-r--r--UPDATE.BTM33
-rw-r--r--XSYSTEM.TXT1163
143 files changed, 60243 insertions, 0 deletions
diff --git a/ARRIVE.BTM b/ARRIVE.BTM
new file mode 100644
index 0000000..5c9ee60
--- /dev/null
+++ b/ARRIVE.BTM
@@ -0,0 +1,7 @@
+@echo off
+iff "%1" == "" then
+ echo Missing drive parameter
+ quit
+endiff
+
+pkunzip %1\*.zip -d -n \
diff --git a/ARTEDIT.TXT b/ARTEDIT.TXT
new file mode 100644
index 0000000..a633036
--- /dev/null
+++ b/ARTEDIT.TXT
@@ -0,0 +1,109 @@
+=============================================================================
+ ARTEDIT History File
+ Copyright (c) 1994-95 Q Studios Corporation
+ Contact: Peter Freese
+ pfreese@qstudios.com or CIS:74170,543
+=============================================================================
+
+MAIN KEYBOARD FUNCTIONS:
+
+ Esc Exit the editor
+ Enter Goto tile editing mode
+ G Goto a specific tile
+ S Change surface type for tile(s)
+ Shift Select a range for moving
+ Insert Move the selected range
+  Change highlight
+ shift  Highlight a range of tiles
+ Home Goto tile 0
+ End Goto last tile (4095)
+ Pad / Zoom in
+ Pad * Zoom out
+ Page Up/Down Go up/down a page
+ Alt Backspace Undo previous move
+ Alt Enter Redo previous undo
+
+
+TILE KEYBOARD FUNCTIONS:
+
+ Esc Exit tile mode
+ G Goto a specific tile
+ Space Play tile animation
+  Nudge tile origin 1 pixel
+ Ctrl  Set origin to respective edge
+ Ctrl Pad 5 Set origin to center
+ O  Nudge global origin
+ W Change tile view type
+ < > Change view angle
+ / Set view angle to 0
+ A Change animation type
+ Pad +/- Change # of animation frames
+ Shift Pad +/- Change animation speed
+ O LMouse Drag origin
+ Page Up/Down Prev/Next (non-blank) tile
+
+
+VIEW
+
+ Single view objects do not change their view based on the players
+ position. In other words, they look the same no matter how you look
+ at them. This works best for objects with radial symtetry, such as
+ pillars, cauldrons, torchiere lamps, etc.
+
+ 5 view objects offer 8 different angles of view by x-flipping certain
+ of the views to take advantage of bilateral symetry. The views
+ represented in the art are 0, 45, 90, 135, and 180 degrees. Views
+ for 225, 270, and 315 are created by x-flipping 135, 90, and 45
+ respectively.
+
+ 8 view objects have unique views for each of the 8 viewing octants.
+
+ 5 half view objects are a special case of 8 way views for objects
+ which are flush against a wall. For these objects, it is not
+ possible to view them from behind the wall, so the 3 rear views are
+ skipped. Views of 0, 45, 90, 270, and 315 are represented. This
+ mode works well for sconces, clocks, relief ornaments, etc.
+
+
+-----------------------------------------------------------------------------
+95/06/19
+
+This document created.
+
+Don't move around anything but creature view/animation tiles, since tile
+changes cannot yet be applied to maps.
+
+-----------------------------------------------------------------------------
+95/06/24
+
+Added multiple do/undo capabilities.
+
+-----------------------------------------------------------------------------
+95/07/11
+
+Added view type description above.
+
+-----------------------------------------------------------------------------
+95/08/21
+
+Changed the way extended tile selection works in the tile pick view. The
+selected range now goes to the upper end - 1, which means you can now
+create an empty selection. The catch is that you can't select the highest
+tile, but this shouldn't be a big problem.
+
+Tile origin adjustments are now applied to the selected range of tiles, which
+means you can easily adjust the origin for an entire animation.
+
+Changed keys which set origin to the edge and center to use Ctrl instead of
+Shift in order to have a more orthogonal key mapping. Get it?
+
+-----------------------------------------------------------------------------
+95/10/25
+
+Added ability to modify tile surface types with the S key
+
+-----------------------------------------------------------------------------
+96/01/17
+
+Added ability to modify tile names with the N key. This does NOT work
+in conjunction with tile movement yet.
diff --git a/ARTPATCH.BTM b/ARTPATCH.BTM
new file mode 100644
index 0000000..8fc3c32
--- /dev/null
+++ b/ARTPATCH.BTM
@@ -0,0 +1,3 @@
+ape -b %@NAME[%1]
+zip %1 %@NAME[%1].APD
+ape -c %@NAME[%1]
diff --git a/BLOOD.RFS b/BLOOD.RFS
new file mode 100644
index 0000000..87f02ae
--- /dev/null
+++ b/BLOOD.RFS
@@ -0,0 +1,15 @@
+fonts\*.fnt
+tables\*.* prelock
+music\*.mid
+music\gmtimbre.tmb
+
+alias fonts\pfont2o.qfn 0x00 PRELOCK
+alias fonts\pfont1.qfn 0x01 PRELOCK
+
+snd\*.raw
+
+# RESOURCE\*.MAP
+
+# include script for sequences
+@SEQS.RFS
+@QAVS.RFS
diff --git a/BLOOD.TXT b/BLOOD.TXT
new file mode 100644
index 0000000..d22e17e
--- /dev/null
+++ b/BLOOD.TXT
@@ -0,0 +1,1072 @@
+=============================================================================
+ BLOOD History File
+ Copyright (c) 1994-95 Q Studios Corporation
+ Contact: Peter Freese
+ pfreese@qstudios.com or CIS:74170,543
+=============================================================================
+
+KEYBOARD FUNCTIONS:
+
+D and +/- Changes visibility
+
+G and +/- Changes gamma correction
+
+S and +/- Change player spring coefficient
+
+X jump
+
+V toggle external view (Alt-V rotates view)
+
+Z Secondary fire key
+
+C Crouch
+
+Ctrl Primary fire
+
+Alt strafe
+
+1 - 9 Selects/deselects weapon (only pitchfork, tommy gun, shotgun
+ for now)
+
+F1 Help (not implemented)
+
+F2
+
+F3
+
+F4
+
+F5
+
+F6
+
+F7
+
+F8
+
+F9 Quick Load
+
+F10
+
+F11 Changes gamma correction
+
+F12 Selects player view
+
+Insert Inserts fake player
+
+Delete Deletes fake player
+
+ScrollLock Selects fake player
+
+
+CHEAT KEYS:
+
+QSMAPS Full map
+QSAMMO Full ammo
+QSKEYS All keys
+QSCLIP No clipping mode
+QSOUCH Pummel death
+QSBURN Burning death
+QSBOOM Explode death
+QSRATE Toggle display of frame rate
+QSGOTO Go to a specific x,y location
+QSJUMP Jump to a particular episode and level
+QSHIGH Delirium mode
+QSONERING Inviso mode
+QSAIM Toggle display of aiming reticle
+
+=============================================================================
+
+95/01/20
+
+Change the tile cache size by editing the "TileCacheSize" parameter
+in BLOOD.INI. (Ex. TileCacheSize=2048 will create a 2MB tile cache.)
+Spaces between the parameter and value are NOT supported yet.
+
+Note the icon in the upper right corner when a cache miss causes
+a disk access to occur. Cool, eh?
+
+Here are the monsters in the game:
+* Gargoyle: By far the coolest when running/jumping.
+* The Hand: Way cool!
+* Gill Beast: Cool, but sometimes humorous.
+* Fat Boy Zombie: Use the butcher knife!!
+* Jive Zombie: No attack yet, kind of weak for a weak enemy.
+* Phantasm: Ok, we need lots o' art for this one, but attack works.
+* Hell Hound: Needs major art touchup.
+
+Here are the weapons in the game (still frames with bobbing.):
+* Pitchfork
+* Tommy Gun (much better than before)
+* Sawed-off shotgun (Love this baby.)
+
+Next week:
+Animated frames for the above weapons.
+The NAIL GUN, yes! ($25 a day for rental...sheesh.)
+
+-----------------------------------------------------------------------------
+
+95/02/06
+
+Sector lighting effects are now functional. See the notes in MAPEDIT.TXT for
+information on creating lighting effects.
+
+Global visibility is now loaded from the mpx file, although you can still
+change it with the V Pad+/i keys.
+
+Tiled parallaxed sky information is loaded from the MPX file (although I
+haven't tested this yet).
+
+Almost all errors are now caught by a global error handler. Let me know if
+you get any mysterious crashes. There are still asserts which will go
+through the monochrome monitor if you have the DEBUG environment variable
+defined.
+
+Multi-player network play works. Yeah! I've added synchronization checks
+for now which should report if any slaves get out of sync.
+
+Player movement dynamics have been overhauled. You can now "slide" sideways,
+spin in mid-air, crumple from a fall, and other stuff based on inertia.
+It's really cool.
+
+The tile cache system has been revamped. It will now attempt to allocate as
+large a cache as possible, up to the limit of available memory. The
+TileCache line in the INI file is now longer used, and can be removed.
+Additional dynamic allocations (such as sound effects) in the game will come
+from the tile cache.
+
+Dynamic fire is working again, and looks better than ever. The tile number
+is 2342, and needs to be added to names.h. We need a new 128x128 pic for
+tiles.art -- the one from Doom is 128x112.
+
+-----------------------------------------------------------------------------
+
+95/02/13
+
+Added new weapon swaying effect that is not directly related to the head bob.
+
+Screen resizing is functional again, although there are a few problems with
+the shade and size of the marble background. Also, don't try resizing in
+640x480 mode -- there a still some serious problems in the engine related to
+view sizing.
+
+2D mapping now working in the game video mode. This should prevent problems
+with mode switching (especially in network play with problematic video
+cards). Also, we'll be able to do more cool stuff with the maps with 256
+colors, such as anti-aliased lines, parchment background, etc. -- we just need
+to figure out what we want.
+
+Zoom is no longer a player variable; it is solely a display metric now.
+
+Zoom and view size are now written to the blood.ini file.
+
+Fixed a inifile bug which caused an assertion failure if a key was not found
+and a section contained blank lines.
+
+Changed the way items are picked up to solve problems in picking up sprites
+which are not in the same sector as the player. In order to be picked up,
+sprites now must be on stat list 1 (kSpriteStatTouch). Sprites are
+automatically tagged with this status in mapedit, so you'll need to load and
+save all your maps.
+
+Changed location and persistence of the disk sprite which appears during
+cache misses. It now appears on top of the status bar to ensure it doesn't
+get left on the background when the view size is less than full screen.
+
+During initialization, the status messages for building the various tables no
+longer appear unless the tables are actually being built. If they are just
+being loaded, the messages do not appear.
+
+Re-enabled chaining to the old timer interrupt handler. This was previously
+disabled due to a bug in Ken's network code. Re-enabling it should cure those
+system date and time problems, as well as help with any timer dependent DOS
+drivers, such as disk caches.
+
+-----------------------------------------------------------------------------
+
+95/02/14
+
+Did a major restructuring of source code. This was a sorely needed cleanup
+to produce organization out of a loose collection of hacks. This should make
+future changes and additions much easier.
+
+Blood no longer uses Ken's keyboard handler. This means functional key
+repeat, less key lockouts, and input stream buffering.
+
+Added support for cheat key sequences. Try QSKFA!
+
+Modified desynchronization so that animations are desynced by time, and not
+be frame. This means that even with only two frames, a lot of sprites of the
+same tile should not animation in unison.
+
+Optimized the routine for picking up sprites. The additional trivial
+rejection tests should speed things up a bit.
+
+Warping no longer sets velocity to zero.
+
+You should now be able to operate a platform sector from outside the sector
+(via neartag). Sector operation code is also optimized.
+
+Head bobbing and weapon swaying are now related to the players actual
+movement, not attempted movement. This means that you should be still when
+you are jammed up against a wall and running like hell. There is a low pass
+filter for the amplitude, so it should never jerk your head or the weapon. I
+also modified the scale for the metrics for bobbing and swaying, but I think
+I've got them pretty close to the way they were before. Please let me know
+any comments you have on the feel of these features.
+
+When you land and recover, I am now dropping the weapon a bit to simulate its
+inertia. I think this adds good visual cues for the landing effect.
+
+-----------------------------------------------------------------------------
+
+95/02/15
+
+DO NOT RUN THIS VERSION IN UNCHAINED MODE. It will probably do something
+really horrible.
+
+Wrote new code to support tiling the background when the screen size is
+reduced. This is some pretty crappy code right now, and I'll probably
+rewrite it eventually, but for now it works except in unchained mode.
+
+Reduced horizontal swaying of weapon by 1/3. Let me know if this is enough.
+
+Added code to support DAC effects, which will be used for visual feedback for
+events such as one-ups, injuries, and special sector effects. I've plugged
+in a little flash effect for picking up items. Also, try QSOUCH for an
+injury effect. If the DAC effects cause any snow problems we can always
+switch to using palette lookups of the sort Doom uses, although this will
+severely limit our use of separate palette lookups for sprites, or increase
+the overhead in terms of the number of tables.
+
+Reduced the amount of inverse Z adjustment for looking up and down.
+
+Add QSRATE - Toggle display of frame rate.
+
+Rewrote the 2D map function so it should look just like the map in Doom.
+
+The status bar is always displayed in 2D map mode.
+
+-----------------------------------------------------------------------------
+
+95/02/18
+
+Modified the way the DAC effects work somewhat to fix a bug which would cause
+the hardware DACs not to be updated when the effect was 0.
+
+Added QSGOTO and QSJUMP cheat keys.
+
+Changed key combinations for setting gamma, visibility, and spring
+coefficient to be compatible with those in MAPEDIT. Use +/- instead of Pad
++/-.
+
+Visibility is no longer written to the BLOOD.INI file, since it is level
+dependent.
+
+Changed key combination to change the depth cueing (visibility) to D +/- for
+compatibility with MAPEDIT.
+
+Provided a workaround for the off by 4 pixel error in getzrange. This means
+you shouldn't pop in the air when stepping on a floor sprite at floorz level
+anymore.
+
+-----------------------------------------------------------------------------
+
+95/02/19
+
+Added a module for processing command line parameters. I'm not doing much
+with this yet, but it's now pretty easy to handle switches and other optional
+arguments. The only visible change now is that when you start Blood, it
+defaults to map E1M1, and if you pass a level (map) name, it ignores the
+extension.
+
+Made a change to the way that player location and velocity is stored. This
+is an internal code change that shouldn't result in any visible change, but
+is necessary for me to write a more general purpose physics system (so any
+sprite can move around with inertia).
+
+Moved several constants controlling player movement dynamics out of the code
+and into the player posture structure. Now nearly every aspect of player
+movement is driven by this data.
+
+Rewrote the key handler to better provide services for getting characters in
+a stream type of fashion.
+
+Fixed a bug in the main drawing loop. I wasn't clearing the gotpic[] array,
+so once you saw the dynamic fire, it was getting calculated every frame.
+This easy fix should give us a nice performance boost.
+
+-----------------------------------------------------------------------------
+
+95/03/05
+
+Projectiles have been added. Shame on Nick for not documenting them here.
+
+Fixed a bug which would cause extended keys (e.g. Page Up and Delete) to be
+dropped if NumLock was on. This bug was introduced when I added code to
+prevent filling up the keyboard buffer. These keys are actually transmitted
+as multiple make codes when Num Lock is on, hence the bug.
+
+Fixed a bug in the debug version of the heap. Sheesh! It would dassert out
+occasionally when it was unable to allocate a block when it thought it had
+enough memory. I wasn't subtracting out the space used for the fenceposts.
+
+-----------------------------------------------------------------------------
+
+95/03/06
+
+Added an assertion to check for overflow of the ext arrays. I found out that
+some maps had sectorExt structures for nearly every sector, and were
+overflowing the array!
+
+Created a branding utility for limited demo versions. This utility should
+not be released outside of Q Studios.
+
+-----------------------------------------------------------------------------
+
+95/03/24
+
+BUG FIXES
+
+Fixed problem with allocatepermanenttile not marking the block of memory as
+locked. It could get added to the purge list during precaching....
+
+Added some missing modify [...] registers to an inline function in the
+dynamic fire system. This would inevitably cause problems when
+optimizations were enabled.
+
+-----------------------------------------------------------------------------
+
+95/04/18
+
+Changed default key codes for jump, crouching, and alternate firing to be
+identical to Dark Forces.
+
+Added support for view centering (default key is Alt Pad 5) when look lock is
+on.
+
+-----------------------------------------------------------------------------
+
+95/04/30
+
+Added support for various screen modes now supported by the Build engine.
+
+Rewrote line clipping routine, but there are still some problems to work out
+in unchained mode.
+
+INI file changes are not written out until the program exits. This should
+minimize disk access during game play.
+
+-----------------------------------------------------------------------------
+
+95/04/30
+
+QAV playback support added and several weapon animations now work.
+
+Trigger switches currently supported are:
+ Toggle Switches and Momentary Switches
+
+For Momentary Switches, the "data" field can be set to indicate
+tenths of a second before the switch toggles back to the original state.
+
+Sectors supporting currently switches are:
+ Topdoor, Bottomdoor, Swingdoor, and Lift
+
+Armed Proximity Bombs can now be placed and will function. More flexibility
+for distance and damage will be added shortly.
+
+Exploding bombs affect all "Crate Face" sprites within its proximity.
+MAPPERS: Make sure that you use the damaged crate face for anything
+that can be exploded, and don't use it anywhere else. I repeat, don't
+use the damaged crate face for anything except crate faces that are
+affected by explosions. Especially you, Richard.
+
+-----------------------------------------------------------------------------
+
+95/05/02
+
+Fixed trigger bug with floor pads.
+
+-----------------------------------------------------------------------------
+
+95/05/08
+
+Added panning sector effects, which now work for water current.
+Water movement now has drag even if there is no current. The drag is
+relative to the depth of the water.
+
+Rewrote/simplified trigger code. Made callbacks a command type.
+
+Added impulse to explosion projectiles.
+
+Added code to support Top door, Bottom Door, and HSplit door types.
+
+-----------------------------------------------------------------------------
+
+95/05/08
+
+Added !state command.
+
+Sent events are now processed immediately, rather than being posted into the
+message queue. This allows cleaner coding for LINK commands. Be careful not
+to create infinite message loops!
+
+Math and palette lookup table building has been removed from the game code.
+These tables will now be created with stand-alone utilities.
+
+Sector lighting amplitude now honors the sector busy state, so you control
+light level with link commands.
+
+-----------------------------------------------------------------------------
+
+95/05/19
+
+Added horizontal and rotation move sector types. This means sliding and
+swinging doors, but you are certainly not limited to these types. Use your
+imagination.
+
+Modified DoSectorLighting() so it is not necessary to call
+UndoSectorLighting() in Blood. Should speed things up just a bit.
+
+Fixed an overflow problem in 2D map mode which cause clipped lines to skew
+weirdly. I went through all the code and made all the multiply and shift
+operations use a full 64 bits of precision.
+
+Got rid of ALL the old door animation code. We are no longer unwashed.
+("This house, is clean." - Nick )
+
+Kinetic sectors would never go the final distance fraction when going to the
+on state. The error fraction was 1/65536. I've provided a work-around for
+this, but the real solution is to increase the X structure busy variable
+size by 1 bit.
+
+-----------------------------------------------------------------------------
+
+95/05/24
+
+WEAPONS:
+* Modified tommy gun.
+* Added first-pass shadow gun.
+* Added first-pass beast hands.
+* Added code to throw dynamite. Try blowing up the crates in MAP04.MAP.
+ (Don't get too close or you'll set off the proximity bomb and ruin it.)
+* Added vector hit check and ricochet for shotgun and tommy gun.
+ (NOTE: All the ricochets for the shotgun appear in one place for now.)
+
+LIGHTING:
+* New lighting effects and triggers added. Check some of them out in
+the sample maps.
+
+LINKED SECTORS:
+* You can now go underwater, but cannot go back up or move around very well.
+ (We intend to have the water physics working by the next upload.)
+
+There's lots of other stuff, but I have to go to dinner. I'll just upload it
+and let you fool around. Later!
+
+-----------------------------------------------------------------------------
+
+95/05/30
+
+Fixed view bobbing/swaying bob which caused them not to work unless you were
+running. Don't you just love misplaced braces? <g>
+
+Bobbing and swaying are now optional. Just added "ViewBobbing=0" to the
+[Options] setting of blood.ini. This should cure George's nausea.
+
+Increased strafe acceleration, and reduced angular velocity at George's
+request. You can also accelerate while landing now, also at his request.
+Can we have an equipment advance? ;)
+
+-----------------------------------------------------------------------------
+
+95/06/24
+
+Added sequence engine which will be used for transient sprite effects and
+creature states.
+
+Reworked weapon selection code to allow cleaner changing between certain
+weapons, such as the dynamite and spray can.
+
+Added sequences for ricochets and explosions.
+
+Added the ability for explosions to chain other explosions.
+
+Added a rudimentary death sequence for cultists.
+
+Added damage/death for vector weapons.
+
+-----------------------------------------------------------------------------
+
+95/06/26
+
+Added time delay for dynamite projectiles.
+
+Added code to put dudes affected by explosions on the projectile list, and
+greatly enhanced projectile code. The net result is you can blow up the
+creatures. "They sure blowed up _real_ good!"
+
+-----------------------------------------------------------------------------
+
+95/07/03
+
+Converted sprite management code to use a FIFO list for proper handling of
+bullet holes and other purgeable sprites.
+
+Fixed problems with projectiles (including bodies) falling through floor
+sprites.
+
+Temporarily removed above mentioned FIFO code until I can get it working
+correctly.
+
+Added support for moving z of sprites on the floor of z motion sectors. This
+means that when the floor moves, so too should sprites that are on it. Also,
+no more player bouncing when you are standing on a z motion floor. The only
+downside is that the player will not receive any intertia from the vertical
+motion, so you can't 'throw' the player in the air with a fast moving floor.
+
+Changed resource system so that it uses hashing and linear probing for rapid
+lookup of resources. This was one of those things that had been waiting too
+long to be done. Now we can move forward with creating RFF (Resource File
+Format) archives and clearing up those directories of thousands of files.
+
+Added weapon QAV and sequence preloading. If you have enough memory, you
+shouldn't get ANY disk hits during game play, except for status bar graphics,
+which will be done shortly.
+
+-----------------------------------------------------------------------------
+
+95/07/05
+
+Players are now impulsed by explosions. Try standing on a lit stick of
+dynamite!
+
+Changed the way that friction works for sliding objects. The current method
+is 'physically' correct.
+
+-----------------------------------------------------------------------------
+
+95/07/08
+
+Rewrote DPMI services and code which attempts to determine how large a heap
+to allocate. It should now attempt to allocate less than the physical memory
+available, even under DPMI hosts providing virtual memory (e.g., Windows)
+
+Provided locking for nearly all interrupt handlers through new DPMI routines.
+This should improve the robustness of Blood under other operating systems
+such as Win 95.
+
+Wrote a new virtual timer services module. This allows any number of clients
+to install their own timer handlers with varying rates all through a central
+timer services handler. This is some pretty low level code, and shouldn't
+affect anyone but Blood programmers.
+
+Fixed a bug in the sequencer in which the first frame would not be displayed
+as long as it should have been.
+
+Increased dispersion for tommy gun, and increase shotgun vectors per barrel
+to 16.
+
+-----------------------------------------------------------------------------
+
+95/07/09
+
+Fixed and reenabled sprite FIFO code.
+
+In Z Motion sectors, sprites on the ceiling as well as sprites on the floor
+will be z adjusted when the floor or ceiling moves, as appropriate. I also
+changed the method used to determine when to move the sprites. Previously,
+the code would take into account the sprite z extents to determine whether
+the sprite was on the floor. Now it looks only at the z value to see if it
+is on or in the ceiling or floor. This should provide results consistent
+with the type of sprite being using, i.e., sprites meant to be placed on the
+ceiling have their origin set at the top, and sprites meant to be placed on
+the floor have their origin at the bottom.
+
+Did extensive work in code for handling interactions between objects and
+explosions. Explosions can now be triggered (this is the way that throw
+sticks of TNT are handled). The impulse effect for explosions is now no
+longer instantaneous, but occurs over the duraction of the explosion. You
+can see this by watching your health while standing in range of an explosion.
+Sprite movement as a result of impulse still seems off, and I thinks it's
+because I have yet to handle sprite collisions (soon, though).
+
+Creature death sequences are determine by the type of damage which caused the
+death. Currently, this is supported only (in terms of art) for the fat
+zombie.
+
+When the fat zombie explodes, he throws off chunks of gore.
+
+-----------------------------------------------------------------------------
+
+95/07/14
+
+Multi-player starting markers now function. Check out MAP02.MAP to see
+how they are inserted.
+
+Network code is semi-stable. Runs for a while then asserts out with a
+synchronization failure. This is probably in Ken's code, since we are
+getting bad or missing packets which cause the de-synch.
+
+In multi-player mode players animate in the walk sequece until they shoot,
+then the shooting animation stays on until they die. We are working on the
+AI and animation states for the players and enemies, so bear with us. At
+least now the players, as well as the monsters, die agonizing deaths. Shoot
+the fat boy for fun!
+
+Breakable walls now function. Check out the new types in MAPEDIT by pressing
+Alt-F6 on a red wall you want to mask and turn to a breakable wall. Similar
+sprite types will be added soon. Look at and shoot the stained glass in
+MAP19(?) which used to be QSEYMY.
+
+We try to test out as many bugs as possible before uploading a demo, but
+sometimes we slip up. As always, let Peter or me know if something no
+longer works.
+
+Lots of new artwork this upload. Better take a trip through all the art
+first. Look at the art using EDITART or MAPEDIT before making level or
+area texture changes; you may find just what you've been waiting for.
+
+-----------------------------------------------------------------------------
+
+95/07/17
+
+Monsters now trigger OFF at death and send their command and txID.
+
+Added BloodBath (deathmatch) starts but though you can't play that mode yet.
+
+-----------------------------------------------------------------------------
+
+95/07/20
+
+ !!! MAP VERSION CHANGE !!!
+
+Once you've backed up all your maps in another directory or ZIP file, run the
+CONVDB5.EXE conversion utility. You can use wildcards, so typing "CONVDB5
+*.MAP" is acceptable. The old maps will be saved with an extension of ".MA4".
+
+Maps are now loaded as resources, which means they can be placed into
+BLOOD.RFF.
+
+Level definition files are loaded now along with the maps. See the sample
+FXTEST.DEF for an example. They can't be placed in resource files yet,
+though.
+
+Explosions now use an expanding sphere of influence, so they won't effect
+things within a possible range immediately. The closer you are to an
+explosion, the faster you'll take damage from it.
+
+-----------------------------------------------------------------------------
+95/07/27
+
+Modified some cheat keys. Make sure your read the cheat section in this doc.
+
+Ammo can be picked up and is added to your stats. The stats are NOT displayed.
+Since this is still being coded, expect some funkiness with the way the weapon
+behaves when it has no ammo, or you pick up ammo the first time. Your best bet
+is to type "QSAMMO" when you first get into the game.
+
+The spear gun is all screwed up. Don't play with it, or at least don't
+complain if using it causes Blood to crash. ;)
+
+-----------------------------------------------------------------------------
+
+95/07/29
+
+Lot's of new additions to the physics system. Explosions can now affect all
+dudes and "things", which include body parts. Everything in the physics
+system now has a mass, which means that lighter things can be blown
+farther. Have fun with the TNT and TNT barrels.
+
+Player death greatly improved.
+
+Dynamic shadows added for all dudes and things. This will be a detail level
+option, since it can have an impact on performance for highly populated
+screens. Try DEATH.MAP for a fun time. This map has around 150 combined
+dudes and TNT barrels in a single sector, and is a pretty good stress test.
+DON'T put this many things in your game maps.
+
+-----------------------------------------------------------------------------
+95/08/01
+
+Temporary fix for sequence caching code that was causing "Out of memory"
+bailout. You will experience slower map loads and slower caching in this
+version. This is just _temporary_ until we have the new resource prelock
+code running.
+
+Added more cheat keys (listed *way* up at the top) and also fixed a bug
+in the QSKEYS cheat code.
+
+!!! IMPORTANT - READ THIS !!!
+For those of you not receiving an art update with this version, the
+Tommy Gun will be positioned incorrectly. This is because we have new art
+that you don't have yet. Since this is primarily a bug fix related update
+I thought it would be better not to include 6MEGS of zipped art.
+
+-----------------------------------------------------------------------------
+95/08/05
+
+After a full 10 hour night of playing Blood deathmatch, we changed the
+following:
+
+Adjusted the dispersion for the tommy gun and the sawed off shotgun for
+tighter dispersal.
+
+Tweaked damage amounts for all weapons.
+
+Adjusted spray can flame speed -- if you run at full speed with it, you
+damage yourself!
+
+Added support for impact triggers to sprites.
+
+Changed impulse center of explosions to be optical center.
+
+-----------------------------------------------------------------------------
+95/08/06
+
+Added respawning for items. Check out DONOR.MAP, which is a test
+deathmatch level.
+
+-----------------------------------------------------------------------------
+95/08/07
+
+Shadows are now only drawn if they are below your view z. This should
+eliminate shadows "floating" in the air.
+
+Some code got changed which caused breakable walls to no longer change their
+state, hence you couldn't use them for triggering other events. This has
+been fixed.
+
+-----------------------------------------------------------------------------
+95/08/16
+
+Thrown dynamite no longer goes through walls.
+
+-----------------------------------------------------------------------------
+95/09/16
+* Added auto-targeting. (Does not work for missiles yet.)
+
+* Added the voodoo doll, which can hurt you if you don't have an enemy
+in your auto-targeting cone.
+
+* Added a test glow feature for the flare gun.
+
+* Added a ShowDetails option, which can be added to BLOOD.INI to enable
+or disable shadow and glow processing.
+
+* Added view mode (V and Alt-V) to view the player form a distance.
+
+* Added new status bar. (This has a some bugs when changing view size.)
+
+* Added "wall crack" object which can be used to trigger exploding walls
+when hit by dynamite. Check out MAP01.MAP.
+
+(BUG: Wall cracks can be damaged by other weapons, but will NOT trigger
+the wall explosion. Once the wall crack is taken to 0 health by a
+non-explosive weapon, the crack cannot be triggered. This will be fixed.)
+
+-----------------------------------------------------------------------------
+95/09/20
+* Added the Delirium Shroom "power-up" effect. Pick up a shroom or
+type 'QSHIGH' to test it out. Peace!
+
+-----------------------------------------------------------------------------
+95/09/26
+* Fixed the teleporter height bug.
+* Player sprites now go translucent in invisible mode. Try "QSONERING".
+* Voodoo doll is working, but one of the sequences is reversed.
+* Cultists now drop cool stuff when they die.
+
+-----------------------------------------------------------------------------
+95/11/15
+
+Uh, uh, huh, huh, huh, huh. We forgot to document stuff for like 2 months, so
+there a billion changes that probably aren't in here. Huh, huh.... huh, huh.
+
+* Dude AI added.
+
+* New network system put in. No more sync errors.
+
+* Level music added. Create new .DEF files for your maps to hear music.
+
+* Mouse input added and updated. The mouse shouldn't diminish keyboard
+movement anymore.
+
+-----------------------------------------------------------------------------
+95/12/18
+
+* Vases can "drop" objects when destroyed. (See the TESTxx maps.)
+* Timer generators added. (See XSYSTEM.TXT.)
+* Fireball generators added. (See the TESTxx maps and XSYSTEM.TXT.)
+* Padlock sprite now works. (See the TESTxx maps.)
+* Z Motion sprite sectors added (See the TESTxx maps.)
+* Linked sectors added (See PFFX.MAP: Dudes don't go through yet, but
+missiles and projectiles, and gibs(!) do.
+
+-----------------------------------------------------------------------------
+96/01/07
+
+Saw blades are now functional. They can be turned on an off, and do damage
+to anyone touching when spinning. Lots of blood, too!
+
+Change player movement code so that it uses the generic MoveDude() function.
+This allows the player to get damaged by the saw blades and other stuff that
+we add later. The negative effect of this is that I have to find a new way
+to do the view inertia effects, so as fall crumbling, stair smoothing, etc.
+You may notice some change in the feel of turning, acceleration,
+deceleration, etc. I think it feels more responsive, but let me know if you
+have any complaints, and I'll try to please! You may also notice that if you
+run and jump against a wall, you'll bounce off it, rather than keeping your
+forward momentum (like those 'other' games).
+
+Players drop all their weapons when they are killed.
+
+All dudes now can move through linked sectors.
+
+Added new explosion types. There are now three concussion types of
+explosions. Single sticks of TNT do small explosions; bundles do medium
+explosions, and barrels do large explosions. Barrels may do more damage than
+previously.
+
+All dudes are now affected by falling damage.
+
+Improved accuracy of Tommygun cultists by allowing them to aim while they are
+shooting (what a concept, eh?) They are pretty brutal monsters now, so we
+may need to tone down the damage or ROF a bit.
+
+New aiming system in place. Specifically, the auto-aim point does not track
+instantly, but sort of 'homes in' on a target. If your target is standing
+still and so are you, you should hit your target dead on. If your target is
+running a serpentine pattern you may miss him a bit unless you do a little
+bit of targeting yourself. The rate at which the auto-aim homes in is
+adjustable in the code. For some weapons, such as the tommy gun, the
+auto-aim point gets updated while you are firing. For others, such as the
+sawed-off, it doesn't. What this means is that you will get a more accurate
+shot with the sawed-off if you wait a tiny bit between each shot rather than
+just holding down the fire key. There is an aiming reticle that is present
+for the moment so you can see how this works. It will probably be taken out
+for the actual game unless people decide they really like it. To enable
+display of the reticle, type "QSAIM".
+
+QAV playback engine enhanced. The QAV engine now uses a different metaphor
+to create and display animations. It is now based on fixed frames intervals,
+with a fixed number of layers (currently 8). This means that it is much
+easier to control the z-ordering of tiles, and simple to adjust the overall
+speed of playback. It now supports scaling and rotation of sprites, so you
+should see enhancements in the weapon animations. This also allows us to cut
+down on some of the weapon art (which is really big and takes lots of
+memory).
+
+Weapon code overhauled. There are now callbacks in the weapon firing QAVs,
+so this simplifies some of the state code in the weapon system. Damage is
+now incurred at the appropriate point in the firing sequence, rather that at
+the beginning. This improvement is particularly noticeable in the pitchfork.
+Automatic weapons such as the tommy gun and the spray can now have a
+consistent rate of fire and smoother animation.
+
+Throwing of TNT enhanced. When you throw a stick or bundle with the primary
+fire key it will explode on impact, making it simpler to use. It's a bit
+unrealistic, but, hey, it's more fun. You can control how hard you throw it
+but how long you hold down the fire key. If you've played Dark Forces, this
+should be familiar to you as the same way the thermal detonators work. If
+you press the Alt-Fire key you be able to "burn off" the TNT. It will light
+and you can hold it in your hand until you throw it or it blows up in your
+face, which ever comes first. You can also drop it with the Alt-Fire key.
+When you burn off the TNT, it will explode only when the fuse burns out, not
+on impact.
+
+TNT Remote Detonators enabled. If you throw the bundles, they explode on
+impact. If you drop them, they can be triggered with the remote detonator.
+You can drop multiple bundles by pressing the Alt-Fire key when you are
+holding just the detonator.
+
+Global physics change. The relative scale of game units to world units has
+been changed. One effect of this is that everything is smaller in terms of
+world units (they still look the same, though). Another effect is that
+things will appear to fall faster. You'll notice that when you jump, you
+don't seem to hang in the air as long.
+
+There is a new status bar. Don't count on this one sticking around too long,
+though. Nobody seems to like it.
+
+Fixed the problem with dudes spinning around in circles. They should now act
+a bit more intelligent in determining where to go.
+
+Fixed problems with running on slopes. You can now jump while moving on
+slopes as well. There may be some residual problems jumping if you are
+running on a flat area and suddenly start down a steep slope, since you will
+be briefly in the air, and you can't jump while falling, but that's life!
+
+Decreased the overall game processing rate, but increased how often dudes
+think. Hopefully, this will improve the frame rate problems on network play
+and slower machines.
+
+All TNT modes enabled and working. Try out the proximity bombs and remote
+detonators -- they're great!
+
+-----------------------------------------------------------------------------
+96/01/10
+
+Fixed problems with z velocity, including bodies not falling, thrown things
+to fly in the air or down in the ground, and disappearing TNT.
+
+Changed gravity and restored world scale ratios to previous values. Gravity
+is now twice normal.
+
+Fixed problems with voodoo doll shaking like it was being held by Beavis on a
+caffeine high when the player stopped.
+
+Cultists now can aim up and down when shooting and throwing TNT.
+
+-----------------------------------------------------------------------------
+96/01/11
+
+Added a .5 second delay between when proximity triggers are activated and
+when they explode.
+
+Added a slight delay when remote detonators are triggers.
+
+Fixed problem with dudes that were on over/under areas spinning around.
+Ken's updatesector() function wasn't taking into account z position, so I
+wrote a replacement that does.
+
+Tweaked axe zombie code to make them a little smarter. Now they don't go
+into chase mode between axe swings, so they won't creep up on you until
+they're forced to go past you.
+
+-----------------------------------------------------------------------------
+
+96/01/12
+
+Sound channel manager written. This allows interface sound effects to
+preempt each other or layer as needed. This code layer will allow the 3D
+sound system to be built on top of it.
+
+-----------------------------------------------------------------------------
+
+96/01/13
+
+Basic 3D sound system in. The system does distance attenuation, but is not
+yet dynamic, and doesn't support stereo or phasing. Just a few sound effects
+are in (switch, breaking pottery, breaking glass), but it does support
+relative volume for each sound, which allows the samples to be as hot as
+possible.
+
+-----------------------------------------------------------------------------
+
+96/01/14
+
+More significant work on 3D sound code. It now supports stereo volume and
+phase effects. Still yet to do are dynamic updating of position and
+frequency shifting.
+
+Added some player sound effects for jumping, landing, falling, and pushing on
+things which don't trigger.
+
+-----------------------------------------------------------------------------
+
+96/01/19
+
+Sound system now supports fixed and dynamic 3d sound effects. Fixed location
+sounds use "bonkles" (see Meaning of Liff), which are automatically recycled
+when the sound is complete. Dynamic sounds are 1 per xsprite, and preempt
+each other automatically.
+
+Many new sound effects added, and existing sound effects changed to use
+bonkles where appropriate.
+
+Dispersion of Tommy gun increased.
+
+-----------------------------------------------------------------------------
+
+96/01/22
+
+Added flares burning in guys.
+
+-----------------------------------------------------------------------------
+
+96/01/24
+
+Player no longer stutters when injured (this was a particular problem when
+burning).
+
+Vector weapons (including melee weapons) now produce a ricochet and sound
+effect appropriate for what they hit. Try stabbing various types of surfaces
+and objects with the pitchfork to hear this -- really cool. Also, listen to
+the sounds of bullets ricocheting of walls and things when you are being
+shot.
+
+-----------------------------------------------------------------------------
+
+96/01/25
+
+Weapons tweaked for damage: spray flame, shotgun, tommy gun.
+
+Starbust flare added in alt fire for flare gun.
+
+New fonts added for messages.
+
+Added auto aiming for TNT barrels.
+
+Added gaussian grouping for player shotgun vectors.
+
+Reduced sizes of TNT explosions.
+
+-----------------------------------------------------------------------------
+
+96/01/28
+
+Credits screen roughed in with new font.
+
+General optimizations to code, and tweaks made to improve 3D sound system.
+
+Keyboard control code modified to make it much more like Doom. Hopefully
+I've got it right this time. Turning uses a two-step threshold for
+rotational velocity, and player (and dude) movement use drag rather than
+friction to slow down.
+
+-----------------------------------------------------------------------------
+
+96/01/30
+
+Added diving suit mask, with (detail option) translucent reflections.
+
+-----------------------------------------------------------------------------
+
+96/02/01
+
+Major optimization to Ken's cansee() function, which was taking up around 20%
+of execution time (in worst cases). This new optimization speeds it up by
+about 50%, which is quite noticeable.
+
+-----------------------------------------------------------------------------
+
+96/02/02
+
+MAJOR COOL NEW FEATURE! Vector masking for masked walls and face sprites!
+What this means is that objects and walls can be partially blocking of
+bullets depending on their shapes. For example, you can shoot through the
+bars of a grate, but still be able to hit the grate itself. This is still
+pretty unoptimized, so expect some potential speed hits -- also, there's
+loads of debugger output.
+
+Debris gibs added. These are pretty rudimentary for now, but we'll be adding
+tables for all the different types of debris soon. Currently, when dudes
+explode, they spawn 20 little "worm" giblets.
+
+-----------------------------------------------------------------------------
+
+96/02/03
+
+Drip generators changed so that spawned drips start at the bottom of the
+sprite, rather than the z center.
+
+Parallaxed skies now preload correctly.
+
+Re-enabled falling for all things and dudes, and also optimized the z motion
+calculations. The result is that all things now fall properly, and the frame
+rate is back where it is supposed to be.
+
+Optimized vector masking so it only happens for vector weapons. This
+prevents is from slowing the AI down. We still need to add a replacement for
+hitscan to test for arbitrary blocking bits. I'll be asking Ken about this.
diff --git a/DEBUG.TXT b/DEBUG.TXT
new file mode 100644
index 0000000..c03d174
--- /dev/null
+++ b/DEBUG.TXT
@@ -0,0 +1,424 @@
+DPMI Memory report:
+ Largest Block Avail: 20402176
+ Max Unlocked Pages: 4981
+ Max Lockable Pages: 4981
+ Linear Addr Pages: 5724
+ Num Unlocked Pages: 4981
+ Num Free Pages: 4987
+ Total Physical Pages: 5724
+ Free Linear Pages: 4987
+ Page File Pages: -1
+QHeap: attempting allocation of 19924K
+QHeap: allocated 19876K
+Reading external resource file: FOG.FLU
+Reading external resource file: CANFIRE.QAV
+Reading external resource file: BLOOD.PAL
+Reading external resource file: FIRE.PAL
+Reading external resource file: RFIRE.CLU
+Reading external resource file: PALETTE.DAT
+Reading external resource file: VGA.CFG
+kmalloc(2400) = 01976AD1
+kmalloc(2400) = 01976161
+kmalloc(64000) = 01966751
+14 tile files opened
+Preload weapon QAV tiles
+USRHOOKS_GetMem allocated 32 bytes at 018aae91
+USRHOOKS_GetMem allocated 32 bytes at 018aae61
+Reading external resource file: DRIPS.MAP
+Preload dude sequence tiles
+evInit()
+trInit()
+Trigger start broadcast
+Coop start broadcast
+Preload floor and ceiling tiles
+Preload wall tiles
+Preload sprite tiles
+Initializing player 0
+Resetting player 0
+0 mirrors initialized
+WeaponRaise: weapon = 1, mode = 0
+actSpawnEffect: missing sequence
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 3
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 0
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 4
+OperateSprite for kGen Object
+Dispatching callback event to sprite 5
+OperateSprite for kGen Object
+Dispatching callback event to sprite 6
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 7
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Dispatching callback event to sprite 2
+OperateSprite for kGen Object
+Dispatching callback event to sprite 1
+OperateSprite for kGen Object
+Drip landed: z=8192, zTop=4352, zBot=8672, floorz=8192
+Drip landed: z=-4096, zTop=-7936, zBot=-3616, floorz=-4096
+MUSIC_Shutdown()
+Removing timer
+USRHOOKS_FreeMem at 018aae91
+USRHOOKS_FreeMem at 018aae61
+uninitengine()
+All subsystems shut down. Processing exit functions
diff --git a/GUI.RFS b/GUI.RFS
new file mode 100644
index 0000000..3de7052
--- /dev/null
+++ b/GUI.RFS
@@ -0,0 +1,6 @@
+mouse1.qbm PRELOAD
+uparrow.qbm PRELOAD
+dnarrow.qbm PRELOAD
+font1.qfn PRELOAD
+font2.qfn PRELOAD
+
diff --git a/LEAVE.BTM b/LEAVE.BTM
new file mode 100644
index 0000000..1a8fc97
--- /dev/null
+++ b/LEAVE.BTM
@@ -0,0 +1,10 @@
+@echo off
+iff "%1" == "" then
+ echo Missing drive parameter
+ quit
+endiff
+
+pkzip -u -P %1\blood \blood\*.* \blood\src\*.* \blood\seq\*.* \blood\qav\*.* \blood\snd\*.*
+pkzip -u -P %1\blood \blood\music\*.* \blood\tables\*.* \blood\fonts\*.*
+pkzip -u -P %1\qtools \qtools\*.* \qtools\src\*.* \qtools\h\*.*
+pkzip -u -P %1\ass \ass\*.* \ass\h\*.*
diff --git a/LEVELS.TXT b/LEVELS.TXT
new file mode 100644
index 0000000..9bf7040
--- /dev/null
+++ b/LEVELS.TXT
@@ -0,0 +1,154 @@
+#: 645-3119 [MAIL]
+ 17-Nov-95 10:46
+Sb: Level Task List
+Fm: Nick Newhard [Q Studios] 74017,163
+To: Peter Freese [Q Studios] 74170,543
+
+=============================================================================
+Status Owner Priority Date Location Description
+=============================================================================
+Open All ***** 11/16 Define the shareware Domain(s)
+Open Jwilson **** 11/16 Cut out usable sections from various levels
+into Domain types
+Open **** 11/16 Divide levels into various directories
+Play *** 11/16 C JCASTLE1 -- Playtest
+Open *** 11/16 C MAP01 -- Expand, add exit, make
+underwater
+Open *** 11/16 C MAP08 -- Expand or connect to another
+map
+Open *** 11/16 C MAP20 -- Separate large areas,
+reconnect w/ rooms not halls
+Open *** 11/16 C QSEFMF -- Expand, possible fortress
+level
+Open *** 11/16 C TMAP05 -- Expand and complete
+Open *** 11/16 C VABLD001 -- Expand and complete,
+Optimize
+Open Jwilson ** 11/16 C/P GMPIAZZA -- Keep temple, library and fountain
+Open ** 11/16 C/P JBOIL -- Castle scraps (boiler room
+and chain area)
+Open ** 11/16 C/P KEVIN1 -- Connect to other castle area
+Open ** 11/16 C/P MAP09 -- Castle parts
+Open ** 11/16 C/P MAP28 -- Castle parts
+Open ** 11/16 C/P QSEXMX -- Castle parts
+Open * 11/16 C/S GMCARNGA -- Castle scrap
+Open *** 11/16 W CALYPSO -- Expand to full map
+Open *** 11/16 W GMFERGUS -- Major relight and
+retexture, lose wood areas
+Open Jwilson *** 11/16 W JSEWER -- Resize Nick area (Playtest)
+Open *** 11/16 W MAP03 -- Connect to other areas, or
+expand, update
+Open *** 11/16 W MAP11 -- Expand sewer, dump fireplace
+Open *** 11/16 W QSEBMB -- Expand possible connect to
+map03
+Open *** 11/16 W TDAM -- Complete
+Open ** 11/16 W/P GMKALOO2 -- water parts
+Open ** 11/16 W/P MAP05 -- Water parts, some retexture
+Open ** 11/16 W/P MAP17 -- Water parts
+Open ** 11/16 W/P MAP27 -- Water Parts
+Open Jwilson *** 11/16 CR JCRYPT -- Optimize (playtest)
+Open *** 11/16 CR TLAIR -- Expand and complete
+Open *** 11/16 CR VAB002 -- Retex/resize, optimize,
+needs a theme
+Open ** 11/16 CR/P GMRUBBLE -- Cemetery/Crypt parts
+Open ** 11/16 CR/P GMKALOON -- Crypt parts
+Open Jwilson ** 11/16 CR/P GMNARNIA -- Crypt parts
+Open Jwilson ** 11/16 CR/P GMRIGMAR -- Keep stained glass crypt area
+Open Jwilson ** 11/16 CR/P GMSARNAT -- Keep Crypt areas
+Open ** 11/16 CR/P MAP10 -- Crypt part
+Open ** 11/16 CR/P TMAP04 -- Crypt part
+Open * 11/16 CR/S GMMADNES -- Optimize before use -
+Crypt scrap
+Open *** 11/16 M GMRUBBLE -- Turn into ruined mansion,
+optimize
+Open Kkilstro ** 11/16 M JHOUSE -- Resize doors, lighten and
+contrast, finish
+Open Kkilstro *** 11/16 M KREDRUMP -- Add exit, finish.
+(increase door swing)
+Open Jwilson *** 11/16 M MAP02B -- Expand/connect first area, basement
+from storage
+Open ** 11/16 M/P GMKALOO3 -- Mansion parts
+Open Jwilson ** 11/16 M/P GMKEDLES -- Mansion parts (library)
+Open ** 11/16 M/P MAP19 -- Expand
+Open ** 11/16 M/P MAP26 -- Mansion parts
+Open Jwilson * 11/16 M/S GMLORD -- Mansion Library area
+Open *** 11/16 F AREA1 -- Use as parts or expand
+Open *** 11/16 F DONOR2 -- Attach or Expand
+Open *** 11/16 F GMAQUILA -- Split caves for
+underwater, major retexture
+Open *** 11/16 F GMKINKEN -- Retexture as fortress,
+lose hedges, industrial
+Open *** 11/16 F GMSEPTIA -- Cut vines, make outdoor,
+fill out fortress, retex.
+Open *** 11/16 F GMSERVAG -- Rework tex., some rooms
+Play *** 11/16 F JALTAR --Playtest (crypt?)
+Open *** 11/16 F MAP14 -- Complete Secret area, add
+exit, optimize, test
+Open *** 11/16 F MAP31 -- Complete
+Open *** 11/16 F Map32 -- Complete
+Open ** 11/16 F TMAP02 -- Complete
+Open ** 11/16 F VAB001 -- Retex and resize, needs
+fewer tex and more room
+Open * 11/16 F/P JDTEMPLE -- Fortress parts
+Open * 11/16 F/P MAP04 -- Move river to a water map,
+possible expand
+Open * 11/16 F/P QSECMC -- Separate center area and
+hallway with grates
+Open * 11/16 F/P RIGMAR1 -- Fortress parts
+Open * 11/16 F/S GMCASTRA -- Fortress scrap
+Open *** 11/16 T GMKADATH -- Cut crappy areas to cut
+down map, finish
+Open ** 11/16 T GMMYRA -- Major rework, retexture,
+relight, Temple level
+Open Kkilstro ** 11/16 T JMAUS -- Contrast lighting, Optimize
+Open thamel ** 11/16 T MAP25 -- Complete and playtest
+Open * 11/16 T/P GMDACIA -- Temple Parts
+Open * 11/16 T/P MAP06 -- Temple Parts
+Open * 11/16 T/P MAP07 -- Use over/under, water area in
+another map
+Open * 11/16 T/P MAP12 -- Temple parts
+Open * 11/16 T/P MAP12B -- Temple Parts
+Open * 11/16 T/P MAP15 -- Temple parts
+Open * 11/16 T/P MAP16 -- Temple parts
+Open * 11/16 T/P MAP24 -- Temple parts
+Open * 11/16 T/P QSEEME -- Simplify/optimize, temple
+parts
+Open * 11/16 MI GMCASTR2 -- interesting structure
+Open * 11/16 MI GMKEDLES -- Nice general use Facade
+Open Jwilson * 11/16 MI GMPARLOR -- Scrap
+Open Jwilson * 11/16 MI GMRURITA -- Facade and Bedroom area
+Open * 11/16 MI GMTRAJAN -- Keep sewer, mansion,
+rework ceiling,furniture
+Open * 11/16 MI QSEZMZ -- Mine area
+Open * 11/16 MI TMAP03 -- Snow area
+=============================================================================
+Legend:
+=============================================================================
+Status:
+ Open = Work In Progress
+ Play = Needs only playtesting
+ Done = Done
+ Can = Cancelled
+ Hold = On hold (see notes *)
+ Pending = Needs some other item completed first (see notes *)
+_____________________________________________________________________________________
+_
+Owner:
+ All =Major game decision requiring everyone's attention
+ nnewhard = that genius-boy-programmer guy
+ jwilson = that rockin' out guy
+ thamel = that oatmeal and fruit guy
+ kkilstro = that poop guy
+ Gray = that lord guy
+ Vab = that Andrew guy
+_____________________________________________________________________________________
+_
+Priority:
+ ****** = I'm an anus-sniffer and can't use legends.
+ ***** = Must be done ASAFP
+ **** = High Priority
+ *** = Soon, but higher priority items must be done first.
+ ** = Must be done for game, but can wait until later.
+ * = Could be done later, may not make it to game.
+_____________________________________________________________________________________
+_
+
diff --git a/MAKEBARF.BTM b/MAKEBARF.BTM
new file mode 100644
index 0000000..21d62f4
--- /dev/null
+++ b/MAKEBARF.BTM
@@ -0,0 +1 @@
+barf blood -f @blood
diff --git a/MAKEFILE b/MAKEFILE
new file mode 100644
index 0000000..4d4bd5b
--- /dev/null
+++ b/MAKEFILE
@@ -0,0 +1,481 @@
+# Undefine this next line for release build
+!define RELEASE
+
+# Undefine this next line to use Causeway in all EXEs
+#!define USE_CAUSEWAY
+
+SRCDIR = SRC
+OBJDIR = OBJ
+LIBDIR = \qtools\lib;\blood\obj;\helix32
+
+.cpp.obj: .AUTODEPEND
+!ifdef RELEASE
+ wpp386 -we -wx -4r -mf -d1 -s -ox -dNOMC $[@ /fo=$(OBJDIR)\$^&
+!else
+ wpp386 -we -wx -4r -mf -d2 -s -dNOMC $[@ /fo=$(OBJDIR)\$^&
+!endif
+
+.c.obj: .AUTODEPEND
+!ifdef RELEASE
+ wcc386 -we -wx -4r -mf -d2 -ox -dNOMC $[@ /fo=$(OBJDIR)\$^&
+!else
+ wcc386 -we -wx -4r -mf -d2 -s -dNOMC $[@ /fo=$(OBJDIR)\$^&
+!endif
+
+.asm.obj: .AUTODEPEND
+!ifdef RELEASE
+ tasm -mx -m2 -q $[@ $(OBJDIR)\$^&
+!else
+ tasm -mx -m2 -zi $[@ $(OBJDIR)\$^&
+!endif
+
+# Specify the location of dependencies
+.obj : $(OBJDIR)
+.lib : $(LIBDIR)
+.c : $(SRCDIR)
+.cpp : $(SRCDIR)
+.h : $(SRCDIR)
+.asm : $(SRCDIR)
+
+BLOODOBJS = &
+ blood.obj &
+ screen.obj &
+ view.obj &
+ db.obj &
+ gameutil.obj &
+ fire.obj &
+ fframe.obj &
+ sectorfx.obj &
+ map2d.obj &
+ asstimer.obj &
+ usrhooks.obj &
+ levels.obj &
+ sound.obj &
+ sfx.obj &
+ controls.obj &
+ actor.obj &
+ player.obj &
+ replace.obj &
+ tile.obj &
+ triggers.obj &
+ eventq.obj &
+ qav.obj &
+ weapon.obj &
+ options.obj &
+ gui.obj &
+ seq.obj &
+ mirrors.obj &
+ dude.obj &
+ warp.obj &
+ credits.obj &
+ mmulti.obj &
+ ai.obj &
+ aicult.obj &
+ aigarg.obj &
+ aihand.obj &
+ aihound.obj &
+ airat.obj &
+ aispid.obj &
+ aizomba.obj &
+ aizombf.obj &
+
+
+MAPEDITOBJS = &
+ bstub.obj &
+ db.obj &
+ screen.obj &
+ gameutil.obj &
+ sectorfx.obj &
+ gui.obj &
+ replace.obj &
+ tile.obj &
+ options.obj &
+ edit2d.obj &
+ edit3d.obj &
+
+QAVEDITOBJS = &
+ qavedit.obj &
+ qav.obj &
+ gui.obj &
+ replace.obj &
+ tile.obj &
+ gameutil.obj &
+ screen.obj &
+ options.obj &
+
+SEQEDITOBJS = &
+ seqedit.obj &
+ gui.obj &
+ replace.obj &
+ tile.obj &
+ gameutil.obj &
+ screen.obj &
+ options.obj &
+
+ARTEDITOBJS = &
+ artedit.obj &
+ gui.obj &
+ replace.obj &
+ tile.obj &
+ gameutil.obj &
+ screen.obj &
+ options.obj &
+
+EDGAROBJS = &
+ edgar.obj &
+ gui.obj &
+ replace.obj &
+ tile.obj &
+ gameutil.obj &
+ screen.obj &
+ options.obj &
+
+TABLESOBJS = &
+ tables.obj &
+
+PALTOOLOBJS = &
+ paltool.obj &
+
+CONVDB6 = &
+ convdb6.obj &
+
+APEOBJS = &
+ ape.obj &
+
+SLUTOBJS = &
+ slut.obj &
+
+REMAP = &
+ remap.obj &
+
+SHPLAY = &
+ shplay.obj &
+
+SETUP = &
+ setup.obj &
+
+JOYTEST = &
+ joytest.obj &
+
+SPRITEST = &
+ spritest.obj &
+ db.obj &
+
+dummy : globals.obj blood.exe mapedit.exe
+
+all : globals.obj blood.exe mapedit.exe qavedit.exe seqedit.exe artedit.exe tables.exe paltool.exe ape.exe slut.exe remap.exe shplay.exe setup.exe joytest.exe
+
+blood.exe : $(BLOODOBJS) qtools.lib helix32.lib engine.lib blood.lnk
+ wlink @$^*
+
+mapedit.exe : $(MAPEDITOBJS) qtools.lib helix32.lib engine.lib mapedit.lnk
+ wlink @$^*
+
+qavedit.exe : $(QAVEDITOBJS) qtools.lib helix32.lib qavedit.lnk
+ wlink @$^*
+
+seqedit.exe : $(SEQEDITOBJS) qtools.lib helix32.lib seqedit.lnk
+ wlink @$^*
+
+artedit.exe : $(ARTEDITOBJS) qtools.lib helix32.lib artedit.lnk
+ wlink @$^*
+
+edgar.exe : $(EDGAROBJS) qtools.lib helix32.lib edgar.lnk
+ wlink @$^*
+
+tables.exe : $(TABLESOBJS) qtools.lib tables.lnk
+ wlink @$^*
+
+paltool.exe : $(PALTOOLOBJS) qtools.lib paltool.lnk
+ wlink @$^*
+
+convdb6.exe : $(CONVDB6) qtools.lib convdb6.lnk
+ wlink @$^*
+
+ape.exe : $(APEOBJS) qtools.lib ape.lnk
+ wlink @$^*
+
+slut.exe : $(SLUTOBJS) qtools.lib slut.lnk
+ wlink @$^*
+
+remap.exe : $(REMAP) qtools.lib helix32.lib remap.lnk
+ wlink @$^*
+
+shplay.exe: $(SHPLAY) qtools.lib helix32.lib shplay.lnk
+ wlink @$^*
+
+setup.exe: $(SETUP) qtools.lib setup.lnk
+ wlink @$^*
+
+joytest.exe: $(JOYTEST) qtools.lib joytest.lnk
+ wlink @$^*
+
+spritest.exe: $(SPRITEST) qtools.lib spritest.lnk
+ wlink @$^*
+
+globals.obj : globals.cpp globals.h qtools.lib engine.lib $(BLOODOBJS) $(MAPEDITOBJS)
+!ifdef RELEASE
+ wpp386 -w2 -4r -mf -oneatx -dNOMC $[@ /fo=$(OBJDIR)\$^&
+!else
+ wpp386 -w2 -4r -mf -d2 $[@ -dNOMC /fo=$(OBJDIR)\$^&
+!endif
+
+blood.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(BLOODOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ FILE \helix32\hgdx.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+ %append $^@ LIB audio_wf.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+
+mapedit.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(MAPEDITOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ FILE \helix32\hgdx.obj
+ %append $^@ FILE $(OBJDIR)\build.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+qavedit.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(QAVEDITOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ FILE \helix32\hgdx.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+seqedit.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(SEQEDITOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ FILE \helix32\hgdx.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+artedit.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(ARTEDITOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ FILE \helix32\hgdx.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+edgar.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ for %i in ($(EDGAROBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+tables.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ for %i in ($(TABLESOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+
+paltool.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ for %i in ($(PALTOOLOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+
+convdb6.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(CONVDB6)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+ape.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(APEOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+slut.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ for %i in ($(SLUTOBJS)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+
+remap.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(REMAP)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ LIB qtools.lib
+
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+shplay.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(SHPLAY)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE \helix32\hgd13.obj
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB audio_wf.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+setup.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(SETUP)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB mca3r.lib
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB audio_wf.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+joytest.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(JOYTEST)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ LIB qtools.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
+spritest.lnk : makefile
+ %create $^@
+ %append $^@ NAME $^&
+ %append $^@ DEBUG ALL
+ for %i in ($(SPRITEST)) do %append $^@ FILE $(OBJDIR)\%i
+ %append $^@ FILE $(OBJDIR)\globals.obj
+ %append $^@ LIB qtools.lib
+ %append $^@ LIB engine.lib
+!ifdef USE_CAUSEWAY
+ %append $^@ SYSTEM CAUSEWAY
+!endif
+ %append $^@ OPTION
+ %append $^@ VERBOSE,
+ %append $^@ REDEFSOK,
+ %append $^@ STACK=8K,
+ %append $^@ MAP=$(OBJDIR)\$^&.map,
+
diff --git a/MAPEDIT.TXT b/MAPEDIT.TXT
new file mode 100644
index 0000000..d63aace
--- /dev/null
+++ b/MAPEDIT.TXT
@@ -0,0 +1,1461 @@
+=============================================================================
+ MAPEDIT History File
+ Copyright (c) 1994-95 Q Studios Corporation
+ Contact: Peter Freese
+ pfreese@qstudios.com or CIS:74170,543
+=============================================================================
+
+================
+2D EDITING MODE:
+================
+
+HIGHLIGHTING:
+
+ The mouse cursor will highlight the nearest object within a specific
+ threshold (see MAPEDIT.INI). The selected object will flash, and any
+ XObject information will be displayed in the lower panel. Key
+ operations in 2D will apply to the selected object.
+
+
+SPRITES:
+--------
+ Sprites have various shapes in the overhead map, depending on
+ their attributes:
+
+ * Face sprites are cyan circles with a line extending from
+ the center to indicate their angle. (ref. '<' and '>')
+
+ * Thick sprites have the blocking attribute set. (ref. 'B')
+
+ * Purple sprites have the hitscan attribute set. (ref. Ctrl-H)
+
+ * Sprites with three or four lines extending from the circle
+ are wall sprites. The shorter lines indicate which sides are
+ visible. (ref. 'R' and '1' in 3D mode keys.)
+
+ * Sprites with square centers are floor sprites, and can be
+ manipulated with all the keys referred to above.
+
+WALLS:
+------
+ This section not complete.
+
+MOUSE FUNCTIONS:
+----------------
+ Mouse Move mouse cursor
+
+ LeftButton Click and hold on an endpoint to drag.
+ See RightShift and RightAlt for other options.
+ To delete an endpoint, drag that point into the
+ next point and it will be deleted. (NOTE: Grid-locking
+ must be enabled. See 'L' key below.)
+
+ RightButton Click to change the 3D view cursor position.
+
+ RightShift Press and hold to highlight endpoints and sprites in
+ a rectangular region. Release the key once the chosen
+ region is highlighted. Drag objects in the highlighted
+ area by clicking and holding the left mouse button
+ down and then dragging the mouse. Release the left
+ mouse button to drop the objects at the target
+ position. Press RightShift without dragging the mouse
+ to deselect the highlighted objects.
+
+ RightAlt Highlight sectors for duplication or dragging. Similar
+ in operation to the RightShift key, this key
+ highlights entire sectors and the sprites within them.
+ See the Insert key for information on duplication.
+
+KEYBOARD FUNCTIONS:
+-------------------
+ Arrows Moves the 3D view cursor position. The cursor will be
+ clipped against blocking wall and sprite boundaries.
+ Press the right mouse button to move the 3D view
+ cursor to a different position on the map.
+
+ ESC Brings up the menu. Press ESC again to cancel.
+
+ PadEnter Toggle between 2D and 3D editing modes
+
+ SpaceBar Press the space bar when drawing new sectors. There
+ are several ways of drawing new sectors. The
+ following three ways of drawing sectors can all be
+ done by only using the space bar. The application is
+ "smart" enough to decide which method you are using.
+
+ 1. Drawing a FULL LOOP - that is, whenever the new
+ sector meets the old sector, draw over that line
+ again. In full loop mode the new sector must not
+ already be in another sector. The loop is done
+ when you press the space bar at the first point
+ again.
+ 2. SPLITTING a sector - press space bar to draw points
+ at which you want to split a sector. The computer
+ knows you are done splitting when you end at
+ another point that's on the edge of the sector you
+ are splitting.
+ 3. Drawing a sector COMPLETELY INSIDE another sector.
+ (for example, columns) To do this, just press space
+ bar at every point in the loop. The loop is done
+ when you press the space bar at the first point
+ again.
+
+ Backspace
+ Deletes previously plotted point during sector creation. Press
+ repeatedly to get rid of all previously plotted points.
+
+ Insert Inserts a new endpoint at the midpoint of the highlighted
+ line. Then you can drag the point to wherever you like. (If
+ you insert on a red line, the point will be inserted on both
+ sides of the sector line.) If a bunch of sectors are selected
+ (see right ALT) then instead of inserted points, the selected
+ sector bunch will be duplicated (stamped). Don't forget to
+ drag the selected sectors after stamping.
+
+ Delete Use this to delete sprites. Do NOT delete marker sprites
+ created by the XSYSTEM. Instead, change the sector type.
+ NOTE: To delete endpoints from a line, DON'T PRESS DELETE.
+ Instead, drag the endpoint onto a neighboring endpoints, while
+ the grid is on. If the endpoint is dragged exactly on top of
+ the neighboring endpoint, it will automatically be deleted.
+
+ Right Ctrl-Delete
+ This deletes the whole sector that the mouse cursor is in. Use
+ this option with caution as it has been know to cause problems
+ in maps.
+
+ ScrollLock
+ Set editing starting position (reddish arrow) to your current
+ position (white arrow). Also used to define the default player
+ starting positions, but level designers are expected to place
+ player starting marker sprites in all maps.
+ NOTE: Eight co-op and eight deathmatch markers must be placed
+ on all maps. (ref. Alt-S, 3D MODE EDITING)
+
+ TAB View attributes of the sector under the cursor.
+
+ AltTAB View attributes of the wall or sprite closest to the cursor.
+
+ A/Z Zoom the 2D view in and out. Text tags may disappear when the
+ view is zoomed out. Zoom in to view them again. (ref. Ctrl-T)
+
+ B Toggles blocking attribute of walls and sprites. A blocked
+ wall or sprite will with a thick line in 2D EDIT MODE. See
+ the description for 'B' in the 3D EDIT MODE section for more
+ details.
+
+ C Turn a line into a circle defined by short line segments.
+ Press 'C' on a highlighted wall, and move the mouse cursor to
+ change the arc of the circle. Press '+' or '-' to change the
+ number of line segments in the circle. Press 'C' again to
+ cancel circle creation, or SpaceBar to accept the changes.
+ (NOTE: Grid size affects the position of the arc. Refer to 'G'
+ and 'L' below.)
+
+ E Change a sprite's status list number. Level designers should
+ not have to change a sprite's status directly, unless a bug is
+ reported when running the game.
+
+ G Change grid resolution. The grid resolution cycles through the
+ following sequence:
+ * Off : no grid
+ * 1x : 64x64 pixel grid
+ * 2x : 32x32 pixel grid
+ * 4x : 16x16 pixel grid (* DEFAULT *)
+ * 8x : 8x8 pixel grid
+ * 16x : 4x4 pixel grid (Has wall texturing problems.)
+ * 32x : 2x2 pixel grid (Has wall texturing problems.)
+
+ Use the two finest grid resolutions only for positioning
+ sprites. Wall vertices should not be closer to each other
+ than the grid lines at 8x resolution.
+
+ H Edit the high tag for the sector under the cursor.
+
+ Alt-H Edit the high tag for the wall or sprite under the cursor.
+ (See also: T and Alt-T. Level designers should not edit these
+ fields directly, as they are used internally by XSYSTEM.)
+
+ Ctrl-H Toggle the hitscan sensitivity of a sprite or masked wall.
+ Objects which are hitscan sensitive can be hit be vector
+ weapons.
+
+ L Turns grid locking on or off. If the mouse cursor is pink then
+ grid locking is on. If it is white then grid locking is off.
+ There is no grid locking if the grid is turned off. Also,
+ grid locking will lock to nearby points.
+
+ J Join two neighboring sectors. Press J when mouse cursor is
+ over the first sector. Then press J again when the mouse
+ cursor is over the neighboring sector. The attributes of the
+ combined sector will be taken from the first sector selected.
+
+ K Mark a wall or sprite for motion
+ (blue = forward motion, green = reverse motion)
+
+ M Make a wall masked
+
+ S Places a sprite at the location under the mouse cursor. Refer
+ to the SPRITES section (above) for more information.
+
+ Alt-S Convert internal white sector (single-sided linedef) to a red
+ sector (double-sided linedef). Ctrl-Delete can be used to
+ reverse this. (NOTE: Outer loop cannot be converted!)
+
+ T Edit the low tag for the sector under the cursor.
+ Alt-T Edit the low tag for the wall or sprite under the cursor.
+ (See also: H and Alt-H. Level designers should not edit these
+ fields directly, as they are used internally by XSYSTEM.)
+
+ Ctrl-T Toggle the text captions for sectors, walls and sprites.
+
+ < | > Rotate a Sprite: Rotation has a precision of 2048 degrees, and
+ sprites can have an angle value range of 0 to 2047. Pressing
+ the shift key allows a finer degree of rotation.
+ Rotate a Sector: Sectors highlighted with RightAlt can also be
+ rotated using these keys.
+
+ [ | ] Rotate a sprite without wrapping. Generally used for marker
+ sprite rotation, allowing marked sprites to rotate several
+ times throughout a busy cycle. If you don't understand this,
+ talk to Peter.
+
+ F5 Display sector specific data
+ F6 Display sprite/wall specific data
+ Alt-F5 Edit sector specific data in the editing dialog.
+ Alt-F6 Edit sprite/wall specific data in the editing dialog.
+
+ While in the editing dialog, the following keys are active:
+ Esc Exit dialog and abandon changes
+ Enter Exit dialog and save changes
+ Left Previous control
+ Sh Tab Previous control
+ Right Next control
+ Tab Next control
+ Up Increase value by 1 or move radio button up
+ Pad + Increase value by 1
+ Down Decrease value by 1 or move radio button down
+ Pad - Decrease value by 1
+ Pg Up Increase value to next multiple of 10
+ Pg Dn Decrease value to next multiple of 10
+ Space Toggle check boxes
+ F10 Get next unused channel (rxID and txID only)
+
+
+================
+3D EDITING MODE:
+================
+
+ CapsLock change zmode
+ Tab Copy to buffer
+ Enter Paste buffer
+ Sh Enter Paste shade and palette
+ Ctrl Enter Paste tile to all walls in loop
+ Sh Ctrl Enter Paste tile, shade, and palette to all walls in loop
+ Alt Enter Copy attribute (extra) information
+ < Rotate sprite CW
+ Sh < Fine rotate sprite CW
+ > Rotate sprite CCW or auto fix wall panning
+ Sh > Fine rotate sprite CCW
+ / Set x and y repeat to default values
+ Sh / Set sprite x repeat to match y repeat
+
+ 1 Toggle 1 sides sprites and 1 way walls
+ 2 Toggle bottom wall swapping
+
+ Pad +/- Brighten / darken texture
+ Ctrl Pad +/- Set texture to max/min brightness
+ Pad 0 Set texture to 0 brightness
+ Pad arrow Adjust tile repeats
+ Ctrl Pad arrow Adjust tile panning
+
+ PageUp Raise z by 4 pixels
+ Sh PageUp Raise z by 1 pixel
+ Alt PageUp Enter new floor or ceiling height
+ Alt PageDn Enter new floor or ceiling height
+ Ctrl PageUp Put sprite on ceiling /
+ Move floor or ceiling to next higher neighbor Z
+ PageDn Lower z by 4 pixels Sh PageDn Lower z by 1 pixel
+ Ctrl PageDn Put sprite on floor /
+ Move floor or ceiling to next lower neighbor Z
+ Delete Delete sprite
+
+ B Toggle blocking
+ Alt C Change all tiles matching buffer to target
+ D -/+ Adjust depth cueing level
+ E Toggle expansion of floor/ceiling texture
+ F Flip texture
+ Alt F Turn on relative alignment in the highlighted sector.
+ If already on, change the first wall in the sector.
+ Relative alignment affects textures or slopes.
+ G -/+ Adjust gamma level
+ Alt G Change Global palette
+ H Hitscan sensitivity for masked walls AND sprites.
+ L Light bomb
+ M Toggle maskable wall
+ Sh M Toggle single side maskable wall
+ O Toggle orientation or post sprite on wall
+ P Toggle parallaxing for floor or ceiling
+ Ctrl P Change sky parallaxing type
+ Alt P Change palookup
+ R Toggle floor/ceiling relative alignment or change sprite
+ rotation type
+ S Insert sprite
+ T Toggle translucency
+ V Select tile
+ Alt V Select heightmap
+ W Change lighting effect waveform
+
+ F2 Toggle state
+ F3 Show sector OFF z position
+ Alt F3 Capture sector OFF z position
+ F4 Show sector ON z position
+ Alt F4 Capture sector ON z position
+ F9 Auto stair builder. Highlight a group of sectors,
+ then point to an adjoining sector floor or ceiling
+ and press F9. The floor or ceiling you point to will
+ be the lowest step.
+ F11 Enable/disable panning
+ F12 Toggle beeps
+
+ Ctrl Alt - | + Decrease/Increase sector visibility
+ Ctrl - | + Decrease/Increase light effect amplitude
+ Shift - | + Decrease/Increase light effect phase
+ [ | ] Change floor or ceiling slope
+ \ Remove floor slope
+
+
+-----------------------------------------------------------------------------
+
+94/12/18
+
+This document created. I have renamed BUILD.EXE to MAPEDIT.EXE. I hope
+this will prevent confusion between Ken's version and our version for Blood.
+Also, this allows changes specific to MAPEDIT to be documented independently.
+For maximum viewing pleasure, this document is formatted at 78 characters
+wide, tab stops of 8, and contains no upper ASCII characters.
+
+MAPEDIT supports loading custom palette remap and translucency tables. It
+looks for NORMAL.PLU and TRANS.TLU in the currect directory. If they are
+present, it will use them; if not, it will use the defaults in PALETTE.DAT.
+
+I am phasing out the CNT files and replacing them with MPX (MaP eXtension)
+files. This files store all the Blood specific information for sprites,
+walls, and sectors. The CNT files were a hack to test out adding custom edit
+controls to BUILD. I don't think anyone was using them for anything, but
+just in case you were, MAPEDIT will automatically convert the CNT files and
+remove them when you save changes.
+
+For a discussion of the various fields and their meanings, see the
+TRIGGERS.TXT document.
+
+At present, only the sprite editing fields are active. Wall and sector
+editing will be completed shortly.
+
+-----------------------------------------------------------------------------
+
+94/12/28
+
+Completely replaced Ken's keyboard handler with my own. This will allow key
+repeat to work for all functions, so now you can just hold down the pad plus
+key to lighten a sprite, etc. This also gives me the ability to add new
+functionality at will, without waiting for Ken to add a new function. To
+this end, I've added stub code for Alt-P while in 3D mode. Currently, this
+just gives you the tile selection screen, but it will be used for editing
+tiled parallaxing skies.
+
+An assertion failure that would occur when an invalid reference was noted
+between a map and an mpx file has been changed. The invalid reference will
+now be simply deleted. This allows you to delete the mpx file if you want to
+trash all the additional map data.
+
+-----------------------------------------------------------------------------
+
+94/12/30
+
+Due to some problems discovered in trying to handle keystrokes from within a
+secondary ISR (Ken's code doesn't handle it well, and I can't work around it
+because his functions are not reentrant), I've adopted a new strategy for
+adding to key functions. I'm still using my own keyboard handler, so you'll
+still get the benefit of key repeat.
+
+I'm now 'subclassing' Ken's editinput() function by renaming his function to
+kensinput() by directly modifying the object file. This is a true hack, but
+it should work cleanly. However, I'll need to make the same change to future
+versions of build.obj unless I can convince Ken to make a similar change.
+
+Just to prove that it works, I've added a few extra key functions for 3D
+mode:
+
+Alt-L while the cursor is on a sprite will set the sprite's shade to -42 (the
+maximum brightness without side effects).
+
+Alt-0 while the cursor is on a sprite will set the sprite's shade to 0. This
+should cause the sprite to have the same shade as the sector it is in (good
+for floor and ceiling sprites).
+
+I was going to add a function to build stairs automatically, but some of
+Ken's variables for handling highlighted sectors are static and I can't
+access them. I'll try to get him to change this and add this in a later
+version.
+
+-----------------------------------------------------------------------------
+
+95/01/02
+
+Make sure you run CONVMAP6 on ALL your maps. The easiest (DOS) method
+to accomplish this is to type:
+
+for %1 in (*.map) do convmap6 %1
+
+Peter implemented a raw auto stair builder. Create your stair sectors in
+2D mode, without (unnecessarily) changing the heights in 3D mode. In 2D mode,
+use the R.Alt key to select all the stairs you want to auto-height adjust.
+In 3D mode, point to a sector outside the selected stairs that you want use
+as the base height for the stairs and press F9. Enter the stair height in
+pixels. Use 8 or 12 pixels or risk death by immolation; other stair heights
+will be too tall to walk over. Expect more cool additions in the future!
+
+Peter made changes to the width of the character, as well as height at
+which things can be walked over. If you notice any peculiarities let him
+know.
+
+Peter has also added sector lighting effects to BUILD, but they have not
+been documented. Also, they don't work in Blood yet, but will very soon.
+The next update you get will be probably executables only.
+
+Note from Nick: Try to make your bonus, one-up and weapon sprites
+non-blocking. Thanks!
+
+-----------------------------------------------------------------------------
+
+95/02/06
+
+Key handling has changed (again), and is now much cleaner, thanks to the
+changes Ken made to his code during his visit with us. I'll no longer have
+to keep hacking build.obj.
+
+Along with Ken's changes, a bug crept in which would cause F5-F8 to crash if
+you pressed them in 3D mode. This has been fixed.
+
+Sector lighting effects are now functional and work in Blood as well. You
+can edit them in 2D mode or 3D mode. See the new keys documented above. I
+may be making another change in the implementation of sector lighting to
+allows walls to be modified, but this shouldn't affect sectors which you have
+already worked on, so bang away on this feature.
+
+The MPX format has been enhanced again. It now contains version information,
+so should be easier to update periodically. Older format files are
+automatically converted when loaded. With this new version, you'll be able
+to add author name and level name to the mpx files (editing not yet
+implemented, though.) Global visibility is now saved in the mpx file and
+properly loaded in Blood.
+
+You can now tile parallax skies. See the new keys documented above.
+
+-----------------------------------------------------------------------------
+
+95/02/08
+
+Updated sector shading effects code. Instead of storing the base floor and
+ceiling shade, the MPX structure now has a single field which is the
+current relative shade. By adding and subtracting this offset, it is
+possible to do relative shading effects to floors, ceilings, and walls
+without extra storage. The only overhead is some additional processing time,
+since I now call DoSectorLighting() and UndoSectorLighting() once per frame.
+
+The sectorExt dialog now has checkboxs for shading floors, ceilings, and
+walls, so it is possible to apply lighting effects to any or all of these.
+Also, for lighting effects to take place, the sector status must be ON. I'm
+going to get the message system working Real Soon Now, and the first thing
+I'll test is turning on and off lighting effects.
+
+A major bug was fixed in the code that ensured referential integrity between
+the sprite, sector, wall and respective Ext structures. I believe that this
+was THE bug that was causing the occasional weird sector lighting values.
+Please let me know ASAP any other problems of this nature.
+
+When loading maps, all item and creature sprites are automatically
+sized to their proper x and y repeats. This is intentional, and will ensure
+uniformity between levels. There is no such thing as a miniature zombie!
+
+Sprite Ext structures are automatically added for creatures when maps are
+loaded. This adds the proper type and view flags based on picnum.
+
+-----------------------------------------------------------------------------
+
+95/02/14
+
+Did a major restructuring of source code. This was a sorely needed cleanup
+to produce organization out of a loose collection of hacks. This should make
+future changes and additions much easier.
+
+Fixed a problem with the IncBy and DecBy inlines functions which caused
+weird step values when you used the PageUp and PageDn keys in dialogs.
+
+-----------------------------------------------------------------------------
+
+95/02/18
+
+Parallax sky tiling should be working again. I had an incorrect assumption
+about the way pskybits works. I thought it represented the width of the sky
+tiles, but it actually indicates the number of tiles. Go figure.
+
+I've assigned the ext editing functions to the Alt combinations of F5 and F6
+instead of F7 and F8. I'm limited to F5-F8 in 2d mode for adding features,
+so I'm going to overload these keys for all their worth. Prepare for the
+worst.
+
+Rewrote initialization code so that it uses the same module as Blood. You'll
+now see the same initialization diagnostic messages in both. This means that
+MAPEDIT also uses the same gamma correct and sin tables as Blood, and loads
+information from the BLOOD.INI file.
+
+MAPEDIT loads the gamma correction from and writes changes to the BLOOD.INI
+file. Do not use F11 to change the gamma level. Instead use the G +/-
+combination, the same as in Blood. The gamma level is displayed momentarily
+when changed.
+
+Changed key combination to change the depth cueing (visibility) to D +/- for
+compatibility with BLOOD.
+
+-----------------------------------------------------------------------------
+
+95/02/18
+
+Fixed a bug in the key handler which caused the (G)oto command to stop
+working in the tile selection screen. The solution was to change the key
+combination for this to Alt-G. Essentially, anything you used G for
+previously, use Alt-G for now (except setting gamma correction).
+
+-----------------------------------------------------------------------------
+
+95/02/27
+
+This version includes a substantial rewrite of ALL 3D editing keys. Please
+report anything that's not working as it should.
+
+PageUp and PageDn in 3D mode now support fine movement (1 pixel) and coarse
+movement (4 pixels). Use shift for fine movement. It did this before, but
+not correctly all the time due to the way I was intercepting the keys.
+Sprite fine z adjustment now works for highlighted sprites. Coarse z
+adjustment now moves to the next multiple of 4 pixels, rather than in steps
+of 4. You can no longer move a floor above a ceiling or vice versa.
+
+Fixed coarse sprite rotation so it moves to the next multiple of 256.
+Fine rotation now moves to the next multiple of 16 instead of 1.
+
+Changed screen capture key to PrintScreen. Now I can use F12 for something
+more useful.
+
+You can no longer wrap shades by going too dark or too light. Shades are now
+correctly clipped at their max values.
+
+Changed keys for setting shade to 0, min, or max. The key combinations for
+setting texture brightness are now Ctrl Pad + for max brightness, Ctrl Pad -
+for min brightness, and Pad 0 for 0 brightness. I would have done this
+originally, but I couldn't without rewriting a fair amount of the 3d editor
+code (which I now have done). These key combinations now present a
+consistent metaphor: Pad +/- is exclusively used for brightness; and Control
+in combination with a step key sets a min or max.
+
+Fixed a bug in which any sprite inserted that had a tile height of 32
+pixels or more would automatically be set to blocking. It will now
+automatically set sprites to blocking only if there is something other than a
+sprite in the tab buffer.
+
+Added some clicks and beeps for feedback. Let me know if they are too
+annoying.
+
+Ctrl Enter on a parallax floor or ceiling will now paste to ALL parallaxed
+floors or ceilings, not just neighboring sectors.
+
+The key to auto adjust panning will no longer change orientation of any
+walls. This means that some walls will not automatically be aligned, but you
+would have had to fix these walls anyway.
+
+This version of MapEdit includes a new 2D animation editor. You start this
+editor by using F7. See the new animator editor keys documented at the top
+of this file. Call me if you need help. Level designers do not need to use
+this tool. The animation editor will probably not work in unchained mode or
+screen buffer mode. Try it in these modes at your own risk. You have been
+warned, Nick!
+
+-----------------------------------------------------------------------------
+
+95/03/05
+
+Fixed a bug which would cause extended keys (e.g. Page Up and Delete) to be
+dropped if NumLock was on. This bug was introduced when I added code to
+prevent filling up the keyboard buffer. These keys are actually transmitted
+as multiple make codes when Num Lock is on, hence the bug.
+
+Fixed a bug in the debug version of the heap. Sheesh! It would dassert out
+occasionally when it was unable to allocate a block when it thought it had
+enough memory. I wasn't subtracting out the space used for the fenceposts.
+
+MapEdit now uses our heap and resource caching system, so it should lurch
+less.
+
+I replaced the gettile() function, so now tile histories for walls, ceilings,
+floors, masked walls, face sprites, and flat sprite can all be maintained
+independently. Tile selection should now be significantly faster.
+
+-----------------------------------------------------------------------------
+
+95/03/06
+
+Added an assertion to check for overflow of the ext arrays. I found out that
+some maps had sectorExt structures for nearly every sector, and were
+overflowing the array!
+
+-----------------------------------------------------------------------------
+
+95/03/24
+
+BUG FIXES
+
+Fixed problem with allocatepermanenttile not marking the block of memory as
+locked. It could get added to the purge list during precaching....
+
+Added some missing modify [...] registers to an inline function in the
+dynamic fire system. This would inevitably cause problems when
+optimizations were enabled.
+
+Fixed the hang on exit problem. Ken's unintengine function was closing
+STDIN. Replaced his function to fix.
+
+-----------------------------------------------------------------------------
+
+95/03/26
+
+Fixed a bug with adjust x and y repeats of wall textures (you couldn't). You
+can now also match x and y repeats by pressing shift / on a wall.
+
+Added preliminary light bomb.
+
+-----------------------------------------------------------------------------
+
+95/04/02
+
+When switching between 2D and 3D mode, I now do a reference check for all ext
+structures. This will ensure that any editing done in 2D mode is
+reflected immediately in 3D mode.
+
+Ctrl-Enter will no longer copy shade or palette values. If you want to copy
+shade and palette values to every wall in a loop, use Shift-Ctrl Enter. Note
+that the previous function of Shift-Ctrl Enter has been removed.
+
+-----------------------------------------------------------------------------
+
+95/04/07
+
+MAJOR FORMAT CHANGE. This version requires you to run CONVDB3 on all your
+maps. This new format eliminates the need for separate MAP and MPX files,
+and adds many new fields, support for which will be added *soon*.
+
+You can now adjust the floor or ceiling z of a red sector from outside the
+sector. Just point to one of the walls and press page up or down. This new
+feature makes it easy to open closed vertical doors.
+
+Increased angle resolution of light bomb. Expect it to be about twice as
+slow.
+
+-----------------------------------------------------------------------------
+
+95/04/07
+
+BUG FIX. There was a problem which resulted in an incorrect file CRC if you
+deleted sprites and then saved. I wasn't checking the statnum of sprites
+which had an attached xsprite.
+
+BUG FIX. If you started MAPEDIT and created a new map, all sectors would have
+an extra field of 0 instead of -1. This caused an assertion failure when you
+tried to enter 3d mode. This is a kenbug which I worked around by
+initializing all extra fields to -1. There is still a problem if you create a
+new map after editing another map. When you add sectors, they will get extra
+references to old xsectors, xwalls, and xsprites, respectively.
+
+BEHAVIOR CHANGE. The program now automatically adds xsprites for all sprites
+and sizes them correctly before saving.
+
+-----------------------------------------------------------------------------
+
+95/04/07
+
+BUG FIX. All extra fields of 0 are now set to -1 on loading. I'll fix this
+nasty bug yet!
+
+BUG FIX. Hooked my unitialization routine into exit so that keyboard
+interrupt and timer rate gets restored, even on crashes. This should cure
+the occasional and bothersome runaway clock.
+
+-----------------------------------------------------------------------------
+
+95/04/18
+
+Separated code for tile selection into it's own module.
+
+Made the sequence editor use the new tile picking code.
+
+-----------------------------------------------------------------------------
+
+95/04/31 - NCN
+
+BUG FIX. Fixed a bug that screwed up masked walls. MAPPERS: You may not have
+seen this bug since it occurred in an interim version.
+
+The H key now toggles the hitscan value in 3D mode for sprites as well as
+masked walls. Use Ctrl-H in 2D mode.
+
+The Alt F key combo was added in 3D mode to change the relative alignment of a
+sector. Press it once to make a sector relative aligned. The key may be
+pressed again (and again) to change the first wall in a sector. To check which
+is the first wall, press TAB in a sector and note the first wall number, then
+press Alt-TAB on each wall to figure out which one it is.
+IMPORTANT NOTE: Only use Alt-F in sectors NOT containing other sectors!
+
+Added crude menus to sector dialog. To edit an extended dialog box,
+highlight one of the menu selections and press Enter. When you are done
+editing in the extended dialog box, press Enter to save the dialog changes
+and return you to 2D editing mode. If you wish to go back to the menu,
+press Escape. Any changes you have made will be held until you either press
+Enter again, or press Escape to abort any changes you have made.
+
+Added a txID verification key to the sector and sprite trigger dialog boxes.
+When the txID field is highlighted, enter a number and press the '?' key. This
+will tell you whether the txID you entered is used and by what. I intend to
+expand on this for the rxID field and add the same features to the wall
+dialog.
+
+Added wind currents to sectors. There are now 8 hard-coded wind levels, which
+are selectable by changing the wind field in the sector dialog. If the field
+is 0, then no wind is generated in that sector.
+
+Added water current data to sectors, which can be edited in the sector dialog.
+First select a water depth from 0 to 3: 0=no current, 1=tread, 2=wade, 3=swim.
+Next select the speed and angle. If a sector has relative alignment set, the
+initial current angle will be determined by the angle of first wall in the
+sector, and the 'angle' field will is added to the wall angle.
+
+Added key support for certain sector effects. Change the key field in a sector
+to indicate that the sector requires a key to operate.
+
+MAPPERS: We will be moving from the tag system to the new extended system, a
+few effects at a time. Please watch for changes in my maps and this text file
+to show you which effects have changed. Some things, like one-shot triggers,
+don't work yet, but set the fields anyway for compatibility and so you
+won't have to go back and edit them later. For now, you should be able to go
+back through all your maps and make switches and keys function, and that
+should eat up a few days. Good luck!
+
+Check out the following sector types: Lift, Swingdoor, Topdoor,
+Bottomdoor, and Floor Pad. Use my example TRIGGERS.MAP to figure some of it
+out, or call me if you need to. Eventually we'll have docs explaining how this
+stuff works.
+
+-----------------------------------------------------------------------------
+
+95/05/02 - NCN
+
+Fixed trigger bug with floor pads. Check out the expanded physical trigger
+flags for sprites and sectors. MAPPERS: Make sure the following things are
+applied to your maps: 1) All switches should be non-blocking with the
+Push and/or Impact triggers set. 2) Door sectors currently need the Push
+flag set. This will change as soon as wall triggers are done. 3) Lifts will
+need the Push flag set to function while the player is in the sector, unless
+a switch is installed at the top and bottom of the lift, either inside or
+outside the shaft, up to you. The switch sprite will not move with the lift.
+
+You can use the physical triggers with ANY sector type, including NORMAL.
+Physical triggers for sectors of type NORMAL will not change state when
+the trigger is activated. The "only once" trigger now works with the NORMAL
+type of sector. Expect the same additions to all sector types soon.
+
+BUGS/PROBLEMS:
+* Lifts no longer work when the action key is pressed while inside,
+though they will activate if you are next to them. This will be fixed
+in the next version.
+
+* Wall flags are still not implemented. Expect them also in the next
+version. These will be used to signal door sectors to open.
+
+NOTES:
+
+* Shrink all switches to 40x40. I'll try and add this in next MAPEDIT.
+* Shrink all gargoyle statues to 40x40. Same deal.
+* Shrink all chairs to 40x40. Okay, this one too.
+
+-----------------------------------------------------------------------------
+
+95/05/07
+
+When relative sector alignment is set with Alt-F, flipping and xy swapping is
+cleared. This should fix water current problems.
+
+-----------------------------------------------------------------------------
+
+95/05/08
+
+Added new fields to sector effects dialog. Redid all dialogs in character
+coordinates and tightened up display. Non-tab control now are highlighted.
+
+Add F2 to toggle state in 3D mode.
+
+-----------------------------------------------------------------------------
+
+95/05/10
+
+Added display of wave form when 'W' pressed in 3D mode.
+
+Modified xsector and xsprite dialogs to access new waitTime and busyTime
+fields. These fields control the speed of door movement (time in .1 secs),
+and the delay before reset (time in .1 secs). If your doors or momentary
+switches don't seem to be working, check the values in these fields.
+
+Added xwall dialog.
+
+-----------------------------------------------------------------------------
+
+95/05/12
+
+Provided a workaround for a memory allocation bug which would cause the
+editor and blood to hang in any mode which required screen buffering. This
+workaround will NOT work in stereo display modes -- I need an engine update
+from Ken to fix this.
+
+-----------------------------------------------------------------------------
+
+95/05/19
+
+Added trouble-shooting section at top.
+
+
+-----------------------------------------------------------------------------
+
+95/05/24
+
+Added lots of great new stuff that will be documented shortly. Take a look at
+the sample maps to see how most of the effects are accomplished. You will
+notice that MAPEDIT has changed significantly.
+
+BLUE "SPRITES" are controls for SLIDE type sectors, and come in pairs. GREEN
+"SPRITES" are controls for ROTATE type sectors.
+
+Use the controls to make rotating and sliding doors or other gimmicks. If you
+delete a control accidently it will be reinserted, though the angles and
+position are not retained. Sorry. Don't delete controls by accident. :)
+
+Here is a mini-tutorial on how things work, please run the maps then look at
+them before you post a bunch of questions. Everything should be the same for
+raising and lowering sector doors.
+
+For a swinging door:
+* Create a red-line sector for a door and raise it to the height you want it.
+* NOTE: Doors should be created in the OPEN position.
+* Use AltF5 to select a sector type of "Rotate" in the sector dialog.
+* WARNING: Do NOT select the "Swing door" type. It won't work.
+* Set the triggers, keys, and busyTime/waitTime EFFECTS in the same dialog.
+* NOTE: A green control is automatically inserted (visible in 2D mode only!)
+* Place the control on the pivot point of the sector.
+* Change the angle of the green control to point right. O---->
+* Now, adjust the angle of the green control to be the angle it needs rotate
+ to OPEN from the CLOSED position. This is a bit confusing but you'll get
+ the hang of it. Look at the swinging doors in MAP01-MAP04.
+
+For a sliding door:
+* Okay, I'll send this to you guys in e-mail. There is a simple example in
+ MAP01.MAP. Look for the BLUE ARROW. Yes! The blue arrow points in the
+ direction of movement. (Let the other teams try and beat that!)
+
+More on sliding doors later, as well as morphing walls. For now just
+experiment with triggers and slide/rotate walls. Good luck.
+
+-----------------------------------------------------------------------------
+
+95/05/28
+
+Changed code that automatically inserts marker sprites so they start out at
+angle 0. O----> This only effect markers in rotate sectors.
+
+-----------------------------------------------------------------------------
+
+95/05/29
+
+Added combination switches. These are multi-way switches which change to the
+ON state when their data field equals the value set in lotag. The sprite's
+hitag contain the number of values that the switch will cycle through. These
+switches can be used for combination locks, or even counters. See my map
+FXTEST for examples of both.
+
+Changed the way that slide sectors work so you only need to tag the walls
+that you want to move, and the code figures out which vertices need to be
+updated. No more stretching wall bullshit.
+
+Added support for sprite link messages so a master combination switch can
+control slave switches.
+
+-----------------------------------------------------------------------------
+
+95/05/30
+
+Created MAPEDIT.INI for storing various edition options.
+
+Fixed an overflow problem which could cause long lines to be unselectable.
+Overhauled line selection so it is zoom dependent, and controlled by a
+configurable distance threshold, HighlightThreshold in [Options] of
+MAPEDIT.INI.
+
+Fixed a lot a subtle problems in the light bomb, which would cause certain
+reflections to be calculated incorrectly. I also (hopefully) eliminated
+problems with overflow in the ray tracing algorithm. Sometimes fixed point
+math sucks. You can control various aspects of the light bomb through
+configuration options in MAPEDIT.INI.
+
+Changed Ctrl-Shift-Enter so it only pastes shade and palette to all walls in
+a loop. Previously, it would copy over textures as well. This is the way
+that I really intended it to work.
+
+-----------------------------------------------------------------------------
+
+95/05/31
+
+YES! I think I found a solution for maintaining marker sprites that will
+ensure references don't get hosed. In level designer terms, this means you
+won't occasionally lose slide or rotate markers.
+
+-----------------------------------------------------------------------------
+
+95/06/01
+
+This time I _really_ think I've fixed the marker sprite problem. There
+shouldn't be any changes required to your maps, and the marker sprites should
+stay put. Let me know any problems ASAP.
+
+-----------------------------------------------------------------------------
+
+95/06/02
+
+Moved troubleshooting section to the new XSYSTEM.TXT file, which will
+document the trigger system, sector, wall, and sprite effects, and other
+crucial information for level designers. This file will continue to document
+editor changes, but won't discuss specific aspects of effects.
+
+-----------------------------------------------------------------------------
+
+95/06/04
+
+Fixed a mouse selection problem which would cause the wrong texture to be
+copied instead if the mouse was at an extreme edge of the screen. The bug is
+in Ken's code, so the only thing I can do is limit the mouse in by a pixel
+from all the edges.
+
+You can now use PageUp and PageDn on the lower wall of a bi-textured wall to
+move a red sector up and down.
+
+Player height is now identical in MapEdit and Blood. Just remember to put
+your switches lower in areas where the player will be in the water.
+
+Added sector visibility keys: Press CtrlAlt+ and CtrlAlt- to change the
+sector visibility values. Don't abuse this feature or Nick will kill you.
+
+-----------------------------------------------------------------------------
+
+95/06/04
+
+========================================================================
+ !! READ THIS FIRST !! (please)
+========================================================================
+Save ALL of your maps before running the new conversion utility.
+
+Once you've saved your maps in another directory or ZIP file, run the
+CONVDB4.EXE conversion utility. You can use wildcards, so typing "CONVDB4
+*.MAP" is acceptable.
+
+Next, run "MAKEPLU 64 96" before viewing, editing or playing any maps.
+========================================================================
+
+* From now on all Door, Lift and moving stair types should be created using
+the "Z Motion" sector type. (These are automatically converted when CONVDB4
+is run on version 3.x maps.) The Z values cannot yet be viewed in the 2D
+dialogs, but are displayed when created or changed in 3D mode. NOTE: Lift
+types will be converted, but you must manually go in and set on and off Z for
+each of them. Sorry. It was this, or linking the entire engine into the
+converter, or wasting time rewriting code.
+
+To create and set the default on and off Z values for a "Z Motion" sector:
+
+First, make the sector a "Z Motion" sector using the Alt-F5 dialog.
+
+Position the sector ceiling and floor heights where you want them to be when
+the sector is in the OFF position. For doors, it is easiest to make OFF the
+CLOSED position. Point at the Z Motion sector (ceiling, floor, adjacent
+non-white wall, or interior white wall) and press Alt F3 to set the OFF Z
+values. Pressing Alt F3 will set the Z values for both the ceiling and floor,
+so you don't have to press it twice.
+
+Now position the ceiling and floor heights where you want them to be when the
+sector is in the ON position. (Best in the OPEN position.) Point at the "Z
+Motion" sector and press Alt F4 to set the ON Z values. Again, this only has
+to be done once, as it will autmoatically set the ceiling and floor ON Z
+values with one keypress.
+
+To view the positions, point at the sector with the mouse cursor, hold the
+left mouse button down and press F3 and/or F4 to view the OFF and ON Z
+values, respectively.
+
+* New graphics for the pod creature and rat have been added, but they don't
+have MAPEDIT values in place. That means their captions won't yet show up in
+2D mode, nor will they be sized correctly.
+
+* Dialogs have changed a bit, and will change more in the future. At least
+this time they all look more alike. Live with it.
+
+-----------------------------------------------------------------------------
+
+95/06/13
+
+Changed 3D movement so you are no longer blocked by the ceiling in sectors
+with parallaxed skies. Moving way 'up there' can give you an interesting
+perspective on (and hopefully new ideas for) your map.
+
+Rewrote the GUI. You probably won't see any significant visible changes, but
+please report any problems.
+
+-----------------------------------------------------------------------------
+
+95/06/30
+
+Added menu to insert game objects. From now onw, press Alt-S instead of S to
+insert game object sprites. You can still use S for ornaments like torches,
+weeds, etc. NOTE: Hazard insertion is not done yet.
+
+If you have not already done do, run CONVDB4 on all your maps. You should
+then load and save *every* map to reset the object types. The only sprites
+that are converted in MAPEDIT (during loading and saving) are listed below.
+If you have a creature sprite not using one of these tiles, fix it
+immediately or risk certain death.
+
+# Name Creature
+896 kAnmPhantasmM1 Phantasm
+1170 kAnmZomb1M1 AxeZombie
+1270 kAnmHellM2 HellHound
+1280 kAnmHellM1 HellHound (also)
+1370 kAnmZomb2M1 FatZombie
+1470 kAnmGargoyleM1 FleshGargoyle
+1530 kAnmGargStatue FleshGargoyle Statue
+1570 kAnmGillM1 GillBeast
+1595 kAnmGillM2 GillBeast (also)
+1745 kAnmRatM1 Rat
+1792 kAnmPodM1 GreenPod
+1797 kAnmTentacleM1 GreenTentacle
+1835 kAnmEelM2 Eel
+1870 kAnmEelM1 Eel (also)
+1920 kAnmSpiderM1 RedSpider
+1948 kAnmBatM2 Bat (sleeping)
+1950 kAnmBatM1 Bat (flying)
+1980 kAnmHandM1 Hand
+2680 kAnmCerberusM1 Cerberus
+2820 kAnmCultistA1 TommyCultist *
+2860 kAnmCultistM1 ShotgunCultist *
+
+-----------------------------------------------------------------------------
+
+95/07/03
+
+Maps of version 4.00 are auto converted to version 4.01. The conversion
+involves adjusting the z position of wall and face sprites to handle the new
+origin alignent mode.
+
+Added new lighting type, Flicker3. This is a mostly on, flakey flourescent
+light sort of flicker effect.
+
+Added our tile preloading code from Blood to MapEdit, so you should have less
+lurching due to tiles not being in memory.
+
+-----------------------------------------------------------------------------
+
+95/07/08
+
+Rewrote "O" code to paste sprite onto a wall. Let me know if you still have
+occasional problems ornamenting sprites (where it won't go onto the wall).
+
+-----------------------------------------------------------------------------
+
+95/07/09
+
+Fixed a problem where marker sprites would occasionally get converted to
+other sprite types if they had a picnum which got recognized by Nick's
+conversion code.
+
+Improved performance of 2D display code by eliminating unnecessary math for
+non-displayed red walls.
+
+Now that sprite FIFO code is working, the insertsprite cover function now
+sets all new sprites to the following defaults:
+
+ sprite[].cstat = kSpriteOriginAlign;
+ sprite[].shade = 0;
+ sprite[].pal = 0;
+ sprite[].clipdist = 32;
+ sprite[].xrepeat = 64;
+ sprite[].yrepeat = 64;
+ sprite[].xoffset = 0;
+ sprite[].yoffset = 0;
+ sprite[].picnum = 0;
+ sprite[].ang = 0;
+ sprite[].xvel = 0;
+ sprite[].yvel = 0;
+ sprite[].zvel = 0;
+ sprite[].owner = -1;
+ sprite[].lotag = 0;
+ sprite[].hitag = 0;
+ sprite[].extra = -1;
+
+This should help cure some of the problems caused by sprites being inserted
+with bogus values in the fields.
+
+Renabled CRC checking for loaded maps. Let me know if you have any problems
+in loading some of your converted maps.
+
+ALERT! Ken made some clipping fixes to the engine which may cause sprites
+that are exactly on the wall to be non-visible. This is especially true with
+red walls. Changed the ornament function ("O" or "S" while pointing at a
+wall) so that sprites are offset from walls by 1/4th a pixel distance. You
+should go through your maps and re-ornament all walls. Unfortunately,
+there's not an easy way to do this automatically.
+
+Ornamented sprites are set by default to non-blocking and 1 sided.
+
+All dudes (creatures) are set to non-blocking and non-hitscan in the editor.
+This makes it a lot easier to move around in 3D mode. Don't worry, the
+status bits are automatically set to the appropriate values in the game.
+
+-----------------------------------------------------------------------------
+
+95/07/17
+
+Fixed a bug with ammo names. Load and save all your maps. We apologize for
+this inconvenience.
+
+-----------------------------------------------------------------------------
+
+95/07/20
+
+ !!! MAP VERSION CHANGE !!!
+
+Once you've backed up all your maps in another directory or ZIP file, run the
+CONVDB5.EXE conversion utility. You can use wildcards, so typing "CONVDB5
+*.MAP" is acceptable. The old maps will be saved with an extension of ".MA4".
+
+This update adds fields necessary for implementing creature AI. You'll see
+fields added to the XSprite dialog shortly.
+
+Fixed a bug in the code to auto align wall textures -- it would occasionally
+get caught in a loop and bail out after a certain number of iterations. Now
+it should correctly detect when it is looping around walls.
+
+You can now press F10 while editing the rxID and txID fields, and it will
+automatically fill in the next unused channel.
+
+Object type is now stored in the former lotag field, and object mass is
+stored in the former hitag field. Do NOT edit lotag or hitag manually.
+
+Fixed a crash bug that would occur if you pressed Enter in 2D mode while a
+wall was not highlighted.
+
+Optimized the sectorofwall() function, which probably won't make much of a
+difference to anybody.
+
+-----------------------------------------------------------------------------
+95/07/27
+
+* Added Ctrl-PgUp / Ctrl-PgDn support (3D mode) for sectors / walls
+=============================================================================
+When pointing at single-sided walls, the ceiling of the sector is modified.
+When pointing double-sided walls, the sector ceiling or floor on the other
+side of the highlighted wall is modified.
+
+Ctrl-PgUp will set the highlighted sector ceiling to the next higher neighbor
+ceiling z, or floor to the next higher neighbor floor z.
+
+Ctrl-PgDn will set the highlighted sector ceiling to the next lower neighbor
+ceiling z, or floor to the next lower neighbor floor z.
+
+* Added Alt-PgUp / Alt-PgDn support (3D mode) for sectors / walls
+=============================================================================
+When pointing at single-sided walls, the ceiling of the sector is modified.
+When pointing double-sided walls, the sector ceiling or floor on the other
+side of the highlighted wall is modified.
+
+Alt-PgUp will bring up a dialog box where you can manually type in the pixel
+height of the ceiling or floor from its counterpart.
+
+In other words, if you Alt-PgUp on a floor, you can type in the floor's
+height from the ceiling. If you Alt-PgUp on a ceiling, you can type in the
+ceiling's height from the floor.
+
+Alt-PgUp and Alt-PgDn work the same way. If you can think of a cooler way to
+do this, let me know.
+
+Since not all of the monsters are selectable in 3D mode using the Alt-S object
+menu, use Alt-S to first insert the closest monster (e.g. flesh gargoyle
+instead of a stone gargoyle) then go into 2D mode and use AltF6 on the sprite
+then select a new type. This should be fixed soon, when all the monster types
+will be selectable through a list box.
+
+-----------------------------------------------------------------------------
+95/08/01
+
+Fixed bug with sector joining. This bug was caused by us while trying to fix
+a bug in the build.obj editor code that wasn't checking for invalid sectors
+and walls when joining sectors. We still have the error checking code in, but
+we the check is less strict than it was, since the strict check was causing
+the code to bail out before joining sectors.
+
+-----------------------------------------------------------------------------
+95/08/02
+
+Added what is hopefully the final fix for the resource/map caching problems
+we were experiencing on machines with > 8MB. All map changes should be now
+be flushed correctly, and the maps should reload without a problem.
+
+Ctrl-Alt-Plus & Ctrl-Alt-Minus:
+
+Made sector visibility keys work with sector highlighting, so you can now
+highlight sectors with the RightAlt key in 2D mode, then go to 3D mode and
+change the visibility of all highlighted sectors.
+
+-----------------------------------------------------------------------------
+95/08/03
+
+Fixed hitscan blocking
+
+Added 3D mode messages to wall and sprite x/y repeat keys.
+
+Added 3D mode messages to wall x/y panning keys.
+
+Added some new sample maps and a sample map definition file.
+
+-----------------------------------------------------------------------------
+95/08/04
+
+Reverted to the previous keyboard method of handling sub-dialogs. To exit
+from the sector effects sub-dialog and save changes, press ENTER. To return
+to the xsector dialog, just press ESC. Sorry for the inconvenience.
+
+-----------------------------------------------------------------------------
+95/08/06
+
+Added respawning for items. Check out the sprite dialog using AltF6.
+Monsters and barrels do not respawn yet, but they will soon.
+
+-----------------------------------------------------------------------------
+95/08/07
+
+MapEdit is now MUCH faster in 2D mode, at the expense of a little memory
+overhead. The speedups should be particularly noticeable in maps where there
+were a lot of captions being displayed.
+
+-----------------------------------------------------------------------------
+95/08/08
+
+Tile #457 has been removed. Use tile #356 with palette 5 to emulate #457.
+We apologize for this inconvenience
+
+-----------------------------------------------------------------------------
+95/08/09
+
+Added support for moving only marked walls and sprites within motion sectors.
+To mark a wall or sprite for motion, press the "K" key while highlighting the
+object in 2D mode. When the object is blue, it will move forward (in the
+direction of the movement arrow, or the angle direction of the axis marker).
+When it is green, it will move in the opposite direction.
+
+You can also mark sprites for motion in Z Motion sectors. If the sprite is
+blue, it will move with the floor. If it is green, it will move with the
+ceiling.
+
+Change the key for marking walls for motion from "M" to "K". This was
+done to allow setting walls to masked in 2D mode with the "M" key. This
+change actually occured a few revs back, but I forget to document either key
+-- sorry.
+
+Changed the color of marker sprites to green/yellow to yellow/white to
+distinguish them from sprites marked with "K". Marker sprites normally
+appear yellow. If the cursor is in the sector they apply to, they are shown
+in white.
+
+There are now three different types of slide and rotate sectors. The first
+type, Slide Marked and Rotate Marked, will move only marked walls and
+sprites. The second type, Slide and Rotate, will move ALL walls and marked
+sprites. This is useful for sectors where you don't want to mark all the
+walls individually. The third type, Slide Crush and Rotate Crush, isn't yet
+implemented, and acts like the move ALL types.
+
+-----------------------------------------------------------------------------
+95/08/16
+
+You can now rotate sectors more than 180 degrees! Use the [/] keys to rotate
+a sprite in 2D mode without wrapping at 0 and 2048. All axis markers
+for doors which rotate CCW are automatically set to negative angles. You'll
+understand how this works when you take a look at the markers.
+
+Slide sectors can rotate! Just set the start and end angles in the on and
+off marker sprites. Rotation will occur around the center, as determined by
+the relative position of the ON marker.
+
+-----------------------------------------------------------------------------
+95/08/16
+
+Added and refined descriptions at the top of this document, mainly duplicating
+material from the older BUILD.TXT file.
+
+-----------------------------------------------------------------------------
+95/08/20
+
+Updated Alt-S game object insertion to use new keys, weapon and dude tiles.
+
+-----------------------------------------------------------------------------
+95/08/22
+
+Added mirrors to our game. Supports up to 64 mirrors per level. Mirrors
+cannot reflect other mirrors, so keep them far apart. Mirrors are created
+using a 1-way masked walls on red lines (double-sided linedefs.) The area
+behind the mirror must be the same size and shape as the area in front of
+the mirror. Apply the "mirror" texture (I think #504) to 1-way walls to
+make them a mirror.
+
+-----------------------------------------------------------------------------
+95/08/29
+
+*** CONFIDENTIAL! DO NOT DISCUSS THE FOLLOWING UPDATE! ***
+We've added slopes to our game. Slopes will be in Blood, Duke,
+Ruins & Shadow Warrior. The 3D mode keys used to add or modify
+slopes are listed above. See [, ], \, and Alt-F
+
+-----------------------------------------------------------------------------
+95/09/05
+
+Added hazards and miscellaneous sprites to the Alt-S menu. Check out
+inserting coop mode starting positions in the Misc menu. NOTE: Inserting
+switches using this menu isn't working yet.
+
+Added simple one-way teleporters to the editor. Create a teleport sector and
+a teleport marker will be inserted. Move that teleporter to the destination.
+For two-way teleporters, create the same at the destination and drag the
+marked to the first teleporter. Switched teleporters may work, but I haven't
+tested them yet. This could make for some interesting deathmatch play where
+you need a temporary ally.
+
+-----------------------------------------------------------------------------
+95/09/10
+
+Modified the way that Alt-F key works. You can now select the first wall of a
+sector by pointing at it in 3D mode and pressing Alt-F. Pointing at the floor
+or ceiling and pressing Alt-F will make the first wall the next wall in
+clocking order.
+
+You can disable all panning with the F11 key. This is useful for aligning the
+textures of panAlways sectors and walls.
+
+-----------------------------------------------------------------------------
+95/09/15
+
+When you insert a new sprite, if a sprite is not already in the tab buffer,
+you are given the opportunity to pick the tile for the sprite. Pressing
+Escape will allow you to abort the sprite insertion. The ability to abort
+also applies to inserting game objects with Alt-S.
+
+The Light bomb has been fixed. Because some of the math has been changed,
+you will need to update some of the constants in your MAPEDIT.INI file. Here
+are my default constants:
+
+ [LightBomb]
+ Intensity=4 ; controls how bright the light is
+ Reflections=3 ; maximum number of reflections
+ Attenuation=0x0000 ; how much light gets diminished
+ MaxBright=-4
+
+Intensity controls how bright the light source is. You can increase this
+value if you don't like pressing L a lot to brighten up a room, but you won't
+get as much fine control with it then.
+
+Reflections is the number of times each light ray is allowed to bounce. If
+this value is 0, no reflections are calculated (and the process is much
+faster). More reflections can help by creating more diffuse lighting
+effects.
+
+Attenuation controls how much the light is diminished when it reflects off a
+wall or floor. A value of 0 is a perfect reflection, and a value of 0x10000
+would result in non-reflective walls.
+
+MaxBright sets the limit on how bright the light bomb is allowed to make a
+wall. By prevent the wall shade from getting too bright, you retain the
+ability to see some of the dynamic sector lighting effects, which won't be
+visible is the shade is too much < 0.
+
+Texture alignment code overhauled! I think this new version should do
+everything you could want it to do (with reason). It now follows multiple
+paths, aligning everything connected with the same texture perfectly. It
+aligns red walls correctly, regardless of where the texture is pegged, and
+even knows about slopes. You will be amazed. :-)
+
+For the sake of speed and congruity, I changed the key strokes for adjusting
+wall x/y repeat and x/y panning. The arrows on the numeric keypad now
+adjust the panning of wall textures by a factor of 8. If you want fine
+alignment, use the Shift key (sound familiar?) To adjust the wall scale,
+press the 5 key on the numeric keypad while pressing the arrows. This change
+should make it a little easier to manually adjust texture panning, since you
+won't have to hold so many darn keys down.
+
+For those of you with ingrained editor habits (or who have to use BUILD for
+other projects), you can retain the old keypad behavior by adding the
+following to the [Options] section of your MAPEDIT.INI file:
+
+ [Options]
+ OldKeyMapping=1
+
+This works the same way except for sprites, in which the function of the pad
+5 is reversed. I did it this way because you seldom (if ever) need to adjust
+the x/y offset of sprites.
+
+-----------------------------------------------------------------------------
+95/09/16
+
+Added a timer event generator to the sprite type list. See the
+appropriate section in XSYSTEM.TXT.
+
+-----------------------------------------------------------------------------
+95/11/26
+
+Fixed a rather subtle but NASTY map screw up bug which would occur if you tried
+to use Alt-F on a ceiling or floor of sector 0. I intruced this bug when we
+went to the new map version. Sorry about that. :-( If you are curious what
+caused this, I made wall->point2 an unsigned value, not realizing that it can
+go negative during wall order rotation.
+
+
+-----------------------------------------------------------------------------
+95/12/27
+
+Modified dialog boxes, adding new fields like interruptable, lock, etc.
+Added dude-specific flags to the sprite dialog box.
+
+-----------------------------------------------------------------------------
+96/01/07
+
+Fixed sprite insertion and z movement functions so that sprites will go on
+floors and ceilings correctly.
+
+Changed '/' key so that is resets flip bits for floors and ceilings.
+
+Changed Alt-C so that it will change palookups if the picnum is the same in the
+tab buffer.
+
+Fixed an assertion bug that would occur when dragging highlighted points.
+
+-----------------------------------------------------------------------------
+96/01/16
+
+Added WaterDrip generator code.
diff --git a/MERGE.BTM b/MERGE.BTM
new file mode 100644
index 0000000..3cb20ed
--- /dev/null
+++ b/MERGE.BTM
@@ -0,0 +1,47 @@
+@echo off
+setlocal
+
+iff "%1"=="" then
+ echo Usage: MERGE mergeDir
+ echo.
+ echo MERGE eliminates files in mergeDir which are duplicated locally and moves
+ echo files which are newer than local copies.
+ goto end
+endiff
+
+if NOT ISDIR backup md backup
+
+echo Backing up source directory...
+set backupName=%@INSTR[0,2,%_DATE]%@INSTR[3,2,%_DATE]%@INSTR[0,2,%_TIME]%@INSTR[3,2,%_TIME]
+pkzip -o -P backup\%backupName makefile src\*.asm src\*.h src\*.c* *.doc *.txt *.btm *.rfs names.h
+
+set lastAge=%@FILEAGE[UPDATE.DAT]
+
+echo Processing files...
+for %a in (%1\*.*) (
+ set b=%@filename[%a]
+ if NOT exist %b set b=src\%@filename[%a]
+ if NOT exist %b set b=seq\%@filename[%a]
+ if NOT exist %b set b=qav\%@filename[%a]
+ iff exist %b then
+ echos Comparing %a:
+ vcmpr %a %b nul > nul
+ iff %? == 1 then
+ echo Same -- removing %a
+ del %a /q
+ else
+ iff %@FILEAGE[%b] LT %lastAge .AND. %@FILEAGE[%a] GT %@FILEAGE[%b] then
+ echo Replacing with newer
+ move %a %b /q
+ else
+ echo Different
+ endiff
+ endiff
+ endiff
+)
+
+echo Merge complete. Remaining files in %1 must be merged manually.
+
+:end
+endlocal
+
diff --git a/NAMES.H b/NAMES.H
new file mode 100644
index 0000000..bfd998d
--- /dev/null
+++ b/NAMES.H
@@ -0,0 +1,372 @@
+//Be careful when changing this file - it is parsed by Editart and Build.
+#define kPicStainGlass1 75
+#define kPicStainGlass2 76
+#define kPicGlass 266
+#define kPicWoodGib1 460
+#define kPicWoodGib2 461
+#define kPicCrateFace 462
+#define kPicBloodGib1 467
+#define kPicMetalGrate1 483
+#define MIRROR 504
+#define kPicPotion 517
+#define kPicTome1 518
+#define kPicDocBag 519
+#define kPicSkullGrail 520
+#define kPicGoblet 521
+#define kPicBlackChest 522
+#define kPicWoodChest 523
+#define kPicFlareGun 524
+#define kPicVoodooDoll 525
+#define kPicShadowGun 526
+#define kPicGun4 527
+#define kPicGun5 528
+#define kPicGun6 529
+#define kPicSpearGun 539
+#define kBurnableTree1 542
+#define kBurnableTree2 547
+#define kPicSpears 548
+#define kPicTorchPot1 550
+#define kPicTorchSconce2 554
+#define kPicTommyGun 558
+#define kPicShotgun 559
+#define kPicTorchStand1 560
+#define kPicTorchStand2 564
+#define kPicTorchSconce1 570
+#define kPicTorchPot2 572
+#define kPicTorchBowl1 574
+#define kPicTNTStick 588
+#define kPicTNTPak 589
+#define kAnmFloatCandles 590
+#define kPicTome2 598
+#define kPicCandle 600
+#define kAnmSkullCandle 601
+#define kAnmExplode1 610
+#define kPicSprayCan 618
+#define kPicShotShells 619
+#define kAnmGlowSpot1 624
+#define kPicTome3 639
+#define kPicVase2 642
+#define kAnmExplodeVase2 643
+#define kAnmFlame 650
+#define kAnmCircSaw1 655
+#define kAnmSeaPlant 660
+#define kAnmSeaweed 664
+#define kAnmBubbleStream 668
+#define kAnmSmoke1 672
+#define kAnmHead 680
+#define kPicCarnage1 682
+#define kPicDeathMask 683
+#define kPicCarnage2 684
+#define kPicCarnage3 685
+#define kPicCarnage4 686
+#define kPicCarnage5 687
+#define kPicCarnage6 688
+#define kPicCarnage7 689
+#define kPicCarnage8 690
+#define kPicCarnage9 691
+#define kPicCarnage10 696
+#define kPicCarnage11 697
+#define kPicCarnage12 698
+#define kPicMirror1On 710
+#define kPicMirror1Off 711
+#define kPicBloodPuddle1 713
+#define kAnmBloodSquib1 725
+#define kPicBloodSplash1 731
+#define kPicBloodPuddle 732
+#define kAnmBloodDrip1 733
+#define kPicVase1 739
+#define kAnmExplodeVase1 740
+#define kAnmSmoke2 754
+#define kPicRedChair 758
+#define kPicBottle1 759
+#define kPicCrystalBall 760
+#define kAnmGoldCoin 762
+#define kAnmCloakNDagger 768
+#define kAnmCircSaw2 772
+#define kAnmBloodSpray 775
+#define kPicGlowFeet 778
+#define kAnmGlowFeet 779
+#define kAnmFeather 783
+#define kPicFluorescent 796
+#define kPicBottle2 800
+#define kPicSilverGrail 803
+#define kPicTNTBox 809
+#define kPicTNTRemote 810
+#define kPicTNTTimer 811
+#define kPicShellBox 812
+#define kPicBullets 813
+#define kPicBulletBox 814
+#define kPicBulletBoxAP 815
+#define kPicFlares 816
+#define kPicTommyDrum 817
+#define kPicFlareHE 818
+#define kPicFlareBurst 819
+#define kPicSpearExplode 820
+#define kPicMedPouch 822
+#define kPicEssence 823
+#define kPicInvulnerable 825
+#define kPicInviso 826
+#define kPicJumpBoots 827
+#define kPicRavenFlight 828
+#define kPicGunsAkimbo 829
+#define kPicDivingSuit 830
+#define kPicGasMask 831
+#define kPicRandomUp 832
+#define kPicGuillotine 835
+#define kPicDecoy 836
+#define kPicAsbestosSuit 837
+#define kPicRoseGlasses 839
+#define kPicShroom1 840
+#define kPicShroom2 841
+#define kPicShroom3 842
+#define kPicShroom4 843
+#define kAnmTNTProxArmed 844
+#define kPicTNTProx 848
+#define kAnmTNTRemArmed 849
+#define kAnmDoppleganger 851
+#define kAnmClone 863
+#define kAnmEctoSkull 870
+#define kPicCarnage13 895
+#define kAnmInviso 896
+#define kPicCarnage14 906
+#define kPicTNTBarrel 907
+#define kAnmFlame3 908
+#define kPicBloodSplash2 915
+#define kAnmSpinSpikes 916
+#define kPicCarnage15 924
+#define kPicSlimePuddle 926
+#define kPicBloodSplash3 929
+#define kPicPadlock 948
+#define kAnmFloorSpike 968
+#define kAnmExplode2 984
+#define kAnmH2OSplash1 1014
+#define kAnmWhirlpool 1019
+#define kAnmWater 1024
+#define kPicScorch 1045
+#define kPicSwitch7Off 1046
+#define kPicSwitch7On 1047
+#define kPicSwitch6Off 1048
+#define kPicSwitch6On 1049
+#define kPicBlockWeb 1069
+#define kPicSwitch4Off 1070
+#define kPicSwitch4On 1071
+#define kPicSwitch1Off 1072
+#define kPicSwitch1On 1073
+#define kPicSwitch2Off 1074
+#define kPicSwitch2On 1075
+#define kPicSwitch3Off 1076
+#define kPicSwitch3On 1077
+#define kPicSwitch5Off 1078
+#define kPicSwitch5On 1079
+#define kAnmPendulum 1080
+#define kAnmRipWeb 1087
+#define kAnmStainedGlass 1106
+#define kAnmSnakeFloor 1116
+#define kPicWallCrack 1127
+#define kPicBubble 1128
+#define kAnmBubbles 1131
+#define kAnmGlass 1138
+#define kPicWoodBeam 1142
+#define kAnmBlockWood 1143
+#define kAnmDrip 1147
+#define kAnmElectrify 1156
+#define kPic4WaySwitch 1164
+#define kAnmZomb1M1 1170
+#define kAnmZomb1R1 1200
+#define kAnmZomb1M2 1209
+#define kAnmZomb1D2 1214
+#define kAnmZomb1D3 1221
+#define kAnmZomb1D4 1225
+#define kAnmZomb1D5 1227
+#define kAnmZomb1D6 1230
+#define kAnmZomb1D1 1240
+#define kPicGibZomb1Head 1259
+#define kPicGibHand1 1267
+#define kPicGibMeat1 1268
+#define kPicGibMeat2 1269
+#define kAnmHellM2 1270
+#define kAnmHellR1 1275
+#define kAnmHellM1 1280
+#define kAnmHellA1 1305
+#define kAnmHellD2 1320
+#define kPicGibHoundFoot 1326
+#define kPicGibEye1 1327
+#define kPicGibGargHead 1361
+#define kPicGibGargWing 1369
+#define kAnmZomb2M1 1370
+#define kAnmZomb2D1 1400
+#define kAnmZomb2R1 1408
+#define kAnmZomb2A1 1418
+#define kAnmZomb2D2 1443
+#define kPicGibEye2 1454
+#define kPicGibHand2 1455
+#define kPicGibMeat3 1456
+#define kAnmBoneClub 1462
+#define kAnmGargoyleM1 1470
+#define kAnmGargoyleM2 1490
+#define kAnmGargoyleM3 1491
+#define kAnmGargoyleA1 1500
+#define kAnmGargStatue 1530
+#define kAnmGargStatueOn 1535
+#define kAnmGargSeat 1538
+#define kAnmGargoyleD1 1539
+#define kAnmGillD1 1564
+#define kAnmGillM1 1570
+#define kAnmGillM2 1595
+#define kAnmGillA1 1620
+#define kAnmGillA2 1635
+#define kAnmGillR1 1650
+#define kAnmGillR2 1655
+#define kAnmGillD2 1660
+#define kAnmGillGore1 1665
+#define kPicBatHeadGib 1692
+#define kAnmRollBarrel 1701
+#define kAnmRatD2 1736
+#define kAnmRatI1 1740
+#define kAnmRatI2 1741
+#define kAnmRatDead2 1744
+#define kAnmRatM1 1745
+#define kAnmRatD1 1765
+#define kAnmRatDead1 1770
+#define kPicPod1Gib 1771
+#define kAnmPodD2 1785
+#define kAnmPodDead2 1791
+#define kAnmPodM1 1792
+#define kAnmTentacleM1 1797
+#define kAnmTentacleM3 1809
+#define kAnmTentacleM2 1813
+#define kAnmPodD1 1815
+#define kAnmPodDead1 1820
+#define kAnmTentacleD1 1821
+#define kAnmTentacleDead 1825
+#define kAnmEelM2 1835
+#define kAnmEelM1 1870
+#define kPicFootBlood 1902
+#define kPicFootWater 1903
+#define kPicFootMud 1904
+#define kPicFootSnow 1905
+#define kPicFootSewage 1906
+#define kAnmSpiderM2 1910
+#define kAnmSpiderM1 1920
+#define kAnmSpiderM1B 1925
+#define kAnmSpiderM1C 1930
+#define kAnmSpiderM1D 1935
+#define kAnmSpiderA2 1940
+#define kAnmSpiderA1 1943
+#define kAnmBatM2 1948
+#define kAnmBatM1 1950
+#define kAnmBatD1 1975
+#define kAnmHandM1 1980
+#define kAnmHandM2 2005
+#define kAnmHandA1 2008
+#define kPicHandA1 2011
+#define kAnmHandD1 2014
+#define kAnmTNTBundle 2028
+#define kAnmTNTStick 2036
+#define kPicMenuSelect 2045
+#define kPicMenu 2046
+#define kPicQLogo 2047
+#define kPicDisk 2048
+#define kPicPubLogo 2050
+#define kAnmRicochet1 2063
+#define kAnmSnow 2091
+#define kAnmFlame2 2101
+#define kSeqSprayCan 2110
+#define kAnmLighterSpark 2111
+#define kAnmLighterFlame 2112
+#define kAnmRain 2115
+#define kAnmFlareBurn 2123
+#define kAnmSpear 2130
+#define kPicSpear 2135
+#define kPicSpearBroken 2136
+#define kAnmButcherKnife 2138
+#define kAnmArrow 2159
+#define kAnmTNTBurnOff 2169
+#define kAnmEctoBall 2177
+#define kPicMachineGun 2178
+#define kAnmSprayFlame 2187
+#define kFontRedNum 2190
+#define kPicStatBar 2200
+#define kStat2Face 2201
+#define kStat2Stats 2202
+#define kStat2StatRed 2203
+#define kStat2StatBlue 2204
+#define kStat2Ammo 2205
+#define kStat2Lust 2206
+#define kStat2LustBar 2207
+#define kPicBeast 2210
+#define kPicNoBox 2211
+#define kPicBoxOff 2212
+#define kPicBoxOn 2213
+#define kFontTinyNum 2214
+#define kPicBlackMeter 2224
+#define kPicRedMeter 2225
+#define kPicBlueMeter 2226
+#define kPicFoes 2227
+#define kPicFood 2228
+#define kPicBoxSelect 2229
+#define kPicLust1 2230
+#define kStat2LustHeart 2234
+#define kPicLustOff 2238
+#define kPicLustOn 2239
+#define kPicJack 2240
+#define kAnmJacksDeath 2290
+#define kAnmJackM1 2324
+#define kPicDeadDuke 2325
+#define kPicFire 2342
+#define kMaskDivingSuit 2344
+#define kMaskReflect1 2346
+#define kMaskReflect2 2347
+#define kMaskFireSuit 2358
+#define kAnmFlare 2424
+#define kAnmFlareHalo 2427
+#define kAnmReflectShots 2428
+#define kAnmLifeSeed 2433
+#define kPicLightning3 2516
+#define kPicStart1 2522
+#define kPicStart2 2523
+#define kPicStart3 2524
+#define kPicStart4 2525
+#define kPicStart5 2526
+#define kPicStart6 2527
+#define kPicStart7 2528
+#define kPicStart8 2529
+#define kPicCombiFrameA 2530
+#define kPicCombiFrameB 2531
+#define kPicCombiLock1 2532
+#define kPicCombiLock2 2533
+#define kPicCombiLock3 2534
+#define kPicCombiLock4 2535
+#define kPicCombiLock5 2536
+#define kPicCombiLock6 2537
+#define kPicKey1 2552
+#define kPicKey2 2553
+#define kPicKey3 2554
+#define kPicKey4 2555
+#define kPicKey5 2556
+#define kPicKey6 2557
+#define kFontTech 2560
+#define kAnmCerberusM1 2680
+#define kAnmCultistA1 2820
+#define kPicShotgunIdle 2825
+#define kAnmCultistA2 2830
+#define kAnmCultistM1 2860
+#define kAnmCultistM2 2890
+#define kAnmCultistD2 2910
+#define kAnmCultistA3 2915
+#define kAnmCultistR1 2925
+#define kAnmCultistD1 2930
+#define kAnmBeastM1 2960
+#define kPicEarthIdle 3054
+#define kAnmPhantasmM1 3060
+#define kAnmPhantasmA1 3085
+#define kAnmTchernobogM1 3140
+#define kAnmRachelM1 3141
+#define kAnmGlassGib1 3261
+#define kAnmGlassGib2 3265
+#define kAnmGlassGib3 3269
+#define kAnmGlassGib4 3273
+#define kAnmGlassGib5 3277
+#define TILTBUFFER 3998
+#define BALLBUFFER 3999
+#define MIRRORLABEL 4000
diff --git a/NEWINI.TXT b/NEWINI.TXT
new file mode 100644
index 0000000..b9585ba
--- /dev/null
+++ b/NEWINI.TXT
@@ -0,0 +1,3 @@
+[Sound Setup]
+MusicDevice=0
+MusicVolume=255
diff --git a/QAV.RFS b/QAV.RFS
new file mode 100644
index 0000000..7901ba7
--- /dev/null
+++ b/QAV.RFS
@@ -0,0 +1,225 @@
+# ---------------------------------------------------------------------------
+# Miscellaneous QAV sequences
+# ---------------------------------------------------------------------------
+alias qav\hand.qav 0x00 PRELOCK # hand choking player
+alias qav\spider.qav 0x01 PRELOCK # spider on player's face
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: Lighter
+#
+# BASE: 0x100
+# ---------------------------------------------------------------------------
+alias qav\liteopen.qav 0x100 PRELOCK # weapon up
+alias qav\liteclo2.qav 0x101 PRELOCK # weapon down
+ # weapon reload
+alias qav\liteidle.qav 0x103 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\liteflam.qav 0x106 PRELOCK # weapon attack 1
+
+#liteclos.qav #are these still necessary?
+#litesprk.qav #are these still necessary?
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: Pitchfork
+#
+# BASE: 0x110
+# ---------------------------------------------------------------------------
+alias qav\forkup.qav 0x110 PRELOCK # weapon up
+alias qav\forkdown.qav 0x111 PRELOCK # weapon down
+ # weapon reload
+alias qav\forkidle.qav 0x113 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\pfork.qav 0x116 PRELOCK # weapon attack 1
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: FlareGun
+#
+# BASE: 0x120
+# ---------------------------------------------------------------------------
+alias qav\flarup.qav 0x120 PRELOCK # weapon up
+alias qav\flardown.qav 0x121 PRELOCK # weapon down
+ # weapon reload
+alias qav\flaridle.qav 0x123 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\flarfire.qav 0x126 PRELOCK # weapon attack 1
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: Shotgun
+#
+# BASE: 0x130
+# ---------------------------------------------------------------------------
+alias qav\shotup.qav 0x130 PRELOCK # weapon up
+alias qav\shotdown.qav 0x131 PRELOCK # weapon down
+alias qav\shotl1.qav 0x132 PRELOCK # weapon reload
+alias qav\shoti1.qav 0x133 PRELOCK # weapon idle full
+alias qav\shoti3.qav 0x134 PRELOCK # weapon idle empty
+alias qav\shoti2.qav 0x135 PRELOCK # weapon alternate idle
+alias qav\shotf1.qav 0x136 PRELOCK # weapon fire 1
+alias qav\shotf2.qav 0x137 PRELOCK # weapon fire 2
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: TommyGun
+#
+# BASE: 0x140
+# ---------------------------------------------------------------------------
+alias qav\tomup.qav 0x140 PRELOCK # weapon up
+alias qav\tomdown.qav 0x141 PRELOCK # weapon down
+ # weapon reload
+alias qav\tomidle.qav 0x143 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\tomfire1.qav 0x146 PRELOCK # weapon fire 1
+alias qav\tomfire2.qav 0x147 PRELOCK # weapon fire 2
+alias qav\tomfire3.qav 0x148 PRELOCK # weapon fire 3
+alias qav\tomfire4.qav 0x149 PRELOCK # weapon fire 4
+
+# toms1.qav -- toms14.qav STRAFING - how will this be handled?
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: Dynamite, STICK
+#
+# BASE: 0x150
+# ---------------------------------------------------------------------------
+alias qav\dynpre.qav 0x150 PRELOCK # weapon up
+alias qav\dyndown.qav 0x151 PRELOCK # weapon down
+ # weapon reload
+alias qav\dynidle1.qav 0x153 PRELOCK # weapon idle full
+ # weapon idle empty
+alias qav\dynfusea.qav 0x155 PRELOCK # weapon alternate idle
+alias qav\dynlita.qav 0x156 PRELOCK # weapon fire 1
+alias qav\throw.qav 0x157 PRELOCK # weapon fire 2
+ # weapon fire 3
+ # weapon fire 4
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: Dynamite, BUNDLE
+#
+# BASE: 0x160
+# ---------------------------------------------------------------------------
+alias qav\dynpre2.qav 0x160 PRELOCK # weapon up
+alias qav\dyndown2.qav 0x161 PRELOCK # weapon down
+ # weapon reload
+alias qav\dynidle2.qav 0x163 PRELOCK # weapon idle full
+ # weapon idle empty
+alias qav\dynfuseb.qav 0x165 PRELOCK # weapon alternate idle
+alias qav\dynlitb.qav 0x166 PRELOCK # weapon fire 1
+alias qav\throw.qav 0x167 PRELOCK # weapon fire 2
+ # weapon fire 3
+ # weapon fire 4
+
+# ---------------------------------------------------------------------------
+# WEAPON: Dynamite, PROXIMITY
+#
+# BASE: 0x170
+# ---------------------------------------------------------------------------
+alias qav\proxup.qav 0x170 PRELOCK # weapon up
+alias qav\proxdown.qav 0x171 PRELOCK # weapon down
+ # weapon reload
+alias qav\proxidle.qav 0x173 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\throw.qav 0x176 PRELOCK # weapon fire 1
+
+# ---------------------------------------------------------------------------
+# WEAPON: Dynamite, REMOTE
+#
+# BASE: 0x180
+# ---------------------------------------------------------------------------
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: SprayCan
+#
+# BASE: 0x190
+# ---------------------------------------------------------------------------
+alias qav\canpref.qav 0x190 PRELOCK # weapon up
+alias qav\candown.qav 0x191 PRELOCK # weapon down
+ # weapon reload
+alias qav\canidle.qav 0x193 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\canfire1.qav 0x196 PRELOCK # weapon fire 1
+alias qav\canfire2.qav 0x197 PRELOCK # weapon fire 2
+alias qav\canfire3.qav 0x198 PRELOCK # weapon fire 3
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: SpearGun
+#
+# BASE: 0x200
+# ---------------------------------------------------------------------------
+alias qav\sgunup.qav 0x200 PRELOCK # weapon up
+alias qav\sgundown.qav 0x201 PRELOCK # weapon down
+ # weapon reload
+alias qav\sgunidl1.qav 0x203 PRELOCK # weapon idle full
+alias qav\sgunidl2.qav 0x204 PRELOCK # weapon idle empty
+ # weapon alternate idle
+alias qav\sgunfire.qav 0x206 PRELOCK # weapon fire 1
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: ShadowBlaster
+#
+# BASE: 0x210
+# ---------------------------------------------------------------------------
+alias qav\darkup.qav 0x210 PRELOCK # weapon up
+alias qav\darkdown.qav 0x211 PRELOCK # weapon down
+ # weapon reload
+alias qav\darkidle.qav 0x213 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\darkfire.qav 0x216 PRELOCK # weapon fire 1
+ # weapon fire 2
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: BeastHands
+#
+# BASE: 0x220
+# ---------------------------------------------------------------------------
+alias qav\bstup.qav 0x220 PRELOCK # weapon up
+alias qav\bstdown.qav 0x221 PRELOCK # weapon down
+ # weapon reload
+alias qav\bstidle.qav 0x223 PRELOCK # weapon idle full
+ # weapon idle empty
+ # weapon alternate idle
+alias qav\bstatak1.qav 0x226 PRELOCK # weapon fire 1
+alias qav\bstatak2.qav 0x227 PRELOCK # weapon fire 2
+alias qav\bstatak1.qav 0x228 PRELOCK # weapon fire 3
+alias qav\bstatak2.qav 0x229 PRELOCK # weapon fire 4
+
+
+# ---------------------------------------------------------------------------
+# WEAPON: DeadHands1
+#
+# BASE: 0x230
+# ---------------------------------------------------------------------------
+alias qav\dead1up.qav 0x230 PRELOCK # weapon up
+ # weapon down
+ # weapon reload
+ # weapon idle full
+ # weapon idle empty
+alias qav\dead1idl.qav 0x235 PRELOCK # weapon alternate idle
+
+# ---------------------------------------------------------------------------
+# WEAPON: DeadHands2
+#
+# BASE: 0x240
+# ---------------------------------------------------------------------------
+alias qav\dead2up.qav 0x240 PRELOCK # weapon up
+ # weapon down
+ # weapon reload
+ # weapon idle full
+ # weapon idle empty
+alias qav\dead2idl.qav 0x245 PRELOCK # weapon alternate idle
+
diff --git a/QAVEDIT.TXT b/QAVEDIT.TXT
new file mode 100644
index 0000000..0025c75
--- /dev/null
+++ b/QAVEDIT.TXT
@@ -0,0 +1,86 @@
+=============================================================================
+ QAVEDIT History File
+ Copyright (c) 1994-95 Q Studios Corporation
+ Contact: Peter Freese
+ pfreese@qstudios.com or CIS:74170,543
+=============================================================================
+
+KEYBOARD FUNCTIONS:
+
+ Esc Exit the editor
+ Enter Play the animation
+ F2 Save
+ Ctrl F2 Save As
+ F3 Load
+ 1/2 Go backward/forward one frame
+ Home Go to first frame
+ End Go to last frame
+ Page Up/Down Move up/down a layer
+ -/+ Change ticks per frame by ñ1
+ Space Play QAV once
+ Enter Play QAV and loop
+
+ O  Nudge origin
+ O LMouse Drag origin
+ O Ctrl Move origin and adjust all frame offsets
+
+ Insert Duplicate a tile
+ Delete Delete the current tile
+ LMouse Drag tile
+  Nudge tile
+ P Change Palookup by +1
+ Alt P Select Palookup via dialog
+ R Toggle tile positioning from origin to corner
+ T Cycle between translucency levels 0, 1, and 2
+ V Select tile
+ X Toggle x flipping
+ Y Toggle y flipping
+ Pad +/- Change tile shade ñ1
+ Ctrl Pad +/- Set tile shade to min/max
+ Pad 0 Set tile shade to 0
+ < > Coarse tile angle change
+ Sh < > Fine tile angle change
+ Pad / * Coarse zoom up/down
+ Sh Pad / * Fine zoom up/down
+ Sh Page Up/Dn Drag tile up/down a layer
+
+ F Edit trigger for current frame
+
+ Ctrl Ins Insert a layer
+ Ctrl Del Delete a layer
+ Ctrl Sh Pg Up/Dn Drag an entire layer up/down
+
+ Alt Ins Insert a frame
+ Alt Del Delete a frame
+
+While playing animation:
+
+ Ctrl Play at 1/2 speed
+ Shift Play at 1/4 speed
+ -/+ Adjust ticks per frame
+
+-----------------------------------------------------------------------------
+
+95/06/13
+
+This document created.
+
+-----------------------------------------------------------------------------
+
+95/12/21
+
+QavEdit, formerly AnimEdit, has undergone a complete overhaul. The QAV engine
+now uses a different metaphor to create and display animations. It is now
+based on fixed frames intervals, with a fixed number of layers (currently 8).
+This means that it is much easier to control the z-ordering of tiles, and
+simple to adjust the overall speed of playback.
+
+You should convert all existing QAVs with the command line:
+
+ QAVEDIT -c path\*.QAV
+
+-----------------------------------------------------------------------------
+
+96/01/15
+
+Fixed a crash bug that would occur when deleting entire frames.
diff --git a/QAVS.RFS b/QAVS.RFS
new file mode 100644
index 0000000..7f3da8a
--- /dev/null
+++ b/QAVS.RFS
@@ -0,0 +1,2 @@
+QAV\*.QAV PRELOCK
+
diff --git a/RAW.RFS b/RAW.RFS
new file mode 100644
index 0000000..8699586
--- /dev/null
+++ b/RAW.RFS
@@ -0,0 +1,43 @@
+fonts\*.fnt
+tables\*.* prelock
+music\*.mid
+music\gmtimbre.tmb
+
+# RESOURCE\*.MAP
+
+# include script for sequences
+@SEQS.RFS
+@QAVS.RFS
+
+RAW "TEST.FNT"
+ ARRAY CHAR 4 "FNT",0 ;char signature[4];
+ SHORT 0x0100 ;short version;
+ ARRAY CHAR 8 "TESTFNT",0 ;char name[8];
+ UCHAR 2 ;uchar charSpace;
+ UCHAR 5 ;uchar lineSpace;
+ UCHAR 10 ;uchar wordSpace;
+ SHORT 1726 ;short firstTile;
+ ARRAY CHAR 128 ;schar tileOffset[128]
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
+ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
+ 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, -1
+ ARRAY SHORT 128 ;short tileWidth[128]
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ENDRAW
+
+;RAW "TEST.FNT" PRELOCK
+;RAW ALIAS "FNT" 200
+;RAW ALIAS "FNT" 227 PRELOCK
+;ENDRAW "C:\DEST\PATH\HERE\TEST.FNT"
diff --git a/SEQEDIT.TXT b/SEQEDIT.TXT
new file mode 100644
index 0000000..d78d3e8
--- /dev/null
+++ b/SEQEDIT.TXT
@@ -0,0 +1,154 @@
+=============================================================================
+ SEQEDIT History File
+ Copyright (c) 1994-95 Q Studios Corporation
+ Contact: Peter Freese
+ pfreese@qstudios.com or CIS:74170,543
+=============================================================================
+
+KEYBOARD FUNCTIONS:
+
+ Esc Exit the editor
+ F2 Save
+ Ctrl F2 Save As
+ F3 Load
+
+ Enter Play the sequence continuously
+ Space Play the sequence once
+ Caps Lock Draw tiles unmasked (while pressed)
+ 1/2 Go backward/forward one frame
+ -/+ Change ticks per frame by ñ1
+ < > Change view angle by 45 degrees
+ / Set view angle to 0
+ Insert Insert a new frame
+ Ctrl Insert Insert a new frame before the current one
+ Delete Delete the current frame
+ B Toggle Blocking
+ F Toggle trigger Firing
+ H Toggle Hitscan
+ S Toggle Smoke
+ T Cycle between translucency levels 0, 1, and 2
+ P Change Palookup by +1
+ Alt P Select Palookup via dialog
+ V Change tile for frame
+ Pad +/- Change shade ñ1
+ Ctrl Pad +/- Set shade to min/max
+ Pad 0 Set shade to 0
+ Alt Pad +/- Change relative global shade
+ Page Up/Down Change tile by ñ1
+ O  Nudge global origin
+ O LMouse Drag global origin
+
+These key modify the art file:
+
+  Nudge tile origin
+ Shift  Set tile origin to respective edge
+ Shift Pad 5 Set tile origin to center
+ W Change tile vieW type
+ O Ctrl Move Origin and adjust all frame offsets
+
+
+Frame flag legend:
+
+ F Fire a trigger
+ S Smoke tsprite
+ T Mostly translucent
+ t Partially translucent
+ B Blocking
+ H Hitscan
+ R Remove when done
+ L Loop when done
+
+-----------------------------------------------------------------------------
+
+95/06/13
+
+This document created.
+
+The global origin (adjusted with the "O" key) affects only where tiles are
+displayed while you are editing.
+
+You have the option to save adjustments to tile origins when you exit.
+
+You cannot yet save, load, or play sequences, but you can try out the editing
+features.
+
+I'm releasing this program now in an incomplete state because it's extremely
+useful for adjusting tile origins (much better than editart).
+
+-----------------------------------------------------------------------------
+
+95/07/13
+
+Somehow, I think I lost a revision to this text file. I'll document the
+information I lost again. The list of keyboard functions above is fairly self
+explanatory except for a few small items.
+
+Global shade is an editor concept which allows you to see how the sequence
+will look when it is played for a sprite in a sector with a non-zero shade.
+In other words, a dark sector might have a floor shade of 12, for example, so
+by setting the global shade to 12 you can see how the sequence would look in
+that sector. Setting global shade to a dark value also gives you the ability
+to see the difference in shade for sequence frames that have shades < 0.
+
+The status bar on the bottom of the screen has two parts. The left part (in
+yellow) describes the current frame:
+
+ 1023 VIEW: SINGLE T:0 B:1 H:0 SH: -8 PAL: 0
+
+The first number is the tile number in the current frame.
+
+View is the view type of the current tile. Remember, if you change the view
+type, you are modifying the art file, since view mode is a tile attribute.
+
+T, B, and H are the frames translucency, blocking, and hitscan flags,
+respectively.
+
+SH is the shade of the current frame.
+
+PAL is the palookup for the current frame.
+
+The right part of the status line (in light gray) gives information about the
+sequence and the display mode:
+
+ SH: 16 ANG: 45 FR: 1/ 8 TPF: 16
+
+SH is the global shade. This is only relevent to the display of the
+sequence in the editor. It is not saved with the sequence.
+
+ANG is the current angle from which the sequence is being viewed. This is
+only relevent if your are using non single view tiles.
+
+FR indicates the current frame and the total number of frames.
+
+TPF is the Ticks Per Frame for the sequence. Each ticks is 1/120th of a
+second, and lower numbers mean faster playback. You can calculate frame
+rate by dividing TPF into 120. For example 12 TPF would result in 10 frames
+per second, and 5 TPF would result in 24 frames per second.
+
+-----------------------------------------------------------------------------
+
+95/07/27
+
+Loading a sequence now sets the current frame index to 0.
+
+You can no longer play a 0 length sequence.
+
+-----------------------------------------------------------------------------
+
+95/11/08
+
+New version! Run slut to convert all your sequences.
+
+SeqEdit now supports multiple levels of translucency. The new level is the
+mostly opaque view.
+
+You can specify which frames fire off a trigger to the callback function with
+the "F" key.
+
+-----------------------------------------------------------------------------
+
+96/02/05
+
+You can now use the Caps Lock key to cause tiles to be displayed unmasked.
+This is useful to check the size of the various tiles to make sure dudes don't
+bounce.
diff --git a/SEQS.RFS b/SEQS.RFS
new file mode 100644
index 0000000..0ff4ab7
--- /dev/null
+++ b/SEQS.RFS
@@ -0,0 +1,342 @@
+# General effect sequences
+alias seq\canfire1.seq 0x00 PRELOCK
+alias seq\canfire2.seq 0x01 PRELOCK
+alias seq\skull.seq 0x02 PRELOCK
+alias seq\explc1l.seq 0x03 PRELOCK
+alias seq\explc1m.seq 0x04 PRELOCK
+alias seq\explc1s.seq 0x05 PRELOCK
+alias seq\explc2l.seq 0x06 PRELOCK
+alias seq\explc2m.seq 0x07 PRELOCK
+alias seq\explc2s.seq 0x08 PRELOCK
+alias seq\explc2t.seq 0x09 PRELOCK
+alias seq\splash1.seq 0x0A PRELOCK
+alias seq\splash2.seq 0x0B PRELOCK
+alias seq\splash3.seq 0x0C PRELOCK
+alias seq\ricoch01.seq 0x0D PRELOCK
+alias seq\gwing.seq 0x0E PRELOCK
+alias seq\headroll.seq 0x0F PRELOCK
+alias seq\barrel.seq 0x10 PRELOCK
+alias seq\blood01.seq 0x11 PRELOCK
+alias seq\respawn.seq 0x12 PRELOCK
+alias seq\fsmoke.seq 0x13 PRELOCK
+alias seq\squib1.seq 0x14 PRELOCK
+alias seq\flolite.seq 0x15 PRELOCK
+alias seq\glass.seq 0x16 PRELOCK
+alias seq\sglass.seq 0x17 PRELOCK
+alias seq\bigweb.seq 0x18 PRELOCK
+alias seq\woodbeam.seq 0x19 PRELOCK
+alias seq\vase1.seq 0x1A PRELOCK
+alias seq\vase2.seq 0x1B PRELOCK
+alias seq\zbones.seq 0x1C PRELOCK
+alias seq\skull2.seq 0x1D PRELOCK
+alias seq\metgrat1.seq 0x1E PRELOCK
+alias seq\fireball.seq 0x1F PRELOCK
+alias seq\bone.seq 0x20 PRELOCK
+alias seq\treefir1.seq 0x21 PRELOCK
+alias seq\treefir2.seq 0x22 PRELOCK
+alias seq\hellfire.seq 0x23 PRELOCK
+alias seq\mgdead.seq 0x24 PRELOCK
+alias seq\ricoch02.seq 0x25 PRELOCK
+
+# Trigger effects
+alias seq\glass.seq 0x80 PRELOCK
+alias seq\sglass.seq 0x81 PRELOCK
+alias seq\bigweb.seq 0x82 PRELOCK
+alias seq\woodbeam.seq 0x83 PRELOCK
+alias seq\jarexplo.seq 0x84 PRELOCK
+alias seq\wateroff.seq 0x85 PRELOCK
+alias seq\wateron.seq 0x86 PRELOCK
+alias seq\spike.seq 0x87 PRELOCK
+alias seq\metgrat1.seq 0x88 PRELOCK
+alias seq\ftrap1.seq 0x89 PRELOCK
+alias seq\ftrap2.seq 0x8A PRELOCK
+alias seq\padlock.seq 0x8B PRELOCK
+alias seq\mgopen.seq 0x8C PRELOCK
+alias seq\mgfire.seq 0x8D PRELOCK
+alias seq\mgclose.seq 0x8E PRELOCK
+alias seq\mgflare.seq 0x8F PRELOCK
+
+# kDudeTommyCultist
+# kDudePlayer1
+# kDudePlayer2
+# kDudePlayer3
+# kDudePlayer4
+# kDudePlayer5
+# kDudePlayer6
+# kDudePlayer7
+# kDudePlayer8
+# kDudePlayer_Owned
+alias seq\cult1i1.seq 0x100 PRELOCK
+alias seq\cult1d1.seq 0x101 PRELOCK
+alias seq\cult1d2.seq 0x102 PRELOCK
+alias seq\cult1d3.seq 0x103 PRELOCK
+alias seq\cult1r1.seq 0x104 PRELOCK
+alias seq\cult1a1.seq 0x105 PRELOCK
+alias seq\cult1a2.seq 0x106 PRELOCK
+alias seq\cult1m1.seq 0x107 PRELOCK
+alias seq\cult1m2a.seq 0x108 PRELOCK
+alias seq\cult1m2b.seq 0x109 PRELOCK
+alias seq\cult1m2c.seq 0x10A PRELOCK
+
+# kDudeAxeZombie
+# kDudeEarthZombie
+
+alias seq\zomb1i1.seq 0x110 PRELOCK
+alias seq\zomb1d1.seq 0x111 PRELOCK
+alias seq\zomb1d2.seq 0x112 PRELOCK
+alias seq\zomb1d3.seq 0x113 PRELOCK
+alias seq\zomb1r1.seq 0x114 PRELOCK
+alias seq\zomb1a1.seq 0x115 PRELOCK
+alias seq\zomb1d4.seq 0x116 PRELOCK
+alias seq\zomb1m1.seq 0x117 PRELOCK
+alias seq\zomb1m2.seq 0x118 PRELOCK # climb out of ground
+alias seq\zomb1up.seq 0x119 PRELOCK # flip up
+alias seq\zomb1i2.seq 0x11A PRELOCK # idle in ground
+
+# kDudeFatZombie
+
+alias seq\zomb2i1.seq 0x120 PRELOCK
+alias seq\zomb2d1.seq 0x121 PRELOCK
+alias seq\zomb2d2.seq 0x122 PRELOCK
+alias seq\zomb2d3.seq 0x123 PRELOCK
+alias seq\zomb2r1.seq 0x124 PRELOCK
+alias seq\zomb2a1.seq 0x125 PRELOCK
+alias seq\zomb2d4.seq 0x126 PRELOCK
+alias seq\zomb2m1.seq 0x127 PRELOCK
+
+# kDudeFleshGargoyle
+
+alias seq\garg1m1.seq 0x130 PRELOCK
+alias seq\garg1d1.seq 0x131 PRELOCK
+alias seq\garg1d2.seq 0x132 PRELOCK
+alias seq\garg1d3.seq 0x133 PRELOCK
+alias seq\garg1r1.seq 0x134 PRELOCK
+alias seq\garg1a1.seq 0x135 PRELOCK
+alias seq\garg1m2a.seq 0x136 PRELOCK
+alias seq\garg1m2b.seq 0x137 PRELOCK
+alias seq\garg1m2c.seq 0x138 PRELOCK
+
+# kDudeStoneGargoyle
+
+alias seq\garg2m1.seq 0x140 PRELOCK
+alias seq\garg2d1.seq 0x141 PRELOCK
+alias seq\garg2d2.seq 0x142 PRELOCK
+# no burn death
+alias seq\garg2r1.seq 0x144 PRELOCK
+alias seq\garg2a1.seq 0x145 PRELOCK
+alias seq\garg2m2a.seq 0x146 PRELOCK
+alias seq\garg2m2b.seq 0x147 PRELOCK
+alias seq\garg2m2c.seq 0x148 PRELOCK
+
+# kDudePhantasm
+
+alias seq\gost1m1.seq 0x150 PRELOCK #translucent
+alias seq\gost1d1.seq 0x151 PRELOCK
+# death2
+# death3
+alias seq\gost1r1.seq 0x154 PRELOCK #translucent
+alias seq\gost1a1.seq 0x155 PRELOCK
+alias seq\gost1m1.seq 0x156 PRELOCK #translucent
+alias seq\gost1m2.seq 0x157 PRELOCK #translucent
+alias seq\gost1r2.seq 0x158 PRELOCK #solid
+
+# kDudeHound
+# kDudeHound_Owned
+
+alias seq\hell1i1.seq 0x160 PRELOCK
+alias seq\hell1d1.seq 0x161 PRELOCK
+alias seq\hell1d2.seq 0x162 PRELOCK
+# death3
+alias seq\hell1r1.seq 0x164 PRELOCK
+alias seq\hell1a1.seq 0x165 PRELOCK
+alias seq\hell1a2.seq 0x166 PRELOCK
+alias seq\hell1m1.seq 0x167 PRELOCK
+
+# kDudeHand
+
+alias seq\hand1i1.seq 0x170 PRELOCK
+alias seq\hand1d1.seq 0x171 PRELOCK
+alias seq\hand1d1.seq 0x172 PRELOCK # same as death1
+alias seq\hand1d3.seq 0x173 PRELOCK
+# recoil
+alias seq\hand1m1.seq 0x175 PRELOCK
+alias seq\hand1m2.seq 0x176 PRELOCK
+
+# kDudeBrownSpider
+# kDudeSpider_Owned
+
+alias seq\spid1i1.seq 0x180 PRELOCK
+alias seq\spid1d1.seq 0x181 PRELOCK
+alias seq\spid1d2.seq 0x182 PRELOCK
+alias seq\spid1d3.seq 0x183 PRELOCK
+# recoil
+alias seq\spid1a2.seq 0x185 PRELOCK
+alias seq\spid1m1.seq 0x186 PRELOCK
+alias seq\spid1m2.seq 0x187 PRELOCK
+
+# kDudeRedSpider
+
+alias seq\spid2i1.seq 0x190 PRELOCK
+alias seq\spid2d1.seq 0x191 PRELOCK
+alias seq\spid2d2.seq 0x192 PRELOCK
+alias seq\spid2d3.seq 0x193 PRELOCK
+# recoil
+alias seq\spid2a2.seq 0x195 PRELOCK
+alias seq\spid2m1.seq 0x196 PRELOCK
+alias seq\spid2m2.seq 0x197 PRELOCK
+
+# kDudeBlackSpider
+
+alias seq\spid3i1.seq 0x1A0 PRELOCK
+alias seq\spid3d1.seq 0x1A1 PRELOCK
+alias seq\spid3d2.seq 0x1A2 PRELOCK
+# death3
+# recoil
+alias seq\spid3a2.seq 0x1A5 PRELOCK
+alias seq\spid3m1.seq 0x1A6 PRELOCK
+alias seq\spid3m2.seq 0x1A7 PRELOCK
+
+# kDudeMotherSpider
+
+# kDudeGillBeast
+
+alias seq\gill1i1.seq 0x1C0 PRELOCK
+alias seq\gill1d1.seq 0x1C1 PRELOCK
+alias seq\gill1d2.seq 0x1C2 PRELOCK
+alias seq\gill1d3.seq 0x1C3 PRELOCK
+alias seq\gill1r1.seq 0x1C4 PRELOCK
+alias seq\gill1a1.seq 0x1C5 PRELOCK
+alias seq\gill1a2.seq 0x1C6 PRELOCK
+alias seq\gill1d4.seq 0x1C7 PRELOCK
+alias seq\gill1m1.seq 0x1C8 PRELOCK
+alias seq\gill1m2.seq 0x1C9 PRELOCK
+alias seq\gill1r2.seq 0x1CA PRELOCK
+
+# kDudeEel
+# kDudeEel_Owned
+
+alias seq\eel1m1.seq 0x1D0 PRELOCK
+alias seq\eel1d1.seq 0x1D1 PRELOCK
+alias seq\eel1d2.seq 0x1D2 PRELOCK
+# death3
+# recoil
+alias seq\eel1m2.seq 0x1D5 PRELOCK
+
+# kDudeBat
+
+alias seq\bat1i1.seq 0x1E0 PRELOCK
+alias seq\bat1d1.seq 0x1E1 PRELOCK
+alias seq\bat1d1.seq 0x1E2 PRELOCK # same as death1
+alias seq\bat1d3.seq 0x1E3 PRELOCK
+# recoil
+alias seq\bat1m1.seq 0x1E5 PRELOCK
+
+# kDudeRat
+
+alias seq\rat1i1.seq 0x1F0 PRELOCK
+alias seq\rat1d1.seq 0x1F1 PRELOCK
+alias seq\rat1d1.seq 0x1F2 PRELOCK # same as death1
+alias seq\rat1d3.seq 0x1F3 PRELOCK
+# recoil
+alias seq\rat1m1.seq 0x1F5 PRELOCK
+
+# kDudeGreenPod
+
+# idle
+alias seq\pod1d1.seq 0x201 PRELOCK
+alias seq\pod1d2.seq 0x202 PRELOCK
+# death3
+alias seq\pod1clos.seq 0x204 PRELOCK #recoil
+alias seq\pod1open.seq 0x205 PRELOCK
+alias seq\pod1clos.seq 0x206 PRELOCK
+
+# kDudeGreenTentacle
+
+alias seq\tent1i1.seq 0x210 PRELOCK
+alias seq\tent1d2.seq 0x211 PRELOCK # same as death 2
+alias seq\tent1d2.seq 0x212 PRELOCK
+# death3
+# recoil
+alias seq\tent1a1.seq 0x215 PRELOCK
+alias seq\tent1up.seq 0x216 PRELOCK
+alias seq\tent1dow.seq 0x217 PRELOCK
+
+# kDudeFirePod
+
+# idle
+alias seq\pod2d1.seq 0x221 PRELOCK
+alias seq\pod2d2.seq 0x222 PRELOCK
+# death3
+alias seq\pod2clos.seq 0x224 PRELOCK # recoil
+alias seq\pod2open.seq 0x225 PRELOCK
+alias seq\pod2clos.seq 0x226 PRELOCK
+
+# kDudeFireTentacle
+
+alias seq\tent2i1.seq 0x230 PRELOCK
+alias seq\tent2d2.seq 0x231 PRELOCK # same as death 2
+alias seq\tent2d2.seq 0x232 PRELOCK
+# death3
+# recoil
+alias seq\tent2a1.seq 0x235 PRELOCK
+alias seq\tent2up.seq 0x236 PRELOCK
+alias seq\tent2dow.seq 0x237 PRELOCK
+
+# kDudeMotherPod
+# kDudeMotherTentacle
+
+# kDudeCerberus
+
+alias seq\cerb1i1.seq 0x260 PRELOCK
+alias seq\cerb1d1.seq 0x261 PRELOCK
+# death2
+# death3
+alias seq\cerb1r1.seq 0x264 PRELOCK
+alias seq\cerb1a1.seq 0x265 PRELOCK
+alias seq\cerb1m1.seq 0x266 PRELOCK
+
+# kDudeCerberus2
+alias seq\cerb2i1.seq 0x270 PRELOCK
+alias seq\cerb2d1.seq 0x271 PRELOCK
+# death2
+# death3
+alias seq\cerb2r1.seq 0x274 PRELOCK
+alias seq\cerb2a1.seq 0x275 PRELOCK
+alias seq\cerb2m1.seq 0x276 PRELOCK
+
+# kDudeTchernobog
+
+# kDudeRachel
+
+# kDudeBurning
+
+# kDudeBeast
+# kDudeBeast_Owned
+alias seq\best1i1.seq 0x2A0 PRELOCK
+alias seq\best1d1.seq 0x2A1 PRELOCK
+# death2
+alias seq\best1d3.seq 0x2A3 PRELOCK
+alias seq\best1r1.seq 0x2A4 PRELOCK
+alias seq\best1a1.seq 0x2A5 PRELOCK
+# attack2
+alias seq\best1m1.seq 0x2A7 PRELOCK
+
+# kDudeFleshStatue
+alias seq\garg1tr.seq 0x2B5 PRELOCK
+
+# kDudeStoneStatue
+alias seq\garg2tr.seq 0x2C5 PRELOCK
+
+# kDudeShotgunCultist
+alias seq\cult2i1.seq 0x2D0 PRELOCK
+alias seq\cult2d1.seq 0x2D1 PRELOCK
+alias seq\cult2d2.seq 0x2D2 PRELOCK
+alias seq\cult2d3.seq 0x2D3 PRELOCK
+alias seq\cult2r1.seq 0x2D4 PRELOCK
+alias seq\cult2a1.seq 0x2D5 PRELOCK
+alias seq\cult2a2.seq 0x2D6 PRELOCK
+alias seq\cult2m1.seq 0x2D7 PRELOCK
+alias seq\cult2m2a.seq 0x2D8 PRELOCK
+alias seq\cult2m2b.seq 0x2D9 PRELOCK
+alias seq\cult2m2c.seq 0x2DA PRELOCK
+
diff --git a/SRC/ACTOR.CPP b/SRC/ACTOR.CPP
new file mode 100644
index 0000000..b03b1d8
--- /dev/null
+++ b/SRC/ACTOR.CPP
@@ -0,0 +1,3054 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "actor.h"
+#include "debug4g.h"
+#include "engine.h"
+#include "trig.h"
+#include "gameutil.h"
+#include "misc.h"
+#include "db.h"
+#include "multi.h"
+#include "names.h"
+#include "screen.h"
+#include "sectorfx.h"
+#include "triggers.h"
+#include "error.h"
+#include "globals.h"
+#include "seq.h"
+#include "eventq.h"
+#include "dude.h"
+#include "ai.h"
+#include "view.h"
+#include "warp.h"
+#include "tile.h"
+#include "player.h"
+#include "options.h"
+#include "sfx.h"
+#include "sound.h" // can be removed once sfx complete
+
+#define kMaxSpareSprites 50
+
+SPRITEHIT gSpriteHit[kMaxXSprites];
+
+static void MakeSplash( SPRITE *pSprite, XSPRITE *pXSprite );
+static void FireballCallback( int /* type */, int nXIndex );
+//static void FlareCallback( int /* type */, int nXIndex );
+
+struct VECTORDATA
+{
+ DAMAGE_TYPE damageType;
+ int damageValue;
+ int maxDist;
+ struct
+ {
+ int nEffect;
+ int nSoundId;
+ } impact[kSurfMax];
+};
+
+
+static VECTORDATA gVectorData[kVectorMax] =
+{
+ // kVectorTine
+ { kDamageStab, 4, M2X(2.25),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorShell
+ { kDamageBullet, 4, 0,
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, -1 }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, -1 }, // kSurfWood
+ { ET_Squib1, -1 }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, -1 }, // kSurfDirt
+ { ET_Ricochet2, -1 }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, -1 }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorBullet
+ { kDamageBullet, 4, 0,
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorBulletAP
+ { kDamageBullet, 4, 0,
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorAxe
+ { kDamageStab, 20, M2X(2.0),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorCleaver
+ { kDamageStab, 10, M2X(2.0),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorClaw
+ { kDamageStab, 20, M2X(2.0),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorHoundBite
+ { kDamageStab, 10, M2X(1.2),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorRatBite
+ { kDamageStab, 4, M2X(1.8),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+
+ // kVectorSpiderBite
+ { kDamageStab, 8, M2X(1.2),
+ {
+ { -1, -1 }, // kSurfNone
+ { ET_Ricochet1, kSfxForkStone }, // kSurfStone
+ { ET_Ricochet1, kSfxForkMetal }, // kSurfMetal
+ { ET_Ricochet2, kSfxForkWood }, // kSurfWood
+ { ET_Squib1, kSfxForkFlesh }, // kSurfFlesh
+ { ET_Splash2, -1 }, // kSurfWater
+ { ET_Ricochet2, kSfxForkWood }, // kSurfDirt
+ { ET_Ricochet2, kSfxForkWood }, // kSurfClay
+ { -1, -1 }, // kSurfSnow
+ { -1, -1 }, // kSurfIce
+ { -1, -1 }, // kSurfLeaves
+ { -1, -1 }, // kSurfCloth
+ { -1, kSfxForkWood }, // kSurfPlant
+ { -1, -1 }, // kSurfGoo
+ { -1, -1 }, // kSurfLava
+ }
+ },
+};
+
+
+ITEMDATA gItemData[ kMaxItemTypes ] =
+{
+/*
+{cstat, picnum, sh, pal,xr, yr, stat, type, flags},
+*/
+ {0, kPicKey1, -8, 0, 32, 32, kStatItem, kItemKey1, 0},
+ {0, kPicKey2, -8, 0, 32, 32, kStatItem, kItemKey2, 0},
+ {0, kPicKey3, -8, 0, 32, 32, kStatItem, kItemKey3, 0},
+ {0, kPicKey4, -8, 0, 32, 32, kStatItem, kItemKey4, 0},
+ {0, kPicKey5, -8, 0, 32, 32, kStatItem, kItemKey5, 0},
+ {0, kPicKey6, -8, 0, 32, 32, kStatItem, kItemKey6, 0},
+ {0, -1, -8, 0, -1, -1, kStatItem, kItemKey7, 0},
+ {0, kPicDocBag, -8, 0, 48, 48, kStatItem, kItemDoctorBag, 0},
+ {0, kPicMedPouch, -8, 0, 40, 40, kStatItem, kItemMedPouch, 0},
+ {0, kPicEssence, -8, 0, 40, 40, kStatItem, kItemLifeEssence, 0},
+ {0, kAnmLifeSeed, -8, 0, 40, 40, kStatItem, kItemLifeSeed, 0},
+ {0, kPicPotion, -8, 0, 40, 40, kStatItem, kItemPotion1, 0},
+ {0, kAnmFeather, -8, 0, 40, 40, kStatItem, kItemFeatherFall, 0},
+ {0, kAnmInviso, -8, 0, 40, 40, kStatItem, kItemLtdInvisibility, 0},
+ {0, kPicInvulnerable, -8, 0, 40, 40, kStatItem, kItemInvulnerability, 0},
+ {0, kPicJumpBoots, -8, 0, 40, 40, kStatItem, kItemJumpBoots, 0},
+ {0, kPicRavenFlight, -8, 0, 40, 40, kStatItem, kItemRavenFlight, 0},
+ {0, kPicGunsAkimbo, -8, 0, 40, 40, kStatItem, kItemGunsAkimbo, 0},
+ {0, kPicDivingSuit, -8, 0, 80, 64, kStatItem, kItemDivingSuit, 0},
+ {0, kPicGasMask, -8, 0, 40, 40, kStatItem, kItemGasMask, 0},
+ {0, kAnmClone, -8, 0, 40, 40, kStatItem, kItemClone, 0},
+ {0, kPicCrystalBall, -8, 0, 40, 40, kStatItem, kItemCrystalBall, 0},
+ {0, kPicDecoy, -8, 0, 40, 40, kStatItem, kItemDecoy, 0},
+ {0, kAnmDoppleganger, -8, 0, 40, 40, kStatItem, kItemDoppleganger, 0},
+ {0, kAnmReflectShots, -8, 0, 40, 40, kStatItem, kItemReflectiveShots, 0},
+ {0, kPicRoseGlasses, -8, 0, 40, 40, kStatItem, kItemRoseGlasses, 0},
+ {0, kAnmCloakNDagger, -8, 0, 64, 64, kStatItem, kItemShadowCloak, 0},
+ {0, kPicShroom1, -8, 0, 48, 48, kStatItem, kItemShroomRage, 0},
+ {0, kPicShroom2, -8, 0, 48, 48, kStatItem, kItemShroomDelirium, 0},
+ {0, kPicShroom3, -8, 0, 48, 48, kStatItem, kItemShroomGrow, 0},
+ {0, kPicShroom4, -8, 0, 48, 48, kStatItem, kItemShroomShrink, 0},
+ {0, kPicDeathMask, -8, 0, 40, 40, kStatItem, kItemDeathMask, 0},
+ {0, kPicGoblet, -8, 0, 40, 40, kStatItem, kItemWineGoblet, 0},
+ {0, kPicBottle1, -8, 0, 40, 40, kStatItem, kItemWineBottle, 0},
+ {0, kPicSkullGrail, -8, 0, 40, 40, kStatItem, kItemSkullGrail, 0},
+ {0, kPicSilverGrail, -8, 0, 40, 40, kStatItem, kItemSilverGrail, 0},
+ {0, kPicTome1, -8, 0, 40, 40, kStatItem, kItemTome, 0},
+ {0, kPicBlackChest, -8, 0, 40, 40, kStatItem, kItemBlackChest, 0},
+ {0, kPicWoodChest, -8, 0, 40, 40, kStatItem, kItemWoodenChest, 0},
+ {0, kPicAsbestosSuit, -8, 0, 80, 64, kStatItem, kItemAsbestosArmor, 0},
+};
+
+AMMOITEMDATA gAmmoItemData[kAmmoItemMax - kAmmoItemBase] =
+{
+/*
+{cstat, picnum, sh, pal,xr, yr, flags, count, ammoType, weaponType},
+*/
+ {0, kPicSprayCan, -8, 0, 40, 40, 0, kTimerRate*8, kAmmoSprayCan, kWeaponSprayCan},
+ {0, kPicTNTStick, -8, 0, 48, 48, 0, 1, kAmmoTNTStick, kWeaponTNT},
+ {0, kPicTNTPak, -8, 0, 48, 48, 0, 7, kAmmoTNTStick, kWeaponTNT},
+ {0, kPicTNTBox, -8, 0, 48, 48, 0, 14, kAmmoTNTStick, kWeaponTNT},
+ {0, kPicTNTProx, -8, 0, 48, 48, 0, 1, kAmmoTNTProximity, kWeaponTNT},
+ {0, kPicTNTRemote, -8, 0, 48, 48, 0, 1, kAmmoTNTRemote, kWeaponTNT},
+ {0, kPicTNTTimer, -8, 0, 48, 48, 0, 1, kAmmoTNTStick, kWeaponTNT},
+ {0, kPicShotShells, -8, 0, 48, 48, 0, 4, kAmmoShell},
+ {0, kPicShellBox, -8, 0, 48, 48, 0, 16, kAmmoShell},
+ {0, kPicBullets, -8, 0, 48, 48, 0, 8, kAmmoBullet},
+ {0, kPicBulletBox, -8, 0, 48, 48, 0, 50, kAmmoBullet},
+ {0, kPicBulletBoxAP, -8, 0, 48, 48, 0, 50, kAmmoBulletAP},
+ {0, kPicTommyDrum, -8, 0, 48, 48, 0, 100, kAmmoBullet},
+ {0, kPicSpear, -8, 0, 48, 48, 0, 1, kAmmoSpear},
+ {0, kPicSpears, -8, 0, 48, 48, 0, 6, kAmmoSpear},
+ {0, kPicSpearExplode, -8, 0, 48, 48, 0, 6, kAmmoSpearXP},
+ {0, kPicFlares, -8, 0, 48, 48, 0, 8, kAmmoFlare},
+ {0, kPicFlareHE, -8, 0, 48, 48, 0, 8, kAmmoFlare},
+ {0, kPicFlareBurst, -8, 0, 48, 48, 0, 8, kAmmoFlareSB},
+};
+
+WEAPONITEMDATA gWeaponItemData[ kWeaponItemMax - kWeaponItemBase ] =
+{
+/*
+{cstat, picnum, sh, pal,xr, yr, flags, weaponType, ammoType, count },
+*/
+ { 0, -1, 0, 0, 0, 0, 0, kWeaponNone, kAmmoNone, 0 },
+ { 0, kPicShotgun, -8, 0, 48, 48, 0, kWeaponShotgun, kAmmoShell, 8 },
+ { 0, kPicTommyGun, -8, 0, 48, 48, 0, kWeaponTommy, kAmmoBullet, 50 },
+ { 0, kPicFlareGun, -8, 0, 48, 48, 0, kWeaponFlare, kAmmoFlare, 8 },
+ { 0, kPicVoodooDoll, -8, 0, 48, 48, 0, kWeaponVoodoo, kAmmoVoodoo, 1 },
+ { 0, kPicSpearGun, -8, 0, 48, 48, 0, kWeaponSpear, kAmmoSpear, 6 },
+ { 0, kPicShadowGun, -8, 0, 48, 48, 0, kWeaponShadow, kAmmoNone, 0 },
+ { 0, -1, 0, 0, 0, 0, 0, kWeaponPitchfork, kAmmoNone, 0 }, // don't actually pick this up
+ { 0, kPicSprayCan, -8, 0, 48, 48, 0, kWeaponSprayCan, kAmmoSprayCan, kTimerRate*8},
+ { 0, kPicTNTStick, -8, 0, 48, 48, 0, kWeaponTNT, kAmmoTNTStick, 1},
+};
+
+
+struct MissileType {
+ short picnum;
+ int velocity;
+ int angleOfs;
+ uchar xrepeat;
+ uchar yrepeat;
+ char shade;
+} missileInfo[] = {
+ { kAnmButcherKnife, (M2X(14.0) << 4) / kTimerRate, kAngle90, 40, 40, -16 }, // kMissileButcherKnife
+ { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileFlare
+ { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileExplodingFlare
+ { kAnmFlare, (M2X(20.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileStarburstFlare
+ { 0, (M2X(4.0) << 4) / kTimerRate, 0, 24, 24, -128 }, // kMissileSprayFlame
+ { 0, (M2X(16.0) << 4) / kTimerRate, 0, 32, 32, -128 }, // kMissileFireball
+ { kAnmSpear, (M2X(16.0) << 4) / kTimerRate, kAngle270, 64, 64, -8 }, // kMissileSpear // 18.0
+ { kAnmEctoSkull, (M2X(16.0) << 4) / kTimerRate, 0, 32, 32, -24 }, // kMissileEctoSkull
+ { 0, (M2X(6.0) << 4) / kTimerRate, 0, 24, 24, -128 }, // kMissileHoundFire
+ { 0, (M2X(12.0) << 4) / kTimerRate, 0, 8, 8, 0 }, // kMissileGreenPuke
+ { 0, (M2X(12.0) << 4) / kTimerRate, 0, 8, 8, 0 }, // kMissileRedPuke
+};
+
+
+static int dragTable[] =
+{
+ 0,
+ 0x0C00, // kDepthTread
+ 0x1A00, // kDepthWade
+ 0x1A00, // kDepthSwim
+};
+
+// miscellaneous effects
+enum
+{
+ kSeqSprayFlame1 = 0,
+ kSeqSprayFlame2,
+ kSeqSkull,
+ kSeqExplodeC1L, // large concussion on ground
+ kSeqExplodeC1M, // medium concussion on ground
+ kSeqExplodeC1S, // small concussion on ground
+ kSeqExplodeC2L, // large concussion in air
+ kSeqExplodeC2M, // medium concussion in air
+ kSeqExplodeC2S, // small concussion in air
+ kSeqExplodeC2T, // tiny concussion in air
+ kSeqSplash1,
+ kSeqSplash2,
+ kSeqSplash3, // blood spash
+ kSeqRicochet1,
+ kSeqGoreWing,
+ kSeqGoreHead,
+ kSeqBarrel,
+ kSeqBloodPool,
+ kSeqRespawn,
+ kSeqFlareSmoke,
+ kSeqSquib1,
+ kSeqFluorescentLight,
+ kSeqClearGlass,
+ kSeqStainedGlass,
+ kSeqWeb,
+ kSeqBeam,
+ kSeqVase1,
+ kSeqVase2,
+ kSeqZombieBones,
+ kSeqSkullExplode,
+ kSeqMetalGrate1,
+ kSeqFireball,
+ kSeqBoneBreak,
+ kSeqBurningTree1,
+ kSeqBurningTree2,
+ kSeqHoundFire,
+ kSeqMGunDead,
+ kSeqRicochet2,
+ kSeqEffectMax,
+};
+
+
+struct THINGINFO
+{
+ short startHealth;
+ short mass; // in KG
+ char clipdist;
+ ushort flags;
+ int damageShift[kDamageMax]; // use to indicate resistance to damage types
+};
+
+static THINGINFO thingInfo[kThingMax - kThingBase] =
+{
+ { // kThingTNTBarrel
+ 40,
+ 150,
+ 32,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ 0, // kDamageFall
+ 0, // kDamageBurn
+ 0, // kDamageBullet
+ 1, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingTNTProxArmed
+ 5,
+ 5,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ 0, // kDamageBullet
+ kNoDamage, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingTNTRemArmed
+ 5,
+ 5,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ 0, // kDamageBullet
+ kNoDamage, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBlueVase
+ 1,
+ 20,
+ 32,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ 0, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBrownVase
+ 1,
+ 150,
+ 32,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ 0, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingCrateFace
+ 10,
+ 0,
+ 0,
+ 0,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingClearGlass
+ 1,
+ 0,
+ 0,
+ 0,
+ {
+ 0, // kDamagePummel
+ 0, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingFluorescent
+ 1,
+ 0,
+ 0,
+ 0,
+ {
+ 0, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingWallCrack
+ 8,
+ 0,
+ 0,
+ 0,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingWoodBeam
+ 8,
+ 0,
+ 0,
+ 0,
+ {
+ 0, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingWeb
+ 4,
+ 0,
+ 0,
+ 0,
+ {
+ 0, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ 1, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingMetalGrate1
+ 20,
+ 0,
+ 0,
+ 0,
+ {
+ 3, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 2, // kDamageBullet
+ 4, // kDamageStab
+ 1, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBurnableTree
+ 1,
+ 0,
+ 0,
+ 0,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ 0, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingMachineGun
+ 50,
+ 0,
+ 0,
+ 0,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 2, // kDamageBullet
+ 2, // kDamageStab
+ 1, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingTNTStick
+ 5,
+ 2,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ 0, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingTNTBundle
+ 5,
+ 14,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ 0, // kDamagePummel
+ kNoDamage, // kDamageFall
+ 0, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBoneClub
+ 5,
+ 6,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingZombieBones
+ 8,
+ 3,
+ 16,
+ kAttrMove | kAttrGravity,
+ {
+ 0, // kDamagePummel
+ 0, // kDamageFall
+ kNoDamage, // kDamageBurn
+ 0, // kDamageBullet
+ 0, // kDamageStab
+ 0, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingWaterDrip
+ 0, // startHealth
+ 1, // mass
+ 1, // clipDist
+ kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBloodDrip
+ 0, // startHealth
+ 1, // mass
+ 1, // clipDist
+ kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBubble
+ 0, // startHealth
+ -1, // mass
+ 1, // clipDist
+ kAttrMove,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingBubbles
+ 0, // startHealth
+ -1, // mass
+ 1, // clipDist
+ kAttrMove,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+
+ { // kThingGibSmall
+ 0, // startHealth
+ 2, // mass
+ 4, // clipDist
+ kAttrMove | kAttrGravity,
+ {
+ kNoDamage, // kDamagePummel
+ kNoDamage, // kDamageFall
+ kNoDamage, // kDamageBurn
+ kNoDamage, // kDamageBullet
+ kNoDamage, // kDamageStab
+ kNoDamage, // kDamageExplode
+ kNoDamage, // kDamageGas
+ kNoDamage, // kDamageDrown
+ kNoDamage, // kDamageSpirit
+ kNoDamage, // kDamageVoodoo
+ },
+ },
+};
+
+
+void actAllocateSpares( void )
+{
+/*
+ dprintf("Creating spare sprites\n");
+ for (int i = 0; i < kMaxSpareSprites; i++)
+ {
+ int nSprite = insertsprite( 0, kStatSpares );
+ dassert(nSprite != -1);
+ dbInsertXSprite(nSprite);
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ }
+*/
+}
+
+
+/*******************************************************************************
+ FUNCTION: actInit()
+
+ DESCRIPTION: Initialize the actor subsystem. Locks all sequences, and
+ preloads all tiles for sequences used in the map.
+
+ NOTES:
+*******************************************************************************/
+void actInit( void )
+{
+ int nSprite;
+ BOOL used[kDudeMax - kDudeBase];
+
+ memset(used, FALSE, sizeof(used));
+
+ // allocate sprites to use for effects
+ actAllocateSpares();
+
+ // see which dudes are present
+ for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ if ( pSprite->type < kDudeBase || pSprite->type >= kDudeMax )
+ {
+ dprintf("ERROR IN SPRITE %i\n", nSprite);
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ }
+
+ used[pSprite->type - kDudeBase] = TRUE;
+ }
+
+
+ // preload tiles for dude sequences
+ dprintf("Preload dude sequence tiles\n");
+ for (int i = 0; i < kDudeMax - kDudeBase; i++)
+ {
+ if ( used[i] )
+ {
+ // only preload art for idle sequence
+// if ( dudeInfo[i].pSeq[0] != NULL )
+// dudeInfo[i].pSeq[0]->Preload();
+ }
+ }
+
+ // initialize all dudes
+ for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ int dudeIndex = pSprite->type - kDudeBase;
+
+ pSprite->cstat |= kSpriteBlocking | kSpriteHitscan;
+ pSprite->clipdist = dudeInfo[dudeIndex].clipdist;
+ pSprite->flags = kAttrMove | kAttrGravity | kAttrFalling;
+ pSprite->xvel = pSprite->yvel = pSprite->zvel = 0;
+ pXSprite->health = dudeInfo[dudeIndex].startHealth << 4;
+
+ if ( gSysRes.Lookup( dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, ".SEQ") != NULL )
+ seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeIdle, SS_SPRITE, nXSprite);
+ }
+
+ // initialize all things
+ for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ int thingIndex = pSprite->type - kThingBase;
+
+ pSprite->cstat |= kSpriteBlocking; // | kSpriteHitscan should be handled in BSTUB by hitbit
+ pSprite->clipdist = thingInfo[thingIndex].clipdist;
+ pSprite->flags = thingInfo[thingIndex].flags;
+ pSprite->xvel = pSprite->yvel = pSprite->zvel = 0;
+ pXSprite->health = thingInfo[thingIndex].startHealth << 4;
+ switch(pSprite->type)
+ {
+ case kThingMachineGun:
+ pXSprite->state = 0; // these must start off
+ break;
+ default:
+ pXSprite->state = 1;
+ break;
+ }
+ }
+
+ aiInit();
+}
+
+
+#define kGlobalForceShift 26 // arbitrary scale for all concussion
+static void ConcussSprite( int nSource, int nSprite, int x, int y, int z, int ticks )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+ int dx = pSprite->x - x;
+ int dy = pSprite->y - y;
+ int dz = (pSprite->z - z) >> 4;
+
+ int dist2 = ClipLow(dx * dx + dy * dy + dz * dz, 32 << 4);
+ int nTile = pSprite->picnum;
+ int area = tilesizx[nTile] * pSprite->xrepeat * tilesizy[nTile] * pSprite->yrepeat >> 12;
+
+ int force = divscale(ticks, dist2, kGlobalForceShift);
+
+ if ( pSprite->flags & kAttrMove )
+ {
+ int mass = 0;
+
+ if ( pSprite->type >= kDudeBase && pSprite->type < kDudeMax )
+ mass = dudeInfo[pSprite->type - kDudeBase].mass;
+ else if ( pSprite->type >= kThingBase && pSprite->type < kThingMax )
+ mass = thingInfo[pSprite->type - kThingBase].mass;
+ else
+ ThrowError("Unexpected type encountered in ConcussSprite()", ES_ERROR);
+
+ dassert(mass != 0);
+
+ int impulse = muldiv(force, area, qabs(mass));
+ dx = mulscale16(impulse, dx);
+ dy = mulscale16(impulse, dy);
+ dz = mulscale16(impulse, dz);
+
+ pSprite->xvel += dx;
+ pSprite->yvel += dy;
+ pSprite->zvel += dz;
+ }
+
+ actDamageSprite(nSource, nSprite, kDamageExplode, force);
+}
+
+
+/*******************************************************************************
+ FUNCTION: ReflectVector()
+
+ DESCRIPTION: Reflects a vector off a wall
+
+ PARAMETERS: nFraction is elasticity (0x10000 == perfectly elastic)
+*******************************************************************************/
+static void ReflectVector( short *dx, short *dy, int nWall, int nFraction )
+{
+ // calculate normal for wall
+ int nx = -(wall[wall[nWall].point2].y - wall[nWall].y) >> 4;
+ int ny = (wall[wall[nWall].point2].x - wall[nWall].x) >> 4;
+ int dotProduct = *dx * nx + *dy * ny;
+
+ int length2 = nx * nx + ny * ny;
+ dassert(length2 > 0);
+
+ int dot2 = dotProduct + mulscale16r(dotProduct, nFraction);
+ int sd = divscale16(dot2, length2);
+
+ *dx -= mulscale16r(nx, sd);
+ *dy -= mulscale16r(ny, sd);
+}
+
+
+static void DropPickupObject( int nActor, int nObject )
+{
+ dassert( nActor >= 0 && nActor < kMaxSprites && sprite[nActor].statnum < kMaxStatus );
+ dassert( (nObject >= kItemBase && nObject < kItemMax)
+ || (nObject >= kAmmoItemBase && nObject < kAmmoItemMax)
+ || (nObject >= kWeaponItemBase && nObject < kWeaponItemMax) );
+
+ // create a sprite for the dropped ammo
+ SPRITE *pActor = &sprite[nActor];
+
+ int nSprite = actSpawnSprite( pActor->sectnum, pActor->x, pActor->y, sector[pActor->sectnum].floorz, kStatItem, FALSE );
+ SPRITE *pSprite = &sprite[ nSprite ];
+
+ if ( nObject >= kItemBase && nObject < kItemMax )
+ {
+ int nItemIndex = nObject - kItemBase;
+
+ pSprite->type = (short)nObject;
+ pSprite->picnum = gItemData[nItemIndex].picnum;
+ pSprite->shade = gItemData[nItemIndex].shade ;
+ pSprite->xrepeat = gItemData[nItemIndex].xrepeat;
+ pSprite->yrepeat = gItemData[nItemIndex].yrepeat;
+ if (nObject >= kItemKey1 && nObject <= kItemKey7)
+ {
+ // PF/NN: should this be in bloodbath too?
+ if ( gNetMode == kNetModeCoop ) // force permanent keys in Coop mode
+ {
+ dbInsertXSprite( nSprite );
+ XSPRITE *pXSprite = &xsprite[ pSprite->extra ];
+ pXSprite->respawn = kRespawnPermanent;
+ pXSprite->respawnTime = 0;
+ }
+ }
+ }
+ else if ( nObject >= kAmmoItemBase && nObject < kAmmoItemMax )
+ {
+ int nAmmoIndex = nObject - kAmmoItemBase;
+
+ pSprite->type = (short)nObject;
+ pSprite->picnum = gAmmoItemData[nAmmoIndex].picnum;
+ pSprite->shade = gAmmoItemData[nAmmoIndex].shade ;
+ pSprite->xrepeat = gAmmoItemData[nAmmoIndex].xrepeat;
+ pSprite->yrepeat = gAmmoItemData[nAmmoIndex].yrepeat;
+ }
+ else if ( nObject >= kWeaponItemBase && nObject < kWeaponItemMax )
+ {
+ int nWeaponIndex = nObject - kWeaponItemBase;
+
+ pSprite->type = (short)nObject;
+ pSprite->picnum = gWeaponItemData[nWeaponIndex].picnum;
+ pSprite->shade = gWeaponItemData[nWeaponIndex].shade ;
+ pSprite->xrepeat = gWeaponItemData[nWeaponIndex].xrepeat;
+ pSprite->yrepeat = gWeaponItemData[nWeaponIndex].yrepeat;
+ }
+ else
+ ThrowError("Unhandled nObject passed to DropPickupObject()", ES_ERROR);
+}
+
+
+BOOL actHealDude( XSPRITE *pXDude, int healValue, int maxHealthClip)
+{
+ dassert(pXDude != NULL);
+
+ healValue <<= 4; // fix this later in the calling code
+ maxHealthClip <<= 4;
+ if ( pXDude->health < maxHealthClip )
+ {
+ pXDude->health = ClipHigh(pXDude->health + healValue, maxHealthClip);
+ dprintf("Health=%d\n", pXDude->health >> 4);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+void actKillSprite( int nSource, int nSprite, DAMAGE_TYPE damageType )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+ SPRITE *pSource = &sprite[nSource];
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int dudeIndex = pSprite->type - kDudeBase;
+ DUDEINFO *pDudeInfo = &dudeInfo[dudeIndex];
+
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0);
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ pXSprite->moveState = kMoveStill;
+
+ // handle first cerberus head death
+ if (pSprite->type == kDudeCerberus)
+ {
+ seqSpawn(dudeInfo[dudeIndex].seqStartID + kSeqDudeDeath1, SS_SPRITE, nXSprite);
+ return;
+ }
+
+ actPostSprite( nSprite, kStatThing );
+ trTriggerSprite( nSprite, pXSprite, kCommandOff ); // trigger death message
+
+ pSprite->flags |= kAttrMove | kAttrGravity | kAttrFalling;
+
+ if ( IsPlayerSprite(nSprite) )
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
+ powerupClear( pPlayer );
+ dprintf("health = %i\n",pXSprite->health);
+ if (pXSprite->health == 0)
+ pPlayer->deathTime = 0;
+
+ if ( IsPlayerSprite(nSource) )
+ {
+ int nKilledIndex = pSprite->type - kDudePlayer1;
+ int nKillerIndex = pSource->type - kDudePlayer1;
+ PLAYER *pFragger = &gPlayer[nKillerIndex];
+ if (nSource == nSprite) // fragged yourself, eh?
+ {
+ pPlayer->fragCount--;
+ pPlayer->fragInfo[nKillerIndex]--; // frags against self is negative
+ }
+ else
+ {
+ pFragger->fragCount++;
+ pFragger->fragInfo[nKilledIndex]++; // frags against others are positive
+ }
+ }
+
+ // drop pickup items
+ for (int i = 1; i < 8; i++)
+ if ( pPlayer->hasKey[i] )
+ DropPickupObject(nSprite, kItemKey1 + i - 1);
+
+ if ( pPlayer->hasWeapon[kWeaponShotgun] )
+ DropPickupObject(nSprite, kWeaponItemShotgun);
+
+ if ( pPlayer->hasWeapon[kWeaponTommy] )
+ DropPickupObject(nSprite, kWeaponItemTommyGun);
+
+ if ( pPlayer->hasWeapon[kWeaponFlare] )
+ DropPickupObject(nSprite, kWeaponItemFlareGun);
+
+ if ( pPlayer->hasWeapon[kWeaponSpear] )
+ DropPickupObject(nSprite, kWeaponItemSpearGun);
+
+ if ( pPlayer->hasWeapon[kWeaponShadow] )
+ DropPickupObject(nSprite, kWeaponItemShadowGun);
+ }
+
+ if ( pXSprite->key > 0 )
+ DropPickupObject( nSprite, kItemKey1 + pXSprite->key - 1 );
+
+ int deathType;
+ switch (damageType)
+ {
+ case kDamageFall:
+ case kDamageExplode:
+ deathType = kSeqDudeDeath2;
+// sfxStart3DSound(nXSprite, kSfxThingSplat);
+ break;
+
+ case kDamageBurn:
+ deathType = kSeqDudeDeath3;
+// sfxStart3DSound(nXSprite, kSfxThingBurn);
+ break;
+
+ default:
+ deathType = kSeqDudeDeath1;
+ break;
+ }
+
+ // are we missing this sequence? if so, just delete it
+ if ( gSysRes.Lookup( dudeInfo[dudeIndex].seqStartID + deathType, ".SEQ") == NULL )
+ {
+ dprintf("sprite missing death sequence: deleted\n");
+ seqKill(SS_SPRITE, nXSprite); // make sure we remove any active sequence
+ actPostSprite( nSprite, kStatFree );
+ return;
+ }
+
+ switch (pSprite->type)
+ {
+ case kDudeAxeZombie:
+ if (pSprite->owner >= 0)
+ {
+ PLAYER *pPlayer = &gPlayer[sprite[pSprite->owner].type - kDudePlayer1];
+ playerDeleteLackey( pPlayer, nSprite );
+ }
+ seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite);
+ break;
+
+ case kDudeFatZombie:
+// if ( damageType == kDamageBurn )
+// {
+// int thingIndex = kThingZombieBones - kThingBase;
+//
+// pSprite->type = kThingZombieBones;
+// pSprite->clipdist = thingInfo[thingIndex].clipdist;
+// pSprite->flags = thingInfo[thingIndex].flags;
+// pSprite->xvel = pSprite->yvel = pSprite->zvel = 0;
+// pXSprite->health = thingInfo[thingIndex].startHealth << 4;
+// }
+ seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite);
+ break;
+
+ default:
+ dprintf("spawning sprite %i death sequence %x\n", nSprite, dudeInfo[dudeIndex].seqStartID + deathType);
+ seqSpawn(dudeInfo[dudeIndex].seqStartID + deathType, SS_SPRITE, nXSprite);
+ break;
+ }
+
+ // drop any items or weapons
+ if (pSprite->type == kDudeTommyCultist)
+ {
+ int nDropCheck = Random(100);
+
+ // constants? table?
+ if (nDropCheck < 80)
+ DropPickupObject( nSprite, kAmmoItemBullets );
+ else if (nDropCheck < 95)
+ DropPickupObject( nSprite, kAmmoItemBulletBox );
+ else
+ DropPickupObject( nSprite, kWeaponItemTommyGun );
+ }
+ else if (pSprite->type == kDudeShotgunCultist)
+ {
+ int nDropCheck = Random(100);
+ if (nDropCheck < 40)
+ DropPickupObject( nSprite, kAmmoItemShells );
+ else if (nDropCheck < 75)
+ DropPickupObject( nSprite, kAmmoItemShellBox );
+ else
+ DropPickupObject( nSprite, kWeaponItemShotgun );
+ }
+
+// SpawnGibs( nSprite, pXSprite );
+ // gib generator
+ if ( damageType == kDamageExplode )
+ {
+ int angle, velocity = 120;
+
+ // thing gibs
+ for (int i = 0; i < kGibMax && pDudeInfo->gib[i].chance > 0; i++)
+ {
+ if ( Random(256) < pDudeInfo->gib[i].chance )
+ {
+ int nGib = actCloneSprite(pSprite);
+ changespritestat( (short)nGib, kStatThing );
+ SPRITE *pGib = &sprite[nGib];
+
+ angle = Random(kAngle360);
+ pGib->type = kThingGibSmall;
+ pGib->picnum = pDudeInfo->gib[i].tile;
+ pGib->xvel += mulscale30(velocity, Cos(angle));
+ pGib->yvel += mulscale30(velocity, Sin(angle));
+ pGib->zvel -= 128; // toss it in the air a bit
+ pGib->cstat &= ~kSpriteBlocking & ~kSpriteHitscan;
+ pGib->flags = kAttrMove | kAttrGravity | kAttrFalling;
+ pGib->pal = kPLUNormal;
+ }
+ }
+
+ // debris gibs
+ for (i = 0; i < 50; i++)
+ {
+ int nGib = actCloneSprite(pSprite);
+ changespritestat( (short)nGib, kStatDebris);
+ SPRITE *pGib = &sprite[nGib];
+
+ pGib->picnum = 2053; // "worm" giblet
+ pGib->xvel += BiRandom(500);
+ pGib->yvel += BiRandom(500);
+ pGib->zvel += BiRandom(500);
+ pGib->cstat &= ~kSpriteBlocking & ~kSpriteHitscan;
+ pGib->flags = kAttrMove | kAttrGravity | kAttrFalling;
+ pGib->pal = kPLUNormal;
+ }
+
+ }
+}
+
+
+void actDamageSprite( int nSource, int nSprite, DAMAGE_TYPE nDamageType, int nDamage )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int nXSprite = pSprite->extra;
+ if (nXSprite < 0)
+ return;
+
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ if (pXSprite->health == 0) // it's already toast
+ return;
+
+ switch ( pSprite->statnum )
+ {
+ case kStatDude:
+ {
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+
+ // calculate and apply damage to the dude or player sprite
+ int nShift = dudeInfo[pSprite->type - kDudeBase].damageShift[nDamageType];
+ nDamage = (nShift >= kNoDamage) ? 0 : nDamage >> nShift;
+ pXSprite->health = ClipLow(pXSprite->health - nDamage, 0);
+
+ // process results and effects of damage
+ if ( IsPlayerSprite(pSprite) )
+ playerDamageSprite( &gPlayer[pSprite->type - kDudePlayer1], nSource, nDamage );
+ else
+ aiDamageSprite( pSprite, pXSprite, nSource, nDamageType, nDamage );
+
+ // kill dudes lacking health to sustain life
+ if (pXSprite->health == 0)
+ {
+ // prevent dudes from exploding from weak explosion damage
+ if ( nDamageType == kDamageExplode && nDamage < (5 << 4) )
+ nDamageType = kDamagePummel;
+ if ( IsPlayerSprite(pSprite) )
+ sfxStart3DSound(nXSprite, kSfxPlayDie);
+ actKillSprite(nSource, nSprite, nDamageType);
+ }
+ break;
+ }
+
+ case kStatThing:
+ case kStatProximity:
+ {
+ dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
+ int thingIndex = pSprite->type - kThingBase;
+ nDamage >>= thingInfo[thingIndex].damageShift[nDamageType];
+ pXSprite->health = ClipLow(pXSprite->health - nDamage, 0);
+ if (pXSprite->health == 0)
+ {
+ pSprite->owner = (short)nSource;
+ trTriggerSprite(nSprite, pXSprite, kCommandOff);
+
+ switch ( pSprite->type )
+ {
+ case kThingMachineGun:
+ seqSpawn(kSeqMGunDead, SS_SPRITE, pSprite->extra);
+ sfxStart3DSound(nXSprite, kSfxMGDie);
+ break;
+ case kThingBlueVase:
+ seqSpawn(kSeqVase1, SS_SPRITE, pSprite->extra);
+ sfxStart3DSound(nXSprite, kSfxPotteryHit);
+ if (pXSprite->data1 > 0)
+ DropPickupObject( nSprite, pXSprite->data1 );
+ if (pXSprite->data2 > 0)
+ DropPickupObject( nSprite, pXSprite->data2 );
+ break;
+ case kThingBrownVase:
+ seqSpawn(kSeqVase2, SS_SPRITE, pSprite->extra);
+ sfxStart3DSound(nXSprite, kSfxPotteryHit);
+ if (pXSprite->data1 > 0)
+ DropPickupObject( nSprite, pXSprite->data1 );
+ if (pXSprite->data2 > 0)
+ DropPickupObject( nSprite, pXSprite->data2 );
+ break;
+ case kThingClearGlass:
+ pSprite->yrepeat >>= 1;
+ seqSpawn(kSeqClearGlass, SS_SPRITE, pSprite->extra);
+ sfxStart3DSound(nXSprite, kSfxGlassHit);
+ break;
+ case kThingFluorescent:
+ seqSpawn(kSeqFluorescentLight, SS_SPRITE, pSprite->extra);
+ break;
+ case kThingWoodBeam:
+ seqSpawn(kSeqBeam, SS_SPRITE, pSprite->extra);
+ break;
+ case kThingWeb:
+ seqSpawn(kSeqWeb, SS_SPRITE, pSprite->extra);
+ break;
+ case kThingMetalGrate1:
+ seqSpawn(kSeqMetalGrate1, SS_SPRITE, pSprite->extra);
+ break;
+ case kThingFlammableTree:
+ if (pXSprite->data1 == 0)
+ seqSpawn(kSeqBurningTree1, SS_SPRITE, pSprite->extra);
+ else if (pXSprite->data1 == 1)
+ seqSpawn(kSeqBurningTree2, SS_SPRITE, pSprite->extra);
+ sfxStart3DSound(nXSprite, kSfxBurn);
+ break;
+ case kThingZombieBones:
+ dprintf("damaging zombie bones\n");
+ if ( seqGetStatus(SS_SPRITE, nXSprite) < 0 ) // body finished burning
+ seqSpawn(kSeqZombieBones, SS_SPRITE, pSprite->extra);
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+
+void actImpactMissile( int nSprite, int hitInfo )
+{
+ SPRITE *pMissile = &sprite[nSprite];
+ int hitType = hitInfo & kHitTypeMask;
+ int hitObject = hitInfo & kHitIndexMask;
+
+ int nXMissile = pMissile->extra;
+
+ switch (pMissile->type)
+ {
+ case kMissileFireball:
+ actExplodeSprite( nSprite );
+ break;
+
+ case kMissileFlare:
+ case kMissileExplodingFlare:
+ case kMissileStarburstFlare:
+ {
+ XSPRITE *pXMissile = &xsprite[pMissile->extra];
+
+ if (pMissile->type == kMissileExplodingFlare || pMissile->type == kMissileStarburstFlare)
+ {
+ actExplodeSprite( nSprite );
+ break;
+ }
+
+ if ( hitType == kHitSprite && sprite[hitObject].extra > 0 )
+ {
+ SPRITE *pObject = &sprite[hitObject];
+ XSPRITE *pXObject = &xsprite[pObject->extra];
+
+ pMissile->picnum = kAnmFlareBurn;
+ pXMissile->target = hitObject;
+ pXMissile->targetZ = pMissile->z - pObject->z;
+ pXMissile->goalAng = getangle( pMissile->x - pObject->x, pMissile->y - pObject->y ) - pObject->ang;
+ pXMissile->state = 1;
+ actAddBurnTime(pMissile->owner, pXObject, 8 * kTimerRate);
+ actDamageSprite(pMissile->owner, hitObject, kDamageStab, 10 << 4);
+ actPostSprite( nSprite, kStatFlare );
+ evPost(nSprite, SS_SPRITE, 8 * kTimerRate); // callback to flare
+ }
+ else
+ {
+ // ADD: spawn an explosion later
+ actPostSprite( nSprite, kStatFree );
+ }
+ break;
+ }
+
+ case kMissileSprayFlame:
+ case kMissileHoundFire:
+// seqKill(SS_SPRITE, nXMissile);
+// deletesprite((short)nSprite);
+ if ( hitType == kHitSprite && sprite[hitObject].extra > 0 )
+ {
+ XSPRITE *pXObject = &xsprite[sprite[hitObject].extra];
+ actAddBurnTime( pMissile->owner, pXObject, kFrameTicks );
+ }
+ break;
+
+ case kMissileEctoSkull:
+ actPostSprite( nSprite, kStatEffect );
+ seqSpawn(kSeqSkullExplode, SS_SPRITE, pMissile->extra);
+ if ( hitType == kHitSprite && sprite[hitObject].statnum == kStatDude )
+ {
+ actDamageSprite(pMissile->owner, hitObject, kDamageSpirit, 50 << 4);
+ SPRITE *pDude = &sprite[pMissile->owner];
+ XSPRITE *pXDude = &xsprite[pDude->extra];
+ if ( pXDude->health > 0 )
+ actHealDude(pXDude, 25);
+ }
+ break;
+
+ case kMissileButcherKnife:
+ actPostSprite( nSprite, kStatEffect );
+ pMissile->cstat &= ~kSpriteWall;
+ pMissile->type = kNothing;
+ seqSpawn(kSeqSkullExplode, SS_SPRITE, pMissile->extra);
+ if ( hitType == kHitSprite && sprite[hitObject].statnum == kStatDude )
+ {
+ actDamageSprite(pMissile->owner, hitObject, kDamageSpirit, 15 << 4);
+ SPRITE *pDude = &sprite[pMissile->owner];
+ XSPRITE *pXDude = &xsprite[pDude->extra];
+
+ int dudeIndex = pDude->type - kDudeBase;
+ if ( pXDude->health > 0 )
+ actHealDude(pXDude, 10, dudeInfo[dudeIndex].startHealth);
+ }
+ break;
+
+ default:
+ seqKill(SS_SPRITE, nXMissile);
+ actPostSprite( nSprite, kStatFree );
+ if (hitType == kHitSprite)
+ actDamageSprite(pMissile->owner, hitObject, kDamagePummel, 5 << 4);
+ break;
+ }
+}
+
+
+void ProcessTouchObjects( int nSprite, int nXSprite )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ SPRITEHIT *pSpriteHit = &gSpriteHit[nXSprite];
+
+ int nHitObject = pSpriteHit->ceilHit & kHitIndexMask;
+ switch( pSpriteHit->ceilHit & kHitTypeMask )
+ {
+ case kHitSprite:
+ if ( sprite[nHitObject].extra > 0 )
+ {
+ SPRITE *pHit = &sprite[nHitObject];
+ XSPRITE *pXHit = &xsprite[pHit->extra];
+ switch ( pHit->type )
+ {
+ case kTrapSawBlade:
+ if ( pXHit->state )
+ {
+ pXHit->data1 = 1;
+ pXHit->data2 = ClipHigh(pXHit->data2 + 2 * kFrameTicks, kTimerRate * 5);
+ actDamageSprite(nSprite, nSprite, kDamageStab, 4);
+ }
+ else
+ actDamageSprite(nSprite, nSprite, kDamageStab, 1);
+ break;
+ }
+ }
+ break;
+
+ case kHitWall:
+ case kHitSector:
+ break;
+ }
+
+ nHitObject = pSpriteHit->moveHit & kHitIndexMask;
+ switch( pSpriteHit->moveHit & kHitTypeMask )
+ {
+ case kHitSprite:
+ break;
+
+ case kHitWall:
+ case kHitSector:
+ break;
+ }
+
+ nHitObject = pSpriteHit->floorHit & kHitIndexMask;
+ switch( pSpriteHit->floorHit & kHitTypeMask )
+ {
+ case kHitSprite:
+ if ( sprite[nHitObject].extra > 0 )
+ {
+ SPRITE *pHit = &sprite[nHitObject];
+ XSPRITE *pXHit = &xsprite[pHit->extra];
+ switch ( pHit->type )
+ {
+ case kTrapSawBlade:
+ if ( pXHit->state )
+ {
+ pXHit->data1 = 1;
+ pXHit->data2 = ClipHigh(pXHit->data2 + 2 * kFrameTicks, kTimerRate * 5);
+ actDamageSprite(nSprite, nSprite, kDamageStab, 4);
+ }
+ else
+ actDamageSprite(nSprite, nSprite, kDamageStab, 1);
+ break;
+ }
+ }
+ break;
+
+ case kHitWall:
+ case kHitSector:
+ break;
+ }
+}
+
+
+#define kMinZVel (6 << 4)
+#define kAirDrag 0x0100
+
+
+static int MoveThing( int nSprite, char cliptype )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ if ( !(pSprite->flags & kAttrFalling) && !pSprite->xvel && !pSprite->yvel && !pSprite->zvel )
+ return 0;
+
+ short nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+
+ gSpriteHit[nXSprite].ceilHit = 0;
+ gSpriteHit[nXSprite].floorHit = 0;
+ gSpriteHit[nXSprite].moveHit = 0;
+
+ int zTop, zBot;
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+
+ if ( pSprite->xvel || pSprite->yvel )
+ {
+ short oldcstat = pSprite->cstat;
+ pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan;
+
+ gSpriteHit[nXSprite].moveHit = ClipMove(&pSprite->x, &pSprite->y, &pSprite->z, &nSector,
+ pSprite->xvel * kFrameTicks >> 4, pSprite->yvel * kFrameTicks >> 4, pSprite->clipdist << 2,
+ (pSprite->z - zTop) / 4, (zBot - pSprite->z) / 4, cliptype);
+
+ pSprite->cstat = oldcstat;
+
+ if ((nSector != pSprite->sectnum) && (nSector >= 0))
+ changespritesect((short)nSprite, nSector);
+
+ switch (gSpriteHit[nXSprite].moveHit & kHitTypeMask)
+ {
+ case kHitWall:
+ {
+ int nWall = gSpriteHit[nXSprite].moveHit & kHitIndexMask;
+ ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0x4000);
+ pSprite->zvel = (short)mulscale16(pSprite->zvel, 0xC000);
+ break;
+ }
+
+ // need to handle sprite collisions here....
+ }
+ }
+
+ long ceilz, ceilhit, floorz, floorhit;
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype);
+
+ if ( pSprite->zvel )
+ {
+ pSprite->z += pSprite->zvel * kFrameTicks;
+ pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag);
+ }
+
+ if ( (pSprite->flags & kAttrGravity) && zBot < floorz )
+ {
+ pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2;
+ pSprite->zvel += kGravity * kFrameTicks;
+ pSprite->flags |= kAttrFalling;
+ }
+
+ // check for warping in linked sectors
+ int nUpper = gUpperLink[nSector], nLower = gLowerLink[nSector];
+ if ( nUpper >= 0 && pSprite->z < sprite[nUpper].z )
+ {
+ nLower = sprite[nUpper].owner;
+ changespritesect((short)nSprite, sprite[nLower].sectnum);
+ pSprite->x += sprite[nLower].x - sprite[nUpper].x;
+ pSprite->y += sprite[nLower].y - sprite[nUpper].y;
+ pSprite->z += sprite[nLower].z - sprite[nUpper].z;
+ viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype);
+ }
+ else if ( nLower >= 0 && pSprite->z > sprite[nLower].z )
+ {
+ nUpper = sprite[nLower].owner;
+ changespritesect((short)nSprite, sprite[nUpper].sectnum);
+ pSprite->x += sprite[nUpper].x - sprite[nLower].x;
+ pSprite->y += sprite[nUpper].y - sprite[nLower].y;
+ pSprite->z += sprite[nUpper].z - sprite[nLower].z;
+ viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, cliptype);
+ }
+
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+
+ // hit floor?
+ if ( zBot >= floorz )
+ {
+ gSpriteHit[nXSprite].floorHit = floorhit;
+ pSprite->z += floorz - zBot;
+
+ if ( pSprite->flags & kAttrFalling )
+ {
+ pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000);
+ pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000);
+ pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x4000);
+ if ( qabs(pSprite->zvel) < kMinZVel)
+ {
+ pSprite->zvel = 0;
+ pSprite->flags &= ~kAttrFalling;
+ }
+ return kHitFloor | nSector;
+ }
+ pSprite->zvel = 0;
+ }
+
+ // hit ceiling
+ if ( zTop < ceilz && ((ceilhit & kHitTypeMask) != kHitSector || !(sector[nSector].ceilingstat & kSectorParallax)) )
+ {
+ gSpriteHit[nXSprite].ceilHit = ceilhit;
+ pSprite->z += ClipLow(ceilz - zTop, 0);
+
+ pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000);
+ pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000);
+ pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x4000);
+ // return kHitCeiling | nSector;
+ }
+
+ // drag and friction
+ if ( pSprite->xvel || pSprite->yvel )
+ {
+ // air drag
+ pSprite->xvel -= mulscale16r(pSprite->xvel, kAirDrag);
+ pSprite->yvel -= mulscale16r(pSprite->yvel, kAirDrag);
+
+ if ( !(pSprite->flags & kAttrFalling) )
+ {
+ // sliding
+ int vel = qdist(pSprite->xvel, pSprite->yvel);
+ int nFrict = ClipHigh(kFrameTicks * kGroundFriction, vel);
+
+ if ( (floorhit & kHitTypeMask) == kHitSprite )
+ {
+ int nUnderSprite = floorhit & kHitIndexMask;
+ if ( (sprite[nUnderSprite].cstat & kSpriteRMask) == kSpriteFace )
+ {
+ // push it off the face sprite
+ pSprite->xvel += mulscale(kFrameTicks, pSprite->x - sprite[nUnderSprite].x, 6);
+ pSprite->yvel += mulscale(kFrameTicks, pSprite->y - sprite[nUnderSprite].y, 6);
+ return gSpriteHit[nXSprite].moveHit;
+ }
+ }
+
+ if (vel > 0)
+ {
+ nFrict = divscale16(nFrict, vel);
+ pSprite->xvel -= mulscale16(nFrict, pSprite->xvel);
+ pSprite->yvel -= mulscale16(nFrict, pSprite->yvel);
+ }
+ }
+
+ }
+
+ return gSpriteHit[nXSprite].moveHit;
+}
+
+
+static void MoveDebris( int nSprite )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+
+ short nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+
+ if ( pSprite->xvel || pSprite->yvel )
+ {
+ pSprite->x += pSprite->xvel * kFrameTicks >> 4;
+ pSprite->y += pSprite->yvel * kFrameTicks >> 4;
+
+ // air drag
+ pSprite->xvel -= mulscale16r(pSprite->xvel, kAirDrag);
+ pSprite->yvel -= mulscale16r(pSprite->yvel, kAirDrag);
+ }
+
+ if ( pSprite->zvel )
+ {
+ pSprite->z += pSprite->zvel * kFrameTicks;
+ pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag);
+ }
+
+ if ( pSprite->flags & kAttrGravity )
+ {
+ pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2;
+ pSprite->zvel += kGravity * kFrameTicks;
+ }
+
+ if ( !FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector) )
+ {
+ actPostSprite(nSprite, kStatFree);
+ return;
+ }
+
+ if ( nSector != pSprite->sectnum )
+ changespritesect((short)nSprite, nSector);
+}
+
+
+
+#define kScreamVel 1200 // zvel at which player screams
+#define kGruntVel 700
+#define kDudeDrag 0x2800
+#define kMinDudeVel (2 << 4)
+
+static void MoveDude( int nSprite )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ if ( !(pSprite->flags & kAttrFalling) && !pSprite->xvel && !pSprite->yvel && !pSprite->zvel )
+ return;
+
+ PLAYER *pPlayer = NULL;
+ if ( IsPlayerSprite(pSprite) )
+ pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ int zTop, zBot;
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+ int floorDist = (zBot - pSprite->z) / 4;
+ int ceilDist = (pSprite->z - zTop) / 4;
+ int clipDist = pSprite->clipdist << 2;
+ short nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+
+// if ( !(pSprite->xvel || pSprite->yvel || pSprite->zvel) && fWasOnFloor )
+// return;
+
+ gSpriteHit[nXSprite].ceilHit = 0;
+ gSpriteHit[nXSprite].floorHit = 0;
+ gSpriteHit[nXSprite].moveHit = 0;
+
+ if ( pSprite->xvel || pSprite->yvel )
+ {
+ int oldX = pSprite->x;
+ int oldY = pSprite->y;
+
+ if ( pPlayer && gNoClip )
+ {
+ pSprite->x += pSprite->xvel * kFrameTicks >> 4;
+ pSprite->y += pSprite->yvel * kFrameTicks >> 4;
+ updatesector( pSprite->x, pSprite->y, &nSector);
+
+ if (nSector == -1)
+ {
+ pSprite->x = oldX;
+ pSprite->y = oldY;
+ nSector = pSprite->sectnum;
+ }
+ }
+ else
+ {
+ short oldcstat = pSprite->cstat;
+ pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan;
+
+ gSpriteHit[nXSprite].moveHit = ClipMove(
+ &pSprite->x, &pSprite->y, &pSprite->z, &nSector,
+ pSprite->xvel * kFrameTicks >> 4, pSprite->yvel * kFrameTicks >> 4, clipDist,
+ ceilDist, floorDist, 0);
+
+ pSprite->cstat = oldcstat;
+ }
+
+ if ( pPlayer )
+ {
+ if ( nSector != pSprite->sectnum && nSector >= 0 )
+ {
+ // process sector exit/enter triggers
+ int nXSector;
+ nXSector = sector[pSprite->sectnum].extra;
+ if ( nXSector > 0 && xsector[nXSector].triggerExit )
+ trTriggerSector(pSprite->sectnum, &xsector[nXSector], kCommandSectorExit);
+
+ nXSector = sector[nSector].extra;
+ if ( nXSector > 0 && xsector[nXSector].triggerEnter )
+ trTriggerSector(nSector, &xsector[nXSector], kCommandSectorEnter);
+ }
+ }
+
+ if ( nSector != pSprite->sectnum )
+ changespritesect((short)nSprite, nSector);
+
+ switch (gSpriteHit[nXSprite].moveHit & kHitTypeMask)
+ {
+ case kHitWall:
+ {
+ int nWall = gSpriteHit[nXSprite].moveHit & kHitIndexMask;
+ WALL *pWall = &wall[nWall];
+
+ // don't bounce off wall if not fully blocking
+ if ( pWall->nextsector != -1 )
+ {
+ SECTOR *pSector = &sector[pWall->nextsector];
+ if ( pSector->floorz > zTop || pSector->ceilingz < zBot )
+ {
+ ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0);
+ break;
+ }
+ }
+
+ ReflectVector(&pSprite->xvel, &pSprite->yvel, nWall, 0x4000);
+ pSprite->zvel = (short)mulscale16(pSprite->zvel, 0xC000);
+ break;
+ }
+
+ // need to handle sprite collisions here....
+ }
+ }
+
+ long ceilz, ceilhit, floorz, floorhit;
+ if ( pPlayer )
+ clipDist += 16; // increase clipdist to allow jumping onto ledges
+
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0);
+
+ if ( pSprite->zvel )
+ {
+ pSprite->z += pSprite->zvel * kFrameTicks;
+ pSprite->zvel -= mulscale16r(pSprite->zvel, kAirDrag);
+ }
+
+ if ( pPlayer && pSprite->zvel > kScreamVel && !pPlayer->fScreamed )
+ {
+ pPlayer->fScreamed = TRUE;
+ sfxStart3DSound(nXSprite, kSfxPlayFall);
+ }
+
+ if ( (pSprite->flags & kAttrGravity) && zBot < floorz )
+ {
+ pSprite->z += kGravity * kFrameTicks * kFrameTicks / 2;
+ pSprite->zvel += kGravity * kFrameTicks;
+ pSprite->flags |= kAttrFalling;
+ }
+
+ // check for warping in linked sectors
+ int nUpper = gUpperLink[nSector], nLower = gLowerLink[nSector];
+ if ( nUpper >= 0 && pSprite->z < sprite[nUpper].z )
+ {
+ nLower = sprite[nUpper].owner;
+ changespritesect((short)nSprite, sprite[nLower].sectnum);
+ pSprite->x += sprite[nLower].x - sprite[nUpper].x;
+ pSprite->y += sprite[nLower].y - sprite[nUpper].y;
+ pSprite->z += sprite[nLower].z - sprite[nUpper].z;
+ viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0);
+ }
+ else if ( nLower >= 0 && pSprite->z > sprite[nLower].z )
+ {
+ nUpper = sprite[nLower].owner;
+ changespritesect((short)nSprite, sprite[nUpper].sectnum);
+ pSprite->x += sprite[nUpper].x - sprite[nLower].x;
+ pSprite->y += sprite[nUpper].y - sprite[nLower].y;
+ pSprite->z += sprite[nUpper].z - sprite[nLower].z;
+ viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, clipDist, 0);
+ }
+
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+
+ if ( pPlayer && zBot >= floorz)
+ {
+ long floorz2 = floorz, floorhit2 = floorhit;
+ GetZRange(pSprite, &ceilz, &ceilhit, &floorz, &floorhit, pSprite->clipdist << 2, 0);
+ if ( zBot <= floorz && pSprite->z - floorz2 < floorDist )
+ {
+ floorz = floorz2;
+ floorhit2 = floorhit;
+ }
+ }
+
+ // hit floor?
+ if ( zBot >= floorz )
+ {
+ gSpriteHit[nXSprite].floorHit = floorhit;
+ pSprite->z += floorz - zBot;
+
+ if ( pSprite->flags & kAttrFalling )
+ {
+ // need some way of converting fall damge to a non-linear curve
+ int fallDamage = mulscale16(pSprite->zvel, pSprite->zvel);
+ fallDamage = mulscale(fallDamage, fallDamage, 1);
+ if ( fallDamage > (20 << 4) )
+ actDamageSprite(nSprite, nSprite, kDamageFall, fallDamage);
+
+ if ( pPlayer && pXSprite->health > 0 )
+ {
+ pPlayer->fScreamed = FALSE;
+ if ( pSprite->zvel > kGruntVel )
+ sfxStart3DSound(nXSprite, kSfxPlayLand);
+ }
+
+ pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000);
+ pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000);
+ pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x2000);
+ if ( pSprite->zvel > -kMinZVel )
+ {
+ pSprite->zvel = 0;
+ pSprite->flags &= ~kAttrFalling;
+ }
+
+ int nSurfType = tileGetSurfType(floorhit);
+ if ( nSurfType == kSurfWater )
+ {
+ actSpawnEffect(pSprite->sectnum, pSprite->x, pSprite->y, floorz, ET_Splash1);
+ }
+ return;
+ }
+ pSprite->zvel = 0;
+ }
+
+ // hit ceiling
+ if ( zTop < ceilz && ((ceilhit & kHitTypeMask) != kHitSector || !(sector[nSector].ceilingstat & kSectorParallax)) )
+ {
+ pSprite->z += ClipLow(ceilz - zTop, 0);
+
+ pSprite->xvel = (short)mulscale16(pSprite->xvel, 0xC000);
+ pSprite->yvel = (short)mulscale16(pSprite->yvel, 0xC000);
+ pSprite->zvel = (short)mulscale16(-pSprite->zvel, 0x2000);
+ }
+
+ if ( zTop <= ceilz )
+ gSpriteHit[nXSprite].ceilHit = ceilhit;
+
+ // drag and friction
+ if ( pSprite->xvel || pSprite->yvel )
+ {
+ // air drag
+ pSprite->xvel -= mulscale16(pSprite->xvel, kAirDrag);
+ pSprite->yvel -= mulscale16(pSprite->yvel, kAirDrag);
+
+ if ( !(pSprite->flags & kAttrFalling) )
+ {
+ if ( (floorhit & kHitTypeMask) == kHitSprite )
+ {
+ int nUnderSprite = floorhit & kHitIndexMask;
+ if ( (sprite[nUnderSprite].cstat & kSpriteRMask) == kSpriteFace )
+ {
+ // push it off the face sprite
+ pSprite->xvel += mulscale(kFrameTicks, pSprite->x - sprite[nUnderSprite].x, 6);
+ pSprite->yvel += mulscale(kFrameTicks, pSprite->y - sprite[nUnderSprite].y, 6);
+ return;
+ }
+ }
+
+ // movement drag
+ pSprite->xvel -= (sshort)mulscale16r(pSprite->xvel, kDudeDrag);
+ pSprite->yvel -= (sshort)mulscale16r(pSprite->yvel, kDudeDrag);
+
+ if ( qdist(pSprite->xvel, pSprite->yvel) < kMinDudeVel )
+ pSprite->xvel = pSprite->yvel = 0;
+ }
+ }
+
+ ProcessTouchObjects( nSprite, nXSprite );
+}
+
+
+// missiles are self-propelled and are unaffected by gravity
+static int MoveMissile( int nSprite )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+
+ return movesprite((short)nSprite, pSprite->xvel, pSprite->yvel, pSprite->zvel,
+ 4 << 8, 4 << 8, 1, kFrameTicks);
+}
+
+
+void actExplodeSprite( int nSprite )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+
+ // already exploding?
+ if (pSprite->statnum == kStatExplosion)
+ return;
+
+ switch ( pSprite->type )
+ {
+ case kMissileExplodingFlare:
+ case kMissileStarburstFlare:
+ seqSpawn(kSeqExplodeC2T, SS_SPRITE, nXSprite);
+ break;
+
+ case kThingTNTStick:
+ if ( gSpriteHit[nXSprite].floorHit == 0 )
+ seqSpawn(kSeqExplodeC2S, SS_SPRITE, nXSprite);
+ else
+ seqSpawn(kSeqExplodeC1S, SS_SPRITE, nXSprite);
+ break;
+
+ case kThingTNTProxArmed:
+ case kThingTNTRemArmed:
+ case kThingTNTBundle:
+// if ( gSpriteHit[nXSprite].floorHit == 0 )
+ seqSpawn(kSeqExplodeC2M, SS_SPRITE, nXSprite);
+// else
+// seqSpawn(kSeqExplodeC1M, SS_SPRITE, nXSprite);
+ break;
+
+ case kThingTNTBarrel:
+ {
+ // spawn an explosion effect
+ int nEffect = actSpawnSprite( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatExplosion, TRUE );
+ sprite[nEffect].owner = pSprite->owner; // set owner for frag/targeting
+
+ // place barrel on the respawn list or just delete it
+ if ( actCheckRespawn( nSprite ) )
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->state = 0;
+ pXSprite->health = thingInfo[kThingTNTBarrel - kThingBase].startHealth << 4;
+ }
+ else
+ actPostSprite( nSprite, kStatFree );
+
+ // reset locals to point at the effect, not the barrel
+ nSprite = nEffect;
+ pSprite = &sprite[nEffect];
+ nXSprite = pSprite->extra;
+ seqSpawn(kSeqExplodeC2L, SS_SPRITE, nXSprite);
+ break;
+ }
+
+ default:
+ seqSpawn(kSeqExplodeC2M, SS_SPRITE, nXSprite);
+ break;
+ }
+ pSprite->xvel = pSprite->yvel = pSprite->zvel = 0;
+ actPostSprite( nSprite, kStatExplosion );
+ pSprite->flags &= ~(kAttrMove | kAttrGravity);
+
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxExplodeCS);
+}
+
+
+void actProcessSprites(void)
+{
+ int nSprite;
+ int nDude, nNextDude;
+
+ // process proximity triggered sprites
+ for (nSprite = headspritestat[kStatProximity]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ for (nDude = headspritestat[kStatDude]; nDude >= 0; nDude = nNextDude)
+ {
+ nNextDude = nextspritestat[nDude];
+
+ if ( CheckProximity(&sprite[nDude], pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 64) )
+ trTriggerSprite(nSprite, pXSprite, kCommandSpriteProximity);
+ }
+ }
+
+ // process things for effects
+ for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->extra > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if ( actGetBurnTime(pXSprite) > 0 )
+ {
+ pXSprite->burnTime = ClipLow(pXSprite->burnTime - kFrameTicks, 0);
+ actDamageSprite( pXSprite->burnSource, nSprite, kDamageBurn, 4 * kFrameTicks );
+ }
+ }
+ }
+
+ // process things for movement
+ for (nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->flags & (kAttrMove | kAttrGravity) )
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ int hitInfo = MoveThing(nSprite, 1);
+ if (hitInfo != 0)
+ {
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if ( pXSprite->triggerProximity )
+ trTriggerSprite(nSprite, pXSprite, kCommandOff);
+ switch( pSprite->type )
+ {
+ case kThingWaterDrip:
+ case kThingBloodDrip:
+ MakeSplash(pSprite, pXSprite);
+ break;
+
+ case kThingBoneClub:
+ seqSpawn(kSeqBoneBreak, SS_SPRITE, nXSprite);
+ if ( (hitInfo & kHitTypeMask) == kHitSprite )
+ actDamageSprite( pSprite->owner, hitInfo & kHitIndexMask, kDamagePummel, 12 );
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // process debris sprites
+ for (nSprite = headspritestat[kStatDebris]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ viewBackupSpriteLoc(nSprite, pSprite);
+ MoveDebris(nSprite);
+ }
+
+ // process missile sprites
+ for (nSprite = headspritestat[kStatMissile]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ int hitInfo = MoveMissile(nSprite);
+
+ // process impacts
+ if (hitInfo != 0)
+ actImpactMissile( nSprite, hitInfo );
+ }
+
+ // process explosions
+ for (nSprite = headspritestat[kStatExplosion]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int x = pSprite->x, y = pSprite->y, z = pSprite->z, nSector = pSprite->sectnum;
+ int nAffected;
+ int radius = tilesizx[pSprite->picnum] * pSprite->xrepeat >> 6;
+
+ for (nAffected = headspritestat[kStatDude]; nAffected >= 0; nAffected = nextspritestat[nAffected])
+ {
+ if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) )
+ ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks);
+ }
+
+ for (nAffected = headspritestat[kStatThing]; nAffected >= 0; nAffected = nextspritestat[nAffected])
+ {
+ if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) )
+ ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks);
+ }
+
+ for (nAffected = headspritestat[kStatProximity]; nAffected >= 0; nAffected = nextspritestat[nAffected])
+ {
+ if ( CheckProximity(&sprite[nAffected], x, y, z, nSector, radius) )
+ ConcussSprite(pSprite->owner, nAffected, x, y, z, kFrameTicks);
+ }
+ }
+
+ // process traps for effects
+ for (nSprite = headspritestat[kStatTraps]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->extra > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ switch( pSprite->type )
+ {
+ case kTrapSawBlade:
+ pXSprite->data2 = ClipLow(pXSprite->data2 - kFrameTicks, 0);
+ break;
+ }
+ }
+ }
+
+ // process dudes for effects
+ for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->extra > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if ( actGetBurnTime(pXSprite) > 0 )
+ {
+ pXSprite->burnTime = ClipLow(pXSprite->burnTime - kFrameTicks, 0);
+ actDamageSprite( pXSprite->burnSource, nSprite, kDamageBurn, 2 * kFrameTicks );
+ }
+ }
+ }
+
+ // process dudes for movement
+ for (nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int nSector = pSprite->sectnum;
+
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ // special sector processing
+ if ( sector[nSector].extra > 0 )
+ {
+ int nXSector = sector[ nSector ]. extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ dassert(xsector[nXSector].reference == nSector);
+
+ XSECTOR *pXSector = &xsector[nXSector];
+ if ( pXSector->panVel && (pXSector->panAlways || pXSector->busy))
+ {
+ int windDrag = 0x0200; // 16:16 fixed point fraction
+ int panVel = pXSector->panVel;
+ int panAngle = pXSector->panAngle;
+
+ if ( !pXSector->panAlways && pXSector->busy )
+ panVel = mulscale16(panVel, pXSector->busy);
+
+ if (sector[nSector].floorstat & kSectorRelAlign)
+ {
+ panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90;
+ panAngle &= kAngleMask;
+ }
+
+ if (pXSector->wind)
+ {
+ int windX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel;
+ int windY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel;
+ pSprite->xvel += mulscale16(kFrameTicks * windX, windDrag);
+ pSprite->yvel += mulscale16(kFrameTicks * windY, windDrag);
+ }
+ }
+
+ // handle water dragging
+ if ( pXSector->depth > 0 )
+ {
+ if ( pSprite->z >= sector[nSector].floorz )
+ {
+ int pushDrag = dragTable[pXSector->depth];
+ int panVel = 0;
+ int panAngle = pXSector->panAngle;
+
+ if (pXSector->panAlways || pXSector->state || pXSector->busy)
+ {
+ panVel = pXSector->panVel;
+ if ( !pXSector->panAlways && pXSector->busy )
+ panVel = mulscale16(panVel, pXSector->busy);
+ }
+
+ if (sector[nSector].floorstat & kSectorRelAlign)
+ {
+ panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90;
+ panAngle &= kAngleMask;
+ }
+ int pushX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel;
+ int pushY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel;
+ int nDrag = ClipHigh(kFrameTicks * pushDrag, 0x10000);
+ pSprite->xvel += mulscale16(pushX, nDrag);
+ pSprite->yvel += mulscale16(pushY, nDrag);
+ }
+ } else if ( pXSector->underwater ) // handle underwater dragging
+ {
+ int pushDrag = dragTable[pXSector->depth];
+ int panVel = 0;
+ int panAngle = pXSector->panAngle;
+
+ if (pXSector->panAlways || pXSector->state || pXSector->busy)
+ {
+ panVel = pXSector->panVel;
+ if ( !pXSector->panAlways && pXSector->busy )
+ panVel = mulscale16(panVel, pXSector->busy);
+ }
+
+ if (sector[nSector].floorstat & kSectorRelAlign)
+ {
+ panAngle += GetWallAngle(sector[nSector].wallptr) + kAngle90;
+ panAngle &= kAngleMask;
+ }
+ int pushX = mulscale30(panVel, Cos(panAngle)) - pSprite->xvel;
+ int pushY = mulscale30(panVel, Sin(panAngle)) - pSprite->yvel;
+ pSprite->xvel += mulscale16(kFrameTicks * pushX, pushDrag);
+ pSprite->yvel += mulscale16(kFrameTicks * pushY, pushDrag);
+ }
+ }
+
+ MoveDude(nSprite);
+ }
+
+ // process flares to keep them burning on dudes
+ for (nSprite = headspritestat[kStatFlare]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ SPRITE *pTarget = &sprite[pXSprite->target];
+
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ int nAngle = pTarget->ang + pXSprite->goalAng;
+ int x = pTarget->x + mulscale30r( Cos(nAngle), pTarget->clipdist << 1 ); // halfway into clipdist
+ int y = pTarget->y + mulscale30r( Sin(nAngle), pTarget->clipdist << 1 );
+ int z = pTarget->z + pXSprite->targetZ;
+
+ setsprite((short)pXSprite->reference, x, y, z);
+ }
+
+ aiProcessDudes();
+}
+
+
+/***********************************************************************
+ * actSpawnSprite()
+ *
+ * Spawns a new sprite at the specified world coordinates.
+ **********************************************************************/
+int actSpawnSprite( short nSector, int x, int y, int z, short nStatus, BOOL bAddXSprite )
+{
+ int nSprite = insertsprite( nSector, nStatus );
+ if (nSprite >= 0)
+ sprite[nSprite].extra = -1;
+ else
+ {
+ dprintf("Out of sprites -- reclaiming sprite from purge list\n");
+ nSprite = headspritestat[kStatPurge];
+ dassert(nSprite >= 0);
+ changespritesect((short)nSprite, nSector);
+ changespritestat((short)nSprite, nStatus);
+ }
+ setsprite( (short)nSprite, x, y, z );
+
+ SPRITE *pSprite = &sprite[nSprite];
+
+ pSprite->type = 0;
+
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ // optionally create an xsprite
+ if ( bAddXSprite && pSprite->extra == -1 )
+ {
+ int nXSprite = dbInsertXSprite(nSprite);
+ gSpriteHit[nXSprite].floorHit = 0;
+ gSpriteHit[nXSprite].ceilHit = 0;
+ }
+
+ return nSprite;
+}
+
+
+int actCloneSprite( SPRITE *pSourceSprite )
+{
+ int nSprite = insertsprite( pSourceSprite->sectnum, pSourceSprite->statnum);
+ if ( nSprite < 0 )
+ {
+ dprintf("Out of sprites -- reclaiming sprite from purge list\n");
+ nSprite = headspritestat[kStatPurge];
+ dassert(nSprite >= 0);
+ changespritesect((short)nSprite, pSourceSprite->sectnum);
+ changespritestat((short)nSprite, pSourceSprite->statnum);
+ }
+ SPRITE *pSprite = &sprite[nSprite];
+ *pSprite = *pSourceSprite;
+ viewBackupSpriteLoc(nSprite, pSprite);
+
+ // don't copy xsprite
+ pSprite->extra = -1;
+ return nSprite;
+}
+
+
+int actSpawnThing( short nSector, int x, int y, int z, int thingType )
+{
+ dassert( thingType >= kThingBase && thingType < kThingMax );
+
+ int nThing = actSpawnSprite(nSector, x, y, z, kStatThing, TRUE );
+
+ SPRITE *pThing = &sprite[nThing];
+ pThing->type = (short)thingType;
+ int thingIndex = thingType - kThingBase;
+
+ int nXThing = pThing->extra;
+ dassert(nXThing > 0 && nXThing < kMaxXSprites);
+ XSPRITE *pXThing = &xsprite[nXThing];
+
+ pThing->clipdist = thingInfo[thingIndex].clipdist;
+ pThing->flags = thingInfo[thingIndex].flags;
+ if ( pThing->flags & kAttrGravity )
+ pThing->flags |= kAttrFalling;
+ pThing->pal = 0;
+
+ pXThing->health = thingInfo[thingIndex].startHealth << 4;
+
+ SetBitString(show2dsprite, nThing);
+
+ switch (thingType)
+ {
+ case kThingTNTStick:
+ pThing->shade = -32;
+ pThing->picnum = 2169;
+ pThing->cstat |= kSpriteHitscan;
+ pThing->xrepeat = pThing->yrepeat = 32;
+ break;
+
+ case kThingTNTBundle:
+ pThing->shade = -32;
+ pThing->picnum = 2172;
+ pThing->cstat |= kSpriteHitscan;
+ pThing->xrepeat = pThing->yrepeat = 32;
+ break;
+
+ case kThingTNTProxArmed:
+ pThing->shade = -16;
+ pThing->picnum = kAnmTNTProxArmed;
+ pThing->cstat |= kSpriteHitscan;
+ pThing->xrepeat = pThing->yrepeat = 32;
+ break;
+
+ case kThingTNTRemArmed:
+ pThing->shade = -16;
+ pThing->picnum = kAnmTNTRemArmed;
+ pThing->cstat |= kSpriteHitscan;
+ pThing->xrepeat = pThing->yrepeat = 32;
+ break;
+
+ case kThingBoneClub:
+ pThing->shade = 0;
+ pThing->picnum = kAnmBoneClub;
+ pThing->cstat |= kSpriteHitscan;
+ pThing->xrepeat = pThing->yrepeat = 32;
+ break;
+
+ case kThingWaterDrip:
+ pThing->picnum = kAnmDrip;
+ pThing->pal = kPLUCold;
+ //pThing->cstat |= kSpriteTranslucent | kSpriteTranslucentR;
+ pThing->shade = 0; // -12;
+ break;
+
+ case kThingBloodDrip:
+ pThing->picnum = kAnmDrip;
+ pThing->pal = kPLURed;
+ //pThing->cstat |= kSpriteTranslucent | kSpriteTranslucentR;
+ pThing->shade = 0; // -12;
+ break;
+ }
+
+ return nThing;
+}
+
+
+int actFireThing( int nActor, int z, int nSlope, int thingType, int velocity )
+{
+ dassert( thingType >= kThingBase && thingType < kThingMax );
+
+ SPRITE *pActor = &sprite[nActor];
+
+ int nThing = actSpawnThing(pActor->sectnum,
+ pActor->x + mulscale30(pActor->clipdist << 2, Cos(pActor->ang)),
+ pActor->y + mulscale30(pActor->clipdist << 2, Sin(pActor->ang)),
+ z, thingType);
+
+ SPRITE *pThing = &sprite[nThing];
+
+ pThing->owner = (short)nActor;
+ pThing->ang = pActor->ang;
+ pThing->xvel = (short)mulscale30(velocity, Cos(pActor->ang));
+ pThing->yvel = (short)mulscale30(velocity, Sin(pActor->ang));
+ pThing->zvel = (short)mulscale(velocity, nSlope, 14);
+ pThing->xvel += pActor->xvel / 2;
+ pThing->yvel += pActor->yvel / 2;
+ pThing->zvel += pActor->zvel / 2;
+
+ return nThing;
+}
+
+
+void actFireMissile( int nActor, int z, int dx, int dy, int dz, int missileType )
+{
+ dassert( missileType >= kMissileBase && missileType < kMissileMax );
+
+ SPRITE *pActor = &sprite[nActor];
+
+ int nSprite = actSpawnSprite(pActor->sectnum,
+ pActor->x + mulscale30(pActor->clipdist << 1, Cos(pActor->ang)),
+ pActor->y + mulscale30(pActor->clipdist << 1, Sin(pActor->ang)),
+ z, kStatMissile, TRUE );
+
+ SPRITE *pSprite = &sprite[nSprite];
+ pSprite->type = (short)missileType;
+
+ MissileType *pMissType = &missileInfo[ missileType - kMissileBase ];
+ int velocity = pMissType->velocity;
+
+ pSprite->shade = pMissType->shade;
+ pSprite->pal = 0;
+ pSprite->clipdist = 16;
+ pSprite->flags = kAttrMove;
+ pSprite->xrepeat = pMissType->xrepeat;
+ pSprite->yrepeat = pMissType->yrepeat;
+ pSprite->picnum = pMissType->picnum;
+ pSprite->ang = (short)((pActor->ang +
+ missileInfo[missileType - kMissileBase].angleOfs) & kAngleMask);
+ pSprite->xvel = (short)mulscale(velocity, dx, 14);
+ pSprite->yvel = (short)mulscale(velocity, dy, 14);
+ pSprite->zvel = (short)mulscale(velocity, dz, 14);
+ pSprite->owner = (short)nActor;
+
+ SetBitString(show2dsprite, nSprite);
+
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ switch( missileType )
+ {
+ case kMissileButcherKnife:
+ pSprite->cstat |= kSpriteWall;
+ break;
+ case kMissileSpear:
+ pSprite->cstat |= kSpriteWall;
+ break;
+ case kMissileEctoSkull:
+ seqSpawn(kSeqSkull, SS_SPRITE, nXSprite);
+ break;
+ case kMissileFireball:
+ seqSpawn(kSeqFireball, SS_SPRITE, nXSprite, &FireballCallback);
+ break;
+ case kMissileHoundFire:
+ seqSpawn(kSeqHoundFire, SS_SPRITE, nXSprite);
+ pSprite->xvel += (pActor->xvel / 2) + BiRandom(50);
+ pSprite->yvel += (pActor->yvel / 2) + BiRandom(50);
+ pSprite->zvel += (pActor->zvel / 2) + BiRandom(50);
+ break;
+ case kMissileSprayFlame:
+ if ( Chance(0x8000) )
+ seqSpawn(kSeqSprayFlame1, SS_SPRITE, nXSprite);
+ else
+ seqSpawn(kSeqSprayFlame2, SS_SPRITE, nXSprite);
+ pSprite->xvel += (pActor->xvel / 2) + BiRandom(50);
+ pSprite->yvel += (pActor->yvel / 2) + BiRandom(50);
+ pSprite->zvel += (pActor->zvel / 2) + BiRandom(50);
+ break;
+ case kMissileStarburstFlare:
+ evPost(nSprite, SS_SPRITE, kTimerRate/6 ); // callback to flare
+ break;
+ case kMissileFlare:
+ case kMissileExplodingFlare:
+ //seqSpawn(kSeqFlare, SS_SPRITE, nXSprite, &FlareCallback);
+ //pSprite->pal = kPLURed;
+ break;
+ default:
+ break;
+ }
+}
+
+
+/***********************************************************************
+ * actSpawnEffect()
+ *
+ * Spawns an effect sprite at the specified world coordinates.
+ **********************************************************************/
+SPRITE *actSpawnEffect( short nSector, int x, int y, int z, int nEffect )
+{
+ int nSprite = actSpawnSprite(nSector, x, y, z, kStatEffect, TRUE);
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+
+ switch( nEffect )
+ {
+ case ET_Splash1: // splash from dude landing in water
+ seqSpawn(kSeqSplash1, SS_SPRITE, nXSprite);
+ break;
+
+ case ET_Splash2: // splash from bullet hitting water
+ seqSpawn(kSeqSplash2, SS_SPRITE, nXSprite);
+ break;
+
+ case ET_Ricochet1: // bullet ricochet off stone/metal
+ if ( Chance(0x8000) )
+ pSprite->cstat |= kSpriteFlipX;
+ seqSpawn(kSeqRicochet1, SS_SPRITE, nXSprite);
+ break;
+
+ case ET_Ricochet2: // bullet ricochet off wood/dirt/clay
+ if ( Chance(0x8000) )
+ pSprite->cstat |= kSpriteFlipX;
+ seqSpawn(kSeqRicochet2, SS_SPRITE, nXSprite);
+ break;
+
+ case ET_Squib1:
+ if ( Chance(0x8000) )
+ pSprite->cstat |= kSpriteFlipX;
+ seqSpawn(kSeqSquib1, SS_SPRITE, nXSprite);
+ break;
+
+ case ET_SmokeTrail:
+ if ( Chance(0x8000) )
+ pSprite->cstat |= kSpriteFlipX;
+ if ( Chance(0x8000) )
+ pSprite->cstat |= kSpriteFlipY;
+ seqSpawn(kSeqFlareSmoke, SS_SPRITE, nXSprite);
+ break;
+
+ default:
+ // don't have a sequence yet, so just delete it for now
+ dprintf("actSpawnEffect: missing sequence\n");
+ actPostSprite( nSprite, kStatFree );
+ return NULL;
+ }
+ return pSprite;
+}
+
+
+BOOL actCheckRespawn( int nSprite )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->extra > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+
+ if (pXSprite->respawn == kRespawnPermanent)
+ return TRUE;
+
+ // if (sprite respawn is forced OR (sprite has optional respawning set
+ // AND (sprite is a dude AND global dude respawning is set)
+ // OR (sprite is a thing AND global thing respawning is set)))
+ if (pXSprite->respawn == kRespawnAlways || (pXSprite->respawn == kRespawnOptional
+ && (pSprite->type >= kDudeBase && pSprite->type < kDudeMax && gRespawnEnemies)
+ || ((pSprite->type < kDudeBase || pSprite->type > kDudeMax) && gRespawnItems)))
+ {
+ if (pXSprite->respawnTime > 0)
+ {
+ dprintf("Respawn statnum: %d\n",pSprite->statnum);
+ pSprite->owner = pSprite->statnum; // store the sprite's status list for respawning
+ actPostSprite( nSprite, kStatRespawn );
+ pSprite->cstat &= ~kSpriteBlocking & ~kSpriteHitscan;
+ pSprite->cstat |= kSpriteInvisible;
+ evPost(nSprite, SS_SPRITE, pXSprite->respawnTime * kTimerRate / 10, kCommandRespawn);
+ return TRUE;
+ }
+ }
+ }
+ return FALSE; // indicate sprite will not respawn, and should be deleted, exploded, etc.
+}
+
+
+void actRespawnDude( int nSprite )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+
+ // probably need more dude-specific initialization
+ pXSprite->moveState = 0;
+ pXSprite->aiState = 0;
+ pXSprite->health = 0;
+ pXSprite->target = 0;
+ pXSprite->targetX = 0;
+ pXSprite->targetY = 0;
+ pXSprite->targetZ = 0;
+ pXSprite->key = 0; // respawned dudes don't drop additional permanent keys
+}
+
+
+void actRespawnSprite( int nSprite )
+{
+ SPRITE *pSprite = &sprite[nSprite];
+
+ actPostSprite( nSprite, pSprite->owner );
+ pSprite->owner = -1;
+ pSprite->cstat &= ~kSpriteInvisible;
+
+ if ( pSprite->extra > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ pXSprite->isTriggered = 0;
+
+ if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ actRespawnDude( nSprite );
+ }
+
+ // spawn the respawn special effect
+ int nRespawn = actSpawnSprite( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatDefault, TRUE );
+ seqSpawn(kSeqRespawn, SS_SPRITE, sprite[nRespawn].extra);
+}
+
+
+void actFireVector( int nActor, int z, int dx, int dy, int dz, VECTOR_TYPE vectorType )
+{
+ SPRITE *pActor = &sprite[nActor];
+ HITINFO hitInfo;
+
+ dassert(vectorType >= 0 && vectorType < kVectorMax);
+ VECTORDATA *pVectorData = &gVectorData[vectorType];
+ int maxDist = pVectorData->maxDist;
+
+ int hitCode = VectorScan( pActor, z, dx, dy, dz, &hitInfo );
+
+ // determine a point just a bit back from the intersection to place the ricochet
+ int rx = hitInfo.hitx - mulscale(dx, 1 << 4, 14);
+ int ry = hitInfo.hity - mulscale(dy, 1 << 4, 14);
+ int rz = hitInfo.hitz - mulscale(dz, 1 << 4, 14);
+ BYTE nSurf = kSurfNone;
+
+ if ( maxDist == 0 || qdist(hitInfo.hitx - pActor->x, hitInfo.hity - pActor->y) < maxDist )
+ {
+ switch ( hitCode )
+ {
+ case SS_CEILING:
+ if ( sector[hitInfo.hitsect].ceilingstat & kSectorParallax )
+ nSurf = kSurfNone;
+ else
+ nSurf = surfType[sector[hitInfo.hitsect].ceilingpicnum];
+ break;
+
+ case SS_FLOOR:
+ if ( sector[hitInfo.hitsect].floorstat & kSectorParallax )
+ nSurf = kSurfNone;
+ else
+ nSurf = surfType[sector[hitInfo.hitsect].floorpicnum];
+ break;
+
+ case SS_WALL:
+ {
+ int nWall = hitInfo.hitwall;
+ dassert( nWall >= 0 && nWall < kMaxWalls );
+
+ nSurf = surfType[wall[nWall].picnum];
+ WALL *pWall = &wall[nWall];
+
+ int nXWall = wall[nWall].extra;
+ if ( nXWall > 0 )
+ {
+ XWALL *pXWall = &xwall[nXWall];
+ if ( pXWall->triggerImpact )
+ trTriggerWall(nWall, pXWall, kCommandWallImpact);
+ }
+ break;
+ }
+
+ case SS_MASKED:
+ {
+ int nWall = hitInfo.hitwall;
+ dassert( nWall >= 0 && nWall < kMaxWalls );
+
+ nSurf = surfType[wall[nWall].overpicnum];
+ WALL *pWall = &wall[nWall];
+
+ int nXWall = wall[nWall].extra;
+ if ( nXWall > 0 )
+ {
+ XWALL *pXWall = &xwall[nXWall];
+ if ( pXWall->triggerImpact )
+ trTriggerWall(nWall, pXWall, kCommandWallImpact);
+ }
+ break;
+ }
+
+ case SS_SPRITE:
+ {
+ int nSprite = hitInfo.hitsprite;
+ nSurf = surfType[sprite[nSprite].picnum];
+
+ dassert( nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+
+ // back off ricochet (squibs) even more
+ rx -= mulscale(dx, 7 << 4, 14);
+ ry -= mulscale(dy, 7 << 4, 14);
+ rz -= mulscale(dz, 7 << 4, 14);
+
+ actDamageSprite(nActor, nSprite, pVectorData->damageType,
+ pVectorData->damageValue << 4);
+
+ int nXSprite = pSprite->extra;
+ if ( nXSprite > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if ( pXSprite->triggerImpact )
+ trTriggerSprite(nSprite, pXSprite, kCommandSpriteImpact);
+ }
+ break;
+ }
+ }
+ }
+
+ if ( pVectorData->impact[nSurf].nEffect >= 0 )
+ actSpawnEffect(hitInfo.hitsect, rx, ry, rz, pVectorData->impact[nSurf].nEffect);
+
+ if ( pVectorData->impact[nSurf].nSoundId >= 0 )
+ sfxCreate3DSound(rx, ry, rz, pVectorData->impact[nSurf].nSoundId);
+}
+
+
+static void FireballCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ SPRITE *pSmoke = actSpawnEffect( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, ET_SmokeTrail );
+ if (gDetail < kDetailLevelMax)
+ pSmoke->cstat |= kSpriteInvisible;
+}
+
+//static void FlareCallback( int /* type */, int nXIndex )
+//{
+// XSPRITE *pXSprite = &xsprite[nXIndex];
+// int nSprite = pXSprite->reference;
+// SPRITE *pSprite = &sprite[nSprite];
+//
+// actSpawnEffect( pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, ET_SmokeTrail );
+//}
+
+struct POSTPONE
+{
+ short nSprite;
+ short nStatus;
+};
+
+int gPostCount = 0;
+POSTPONE gPost[ kMaxSprites ];
+
+/***********************************************************************
+ * actPostSprite()
+ *
+ * Postpones deletion or status list change for a sprite.
+ * An nStatus value of kStatFree passed to this function will
+ * postpone deletion of the sprite until the gPostpone list is
+ * next processed.
+ **********************************************************************/
+void actPostSprite( int nSprite, int nStatus )
+{
+ dassert( gPostCount < kMaxSprites );
+ dassert( nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus );
+ dassert( nStatus >= 0 && nStatus <= kStatFree );
+
+ // see if it is already in the list (we may want to semaphore with an attr bit for speed)
+ for (int n = 0; n < gPostCount; n++)
+ if ( gPost[n].nSprite == nSprite )
+ break;
+
+ gPost[n].nSprite = (short)nSprite;
+ gPost[n].nStatus = (short)nStatus;
+
+ if ( n == gPostCount )
+ gPostCount++;
+}
+
+/***********************************************************************
+ * actPostProcess()
+ *
+ * Processes postponed sprite events to ensure that sprite list
+ * processing functions normally when sprites are deleted or change
+ * status.
+ *
+ **********************************************************************/
+void actPostProcess( void )
+{
+ if (gPostCount)
+ {
+ for ( int i = 0; i < gPostCount; i++ )
+ {
+ POSTPONE *pPost = &gPost[i];
+
+ if ( pPost->nStatus == kStatFree )
+ deletesprite( pPost->nSprite );
+ else
+ changespritestat( pPost->nSprite, pPost->nStatus );
+ }
+ gPostCount = 0;
+ }
+}
+
+
+static void MakeSplash( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int nXSprite = pSprite->extra;
+ int nSprite = pXSprite->reference;
+
+ pSprite->flags &= ~kAttrGravity; // no bouncing...
+ pSprite->z -= 4 << 8; // up one pixel
+ viewBackupSpriteLoc(nSprite, pSprite); // prevent interpolation
+
+ int nSurfType = tileGetSurfType(gSpriteHit[nXSprite].floorHit);
+
+ switch ( pSprite->type )
+ {
+ case kThingWaterDrip:
+ switch (nSurfType)
+ {
+ case kSurfWater:
+ seqSpawn(kSeqSplash1, SS_SPRITE, nXSprite);
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z,
+ kSfxDrip3, 0x2000);
+ // add a check for depth that uses either kSfxDrip2 (deep) or kSfxDrip3 (not deep)
+ break;
+
+ default:
+ seqSpawn(kSeqSplash2, SS_SPRITE, nXSprite);
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z,
+ kSfxDrip1, 0x2000);
+ break;
+ }
+ break;
+
+ case kThingBloodDrip:
+ seqSpawn(kSeqSplash3, SS_SPRITE, nXSprite);
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z,
+ kSfxDrip1, 0x2000);
+ break;
+ }
+}
diff --git a/SRC/ACTOR.H b/SRC/ACTOR.H
new file mode 100644
index 0000000..a8971f4
--- /dev/null
+++ b/SRC/ACTOR.H
@@ -0,0 +1,187 @@
+#ifndef __ACTOR_H
+#define __ACTOR_H
+
+#include "globals.h"
+#include "qav.h"
+#include "db.h"
+#include "misc.h"
+
+#define kGroundFriction 20
+
+// Player movement types
+enum {
+ kMoveDead = 0,
+ kMoveStill,
+ kMoveWalk,
+ kMoveFall,
+ kMoveLand,
+ kMoveStand,
+ kMoveSwim,
+ kMoveFly,
+ kMoveHang,
+};
+
+// Damage types
+enum DAMAGE_TYPE
+{
+ kDamagePummel = 0, // punching/pummel/impact/crush
+ kDamageFall, // damage from falling (landing, actually)
+ kDamageBurn, // burning/heat/fire
+ kDamageBullet, // bullets or other piercing projectiles
+ kDamageStab, // stabbing, impaling, slicing, dicing
+ kDamageExplode, // explosion damage from concussive effect
+ kDamageGas, // damage from inhaling poison gas
+ kDamageDrown, // been underwater too long, eh?
+ kDamageSpirit, // ectoplasm or spirit damage
+ kDamageVoodoo, // hoodoo voodoo damage
+ kDamageMax
+};
+
+#define kNoDamage 31 // no damage constant for damage shift table
+
+enum VECTOR_TYPE
+{
+ kVectorNone = -1,
+ kVectorTine = 0,
+ kVectorShell,
+ kVectorBullet,
+ kVectorBulletAP,
+ kVectorAxe,
+ kVectorCleaver,
+ kVectorClaw,
+ kVectorHoundBite,
+ kVectorRatBite,
+ kVectorSpiderBite,
+ kVectorMax,
+};
+
+
+#define kMaxBurnTime (30 * kTimerRate) // 30 seconds
+
+/* effect types */
+enum {
+ ET_Splash1, // splash from dude landing in water
+ ET_Splash2, // splash from bullet hitting water
+ ET_Ricochet1, // bullet ricochet off stone/metal
+ ET_Ricochet2, // bullet ricochet off wood
+ ET_Squib1, // bloody squib
+ ET_SmokeTrail, // smoke trail for fireball
+};
+
+struct SPRITEHIT
+{
+ int moveHit;
+ int ceilHit;
+ int floorHit;
+};
+
+extern SPRITEHIT gSpriteHit[ kMaxXSprites ];
+
+struct ITEMDATA
+{
+ ushort cstat;
+ sshort picnum;
+ schar shade;
+ uchar pal;
+ uchar xrepeat, yrepeat;
+ short statnum;
+ short type;
+ ushort flags;
+};
+
+struct AMMOITEMDATA
+{
+ ushort cstat;
+ sshort picnum;
+ schar shade;
+ uchar pal;
+ uchar xrepeat, yrepeat;
+ ushort flags;
+ short count;
+ uchar ammoType;
+ uchar weaponType;
+};
+
+struct WEAPONITEMDATA
+{
+ ushort cstat;
+ sshort picnum;
+ schar shade;
+ uchar pal;
+ uchar xrepeat, yrepeat;
+ ushort flags;
+ short weaponType;
+ short ammoType;
+ short count;
+};
+
+// ammo and power up constants
+#define kMaxItemTypes (kItemMax - kItemBase)
+#define kMaxPowerUps (kItemMax - kItemBase)
+
+extern ITEMDATA gItemData[ kMaxItemTypes ];
+extern AMMOITEMDATA gAmmoItemData[ kAmmoItemMax - kAmmoItemBase ];
+extern WEAPONITEMDATA gWeaponItemData[ kWeaponItemMax - kWeaponItemBase ];
+
+
+/***********************************************************************
+ * Function Prototypes
+ **********************************************************************/
+void actInit( void );
+void actAllocateSpares( void );
+BOOL actHealDude(XSPRITE *pXSprite, int healValue, int maxHealthClip = 100);
+void actDamageSprite( int nSource, int nSprite, DAMAGE_TYPE damageType, int damageValue );
+void actProcessSprites( void );
+int actSpawnSprite( short nSector, int x, int y, int z, short nStatus, BOOL bAddXSprite );
+int actCloneSprite( SPRITE *pSprite );
+SPRITE *actSpawnEffect( short nSector, int x, int y, int z, int nEffect );
+void actExplodeSprite( int nSprite );
+
+BOOL actCheckRespawn( int nSprite );
+void actRespawnSprite( int nSprite ); // respawn dude or item
+
+int actSpawnThing( short nSector, int x, int y, int z, int nThing );
+int actFireThing( int nActor, int z, int nSlope, int thingType, int velocity );
+void actFireMissile( int nActor, int z, int dx, int dy, int dz, int missileType );
+void actFireVector( int nActor, int z, int dx, int dy, int dz, VECTOR_TYPE vectorType );
+
+void actPostSprite( int nSprite, int nStatus );
+void actPostProcess( void );
+
+
+/***********************************************************************
+ * Inline Functions
+ **********************************************************************/
+inline BOOL IsPlayerSprite( SPRITE *pSprite )
+{
+ return (pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8) ? TRUE : FALSE;
+}
+
+inline BOOL IsPlayerSprite( int nSprite )
+{
+ return IsPlayerSprite(&sprite[nSprite]);
+}
+
+inline BOOL IsDudeSprite( SPRITE *pSprite )
+{
+ return (pSprite->type >= kDudeBase && pSprite->type < kDudeMax) ? TRUE : FALSE;
+}
+
+inline BOOL IsDudeSprite( int nSprite )
+{
+ return IsDudeSprite(&sprite[nSprite]);
+}
+
+inline void actAddBurnTime( int nBurnSource, XSPRITE *pXActor, int nBurnTime )
+{
+ pXActor->burnTime = ClipHigh(pXActor->burnTime + nBurnTime, kMaxBurnTime );
+ pXActor->burnSource = (short)nBurnSource;
+}
+
+
+inline int actGetBurnTime( XSPRITE *pXActor )
+{
+ return pXActor->burnTime;
+}
+
+#endif //__ACTOR_H
diff --git a/SRC/AI.CPP b/SRC/AI.CPP
new file mode 100644
index 0000000..717b904
--- /dev/null
+++ b/SRC/AI.CPP
@@ -0,0 +1,761 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "engine.h"
+#include "ai.h"
+#include "db.h"
+#include "trig.h"
+#include "misc.h"
+#include "actor.h"
+#include "player.h"
+#include "globals.h"
+#include "gameutil.h"
+#include "multi.h"
+#include "dude.h"
+#include "debug4g.h"
+#include "eventq.h"
+
+#include "aicult.h"
+#include "aigarg.h"
+#include "aihand.h"
+#include "aihound.h"
+#include "airat.h"
+#include "aispid.h"
+#include "aizomba.h"
+#include "aizombf.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kAIThinkRate 8 // how often high level AI is sampled (in frames)
+#define kAIThinkMask (kAIThinkRate - 1)
+#define kAIThinkTime (kAIThinkRate * kFrameTicks)
+
+#define kSGargoyleMeleeDist M2X(2.0) //M2X(1.6)
+#define kSGargoyleBlastDist1 M2X(20) // used for paralyzing blast
+#define kSGargoyleBlastDist2 M2X(14)
+
+#define kTentacleActivateDist M2X(5) // activates and stays on until target reaches deactivate distance?
+#define kTentacleDeactivateDist M2X(9)
+#define kTentacleMeleeDist M2X(2)
+
+#define kPodActivateDist M2X(8)
+#define kPodDeactivateDist M2X(14)
+#define kPodFireDist1 M2X(12)
+#define kPodFireDist2 M2X(8)
+
+#define kGillBeastMeleeDist M2X(1.6)
+#define kGillBeastSummonDist1 M2X(16)
+#define kGillBeastSummonDist2 M2X(12)
+
+#define kEelMeleeDist M2X(1)
+#define kRatMeleeDist M2X(1)
+#define kHandMeleeDist M2X(1)
+
+#define kCerberusMeleeDist M2X(2)
+#define kCerberusBlastDist1 M2X(14) // used for fireball
+#define kCerberusBlastDist2 M2X(10)
+
+#define kPhantasmMeleeDist M2X(1.6)
+#define kPhantasmThrowDist1 M2X(16)
+#define kPhantasmThrowDist2 M2X(12)
+
+
+static int cumulDamage[kMaxXSprites]; // cumulative damage per frame
+int gDudeSlope[kMaxXSprites];
+
+
+static AISTATE genIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, NULL, NULL };
+static AISTATE genRecoil = { kSeqDudeRecoil, NULL, 20, NULL, NULL, NULL, &genIdle };
+
+//struct AISOUND
+//{
+// short sightID;
+// short painID;
+// short attackID;
+// short altAttackID;
+// short deathID;
+//};
+
+
+/*******************************************************************************
+
+AI design idea: p-code kernel which a short program for each dude type.
+
+P-code primitives:
+
+PlaySeq [id] [callback]
+PlaySound [id]
+*******************************************************************************/
+
+
+
+/****************************************************************************
+** GLOBALS
+****************************************************************************/
+
+
+/****************************************************************************
+** LOCALS
+****************************************************************************/
+
+void aiNewState( SPRITE *pSprite, XSPRITE *pXSprite, AISTATE *pState )
+{
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ pXSprite->stateTimer = pState->ticks;
+ pXSprite->aiState = pState;
+
+ if ( pState->seqId >= 0 )
+ {
+ if ( gSysRes.Lookup(pDudeInfo->seqStartID + pState->seqId,".SEQ") == NULL )
+ {
+ dprintf("NULL sequence, dudeType = %d, seqId = %d\n", pSprite->type, pState->seqId);
+ return;
+ }
+ seqSpawn(pDudeInfo->seqStartID + pState->seqId, SS_SPRITE, pSprite->extra, pState->seqCallback);
+ }
+
+ // call the enter function if defined
+ if ( pState->enter )
+ pState->enter(pSprite, pXSprite);
+}
+
+
+BOOL CanMove( SPRITE *pSprite, int nTarget, int ang, int dist )
+{
+ int zTop, zBot;
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+
+ int dx = mulscale30(dist, Cos(ang));
+ int dy = mulscale30(dist, Sin(ang));
+
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+
+ HITINFO hitInfo;
+ HitScan(pSprite, z, dx, dy, 0, &hitInfo);
+ int hitDist = qdist(x - hitInfo.hitx, y - hitInfo.hity);
+ if ( hitDist - (pSprite->clipdist << 2) < dist )
+ {
+ // okay to be blocked by target
+ if ( hitInfo.hitsprite >= 0 && hitInfo.hitsprite == nTarget )
+ return TRUE;
+ return FALSE;
+ }
+
+ x += dx;
+ y += dy;
+ short nSector = pSprite->sectnum;
+ if ( !FindSector(x, y, z, &nSector) )
+ return FALSE;
+
+ long floorZ = getflorzofslope(nSector, x, y);
+
+ // this should go into the dude table and be time relative
+ if ( floorZ - zBot > M2Z(1.0) )
+ return FALSE;
+
+ return TRUE;
+}
+
+
+void aiChooseDirection( SPRITE *pSprite, XSPRITE *pXSprite, int ang )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ int dang = ((kAngle180 + ang - pSprite->ang) & kAngleMask) - kAngle180;
+
+ long sin = Sin(pSprite->ang);
+ long cos = Cos(pSprite->ang);
+
+ // find vel and svel relative to current angle
+// long vel = dmulscale30(pSprite->xvel, cos, pSprite->yvel, sin);
+// long svel = dmulscale30(pSprite->xvel, sin, -pSprite->yvel, cos);
+
+ // look 1.0 second ahead
+ int avoidDist = pDudeInfo->frontSpeed * kTimerRate >> 4;
+ int turnTo = kAngle60;
+ if (dang < 0 )
+ turnTo = -turnTo;
+
+ // clear movement toward target?
+ if ( CanMove(pSprite, pXSprite->target, pSprite->ang + dang, avoidDist) )
+ {
+ pXSprite->goalAng = (pSprite->ang + dang) & kAngleMask;
+ }
+ // clear movement partially toward target?
+ else if ( CanMove(pSprite, pXSprite->target, pSprite->ang + dang / 2, avoidDist) )
+ {
+ pXSprite->goalAng = (pSprite->ang + dang / 2) & kAngleMask;
+ }
+ // try turning in target direction
+ else if ( CanMove(pSprite, pXSprite->target, pSprite->ang + turnTo, avoidDist) )
+ {
+ pXSprite->goalAng = (pSprite->ang + turnTo) & kAngleMask;
+ }
+ // clear movement straight?
+ else if ( CanMove(pSprite, pXSprite->target, pSprite->ang, avoidDist) )
+ {
+ pXSprite->goalAng = pSprite->ang;
+ }
+ // try turning away
+ else if ( CanMove(pSprite, pXSprite->target, pSprite->ang - turnTo, avoidDist) )
+ {
+ pXSprite->goalAng = (pSprite->ang - turnTo) & kAngleMask;
+ }
+ else
+ {
+ // just turn around
+ pXSprite->goalAng = (pSprite->ang + kAngle180) & kAngleMask;
+ }
+
+ // choose dodge direction
+ pXSprite->dodgeDir = Chance(0x8000) ? 1 : -1;
+ if ( !CanMove(pSprite, pXSprite->target, pSprite->ang + kAngle90 * pXSprite->dodgeDir,
+ pDudeInfo->sideSpeed * 90 >> 4) )
+ {
+ pXSprite->dodgeDir = -pXSprite->dodgeDir;
+ if ( !CanMove(pSprite, pXSprite->target, pSprite->ang + kAngle90 * pXSprite->dodgeDir,
+ pDudeInfo->sideSpeed * 90 >> 4) )
+ pXSprite->dodgeDir = 0;
+ }
+
+// pSprite->zvel = (short)(dz >> 8);
+}
+
+
+void aiMoveForward( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ pXSprite->moveState = kMoveWalk;
+
+ int dang = ((kAngle180 + pXSprite->goalAng - pSprite->ang) & kAngleMask) - kAngle180;
+ int maxTurn = pDudeInfo->angSpeed * kFrameTicks >> 4;
+
+ pSprite->ang = (short)((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & kAngleMask);
+ int accel = (pDudeInfo->frontAccel + kGroundFriction) * kFrameTicks;
+
+ // don't move forward if trying to turn around
+ if ( qabs(dang) > kAngle60 )
+ return;
+
+ long sin = Sin(pSprite->ang);
+ long cos = Cos(pSprite->ang);
+
+ // find vel and svel relative to current angle
+ long vel = dmulscale30(pSprite->xvel, cos, pSprite->yvel, sin);
+ long svel = dmulscale30(pSprite->xvel, sin, -pSprite->yvel, cos);
+
+ // acceleration
+ if ( accel > 0 )
+ {
+ if ( vel < pDudeInfo->frontSpeed )
+ vel = ClipHigh(vel + accel, pDudeInfo->frontSpeed);
+ }
+ else
+ {
+ if ( vel > -pDudeInfo->backSpeed )
+ vel = ClipLow(vel + accel, pDudeInfo->backSpeed);
+ }
+
+ // reconstruct x and y velocities
+ pSprite->xvel = (short)dmulscale30(vel, cos, svel, sin);
+ pSprite->yvel = (short)dmulscale30(vel, sin, -svel, cos);
+}
+
+
+void aiMoveTurn( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ pXSprite->moveState = kMoveWalk;
+
+ int dang = ((kAngle180 + pXSprite->goalAng - pSprite->ang) & kAngleMask) - kAngle180;
+ int maxTurn = pDudeInfo->angSpeed * kFrameTicks >> 4;
+
+ pSprite->ang = (short)((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & kAngleMask);
+}
+
+
+void aiMoveDodge( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ pXSprite->moveState = kMoveWalk;
+
+ int dang = ((kAngle180 + pXSprite->goalAng - pSprite->ang) & kAngleMask) - kAngle180;
+ int maxTurn = pDudeInfo->angSpeed * kFrameTicks >> 4;
+ pSprite->ang = (short)((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & kAngleMask);
+
+ if ( pXSprite->dodgeDir == 0 )
+ return;
+
+ int accel = (pDudeInfo->sideAccel + kGroundFriction) * kFrameTicks;
+ long sin = Sin(pSprite->ang);
+ long cos = Cos(pSprite->ang);
+
+ // find vel and svel relative to current angle
+ long vel = dmulscale30(pSprite->xvel, cos, pSprite->yvel, sin);
+ long svel = dmulscale30(pSprite->xvel, sin, -pSprite->yvel, cos);
+
+ if ( pXSprite->dodgeDir > 0 )
+ {
+ if ( svel < pDudeInfo->sideSpeed )
+ svel = ClipHigh(svel + accel, pDudeInfo->sideSpeed);
+ }
+ else
+ {
+ if ( svel > -pDudeInfo->sideSpeed )
+ svel = ClipLow(svel - accel, -pDudeInfo->sideSpeed);
+ }
+
+ // reconstruct x and y velocities
+ pSprite->xvel = (short)dmulscale30(vel, cos, svel, sin);
+ pSprite->yvel = (short)dmulscale30(vel, sin, -svel, cos);
+}
+
+
+void aiActivateDude( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+
+ if ( pXSprite->state == 0 )
+ {
+ // this doesn't take into account sprites triggered w/o a target location....
+ int nAngle = getangle(pXSprite->targetX - pSprite->x, pXSprite->targetY - pSprite->y);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ pXSprite->state = 1;
+ }
+
+ switch ( pSprite->type )
+ {
+ case kDudeTommyCultist:
+ case kDudeShotgunCultist:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ else
+ aiNewState(pSprite, pXSprite, &cultistChase);
+ break;
+
+ case kDudeAxeZombie:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ else
+ aiNewState(pSprite, pXSprite, &zombieAChase);
+ break;
+
+ case kDudeEarthZombie:
+ if (pXSprite->aiState == &zombieEIdle)
+ aiNewState(pSprite, pXSprite, &zombieEUp);
+ break;
+
+ case kDudeFatZombie:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFChase);
+ break;
+
+ case kDudeFleshStatue:
+ aiNewState(pSprite, pXSprite, &gargoyleFMorph);
+ break;
+
+ case kDudeStoneStatue:
+// seqSpawn(pDudeInfo->seqStartID + kSeqStoneStatueTurn, SS_SPRITE, pSprite->extra);
+ pSprite->type = kDudeStoneGargoyle;
+ break;
+
+ case kDudeHound:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ else
+ aiNewState(pSprite, pXSprite, &houndChase);
+ break;
+
+ case kDudeHand:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &handSearch);
+ else
+ aiNewState(pSprite, pXSprite, &handChase);
+ break;
+
+ case kDudeRat:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ else
+ aiNewState(pSprite, pXSprite, &ratChase);
+ break;
+
+ case kDudeBrownSpider:
+ case kDudeRedSpider:
+ case kDudeBlackSpider:
+ case kDudeMotherSpider:
+ pSprite->flags |= kAttrGravity;
+ pSprite->cstat &= ~kSpriteFlipY;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ else
+ aiNewState(pSprite, pXSprite, &spidChase);
+ break;
+ }
+}
+
+
+/*******************************************************************************
+ FUNCTION: aiSetTarget()
+
+ DESCRIPTION: Target a location (as opposed to a sprite)
+*******************************************************************************/
+void aiSetTarget( XSPRITE *pXSprite, int x, int y, int z )
+{
+ pXSprite->target = -1;
+ pXSprite->targetX = x;
+ pXSprite->targetY = y;
+ pXSprite->targetZ = z;
+}
+
+
+void aiSetTarget( XSPRITE *pXSprite, int nTarget )
+{
+ dassert(nTarget >= 0 && nTarget < kMaxSprites);
+ SPRITE *pTarget = &sprite[nTarget];
+
+ if ( pTarget->type < kDudeBase || pTarget->type >= kDudeMax )
+ return;
+
+ if ( nTarget == sprite[pXSprite->reference].owner )
+ {
+ dprintf("aiSetTarget: skipping owner\n");
+ return;
+ }
+
+// dassert(pTarget->type >= kDudeBase && pTarget->type < kDudeMax);
+ DUDEINFO *pTargetInfo = &dudeInfo[pTarget->type - kDudeBase];
+
+ pXSprite->target = nTarget;
+ pXSprite->targetX = pTarget->x;
+ pXSprite->targetY = pTarget->y;
+ pXSprite->targetZ = pTarget->z - (pTargetInfo->eyeHeight * pTarget->yrepeat << 2);
+}
+
+
+void aiDamageSprite( SPRITE *pSprite, XSPRITE *pXSprite, int nSource, DAMAGE_TYPE nDamageType, int nDamage )
+{
+ (void)nDamageType;
+
+ dassert(nSource < kMaxSprites);
+
+ if (pXSprite->health == 0)
+ return;
+
+ cumulDamage[pSprite->extra] += nDamage; // add to cumulative damage
+
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ if (nSource >= 0 && nSource != pXSprite->reference)
+ {
+ if (pXSprite->target == -1)
+ {
+ // give a dude a target
+ aiSetTarget(pXSprite, nSource);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nSource != pXSprite->target)
+ {
+ // retarget
+ int nThresh = nDamage;
+
+ if ( sprite[nSource].type == pSprite->type )
+ nThresh *= pDudeInfo->changeTargetKin;
+ else
+ nThresh *= pDudeInfo->changeTarget;
+
+ if ( Chance(nThresh) )
+ {
+ aiSetTarget(pXSprite, nSource);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ }
+
+ // you DO need special processing here or somewhere else (your choice) for dodging
+ switch ( pSprite->type )
+ {
+ case kDudeTommyCultist:
+ case kDudeShotgunCultist:
+// if (nDamage >= (pDudeInfo->hinderDamage << 2))
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ break;
+
+ case kDudeFleshGargoyle:
+ aiNewState(pSprite, pXSprite, &gargoyleFChase);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+void RecoilDude( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dprintf("Recoiling dude\n");
+ switch ( pSprite->type )
+ {
+ case kDudeTommyCultist:
+ case kDudeShotgunCultist:
+ aiNewState(pSprite, pXSprite, &cultistRecoil);
+ break;
+
+ case kDudeFatZombie:
+ aiNewState(pSprite, pXSprite, &zombieFRecoil);
+ break;
+
+ case kDudeAxeZombie:
+ case kDudeEarthZombie:
+ aiNewState(pSprite, pXSprite, &zombieARecoil);
+ break;
+
+ case kDudeFleshGargoyle:
+ aiNewState(pSprite, pXSprite, &gargoyleFRecoil);
+ break;
+
+ case kDudeHound:
+ aiNewState(pSprite, pXSprite, &houndRecoil);
+ break;
+
+ case kDudeHand:
+ aiNewState(pSprite, pXSprite, &handRecoil);
+ break;
+
+ case kDudeRat:
+ aiNewState(pSprite, pXSprite, &handRecoil);
+ break;
+
+ case kDudeBrownSpider:
+ case kDudeRedSpider:
+ case kDudeBlackSpider:
+ case kDudeMotherSpider:
+ aiNewState(pSprite, pXSprite, &spidDodge);
+ break;
+
+ default:
+ aiNewState(pSprite, pXSprite, &genRecoil);
+ break;
+ }
+}
+
+
+void aiThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ if ( !Chance(pDudeInfo->alertChance) )
+ return;
+
+ for (int i = 0; i < numplayers; i++)
+ {
+ PLAYER *pPlayer = &gPlayer[i];
+
+ // skip this player if the he owns the dude or is invisible
+ if ( pSprite->owner == pPlayer->nSprite
+ || pPlayer->xsprite->health == 0
+ || powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ continue;
+
+ int x = pPlayer->sprite->x;
+ int y = pPlayer->sprite->y;
+ int z = pPlayer->sprite->z;
+ short nSector = pPlayer->sprite->sectnum;
+
+ int dx = x - pSprite->x;
+ int dy = y - pSprite->y;
+
+ int dist = qdist(dx, dy);
+
+ if ( dist <= pDudeInfo->seeDist || dist <= pDudeInfo->hearDist )
+ {
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the player?
+ if ( cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ,
+ pSprite->sectnum) )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+
+ // is the player visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget( pXSprite, pPlayer->nSprite );
+ aiActivateDude( pSprite, pXSprite );
+ return;
+ }
+
+ // we may want to make hearing a function of sensitivity, rather than distance
+ if ( dist < pDudeInfo->hearDist )
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude( pSprite, pXSprite );
+ return;
+ }
+ }
+ }
+ }
+}
+
+
+void aiProcessDudes( void )
+{
+ // process active sprites
+ for (short nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ // don't manipulate players or dead guys
+ if (( pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8 ) || ( pXSprite->health == 0 ))
+ continue;
+
+ pXSprite->stateTimer = ClipLow(pXSprite->stateTimer - kFrameTicks, 0);
+
+ if ( pXSprite->aiState->move )
+ pXSprite->aiState->move(pSprite, pXSprite);
+
+ if ( pXSprite->aiState->think && (gFrame & kAIThinkMask) == (nSprite & kAIThinkMask) )
+ pXSprite->aiState->think(pSprite, pXSprite);
+
+ if ( pXSprite->stateTimer == 0 && pXSprite->aiState->next != NULL )
+ {
+ if ( pXSprite->aiState->ticks > 0 )
+ aiNewState(pSprite, pXSprite, pXSprite->aiState->next);
+ else if ( seqGetStatus(SS_SPRITE, nXSprite) < 0 )
+ aiNewState(pSprite, pXSprite, pXSprite->aiState->next);
+ }
+
+ // process dudes for recoil
+ if ( cumulDamage[nXSprite] >= pDudeInfo->hinderDamage << 4 )
+ RecoilDude(pSprite, pXSprite);
+ }
+
+ // reset the cumulative damages for the next frame
+ memset(cumulDamage, 0, sizeof(cumulDamage));
+
+/*
+ // special processing for converting dude types
+ switch ( pSprite->type )
+ {
+ case kDudeCerberus:
+ if ( pXSprite->health <= 0 && seqGetStatus(SS_SPRITE, nXSprite) < 0 ) // head #1 finished dying?
+ {
+ pXSprite->health = dudeInfo[kDudeCerberus2 - kDudeBase].startHealth << 4;
+ pSprite->type = kDudeCerberus2; // change his type
+// aiActivateDude(pSprite, pXSprite); // reactivate him
+ }
+ break;
+ }
+*/
+}
+
+
+/*******************************************************************************
+ FUNCTION: aiInit()
+
+ DESCRIPTION:
+
+ PARAMETERS: void
+
+ RETURNS: void
+
+ NOTES:
+*******************************************************************************/
+void aiInit( void )
+{
+ for (short nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ SPRITE *pDude = &sprite[nSprite];
+ XSPRITE *pXDude = &xsprite[pDude->extra];
+
+ switch ( pDude->type )
+ {
+ case kDudeTommyCultist:
+ case kDudeShotgunCultist:
+ aiNewState(pDude, pXDude, &cultistIdle);
+ break;
+
+ case kDudeFatZombie:
+ aiNewState(pDude, pXDude, &zombieFIdle);
+ break;
+
+ case kDudeAxeZombie:
+ aiNewState(pDude, pXDude, &zombieAIdle);
+ break;
+
+ case kDudeEarthZombie:
+ aiNewState(pDude, pXDude, &zombieEIdle);
+ pDude->flags &= ~kAttrMove;
+ break;
+
+ case kDudeFleshGargoyle:
+ aiNewState(pDude, pXDude, &gargoyleFIdle);
+ break;
+
+ case kDudeHound:
+ aiNewState(pDude, pXDude, &houndIdle);
+ break;
+
+ case kDudeHand:
+ aiNewState(pDude, pXDude, &handIdle);
+ break;
+
+ case kDudeRat:
+ aiNewState(pDude, pXDude, &ratIdle);
+ break;
+
+ case kDudeBrownSpider:
+ case kDudeRedSpider:
+ case kDudeBlackSpider:
+ case kDudeMotherSpider:
+ aiNewState(pDude, pXDude, &spidIdle);
+ break;
+
+ default:
+ aiNewState(pDude, pXDude, &genIdle);
+ break;
+ }
+
+ aiSetTarget(pXDude, 0, 0, 0);
+
+ pXDude->stateTimer = 0;
+
+ switch ( pDude->type )
+ {
+ case kDudeBrownSpider:
+ case kDudeRedSpider:
+ case kDudeBlackSpider:
+ if ( pDude->cstat & kSpriteFlipY )
+ pDude->flags &= ~kAttrGravity;
+ break;
+
+ case kDudeBat:
+ case kDudeFleshGargoyle:
+ case kDudeStoneGargoyle:
+ case kDudePhantasm:
+ pDude->flags &= ~kAttrGravity;
+ break;
+ }
+ }
+}
+
diff --git a/SRC/AI.H b/SRC/AI.H
new file mode 100644
index 0000000..903237e
--- /dev/null
+++ b/SRC/AI.H
@@ -0,0 +1,86 @@
+#ifndef __AI_H
+#define __AI_H
+
+#include "actor.h"
+#include "db.h"
+#include "seq.h"
+
+extern int gDudeSlope[kMaxXSprites];
+
+/*
+// Movement states
+enum {
+ kMoveDead = 0,
+ kMoveStill,
+ kMoveWalk,
+ kMoveFall,
+ kMoveLand,
+ kMoveStand,
+ kMoveSwim,
+ kMoveFly,
+ kMoveHang,
+};
+
+ // this stuff needed for dudes
+ unsigned moveState : 8; // same as player move states
+ unsigned aiState : 8;
+ unsigned health : 12;
+ unsigned dudeDeaf : 1;
+ unsigned dudeAmbush : 1;
+ unsigned dudeGuard : 1;
+ unsigned dudeFlag4 : 1;
+ signed target : 16;
+ signed targetX : 32;
+ signed targetY : 32;
+ signed targetZ : 32;
+ signed avel : 16;
+ unsigned weaponTimer : 16;
+ unsigned stateTimer : 16;
+*/
+
+
+/** Monster AI States **/
+enum {
+ kAIScan = 0, // wait to be activated
+ kAIGoto = 1, // move to target x,y,z
+ kAIFollow = 2, // move to target sprite
+ kAIFlee = 3, // move away from target sprite
+ kAIMorph = 4, // transforming (i.e., statue to gargoyle)
+ kAIRecoil = 5,
+ kAISearch = 6,
+ kAIAttack = 7,
+};
+
+typedef void (*AISTATEFUNC)( SPRITE *sprite, XSPRITE *xsprite);
+
+
+struct AISTATE
+{
+ int seqId;
+ SEQCALLBACK seqCallback;
+ int ticks;
+ AISTATEFUNC enter;
+ AISTATEFUNC move;
+ AISTATEFUNC think;
+ AISTATE *next;
+};
+
+
+extern void aiInit( void );
+extern void aiProcessDudes( void );
+
+extern void aiActivateDude( SPRITE *pSprite, XSPRITE *pXSprite );
+extern void aiMoveForward( SPRITE *pSprite, XSPRITE *pXSprite );
+extern void aiMoveTurn( SPRITE *pSprite, XSPRITE *pXSprite );
+extern void aiMoveDodge( SPRITE *pSprite, XSPRITE *pXSprite );
+extern void aiThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite );
+extern void aiChooseDirection( SPRITE *pSprite, XSPRITE *pXSprite, int ang );
+extern void aiNewState( SPRITE *pSprite, XSPRITE *pXSprite, AISTATE *pState );
+extern void aiSetTarget( XSPRITE *pXSprite, int x, int y, int z );
+extern void aiSetTarget( XSPRITE *pXSprite, int nTarget );
+extern void aiDamageSprite( SPRITE *pSprite, XSPRITE *pXSprite, int nSource, DAMAGE_TYPE nDamageType, int nDamage );
+
+
+#endif
+
+
diff --git a/SRC/AIBURN.CPP b/SRC/AIBURN.CPP
new file mode 100644
index 0000000..36be6c7
--- /dev/null
+++ b/SRC/AIBURN.CPP
@@ -0,0 +1,330 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aiburn.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kBurnDist M2X(1.6)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+//static void ExplodeCallback( int /* type */, int nXIndex );
+static void BurnCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move/entry function prototypes
+****************************************************************************/
+
+static void entryTorchy( SPRITE *pSprite, XSPRITE *pXSprite );
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+AISTATE cultistBurnChase = { kSeqCultistWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cultistBurnGoto = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &cultistBurnSearch };
+AISTATE cultistBurnSearch = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &cultistBurnSearch };
+AISTATE cultistBurnAttack = { kSeqCultistWalk, BurnCallback, 120, NULL, NULL, NULL, &cultistBurnChase };
+
+AISTATE playerBurnChase = { kSeqCultistWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE playerBurnGoto = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &playerBurnSearch };
+AISTATE playerBurnSearch = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &playerBurnSearch };
+AISTATE playerBurnAttack = { kSeqCultistWalk, BurnCallback, 120, NULL, NULL, NULL, &playerBurnChase };
+
+AISTATE zombieABurnChase = { kSeqAxeZombieWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieABurnGoto = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &zombieABurnSearch };
+AISTATE zombieABurnSearch = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &zombieABurnSearch };
+AISTATE zombieABurnAttack = { kSeqAxeZombieWalk, BurnCallback, 120, NULL, NULL, NULL, &zombieABurnChase };
+
+AISTATE zombieFBurnChase = { kSeqFatZombieWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieFBurnGoto = { kSeqFatZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &zombieFBurnSearch };
+AISTATE zombieFBurnSearch = { kSeqFatZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &zombieFBurnSearch };
+AISTATE zombieFBurnAttack = { kSeqFatZombieWalk, BurnCallback, 120, NULL, NULL, NULL, &zombieFBurnChase };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** entryTorchy()
+**
+**
+****************************************************************************/
+static void entryCultist( SPRITE *pSprite, XSPRITE */*pXSprite*/ )
+{
+ pSprite->type =
+
+ switch( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnSearch);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ break;
+ }
+}
+
+/****************************************************************************
+** ExplodeCallback()
+**
+** This should be called when a burning cultist or burning player with
+** dynamite randomly explodes.
+****************************************************************************/
+//static void ExplodeCallback( int /* type */, int nXIndex )
+//{
+// XSPRITE *pXSprite = &xsprite[nXIndex];
+// int nSprite = pXSprite->reference;
+// SPRITE *pSprite = &sprite[nSprite];
+//
+// (void)pSprite;
+//}
+
+
+/****************************************************************************
+** BurnCallback()
+**
+** This should be called when a burning cultist or burning player touches
+** any other dudes or explodable objects, like barrels. Basically, a close
+** hitscan that further immolates whatever it hits with burn damage.
+****************************************************************************/
+static void BurnCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ (void)pSprite;
+}
+
+
+/****************************************************************************
+** CheckNearBarrel()
+**
+**
+****************************************************************************/
+//static int CheckNearBarrel( SPRITE *pTarget )
+//{
+// for (short nSprite = headspritesect[pTarget->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+// {
+// // check for TNT sticks or explosions in the same sector as the target
+// if (sprite[nSprite].type == kThingTNTBarrel || sprite[nSprite].statnum == kStatExplosion)
+// return nSprite; // indicate new target
+// }
+// return -1;
+//}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ {
+ switch( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnSearch);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ break;
+ }
+ }
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ switch( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnGoto);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ break;
+ }
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ switch( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnSearch);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ break;
+ }
+ return;
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ if ( dist < kBurnDist && qabs(losAngle) < kAngle15 )
+ {
+ switch ( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnAttack);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnAttack);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnAttack);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnAttack);
+ break;
+ }
+ }
+ return;
+ }
+ }
+ }
+ dprintf("Torchy %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+
+ switch ( pSprite->type )
+ {
+ case kDudePlayerBurning:
+ aiNewState(pSprite, pXSprite, &playerBurnGoto);
+ break;
+ case kDudeCultistBurning:
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ break;
+ case kDudeAxeZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ break;
+ case kDudeFatZombieBurning:
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ break;
+ }
+
+ pXSprite->target = -1;
+}
+
diff --git a/SRC/AIBURN.H b/SRC/AIBURN.H
new file mode 100644
index 0000000..c6add6a
--- /dev/null
+++ b/SRC/AIBURN.H
@@ -0,0 +1,24 @@
+#ifndef __AICULT_H
+#define __AICULT_H
+
+#include "ai.h"
+
+
+extern AISTATE cultistBurnChase;
+extern AISTATE cultistBurnGoto;
+extern AISTATE cultistBurnSearch;
+
+extern AISTATE playerBurnChase;
+extern AISTATE playerBurnGoto;
+extern AISTATE playerBurnSearch;
+
+extern AISTATE zombieABurnChase;
+extern AISTATE zombieABurnGoto;
+extern AISTATE zombieABurnSearch;
+
+extern AISTATE zombieFBurnChase;
+extern AISTATE zombieFBurnGoto;
+extern AISTATE zombieFBurnSearch;
+
+
+#endif // __AICULT_H
diff --git a/SRC/AICULT.CPP b/SRC/AICULT.CPP
new file mode 100644
index 0000000..4afacfa
--- /dev/null
+++ b/SRC/AICULT.CPP
@@ -0,0 +1,301 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aicult.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "sfx.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kCultistTFireDist M2X(100)
+#define kCultistSFireDist M2X(10)
+#define kCultistThrowDist1 M2X(16)
+#define kCultistThrowDist2 M2X(10)
+
+#define kSlopeThrow -8192 // tan(26)
+
+#define kVectorsPerBarrel 8
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void TommyCallback( int /* type */, int nXIndex );
+static void ShotCallback( int /* type */, int nXIndex );
+static void ThrowCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE cultistIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistChase = { kSeqCultistWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cultistDodge = { kSeqCultistWalk, NULL, 90, NULL, aiMoveDodge, NULL, &cultistChase };
+AISTATE cultistGoto = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &cultistIdle };
+AISTATE cultistTThrow = { kSeqCultistAttack2, ThrowCallback, 120, NULL, NULL, NULL, &cultistTFire };
+AISTATE cultistSThrow = { kSeqCultistAttack2, ThrowCallback, 120, NULL, NULL, NULL, &cultistSFire };
+AISTATE cultistSearch = { kSeqCultistWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &cultistIdle };
+AISTATE cultistSFire = { kSeqCultistAttack1, ShotCallback, 60, NULL, NULL, NULL, &cultistChase };
+AISTATE cultistTFire = { kSeqCultistAttack1, TommyCallback, 0, NULL, aiMoveTurn, thinkChase, &cultistTFire };
+AISTATE cultistRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &cultistDodge };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** TommyCallback()
+**
+**
+****************************************************************************/
+static void TommyCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = gDudeSlope[nXIndex];
+
+ // dispersal modifiers here
+ dx += BiRandom(1000);
+ dy += BiRandom(1000);
+ dz += BiRandom(1000);
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorBullet);
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxTomFire);
+}
+
+
+/****************************************************************************
+** ShotCallback()
+**
+**
+****************************************************************************/
+static void ShotCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = gDudeSlope[nXIndex];
+
+ // aim modifiers
+ dx += BiRandom(4000);
+ dy += BiRandom(4000);
+ dz += BiRandom(4000);
+
+ for ( int i = 0; i < kVectorsPerBarrel; i++ )
+ {
+ actFireVector(nSprite, pSprite->z, dx + BiRandom(3000), dy + BiRandom(3000),
+ dz + BiRandom(3000), kVectorShell);
+ }
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxShotFire);
+}
+
+
+/****************************************************************************
+** ThrowCallback()
+**
+**
+****************************************************************************/
+static void ThrowCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxTNTToss);
+ int nThing = actFireThing(
+ nSprite, pSprite->z, gDudeSlope[nXIndex] + kSlopeThrow, kThingTNTStick, (M2X(10.0) << 4) / kTimerRate);
+ evPost(nThing, SS_SPRITE, 2 * kTimerRate); // 2 second burn off
+}
+
+
+/****************************************************************************
+** TargetNearExplosion()
+**
+**
+****************************************************************************/
+static BOOL TargetNearExplosion( SPRITE *pTarget )
+{
+ for (short nSprite = headspritesect[pTarget->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ // check for TNT sticks or explosions in the same sector as the target
+ if (sprite[nSprite].type == kThingTNTStick || sprite[nSprite].statnum == kStatExplosion)
+ return TRUE; // indicate danger
+ }
+ return FALSE;
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &cultistGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ int nXSprite = sprite[pXSprite->reference].extra;
+ gDudeSlope[nXSprite] = divscale(pTarget->z - pSprite->z, dist, 10);
+
+ // check to see if we can attack
+ switch ( pSprite->type )
+ {
+ case kDudeTommyCultist:
+ if ( dist < kCultistThrowDist1 && dist > kCultistThrowDist2 && qabs(losAngle) < kAngle15
+ && !TargetNearExplosion(pTarget) && (pTarget->flags & kAttrGravity)
+ && !( IsPlayerSprite(pXSprite->target) && gPlayer[pTarget->type - kDudePlayer1].run )
+ && Chance(0x4000) )
+ aiNewState(pSprite, pXSprite, &cultistTThrow);
+ else if ( dist < kCultistTFireDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &cultistTFire);
+ break;
+
+ case kDudeShotgunCultist:
+ if ( dist < kCultistThrowDist1 && dist > kCultistThrowDist2 && qabs(losAngle) < kAngle15
+ && !TargetNearExplosion(pTarget) && (pTarget->flags & kAttrGravity)
+ && !(IsPlayerSprite(pXSprite->target) && gPlayer[pTarget->type - kDudePlayer1].run)
+ && Chance(0x4000) )
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ else if ( dist < kCultistSFireDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ break;
+ }
+ return;
+ }
+ }
+ }
+ dprintf("Cultist %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &cultistGoto);
+ pXSprite->target = -1;
+}
+
+
+
diff --git a/SRC/AICULT.H b/SRC/AICULT.H
new file mode 100644
index 0000000..4acb40e
--- /dev/null
+++ b/SRC/AICULT.H
@@ -0,0 +1,18 @@
+#ifndef __AICULT_H
+#define __AICULT_H
+
+#include "ai.h"
+
+
+extern AISTATE cultistIdle;
+extern AISTATE cultistChase;
+extern AISTATE cultistDodge;
+extern AISTATE cultistGoto;
+extern AISTATE cultistThrow;
+extern AISTATE cultistSearch;
+extern AISTATE cultistSFire;
+extern AISTATE cultistTFire;
+extern AISTATE cultistRecoil;
+
+
+#endif // __AICULT_H
diff --git a/SRC/AIGARG.CPP b/SRC/AIGARG.CPP
new file mode 100644
index 0000000..a89a55e
--- /dev/null
+++ b/SRC/AIGARG.CPP
@@ -0,0 +1,246 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aigarg.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "sfx.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kFGargoyleMeleeDist M2X(2.0)
+#define kFGargoyleThrowDist1 M2X(12) // thrown bone
+#define kFGargoyleThrowDist2 M2X(6)
+
+#define kSGargoyleMeleeDist M2X(2.0)
+#define kSGargoyleBlastDist1 M2X(20) // thrown paralyzing blast
+#define kSGargoyleBlastDist2 M2X(14)
+
+#define kSlopeThrow -7168
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+// flesh gargoyle callbacks
+static void SlashFCallback( int /* type */, int nXIndex );
+static void ThrowFCallback( int /* type */, int nXIndex );
+
+// stone gargoyle callbacks
+//static void SlashSCallback( int /* type */, int nXIndex );
+//static void ThrowSCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move/entry function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+static void entryFStatue( SPRITE *pSprite, XSPRITE */*pXSprite*/ );
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE gargoyleFIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE gargoyleFChase = { kSeqDudeIdle, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE gargoyleFGoto = { kSeqDudeIdle, NULL, 3600, NULL, aiMoveForward, thinkGoto, &gargoyleFIdle };
+AISTATE gargoyleFSlash = { kSeqGargoyleAttack, SlashFCallback, 120, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleFThrow = { kSeqGargoyleAttack, ThrowFCallback, 120, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleFRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleFSearch = { kSeqDudeIdle, NULL, 3600, NULL, aiMoveForward, thinkSearch, &gargoyleFIdle };
+AISTATE gargoyleFMorph2 = { -1, NULL, 0, entryFStatue, NULL, NULL, &gargoyleFSearch };
+AISTATE gargoyleFMorph = { kSeqFleshStatueTurn,NULL, 0, NULL, NULL, NULL, &gargoyleFMorph2 };
+
+//AISTATE fleshGargoyleDodge = { kSeqDudeIdle, NULL, 90, NULL, aiMoveDodge, NULL, &gargoyleFChase };
+//AISTATE fleshGargoyleSwoop = { kSeqGargoyleFly, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** SlashFCallback()
+**
+**
+****************************************************************************/
+static void SlashFCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorClaw);
+}
+
+
+/****************************************************************************
+** ThrowFCallback()
+**
+**
+****************************************************************************/
+static void ThrowFCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int nThing = actFireThing(
+ nSprite, pSprite->z, gDudeSlope[nXIndex] + kSlopeThrow, kThingBoneClub, (M2X(14.0) << 4) / kTimerRate);
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ switch ( pSprite->type )
+ {
+ case kDudeFleshGargoyle:
+ if ( dist < kFGargoyleThrowDist1 && dist > kFGargoyleThrowDist2 && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &gargoyleFThrow);
+ else if ( dist < kFGargoyleMeleeDist && qabs(losAngle) < kAngle15 )
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ sfxStart3DSound(pSprite->extra, kSfxGargAttack);
+ }
+ break;
+ }
+ return;
+ }
+ }
+ }
+ dprintf("Gargoyle %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &gargoyleFGoto);
+ pXSprite->target = -1;
+}
+
+static void entryFStatue( SPRITE *pSprite, XSPRITE */*pXSprite*/ )
+{
+ pSprite->type = kDudeFleshGargoyle;
+}
+
diff --git a/SRC/AIGARG.H b/SRC/AIGARG.H
new file mode 100644
index 0000000..2b9b40f
--- /dev/null
+++ b/SRC/AIGARG.H
@@ -0,0 +1,18 @@
+#ifndef __AIGARG_H
+#define __AIGARG_H
+
+#include "ai.h"
+
+
+extern AISTATE gargoyleFIdle;
+extern AISTATE gargoyleFChase;
+extern AISTATE gargoyleFGoto;
+extern AISTATE gargoyleFSlash;
+extern AISTATE gargoyleFThrow;
+extern AISTATE gargoyleFRecoil;
+extern AISTATE gargoyleFSearch;
+extern AISTATE gargoyleFMorph2;
+extern AISTATE gargoyleFMorph;
+
+
+#endif // __AIGARG_H
diff --git a/SRC/AIHAND.CPP b/SRC/AIHAND.CPP
new file mode 100644
index 0000000..c731801
--- /dev/null
+++ b/SRC/AIHAND.CPP
@@ -0,0 +1,170 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aihand.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kHandChokeDist M2X(1.8)
+#define kHandJumpDist1 M2X(16)
+#define kHandJumpDist2 M2X(10)
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE handIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE handSearch = { kSeqHandWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &handIdle };
+AISTATE handChase = { kSeqHandWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE handRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &handSearch }; //ADD: handDodge
+AISTATE handGoto = { kSeqHandWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &handIdle };
+AISTATE handJump = { kSeqHandWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &handIdle }; //ADD: jumping
+AISTATE handChoke = { kSeqHandWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &handIdle }; //ADD: choking
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &handSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &handGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &handSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &handSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ if ( dist < kHandJumpDist1 && dist > kHandJumpDist2 && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &handJump);
+ else if ( dist < kHandChokeDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &handChoke);
+
+ return;
+ }
+ }
+ }
+ dprintf("Hand %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &handGoto);
+ pXSprite->target = -1;
+}
diff --git a/SRC/AIHAND.H b/SRC/AIHAND.H
new file mode 100644
index 0000000..d046485
--- /dev/null
+++ b/SRC/AIHAND.H
@@ -0,0 +1,16 @@
+#ifndef __AIHAND_H
+#define __AIHAND_H
+
+#include "ai.h"
+
+
+extern AISTATE handIdle;
+extern AISTATE handSearch;
+extern AISTATE handChase;
+extern AISTATE handRecoil;
+extern AISTATE handGoto;
+extern AISTATE handJump;
+extern AISTATE handChoke;
+
+
+#endif // __AIHAND_H
diff --git a/SRC/AIHOUND.CPP b/SRC/AIHOUND.CPP
new file mode 100644
index 0000000..90b813c
--- /dev/null
+++ b/SRC/AIHOUND.CPP
@@ -0,0 +1,216 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aihound.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kHoundBiteDist M2X(1.2)
+#define kHoundBurnDist1 M2X(5.5)
+#define kHoundBurnDist2 M2X(2.5)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void BiteCallback( int /* type */, int nXIndex );
+static void BurnCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE houndIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE houndSearch = { kSeqHoundWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &houndIdle };
+AISTATE houndChase = { kSeqHoundWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE houndRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &houndSearch }; //ADD: houndDodge
+AISTATE houndGoto = { kSeqHoundWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &houndIdle };
+AISTATE houndBite = { kSeqHoundBite, BiteCallback, 60, NULL, NULL, NULL, &houndChase };
+AISTATE houndBurn = { kSeqHoundBurn, BurnCallback, 60, NULL, NULL, NULL, &houndChase };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+/****************************************************************************
+** BiteCallback()
+**
+**
+****************************************************************************/
+static void BiteCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorHoundBite);
+}
+
+
+/****************************************************************************
+** BurnCallback()
+**
+**
+****************************************************************************/
+static void BurnCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireMissile(nSprite, pSprite->z, dx, dy, dz, kMissileHoundFire);
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &houndSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &houndGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ if ( dist < kHoundBurnDist1 && dist > kHoundBurnDist2 && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &houndBurn);
+ else if ( dist < kHoundBiteDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &houndBite);
+
+ return;
+ }
+ }
+ }
+ dprintf("Hound %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &houndGoto);
+ pXSprite->target = -1;
+}
diff --git a/SRC/AIHOUND.H b/SRC/AIHOUND.H
new file mode 100644
index 0000000..284ade7
--- /dev/null
+++ b/SRC/AIHOUND.H
@@ -0,0 +1,16 @@
+#ifndef __AIHOUND_H
+#define __AIHOUND_H
+
+#include "ai.h"
+
+
+extern AISTATE houndIdle;
+extern AISTATE houndSearch;
+extern AISTATE houndChase;
+extern AISTATE houndRecoil;
+extern AISTATE houndGoto;
+extern AISTATE houndBite;
+extern AISTATE houndBurn;
+
+
+#endif // __AIHOUND_H
diff --git a/SRC/AIRAT.CPP b/SRC/AIRAT.CPP
new file mode 100644
index 0000000..8bb98df
--- /dev/null
+++ b/SRC/AIRAT.CPP
@@ -0,0 +1,192 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "airat.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kRatBiteDist M2X(1.8)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void BiteCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE ratIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE ratSearch = { kSeqRatWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &ratIdle };
+AISTATE ratChase = { kSeqRatWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE ratDodge = { kSeqRatWalk, NULL, 0, NULL, NULL, NULL, &ratChase };
+AISTATE ratRecoil = { kSeqRatWalk, NULL, 0, NULL, NULL, NULL, &ratDodge };
+AISTATE ratGoto = { kSeqRatWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &ratIdle };
+AISTATE ratBite = { kSeqDudeIdle, BiteCallback, 120, NULL, NULL, NULL, &ratChase };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+/****************************************************************************
+** BiteCallback()
+**
+**
+****************************************************************************/
+static void BiteCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorRatBite);
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &ratSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &ratGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ if ( dist < kRatBiteDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &ratBite);
+
+ return;
+ }
+ }
+ }
+ dprintf("Rat %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &ratGoto);
+ pXSprite->target = -1;
+}
diff --git a/SRC/AIRAT.H b/SRC/AIRAT.H
new file mode 100644
index 0000000..94d4532
--- /dev/null
+++ b/SRC/AIRAT.H
@@ -0,0 +1,16 @@
+#ifndef __AIRAT_H
+#define __AIRAT_H
+
+#include "ai.h"
+
+
+extern AISTATE ratIdle;
+extern AISTATE ratSearch;
+extern AISTATE ratChase;
+extern AISTATE ratDodge;
+extern AISTATE ratRecoil;
+extern AISTATE ratGoto;
+extern AISTATE ratBite;
+
+
+#endif // __AIRAT_H
diff --git a/SRC/AISPID.CPP b/SRC/AISPID.CPP
new file mode 100644
index 0000000..ffdfa8d
--- /dev/null
+++ b/SRC/AISPID.CPP
@@ -0,0 +1,209 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aispid.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kSpiderBiteDist M2X(1.8)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void BiteCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE spidIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE spidChase = { kSeqSpiderWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE spidDodge = { kSeqSpiderWalk, NULL, 90, NULL, aiMoveDodge, NULL, &spidChase };
+AISTATE spidGoto = { kSeqSpiderWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &spidIdle };
+AISTATE spidSearch = { kSeqSpiderWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &spidIdle };
+AISTATE spidBite = { kSeqSpiderBite, BiteCallback, 60, NULL, NULL, NULL, &spidChase };
+AISTATE spidJump = { kSeqSpiderJump, BiteCallback, 60, NULL, NULL, NULL, &spidChase };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** BiteCallback()
+**
+**
+****************************************************************************/
+static void BiteCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ // dispersal modifiers here
+ dx += BiRandom(2000);
+ dy += BiRandom(2000);
+ dz += BiRandom(2000);
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorSpiderBite);
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &spidSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &spidGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ switch ( pSprite->type )
+ {
+ case kDudeBrownSpider:
+ case kDudeRedSpider:
+ case kDudeBlackSpider:
+ case kDudeMotherSpider:
+ if ( dist < kSpiderBiteDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &spidBite);
+ break;
+ }
+ return;
+ }
+ }
+ }
+ dprintf("Spider %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &spidGoto);
+ pXSprite->target = -1;
+}
+
+
+
diff --git a/SRC/AISPID.H b/SRC/AISPID.H
new file mode 100644
index 0000000..474ce2a
--- /dev/null
+++ b/SRC/AISPID.H
@@ -0,0 +1,16 @@
+#ifndef __AISPID_H
+#define __AISPID_H
+
+#include "ai.h"
+
+
+extern AISTATE spidIdle;
+extern AISTATE spidChase;
+extern AISTATE spidDodge;
+extern AISTATE spidGoto;
+extern AISTATE spidSearch;
+extern AISTATE spidBite;
+extern AISTATE spidJump;
+
+
+#endif // __AISPID_H
diff --git a/SRC/AIZOMBA.CPP b/SRC/AIZOMBA.CPP
new file mode 100644
index 0000000..17e33a5
--- /dev/null
+++ b/SRC/AIZOMBA.CPP
@@ -0,0 +1,426 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aizomba.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "sfx.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kAxeZombieMeleeDist M2X(2.0) //M2X(1.6)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void HackCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move/entry function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkPonder( SPRITE *pSprite, XSPRITE *pXSprite );
+
+static void entryEZombie( SPRITE *pSprite, XSPRITE *pXSprite );
+static void entryAIdle( SPRITE */*pSprite*/, XSPRITE *pXSprite );
+
+static void myThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite );
+static void myThinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE zombieAIdle = { kSeqDudeIdle, NULL, 0, entryAIdle, NULL, aiThinkTarget, NULL };
+AISTATE zombieAChase = { kSeqAxeZombieWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieAPonder = { kSeqDudeIdle, NULL, 0, NULL, aiMoveTurn, thinkPonder, NULL };
+AISTATE zombieAGoto = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &zombieAIdle };
+AISTATE zombieAHack = { kSeqAxeZombieAttack, HackCallback, 120, NULL, NULL, NULL, &zombieAPonder };
+AISTATE zombieASearch = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &zombieAIdle };
+AISTATE zombieARecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &zombieAPonder };
+
+AISTATE zombieEIdle = { kSeqEarthZombieIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE zombieEUp2 = { kSeqDudeIdle, NULL, 1, entryEZombie, NULL, NULL, &zombieASearch };
+AISTATE zombieEUp = { kSeqEarthZombieUp, NULL, 180, NULL, NULL, NULL, &zombieEUp2 };
+
+AISTATE zombie2Idle = { kSeqDudeIdle, NULL, 0, entryAIdle, NULL, myThinkTarget, NULL };
+AISTATE zombie2Search = { kSeqAxeZombieWalk, NULL, 3600, NULL, aiMoveForward, myThinkSearch, &zombie2Idle };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** TommyCallback()
+**
+**
+****************************************************************************/
+static void HackCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorAxe);
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ {
+ dprintf("Axe zombie %d switching to search mode\n", pXSprite->reference);
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ }
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0
+ || powerupCheck( pPlayer, kItemDeathMask - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ if ( dist < kAxeZombieMeleeDist && qabs(losAngle) < kAngle15 )
+ {
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxAxeAir);
+ aiNewState(pSprite, pXSprite, &zombieAHack);
+ }
+ return;
+ }
+ }
+ }
+ dprintf("Axe zombie %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ pXSprite->target = -1;
+}
+
+
+/****************************************************************************
+** thinkPonder()
+**
+**
+****************************************************************************/
+static void thinkPonder( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0
+ || powerupCheck( pPlayer, kItemDeathMask - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ if ( dist < kAxeZombieMeleeDist )
+ {
+ if ( qabs(losAngle) < kAngle15 )
+ {
+ sfxCreate3DSound(pSprite->x, pSprite->y, pSprite->z, kSfxAxeAir);
+ aiNewState(pSprite, pXSprite, &zombieAHack);
+ }
+ return;
+ }
+ }
+ }
+ }
+ aiNewState(pSprite, pXSprite, &zombieAChase);
+}
+
+
+/****************************************************************************
+** myThinkTarget()
+**
+**
+****************************************************************************/
+static void myThinkTarget( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ for (int i = 0; i < numplayers; i++)
+ {
+ PLAYER *pPlayer = &gPlayer[i];
+
+ // skip this player if the he owns the dude or is invisible
+ if ( pSprite->owner == pPlayer->nSprite
+ || pPlayer->xsprite->health == 0
+ || powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0 )
+ continue;
+
+ int x = pPlayer->sprite->x;
+ int y = pPlayer->sprite->y;
+ int z = pPlayer->sprite->z;
+ short nSector = pPlayer->sprite->sectnum;
+
+ int dx = x - pSprite->x;
+ int dy = y - pSprite->y;
+
+ int dist = qdist(dx, dy);
+
+ if ( dist <= pDudeInfo->seeDist || dist <= pDudeInfo->hearDist )
+ {
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the player?
+ if ( cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ,
+ pSprite->sectnum) )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+
+ // is the player visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget( pXSprite, pPlayer->nSprite );
+ aiActivateDude( pSprite, pXSprite );
+ return;
+ }
+
+ // we may want to make hearing a function of sensitivity, rather than distance
+ if ( dist < pDudeInfo->hearDist )
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude( pSprite, pXSprite );
+ return;
+ }
+ }
+ }
+ }
+}
+
+
+/****************************************************************************
+** myThinkSearch()
+**
+**
+****************************************************************************/
+static void myThinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ myThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** entryEZombie()
+**
+**
+****************************************************************************/
+static void entryEZombie( SPRITE *pSprite, XSPRITE */*pXSprite*/ )
+{
+ pSprite->flags |= kAttrMove;
+ pSprite->type = kDudeAxeZombie;
+}
+
+/****************************************************************************
+** entryAZombieIdle()
+**
+**
+****************************************************************************/
+static void entryAIdle( SPRITE */*pSprite*/, XSPRITE *pXSprite )
+{
+ pXSprite->target = -1;
+}
+
+
+/****************************************************************************
+** aiAlertLackey()
+**
+**
+****************************************************************************/
+void aiAlertLackey( PLAYER *pPlayer, int nLackey, int nSource, int nDamage )
+{
+ // tell lackeys all about your troubles...
+ SPRITE *pLackey = &sprite[nLackey];
+ XSPRITE *pXLackey = &xsprite[pLackey->extra];
+
+ if ( pXLackey->target == -1 || pXLackey->target == pPlayer->nSprite )
+ {
+ // give a dude a target
+ //dprintf("giving dude a new target\n");
+ aiSetTarget(pXLackey, nSource);
+ aiActivateDude(pLackey, pXLackey);
+ }
+ else if (nSource != pXLackey->target)
+ {
+ // retarget
+ SPRITE *pSource = &sprite[nSource];
+ int nThresh = nDamage * dudeInfo[pSource->type - kDudeBase].changeTarget;
+
+ if ( Chance(nThresh) )
+ {
+ //dprintf("aiSetTarget lackey #%i to %i\n",i,nSource);
+ aiSetTarget(pXLackey, nSource);
+ aiActivateDude(pLackey, pXLackey);
+ }
+ }
+}
diff --git a/SRC/AIZOMBA.H b/SRC/AIZOMBA.H
new file mode 100644
index 0000000..22435e0
--- /dev/null
+++ b/SRC/AIZOMBA.H
@@ -0,0 +1,22 @@
+#ifndef __AIZOMBA_H
+#define __AIZOMBA_H
+
+#include "ai.h"
+#include "player.h"
+
+
+extern AISTATE zombieAIdle;
+extern AISTATE zombieAChase;
+extern AISTATE zombieAGoto;
+extern AISTATE zombieAHack;
+extern AISTATE zombieASearch;
+extern AISTATE zombieARecoil;
+
+extern AISTATE zombieEIdle;
+extern AISTATE zombieEUp2;
+extern AISTATE zombieEUp;
+
+void aiAlertLackey( PLAYER *pPlayer, int nLackey, int nSource, int nDamage );
+
+
+#endif // __AIZOMBA_H
diff --git a/SRC/AIZOMBF.CPP b/SRC/AIZOMBF.CPP
new file mode 100644
index 0000000..9d8605d
--- /dev/null
+++ b/SRC/AIZOMBF.CPP
@@ -0,0 +1,249 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "aizombf.h"
+
+#include "actor.h"
+#include "db.h"
+#include "debug4g.h"
+#include "dude.h"
+#include "engine.h"
+#include "eventq.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "misc.h"
+#include "multi.h"
+#include "player.h"
+#include "trig.h"
+
+/****************************************************************************
+** LOCAL CONSTANTS
+****************************************************************************/
+
+#define kFatZombieMeleeDist M2X(2.0) //M2X(1.6)
+#define kFatZombiePukeDist1 M2X(3)
+#define kFatZombiePukeDist2 M2X(5)
+#define kFatZombieThrowDist1 M2X(10)
+#define kFatZombieThrowDist2 M2X(6)
+
+
+/****************************************************************************
+** LOCAL callback prototypes
+****************************************************************************/
+
+static void HackCallback( int /* type */, int nXIndex );
+static void PukeCallback( int /* type */, int nXIndex );
+static void ThrowCallback( int /* type */, int nXIndex );
+
+
+/****************************************************************************
+** LOCAL think/move function prototypes
+****************************************************************************/
+
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite );
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite );
+
+
+/****************************************************************************
+** GLOBAL CONSTANTS
+****************************************************************************/
+
+AISTATE zombieFIdle = { kSeqDudeIdle, NULL, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE zombieFChase = { kSeqFatZombieWalk, NULL, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieFGoto = { kSeqFatZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkGoto, &zombieFIdle };
+AISTATE zombieFHack = { kSeqFatZombieAttack,HackCallback, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFPuke = { kSeqFatZombieAttack,PukeCallback, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFThrow = { kSeqFatZombieAttack,ThrowCallback, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFSearch = { kSeqFatZombieWalk, NULL, 3600, NULL, aiMoveForward, thinkSearch, &zombieFIdle };
+AISTATE zombieFRecoil = { kSeqDudeRecoil, NULL, 0, NULL, NULL, NULL, &zombieFChase };
+
+
+/****************************************************************************
+** LOCAL FUNCTIONS
+****************************************************************************/
+
+
+/****************************************************************************
+** HackCallback()
+**
+**
+****************************************************************************/
+static void HackCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ actFireVector(nSprite, pSprite->z, dx, dy, dz, kVectorCleaver);
+}
+
+
+/****************************************************************************
+** HackCallback()
+**
+**
+****************************************************************************/
+static void PukeCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ int nThing = actFireThing(nSprite, pSprite->z, 0, kThingTNTStick, (M2X(4.0) << 4) / kTimerRate);
+ evPost(nThing, SS_SPRITE, 2 * kTimerRate); // 2 second burn off
+}
+
+
+/****************************************************************************
+** ThrowCallback()
+**
+**
+****************************************************************************/
+static void ThrowCallback( int /* type */, int nXIndex )
+{
+ XSPRITE *pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ SPRITE *pSprite = &sprite[nSprite];
+
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = 0;
+
+ int nZOffset = dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+
+ actFireMissile( nSprite, pSprite->z - nZOffset, dx, dy, dz, kMissileButcherKnife );
+}
+
+
+/****************************************************************************
+** thinkSearch()
+**
+**
+****************************************************************************/
+static void thinkSearch( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkGoto()
+**
+**
+****************************************************************************/
+static void thinkGoto( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = qdist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( dist < M2X(1.0) && qabs(pSprite->ang - nAngle) < pDudeInfo->periphery )
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+
+/****************************************************************************
+** thinkChase()
+**
+**
+****************************************************************************/
+static void thinkChase( SPRITE *pSprite, XSPRITE *pXSprite )
+{
+ if ( pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieFGoto);
+ return;
+ }
+
+ int dx, dy, dist;
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ SPRITE *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget->health == 0 )
+ {
+ // target is dead
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER *pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, kItemLtdInvisibility - kItemBase ) > 0
+ || powerupCheck( pPlayer, kItemDeathMask - kItemBase ) > 0 )
+ {
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ return;
+ }
+ }
+
+ dist = qdist(dx, dy);
+
+ if ( dist <= pDudeInfo->seeDist )
+ {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((kAngle180 + nAngle - pSprite->ang) & kAngleMask) - kAngle180;
+ int eyeAboveZ = pDudeInfo->eyeHeight * pSprite->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if ( cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) )
+ {
+ // is the target visible?
+ if ( dist < pDudeInfo->seeDist && qabs(losAngle) <= pDudeInfo->periphery )
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ // check to see if we can attack
+ if ( dist < kFatZombieThrowDist1 && dist > kFatZombieThrowDist2 && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &zombieFThrow);
+ else
+ if ( dist < kFatZombiePukeDist1 && dist > kFatZombiePukeDist2 && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &zombieFPuke);
+ else
+ if ( dist < kFatZombieMeleeDist && qabs(losAngle) < kAngle15 )
+ aiNewState(pSprite, pXSprite, &zombieFHack);
+ return;
+ }
+ }
+ }
+ dprintf("Fat zombie %d lost sight of target %d\n", pXSprite->reference, pXSprite->target);
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ pXSprite->target = -1;
+}
diff --git a/SRC/AIZOMBF.H b/SRC/AIZOMBF.H
new file mode 100644
index 0000000..be85543
--- /dev/null
+++ b/SRC/AIZOMBF.H
@@ -0,0 +1,17 @@
+#ifndef __AIZOMBF_H
+#define __AIZOMBF_H
+
+#include "ai.h"
+
+
+extern AISTATE zombieFIdle;
+extern AISTATE zombieFChase;
+extern AISTATE zombieFGoto;
+extern AISTATE zombieFHack;
+extern AISTATE zombieFPuke;
+extern AISTATE zombieFThrow;
+extern AISTATE zombieFSearch;
+extern AISTATE zombieFRecoil;
+
+
+#endif // __AIZOMBF_H
diff --git a/SRC/APE.CPP b/SRC/APE.CPP
new file mode 100644
index 0000000..2a39a85
--- /dev/null
+++ b/SRC/APE.CPP
@@ -0,0 +1,440 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <io.h>
+#include <fcntl.h>
+#include "typedefs.h"
+#include "misc.h"
+#include "debug4g.h"
+#include "getopt.h"
+#include "error.h"
+
+#define kMaxTiles 4096
+
+struct PICANM {
+ unsigned frames : 5; // number of frames - 1
+ unsigned update : 1; // this came from upper bit of frames
+ unsigned type : 2; // 0 = none, 1 = Oscil, 2 = Frwd, 3 = Bkwd
+ signed xcenter : 8;
+ signed ycenter : 8;
+ unsigned speed : 4; // (clock >> speed) determines rate
+ unsigned view : 2;
+ unsigned registered : 1;
+} picanm[kMaxTiles];
+
+long artversion;
+short tilesizx[kMaxTiles];
+short tilesizy[kMaxTiles];
+int checksum[kMaxTiles];
+
+
+/***********************************************************************
+ * ShowBanner
+ *
+ * Display application banner.
+ **********************************************************************/
+void ShowBanner( void )
+{
+ printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n");
+ printf(" APE - Art Patch Engine Version 1.1 Copyright (c) 1995 Q Studios Corporation\n");
+ printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n");
+}
+
+
+/***********************************************************************
+ * ShowUsage
+ *
+ * Display command-line parameter usage, then exit.
+ **********************************************************************/
+void ShowUsage(void)
+{
+ printf("Syntax: APE [commands]\n");
+ printf("-a filename Apply delta file\n");
+ printf("-b filename Build delta file from checksum\n");
+ printf("-c filename Create checksum file\n");
+ printf("-? This help\n");
+ printf("\n");
+ exit(0);
+}
+
+
+void CreateChecksumFile( char *argument )
+{
+ char filename[_MAX_PATH];
+ int i;
+ int nTiles;
+ int nFile, hFile;
+ int nSize;
+ int tileStart, tileEnd;
+ long numtiles;
+
+ printf("Processing tile ");
+ nFile = 0;
+ while (1)
+ {
+ sprintf(filename, "TILES%03i.ART", nFile);
+ hFile = open(filename, O_BINARY | O_RDONLY);
+
+ if ( hFile == -1 )
+ break;
+
+ FileRead(hFile, &artversion, sizeof(artversion));
+ dassert(artversion == 1);
+
+ FileRead(hFile, &numtiles, sizeof(numtiles));
+ FileRead(hFile, &tileStart, sizeof(tileStart));
+ FileRead(hFile, &tileEnd, sizeof(tileEnd));
+
+ nTiles = tileEnd - tileStart + 1;
+ FileRead(hFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0]));
+ FileRead(hFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0]));
+ FileRead(hFile, &picanm[tileStart], nTiles * sizeof(picanm[0]));
+
+ for (i = tileStart; i <= tileEnd; i++)
+ {
+ nSize = tilesizx[i] * tilesizy[i];
+
+ if ( nSize > 0 )
+ {
+ printf("\b\b\b\b%4d", i);
+ BYTE *p = (BYTE *)malloc(nSize);
+ dassert(p != NULL);
+
+ if ( !FileRead(hFile, p, nSize) )
+ ThrowError("Error FileReading tile file", ES_ERROR);
+
+ checksum[i] = CRC32(p, nSize);
+ free(p);
+ }
+ }
+ close(hFile);
+ nFile++;
+ }
+ printf("\n");
+
+ strcpy(filename, argument);
+ AddExtension(filename, "APC");
+ if ( !FileSave(filename, checksum, sizeof(checksum)) )
+ ThrowError("Error creating output file", ES_ERROR);
+
+ printf("Checksum file %s created.\n", filename);
+}
+
+
+void BuildDeltaFile( char *argument )
+{
+ char filename[_MAX_PATH];
+ int i;
+ int nTiles;
+ int nFile, hTileFile, hDeltaFile;
+ int nSize;
+ int tileStart, tileEnd;
+ long numtiles;
+ int crc;
+ BYTE *p = NULL;
+
+ strcpy(filename, argument);
+ ChangeExtension(filename, "APC");
+ if ( !FileLoad(filename, checksum, sizeof(checksum)) )
+ ThrowError("Error FileReading checksum file", ES_ERROR);
+
+ ChangeExtension(filename, "APD");
+ hDeltaFile = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWUSR);
+ if (hDeltaFile == -1)
+ ThrowError("Error creating delta file", ES_ERROR);
+
+ printf("Processing tile ");
+ nFile = 0;
+ while (1)
+ {
+ sprintf(filename, "TILES%03i.ART", nFile);
+ hTileFile = open(filename, O_BINARY | O_RDONLY);
+
+ if ( hTileFile == -1 )
+ break;
+
+ FileRead(hTileFile, &artversion, sizeof(artversion));
+ dassert(artversion == 1);
+
+ FileRead(hTileFile, &numtiles, sizeof(numtiles));
+ FileRead(hTileFile, &tileStart, sizeof(tileStart));
+ FileRead(hTileFile, &tileEnd, sizeof(tileEnd));
+
+ nTiles = tileEnd - tileStart + 1;
+ FileRead(hTileFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0]));
+ FileRead(hTileFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0]));
+ FileRead(hTileFile, &picanm[tileStart], nTiles * sizeof(picanm[0]));
+
+ for (i = tileStart; i <= tileEnd; i++)
+ {
+ printf("\b\b\b\b%4d", i);
+ crc = 0;
+
+ nSize = tilesizx[i] * tilesizy[i];
+ if ( nSize > 0 )
+ {
+ p = (BYTE *)malloc(nSize);
+ dassert(p != NULL);
+
+ FileRead(hTileFile, p, nSize);
+ crc = CRC32(p, nSize);
+ }
+
+ if ( crc != checksum[i] )
+ {
+ picanm[i].update = 1;
+ FileWrite(hDeltaFile, &picanm[i], sizeof(picanm[i]));
+ FileWrite(hDeltaFile, &tilesizx[i], sizeof(tilesizx[i]));
+ FileWrite(hDeltaFile, &tilesizy[i], sizeof(tilesizy[i]));
+ FileWrite(hDeltaFile, p, nSize);
+ }
+ else
+ {
+ picanm[i].update = 0;
+ FileWrite(hDeltaFile, &picanm[i], sizeof(picanm[i]));
+ }
+
+ if ( p != NULL )
+ free(p);
+ }
+ close(hTileFile);
+ nFile++;
+ }
+ printf("\nDelta file created\n");
+ close(hDeltaFile);
+}
+
+
+void ApplyDeltaFile( char *argument )
+{
+ char tileName[_MAX_PATH], bakName[_MAX_PATH], tempName[_MAX_PATH], deltaName[_MAX_PATH];
+ long offset;
+ int i;
+ int nTiles = 0; // tiles per art file
+ int nFile, hTileFile, hDeltaFile, hTemp;
+ int nSize;
+ int tileStart, tileEnd;
+ long numtiles;
+ BYTE *p = NULL;
+ int nChanged = 0;
+ FILE *fp; // use stream so we can write formatted text more easily
+
+ fp = fopen("ape.rpt", "wt");
+ if ( !fp )
+ ThrowError("Error opening report file", ES_ERROR);
+
+ strcpy(deltaName, argument);
+ ChangeExtension(deltaName, "APD");
+ hDeltaFile = open(deltaName, O_RDONLY | O_BINARY);
+ if (hDeltaFile == -1)
+ ThrowError("Error opening delta file", ES_ERROR);
+
+ memset(picanm, 0, sizeof(picanm));
+ memset(tilesizx, 0, sizeof(tilesizx));
+ memset(tilesizy, 0, sizeof(tilesizy));
+
+ printf("Processing tile ");
+ nFile = 0;
+ while ( !eof(hDeltaFile) )
+ {
+ hTemp = -1;
+
+ tmpnam(tempName);
+ sprintf(tileName, "TILES%03i.ART", nFile);
+ hTileFile = open(tileName, O_BINARY | O_RDONLY);
+ if ( hTileFile != -1 )
+ {
+ FileRead(hTileFile, &artversion, sizeof(artversion));
+
+ FileRead(hTileFile, &numtiles, sizeof(numtiles));
+ FileRead(hTileFile, &tileStart, sizeof(tileStart));
+ FileRead(hTileFile, &tileEnd, sizeof(tileEnd));
+
+ nTiles = tileEnd - tileStart + 1;
+ FileRead(hTileFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0]));
+ FileRead(hTileFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0]));
+ FileRead(hTileFile, &picanm[tileStart], nTiles * sizeof(picanm[0]));
+
+ // get current position
+ offset = lseek(hTileFile, 0, SEEK_CUR);
+
+ // create the output file
+ hTemp = open(tempName, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWUSR);
+
+ // position the output file to the start of the tile data
+ lseek(hTemp, offset, SEEK_SET);
+ }
+
+ // merge all the changed tiles
+ for (i = tileStart; i <= tileEnd; i++)
+ {
+ printf("\b\b\b\b%4d", i);
+
+ FileRead(hDeltaFile, &picanm[i], sizeof(picanm[i]));
+ nSize = tilesizx[i] * tilesizy[i];
+ if ( picanm[i].update )
+ {
+ // we may need to create a new art file
+ if (hTemp == -1)
+ {
+ // we've got to have opened at least one previous file...
+ dassert(nTiles != 0);
+
+ // create the output file
+ hTemp = open(tempName, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWUSR);
+
+ // position the output file to the start of the tile data
+ lseek(hTemp, offset, SEEK_SET);
+ }
+
+ picanm[i].update = 0;
+
+ // skip past the tile we're replacing
+ if ( hTileFile != -1 )
+ lseek(hTileFile, nSize, SEEK_CUR);
+
+ FileRead(hDeltaFile, &tilesizx[i], sizeof(tilesizx[i]));
+ FileRead(hDeltaFile, &tilesizy[i], sizeof(tilesizy[i]));
+ nSize = tilesizx[i] * tilesizy[i];
+
+ if ( nSize > 0 )
+ {
+ p = (BYTE *)malloc(nSize);
+ dassert(p != NULL);
+
+ FileRead(hDeltaFile, p, nSize);
+ FileWrite(hTemp, p, nSize);
+
+ fprintf(fp, "Tile %4d replaced or added\n", i);
+ }
+ else
+ fprintf(fp, "Tile %4d removed\n", i);
+
+ nChanged++;
+ }
+ else
+ {
+ if ( nSize > 0 )
+ {
+ // if we're not updating, it must exist
+ dassert(hTileFile != -1);
+
+ p = (BYTE *)malloc(nSize);
+ dassert(p != NULL);
+
+ FileRead(hTileFile, p, nSize);
+ FileWrite(hTemp, p, nSize);
+ }
+ }
+
+ if ( p != NULL )
+ free(p);
+ }
+
+ if ( hTileFile != -1 )
+ close(hTileFile);
+
+ if ( hTemp != -1 )
+ {
+ // now write out the art file header
+ lseek(hTemp, 0, SEEK_SET);
+ FileWrite(hTemp, &artversion, sizeof(artversion));
+ FileWrite(hTemp, &numtiles, sizeof(numtiles));
+ FileWrite(hTemp, &tileStart, sizeof(tileStart));
+ FileWrite(hTemp, &tileEnd, sizeof(tileEnd));
+ FileWrite(hTemp, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0]));
+ FileWrite(hTemp, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0]));
+ FileWrite(hTemp, &picanm[tileStart], nTiles * sizeof(picanm[0]));
+ close(hTemp);
+
+ // make the current tile file into a backup
+ strcpy(bakName, tileName);
+ ChangeExtension(bakName, ".BAK");
+ rename(tileName, bakName);
+ rename(tempName, tileName);
+ }
+
+ nFile++;
+
+ // increment these just in case the next tile file doesn't exist yet
+ tileStart += nTiles;
+ tileEnd += nTiles;
+ }
+ close(hDeltaFile);
+ fclose(fp);
+ printf("\nUpdate complete. %d tiles modified. See APE.RPT for details.\n", nChanged);
+}
+
+
+/***********************************************************************
+ * Process command line arguments
+ **********************************************************************/
+void ParseOptions( void )
+{
+ enum {
+ kSwitchHelp,
+ kSwitchApply,
+ kSwitchBuild,
+ kSwitchCreate,
+ };
+ static SWITCH switches[] = {
+ { "?", kSwitchHelp, FALSE },
+ { "A", kSwitchApply, TRUE },
+ { "B", kSwitchBuild, TRUE },
+ { "C", kSwitchCreate, TRUE },
+ { NULL, 0, FALSE },
+ };
+ char buffer[256];
+ int r;
+ while ( (r = GetOptions(switches)) != GO_EOF )
+ {
+ switch (r)
+ {
+ case GO_INVALID:
+ sprintf(buffer, "Invalid argument: %s", OptArgument);
+ ThrowError(buffer, ES_ERROR);
+ break;
+
+ case GO_MISSING:
+ ThrowError("Missing argument", ES_ERROR);
+ break;
+
+ case kSwitchHelp:
+ ShowUsage();
+ break;
+
+ case GO_FULL:
+ ShowUsage();
+ break;
+
+ case kSwitchApply:
+ ApplyDeltaFile(OptArgument);
+ break;
+
+ case kSwitchBuild:
+ BuildDeltaFile(OptArgument);
+ break;
+
+ case kSwitchCreate:
+ CreateChecksumFile(OptArgument);
+ break;
+
+ }
+ }
+}
+
+
+/***********************************************************************
+ * Main
+ **********************************************************************/
+int main( int argc )
+{
+ ShowBanner();
+
+ if ( argc == 1 )
+ ShowUsage();
+
+ ParseOptions();
+
+ return 0;
+}
diff --git a/SRC/ARTEDIT.CPP b/SRC/ARTEDIT.CPP
new file mode 100644
index 0000000..208f9db
--- /dev/null
+++ b/SRC/ARTEDIT.CPP
@@ -0,0 +1,1290 @@
+/*******************************************************************************
+ FILE: ARTEDIT.CPP
+
+ DESCRIPTION: Allows rearrangement of art tiles
+
+ AUTHOR: Peter M. Freese
+ CREATED: 06-19-95
+ COPYRIGHT: Copyright (c) 1995 Q Studios Corporation
+*******************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <helix.h>
+#include <string.h>
+#include <io.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "typedefs.h"
+#include "engine.h"
+#include "key.h"
+#include "misc.h"
+#include "gameutil.h"
+#include "gfx.h"
+#include "bstub.h"
+#include "globals.h"
+#include "debug4g.h"
+#include "error.h"
+#include "gui.h"
+#include "tile.h"
+#include "screen.h"
+#include "textio.h"
+#include "inifile.h"
+#include "options.h"
+#include "timer.h"
+#include "getopt.h"
+#include "mouse.h"
+#include "db.h"
+#include "trig.h"
+
+#include <memcheck.h>
+
+#define kAttrTitle (kColorGreen * 16 + kColorYellow)
+
+#define kMaxUndo 100
+
+struct UNDO
+{
+ int start, length, shift, cursor;
+};
+
+struct NAMED_TYPE
+{
+ short id;
+ char *name;
+};
+
+int pickSize[] = { 20, 32, 40, 64, 80, 128, 160 };
+BOOL tilesModified = FALSE;
+BOOL tilesMoved = FALSE;
+POINT origin;
+int nOctant = 0;
+char buffer[256];
+EHF prevErrorHandler;
+UNDO undo[kMaxUndo];
+int undoCount = 0, undoCountMax = 0;
+int nCursor;
+BOOL bForceVersion = FALSE;
+int nVersion;
+int nSelect1 = -1, nSelect2 = -1;
+int nSelectBegin = 0, nSelectEnd = -1;
+PICANM oldpicanm[kMaxTiles];
+static BYTE *inverseCLU = NULL;
+
+#define kMaxNameLength 17 // this is ken's lame-o size that can't be changed...
+static char oldNames[ kMaxTiles ][ kMaxNameLength ];
+static char tileNames[ kMaxTiles ][ kMaxNameLength ];
+BOOL namesModified = FALSE;
+
+
+char *viewNames[] =
+{
+ "SINGLE",
+ "5 FULL",
+ "8 FULL",
+ "5 HALF",
+ "3 FLAT",
+ "4 FLAT",
+};
+
+char *animNames[] =
+{
+ "NoAnm",
+ "Oscil",
+ "AnmFw",
+ "AnmBk",
+};
+
+
+char *surfNames[256];
+
+NAMED_TYPE surfNTNames[] =
+{
+ kSurfNone, "NONE",
+ kSurfStone, "STONE",
+ kSurfMetal, "METAL",
+ kSurfWood, "WOOD",
+ kSurfFlesh, "FLESH",
+ kSurfWater, "WATER",
+ kSurfDirt, "DIRT",
+ kSurfClay, "CLAY",
+ kSurfSnow, "SNOW",
+ kSurfIce, "ICE",
+ kSurfLeaves, "LEAVES",
+ kSurfCloth, "CLOTH",
+ kSurfPlant, "PLANT",
+ kSurfGoo, "GOO",
+ kSurfLava, "LAVA",
+};
+
+
+/***********************************************************************
+ * LoadTileNames
+ *
+ **********************************************************************/
+int LoadTileNames( void )
+{
+ char buffer[160], ch;
+ int fil, i, num, buffercnt;
+
+ if ((fil = open("names.h", O_TEXT | O_RDWR, S_IREAD)) == -1)
+ return(-1);
+
+ i = read(fil,&ch,1);
+ while ( ch != '#' && i > 0)
+ {
+ i = read(fil,&ch,1);
+ }
+
+ while ( ch == '#' )
+ {
+ // find first space
+ while ( !isspace(ch) )
+ read(fil,&ch,1);
+
+ // find non-space
+ while ( isspace(ch) )
+ read(fil,&ch,1);
+
+ // get name
+ buffercnt = 0;
+ while ( !isspace(ch) )
+ {
+ buffer[buffercnt++] = ch;
+ read(fil,&ch,1);
+ }
+
+ // find non-space
+ while ( isspace(ch) )
+ read(fil,&ch,1);
+
+ // get number
+ num = 0;
+ while ( isdigit(ch) )
+ {
+ num = num*10+(ch-48);
+ read(fil,&ch,1);
+ }
+
+ // find end of line
+ while ( ch != '\n' )
+ read(fil,&ch,1);
+
+ memcpy(tileNames[num], buffer, buffercnt);
+ tileNames[num][buffercnt] = 0;
+
+ read(fil,&ch,1);
+ }
+
+ close(fil);
+ memcpy(oldNames, tileNames, sizeof(oldNames));
+ namesModified = FALSE;
+ return(0);
+}
+
+
+/***********************************************************************
+ * SaveTileNames
+ *
+ **********************************************************************/
+void SaveTileNames( void )
+{
+ char buffer[160];
+ int hFile;
+ int len;
+
+ if (namesModified == FALSE)
+ return;
+
+ if ((hFile = open("names.h",O_TEXT | O_TRUNC | O_CREAT | O_WRONLY, S_IWRITE)) == -1)
+ return;
+
+ strcpy(buffer,"//Be careful when changing this file - it is parsed by Editart and Build.\n");
+ write(hFile, buffer, strlen(buffer));
+
+ for (int i = 0; i < kMaxTiles; i++)
+ {
+ if (tileNames[i][0] != 0)
+ {
+ len = sprintf(buffer, "#define %s %d\n", tileNames[i], i);
+ write(hFile, buffer, len);
+ }
+ }
+
+ close(hFile);
+ namesModified = FALSE;
+ return;
+}
+
+
+/***********************************************************************
+ * FillNameArray
+ *
+ **********************************************************************/
+void FillNameArray( char **names, NAMED_TYPE *ntNames, int length )
+{
+ for (int i = 0; i < length; i++)
+ {
+ names[ntNames->id] = ntNames->name;
+ ntNames++;
+ }
+}
+
+
+void faketimerhandler( void )
+{ }
+
+
+/***********************************************************************
+ * EditorErrorHandler()
+ *
+ * Terminate from error condition, displaying a message in text mode.
+ *
+ **********************************************************************/
+ErrorResult EditorErrorHandler( const Error& error )
+{
+ uninitengine();
+ keyRemove();
+ setvmode(gOldDisplayMode);
+
+ // chain to the default error handler
+ return prevErrorHandler(error);
+};
+
+
+void SetOrigin( int x, int y )
+{
+ origin.x = ClipRange(x, 0, xdim - 1);
+ origin.y = ClipRange(y, 0, ydim - 1);
+}
+
+
+void DrawOrigin( int nColor )
+{
+ Video.SetColor(nColor);
+ gfxHLine(origin.y, origin.x - 6, origin.x - 1);
+ gfxHLine(origin.y, origin.x + 1, origin.x + 6);
+ gfxVLine(origin.x, origin.y - 5, origin.y - 1);
+ gfxVLine(origin.x, origin.y + 1, origin.y + 5);
+}
+
+
+void AddUndo( int start, int length, int shift, int cursor)
+{
+ if (undoCount == kMaxUndo)
+ {
+ undoCount--;
+ memmove(&undo[0], &undo[1], undoCount * sizeof(UNDO));
+ }
+ undo[undoCount].start = start;
+ undo[undoCount].length = length;
+ undo[undoCount].shift = shift;
+ undo[undoCount].cursor = cursor;
+ undoCountMax = ++undoCount;
+}
+
+
+void Undo( void )
+{
+ if (undoCount > 0)
+ {
+ undoCount--;
+ tileRotateTiles(undo[undoCount].start, undo[undoCount].length, -undo[undoCount].shift);
+ scrSetMessage("Move undone");
+ nCursor = undo[undoCount].cursor;
+ }
+ else
+ scrSetMessage("Nothing to undo.");
+}
+
+
+void Redo( void )
+{
+ if (undoCount < undoCountMax)
+ {
+ tileRotateTiles(undo[undoCount].start, undo[undoCount].length, undo[undoCount].shift);
+ scrSetMessage("Move redone");
+ nCursor = undo[undoCount].cursor;
+ undoCount++;
+ }
+ else
+ scrSetMessage("Nothing to redo.");
+}
+
+
+void MoveTiles( int dest, int source, int count )
+{
+ int start, length, shift;
+ if (source < dest)
+ {
+ start = source;
+ length = dest - source;
+ shift = count;
+ }
+ else
+ {
+ start = dest;
+ length = source - dest + count;
+ shift = source - dest;
+ }
+ tileRotateTiles(start, length, shift);
+ AddUndo(start, length, shift, nCursor);
+ scrSetMessage("Tiles moved");
+ tilesMoved = tilesModified = TRUE;
+}
+
+
+BOOL AcknowledgeTileChange(void)
+{
+ // create the dialog
+ Window dialog(0, 0, 202, 46, "Save Art Changes?");
+
+ TextButton *pbYes = new TextButton( 4, 4, 60, 20, "&Yes", mrYes );
+ TextButton *pbNo = new TextButton( 68, 4, 60, 20, "&No", mrNo );
+ TextButton *pbCancel = new TextButton( 132, 4, 60, 20, "&Cancel", mrCancel );
+ dialog.Insert(pbYes);
+ dialog.Insert(pbNo);
+ dialog.Insert(pbCancel);
+
+ ShowModal(&dialog);
+ switch ( dialog.endState )
+ {
+ case mrCancel:
+ return FALSE;
+
+ case mrOk:
+ case mrYes:
+ if ( tilesMoved )
+ tileSaveArt();
+ else
+ tileSaveArtInfo();
+ if ( namesModified )
+ SaveTileNames();
+ break;
+ }
+
+ return TRUE;
+}
+
+
+MODAL_RESULT KeepTileChanges(void)
+{
+ // create the dialog
+ Window dialog(0, 0, 202, 46, "Keep tile changes?");
+
+ TextButton *pbYes = new TextButton( 4, 4, 60, 20, "&Yes", mrYes );
+ TextButton *pbNo = new TextButton( 68, 4, 60, 20, "&No", mrNo );
+ TextButton *pbCancel = new TextButton( 132, 4, 60, 20, "&Cancel", mrCancel );
+ dialog.Insert(pbYes);
+ dialog.Insert(pbNo);
+ dialog.Insert(pbCancel);
+
+ ShowModal(&dialog);
+ return dialog.endState;
+}
+
+
+BOOL EditSelectedTiles(void)
+{
+ // create the dialog
+ Window dialog(0, 0, 202, 46, "Edit all selected?");
+
+ TextButton *pbYes = new TextButton( 4, 4, 60, 20, "&Yes", mrYes );
+ TextButton *pbNo = new TextButton( 68, 4, 60, 20, "&No", mrNo );
+ TextButton *pbCancel = new TextButton( 132, 4, 60, 20, "&Cancel", mrCancel );
+ dialog.Insert(pbYes);
+ dialog.Insert(pbNo);
+ dialog.Insert(pbCancel);
+
+ ShowModal(&dialog);
+ return dialog.endState;
+}
+
+
+int DrawTile( int nTile )
+{
+ char nShade = 0;
+ char nFlags = kRotateNormal;
+ short nAngle = 0;
+ switch ( picanm[nTile].view )
+ {
+ case kSpriteViewSingle:
+ break;
+
+ case kSpriteView5Full:
+ if (nOctant <= 4)
+ nTile += nOctant;
+ else
+ {
+ nTile += 8 - nOctant;
+ nAngle = kAngle180;
+ nFlags |= kRotateYFlip;
+ }
+
+ break;
+
+ case kSpriteView8Full:
+ nTile += nOctant;
+ break;
+
+ case kSpriteView5Half:
+ break;
+ }
+ rotatesprite(origin.x << 16, origin.y << 16, 0x10000, 0, (short)nTile, nShade, kPLUNormal,
+ nFlags, 0, 0, xdim - 1, ydim - 1);
+ return nTile;
+}
+
+
+int EditTileLoop( int nTile )
+{
+ BYTE key, shift, ctrl;
+ int nbuttons;
+ static int obuttons = 0;
+ char buffer[256];
+ int nTileView = 0;
+ BOOL playing = FALSE;
+ BOOL localModified = FALSE;
+ int xcenter, ycenter;
+ BOOL localNamesModified = FALSE;
+
+ memcpy(oldpicanm, picanm, sizeof(oldpicanm));
+ memcpy(oldNames, tileNames, sizeof(oldNames));
+
+ while (1)
+ {
+ gFrameTicks = gGameClock - gFrameClock;
+ gFrameClock += gFrameTicks;
+ UpdateBlinkClock(gFrameTicks);
+
+ clearview(0);
+
+ // turn off animation playing if the tile doesn't support it
+ if (picanm[nTile].type == 0)
+ playing = FALSE;
+
+ if (playing)
+ {
+ rotatesprite(origin.x << 16, origin.y << 16, 0x10000, 0,
+ (short)(nTile + animateoffs((short)nTile, 0)), 0, kPLUNormal,
+ kRotateNormal, 0, 0, xdim - 1, ydim - 1);
+ nTileView = nTile;
+ }
+ else
+ nTileView = DrawTile(nTile);
+
+ if (tileNames[nTile][0] != '\0')
+ {
+ sprintf(buffer, "Name: %s", tileNames[nTile]);
+ printext256(0, 0, gStdColor[7], -1, buffer, 0);
+ }
+
+ sprintf(buffer, "Tile: %4i Surf: %s", nTile, surfNames[surfType[nTile]]);
+ printext256(0, ydim - 20, gStdColor[7], -1, buffer, 0);
+
+ sprintf(buffer, "%s:%2i [%2i] VIEW: %s", animNames[picanm[nTile].type],
+ picanm[nTile].frames, picanm[nTile].speed, viewNames[picanm[nTile].view]);
+ printext256(0, ydim - 10, gStdColor[15], -1, buffer, 0);
+
+ DrawOrigin(gStdColor[keystatus[KEY_O] ? 15 : 8]);
+
+ sprintf(buffer,"ANG: %3i", nOctant * 45);
+ printext256(xdim - 80, ydim - 20, gStdColor[7], -1, buffer, 0);
+
+ scrDisplayMessage(gStdColor[kColorWhite]);
+
+ WaitVSync();
+ scrNextPage();
+
+ xcenter = picanm[nTileView].xcenter;
+ ycenter = picanm[nTileView].ycenter;
+
+ key = keyGet();
+ shift = keystatus[KEY_LSHIFT] | keystatus[KEY_RSHIFT];
+ ctrl = keystatus[KEY_LCTRL] | keystatus[KEY_RCTRL];
+
+ switch (key)
+ {
+ case KEY_ESC:
+ if ( !localModified && !localNamesModified )
+ return nTile;
+
+ switch ( KeepTileChanges() )
+ {
+ case mrOk:
+ case mrYes:
+ if (localModified)
+ tilesModified = TRUE;
+ if (localNamesModified)
+ namesModified = TRUE;
+ return nTile;
+
+ case mrNo:
+ if (localModified)
+ memcpy(picanm, oldpicanm, sizeof(oldpicanm));
+ if (localNamesModified)
+ memcpy(tileNames, oldNames, sizeof(tileNames));
+ return nTile;
+ }
+ break;
+
+ case KEY_SPACE:
+ playing = !playing;
+ break;
+
+ case KEY_COMMA: // "<"
+ nOctant = DecRotate(nOctant, 8);
+ break;
+
+ case KEY_PERIOD: // ">"
+ nOctant = IncRotate(nOctant, 8);
+ break;
+
+ case KEY_SLASH:
+ nOctant = 0;
+ break;
+
+ case KEY_A:
+ picanm[nTile].type++;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ break;
+
+ case KEY_G:
+ nTile = GetNumberBox("Goto tile", 0, nTile);
+ break;
+
+ case KEY_N:
+ {
+ char buffer[80];
+ strcpy(buffer, tileNames[nTile]);
+ if (GetStringBox( " Tile Name (max. 16 letters) ", buffer) != 0) // check for cancelled dialog box
+ {
+ strncpy(tileNames[nTile], buffer, 16);
+ tileNames[nTile][16] = '\0';
+ localNamesModified = TRUE;
+ }
+ break;
+ }
+
+ case KEY_P:
+ {
+ int c = 0;
+ for ( int i = 0; i < 16; i++)
+ {
+ for (int j = 0; j < 16; j++)
+ {
+ Video.SetColor(c++);
+ gfxFillBox(i * 8, j * 8, i * 8 + 8, j * 8 + 8);
+ }
+ }
+ scrNextPage();
+ while ( keyGet() == 0 );
+ break;
+ }
+
+ case KEY_W:
+ picanm[nTile].view = IncRotate(picanm[nTile].view, LENGTH(viewNames));
+ tileMarkDirty(nTile);
+ localModified = TRUE;
+ break;
+
+ case KEY_PADPLUS:
+ if (shift)
+ {
+ if (picanm[nTile].speed > 0)
+ {
+ picanm[nTile].speed--;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ }
+ else
+ {
+ if (picanm[nTile].frames < 63)
+ {
+ picanm[nTile].frames++;
+ if (picanm[nTile].type == 0)
+ picanm[nTile].type = 2; // forward
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ }
+ break;
+
+ case KEY_PADMINUS:
+ if (shift)
+ {
+ if (picanm[nTile].speed < 15)
+ {
+ picanm[nTile].speed++;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ }
+ else
+ {
+ if (picanm[nTile].frames > 0)
+ {
+ picanm[nTile].frames--;
+ if (picanm[nTile].frames == 0)
+ picanm[nTile].type = 0;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ }
+ break;
+
+ case KEY_PAD0:
+ break;
+
+ case KEY_PAGEUP:
+ while (nTile > 0)
+ {
+ nTile--;
+ if (tilesizx[nTile] > 0 && tilesizy[nTile] > 0)
+ break;
+ }
+ break;
+
+ case KEY_PAGEDN:
+ while (nTile < kMaxTiles - 1)
+ {
+ nTile++;
+ if (tilesizx[nTile] > 0 && tilesizy[nTile] > 0)
+ break;
+ }
+ break;
+
+ case KEY_UP:
+ case KEY_PADUP:
+ if ( keystatus[KEY_O] )
+ {
+ SetOrigin(origin.x, origin.y - 1);
+ }
+ else if (ctrl)
+ {
+ picanm[nTileView].ycenter = -tilesizy[nTileView] / 2;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ else
+ {
+ picanm[nTileView].ycenter++;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ break;
+
+ case KEY_DOWN:
+ case KEY_PADDOWN:
+ if ( keystatus[KEY_O] )
+ {
+ SetOrigin(origin.x, origin.y + 1);
+ }
+ else if (ctrl)
+ {
+ picanm[nTileView].ycenter = tilesizy[nTileView] - tilesizy[nTileView] / 2;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ else
+ {
+ picanm[nTileView].ycenter--;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ break;
+
+ case KEY_PAD5:
+ if (ctrl)
+ {
+ picanm[nTileView].xcenter = 0;
+ picanm[nTileView].ycenter = 0;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ break;
+
+ case KEY_LEFT:
+ case KEY_PADLEFT:
+ if ( keystatus[KEY_O] )
+ {
+ SetOrigin(origin.x - 1, origin.y);
+ }
+ else if (ctrl)
+ {
+ picanm[nTileView].xcenter = -tilesizx[nTileView] / 2;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ else
+ {
+ picanm[nTileView].xcenter++;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ break;
+
+ case KEY_RIGHT:
+ case KEY_PADRIGHT:
+ if ( keystatus[KEY_O] )
+ {
+ SetOrigin(origin.x + 1, origin.y);
+ }
+ else if (ctrl)
+ {
+ picanm[nTileView].xcenter = tilesizx[nTileView] - tilesizx[nTileView] / 2;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ else
+ {
+ picanm[nTileView].xcenter--;
+ tileMarkDirty(nTileView);
+ localModified = TRUE;
+ }
+ break;
+ }
+
+ Mouse::Read(gFrameTicks);
+
+ // which buttons just got pressed
+ nbuttons = (short)(~obuttons & Mouse::buttons);
+ obuttons = Mouse::buttons;
+
+ if ( Mouse::buttons & 1 )
+ {
+ if ( keystatus[KEY_O] )
+ SetOrigin(origin.x + Mouse::dX2, origin.y + Mouse::dY2);
+ }
+
+ if ( nTileView >= nSelectBegin && nTileView <= nSelectEnd )
+ {
+ for (int i = nSelectBegin; i <= nSelectEnd; i++)
+ {
+ if ( i != nTileView )
+ {
+ picanm[i].xcenter += picanm[nTileView].xcenter - xcenter;
+ picanm[i].ycenter += picanm[nTileView].ycenter - ycenter;
+ tileMarkDirty(i);
+ }
+ }
+ }
+ }
+}
+
+
+void DrawTileScreen( long nStart, long nCursor, int size, int nMax )
+{
+ int n, i, j, k, nTile, uSize, vSize;
+ long duv;
+ int x, y;
+ long u;
+ uchar *pTile, *pScreen;
+ int height, width;
+ int nCols = xdim / size;
+ int nRows = ydim / size;
+ char buffer[256];
+
+ setupvlineasm(16);
+ Video.SetColor(gStdColor[0]);
+
+ clearview(0);
+
+ n = 0;
+
+ y = 0;
+ for ( i = 0; i < nRows; i++, y += size )
+ {
+ x = 0;
+ for ( j = 0; j < nCols; j++, n++, x += size )
+ {
+ if (nStart + n >= nMax)
+ break;
+
+ nTile = tileIndex[nStart + n];
+ if ( tilesizx[nTile] > 0 && tilesizy[nTile] > 0 )
+ {
+ palookupoffse[0] = palookupoffse[1] = palookupoffse[2] = palookupoffse[3] = palookup[kPLUNormal];
+
+ if ( nTile >= nSelectBegin && nTile <= nSelectEnd )
+ palookupoffse[0] = palookupoffse[1] = palookupoffse[2] = palookupoffse[3] = inverseCLU;
+
+ pTile = tileLoadTile(nTile);
+ uSize = tilesizx[nTile];
+ vSize = tilesizy[nTile];
+
+ pScreen = frameplace + ylookup[y] + x;
+
+ // draw actual size
+ if ( uSize <= size && vSize <= size )
+ {
+ vince[0] = vince[1] = vince[2] = vince[3] = 0x00010000;
+ bufplce[3] = pTile - vSize;
+ for (u = 0; u + 3 < uSize; u += 4)
+ {
+ bufplce[0] = bufplce[3] + vSize;
+ bufplce[1] = bufplce[0] + vSize;
+ bufplce[2] = bufplce[1] + vSize;
+ bufplce[3] = bufplce[2] + vSize;
+ vplce[0] = vplce[1] = vplce[2] = vplce[3] = 0;
+ vlineasm4(vSize, pScreen);
+ pTile += 4 * vSize;
+ pScreen += 4;
+ }
+ for (; u < uSize; u++)
+ {
+ vlineasm1(0x00010000, palookupoffse[0], vSize - 1, 0, pTile, pScreen);
+ pTile += vSize;
+ pScreen++;
+ }
+ }
+ else
+ {
+ if (uSize > vSize) // wider
+ {
+ duv = (uSize << 16) / size;
+ height = size * vSize / uSize;
+ width = size;
+ }
+ else // taller
+ {
+ duv = (vSize << 16) / size;
+ height = size;
+ width = size * uSize / vSize;
+ }
+
+ // don't draw any that are too small to see
+ if (height == 0)
+ continue;
+
+ vince[0] = vince[1] = vince[2] = vince[3] = duv;
+ u = 0;
+ for (k = 0; k + 3 < width; k += 4)
+ {
+ bufplce[0] = waloff[nTile] + vSize * (u >> 16);
+ u += duv;
+ bufplce[1] = waloff[nTile] + vSize * (u >> 16);
+ u += duv;
+ bufplce[2] = waloff[nTile] + vSize * (u >> 16);
+ u += duv;
+ bufplce[3] = waloff[nTile] + vSize * (u >> 16);
+ u += duv;
+ vplce[0] = vplce[1] = vplce[2] = vplce[3] = 0;
+ vlineasm4(height, pScreen);
+ pTile += 4 * vSize;
+ pScreen += 4;
+ }
+ for (; k < width; k++)
+ {
+ pTile = waloff[nTile] + vSize * (u >> 16);
+ vlineasm1(duv, palookupoffse[0], height - 1, 0, pTile, pScreen);
+ pScreen++;
+ u += duv;
+ }
+ }
+
+ // overlay the surface type
+ if ( !keystatus[KEY_CAPSLOCK] )
+ {
+ sprintf(buffer, "%s", surfNames[surfType[nTile]]);
+ Video.FillBox(0, x, y + size - 7, x + strlen(buffer) * 4 + 1, y + size);
+ printext256(x + 1, y + size - 7, gStdColor[surfType[nTile] == kSurfNone ? 4 : 11], -1, buffer, 1);
+ }
+ }
+
+ // overlay the tile number
+ if ( !keystatus[KEY_CAPSLOCK] )
+ {
+ sprintf(buffer, "%d", nTile);
+ Video.FillBox(0, x, y, x + strlen(buffer) * 4 + 1, y + 7);
+ printext256(x + 1, y, gStdColor[14], -1, buffer, 1);
+ }
+ }
+ }
+
+ if ( IsBlinkOn() )
+ {
+ k = nCursor - nStart; //draw open white box
+ x = (k % nCols) * size;
+ y = (k / nCols) * size;
+
+ Video.SetColor(gStdColor[15]);
+ Video.HLine(0, y, x, x + size - 1);
+ Video.HLine(0, y + size - 1, x, x + size - 1);
+ Video.VLine(0, x, y, y + size - 1);
+ Video.VLine(0, x + size - 1, y, y + size - 1);
+ }
+}
+
+
+
+void MainEditLoop( void )
+{
+ static int nZoom = 3;
+ int nStart;
+ BYTE key, shift, alt;
+ int size = pickSize[nZoom];
+ int nCols = xdim / size;
+ int nRows = ydim / size;
+ BOOL selecting = FALSE;
+
+ for (int i = 0; i < kMaxTiles; i++)
+ tileIndex[i] = (short)i;
+ nCursor = 0;
+
+ nStart = ClipLow((nCursor - nRows * nCols + nCols) / nCols * nCols, 0);
+
+ while (1)
+ {
+ DrawTileScreen(nStart, nCursor, pickSize[nZoom], kMaxTiles );
+ scrDisplayMessage(gStdColor[15]);
+
+ scrNextPage();
+
+ if (vidoption != 1)
+ WaitVSync();
+
+ gFrameTicks = gGameClock - gFrameClock;
+ gFrameClock += gFrameTicks;
+ UpdateBlinkClock(gFrameTicks);
+
+ key = keyGet();
+ shift = keystatus[KEY_LSHIFT] | keystatus[KEY_RSHIFT];
+ alt = keystatus[KEY_LALT] | keystatus[KEY_RALT];
+
+ switch (key)
+ {
+ case KEY_LSHIFT:
+ case KEY_RSHIFT:
+ nSelect1 = nCursor;
+ selecting = TRUE;
+ break;
+
+ case KEY_PADSTAR:
+ if (nZoom > 0)
+ {
+ nZoom--;
+ size = pickSize[nZoom];
+ nCols = xdim / size;
+ nRows = ydim / size;
+ nStart = ClipLow((nCursor - nRows * nCols + nCols) / nCols * nCols, 0);
+ }
+ break;
+
+ case KEY_PADSLASH:
+ if (nZoom + 1 < LENGTH(pickSize))
+ {
+ nZoom++;
+ size = pickSize[nZoom];
+ nCols = xdim / size;
+ nRows = ydim / size;
+ nStart = ClipLow((nCursor - nRows * nCols + nCols) / nCols * nCols, 0);
+ }
+ break;
+
+ case KEY_BACKSPACE:
+ if (alt)
+ Undo();
+ break;
+
+ case KEY_ENTER:
+ if (alt)
+ Redo();
+ else
+ {
+ if ( nSelectEnd >= nSelectBegin )
+ {
+ switch ( EditSelectedTiles() )
+ {
+ case mrOk:
+ case mrYes:
+ nCursor = EditTileLoop(nCursor);
+ break;
+
+ case mrNo:
+ nSelectEnd = nSelectBegin - 1;
+ nCursor = EditTileLoop(nCursor);
+ break;
+ }
+ }
+ else
+ nCursor = EditTileLoop(nCursor);
+ }
+ break;
+
+ case KEY_S:
+ {
+ surfType[nCursor] = (BYTE)IncRotate(surfType[nCursor], kSurfMax);
+ tilesModified = TRUE;
+
+ if ( nCursor >= nSelectBegin && nCursor <= nSelectEnd )
+ {
+ for (int i = nSelectBegin; i <= nSelectEnd; i++)
+ {
+ surfType[i] = surfType[nCursor];
+ }
+ }
+ break;
+ }
+
+ case KEY_LEFT:
+ if (nCursor - 1 >= 0)
+ nCursor--;
+ SetBlinkOn();
+ break;
+
+ case KEY_RIGHT:
+ if (nCursor + 1 < kMaxTiles)
+ nCursor++;
+ SetBlinkOn();
+ break;
+
+ case KEY_UP:
+ if (nCursor - nCols >= 0)
+ {
+ nCursor -= nCols;
+ }
+ SetBlinkOn();
+ break;
+
+ case KEY_DOWN:
+ if (nCursor + nCols < kMaxTiles)
+ {
+ nCursor += nCols;
+ }
+ SetBlinkOn();
+ break;
+
+ case KEY_PAGEUP:
+ if (nCursor - nRows * nCols >= 0)
+ {
+ nCursor -= nRows * nCols;
+ nStart -= nRows * nCols;
+ if (nStart < 0) nStart = 0;
+ }
+ SetBlinkOn();
+ break;
+
+ case KEY_PAGEDN:
+ if (nCursor + nRows * nCols < kMaxTiles)
+ {
+ nCursor += nRows * nCols;
+ nStart += nRows * nCols;
+ }
+ SetBlinkOn();
+ break;
+
+ case KEY_HOME:
+ nCursor = 0;
+ SetBlinkOn();
+ break;
+
+ case KEY_END:
+ nCursor = kMaxTiles - 1;
+ SetBlinkOn();
+ break;
+
+ case KEY_G:
+ nCursor = (short)GetNumberBox("Goto tile", 0, tileIndex[nCursor]);
+ break;
+
+ case KEY_ESC:
+ keystatus[KEY_ESC] = 0;
+ return;
+
+ case KEY_INSERT:
+ if ( nSelectBegin <= nSelectEnd )
+ {
+ MoveTiles(nCursor, nSelectBegin, nSelectEnd - nSelectBegin + 1);
+ nSelect1 = nSelect2 = nSelectBegin = nSelectEnd = -1;
+ }
+ break;
+ }
+
+ if (selecting)
+ {
+ nSelect2 = nCursor;
+ if (nSelect1 < nSelect2)
+ {
+ nSelectBegin = nSelect1;
+ nSelectEnd = nSelect2 - 1;
+ }
+ else
+ {
+ nSelectBegin = nSelect2;
+ nSelectEnd = nSelect1 - 1;
+ }
+ }
+
+ if ( !shift )
+ selecting = FALSE;
+
+ while (nCursor < nStart)
+ nStart -= nCols;
+
+ while (nStart + nRows * nCols <= nCursor)
+ nStart += nCols;
+
+ if (key != 0)
+ keyFlushStream();
+ }
+}
+
+
+/***********************************************************************
+ * ShowUsage
+ *
+ * Display command-line parameter usage, then exit.
+ **********************************************************************/
+void ShowUsage(void)
+{
+ printf("Syntax: ARTEDIT [options]\n");
+ printf("-vN force art version to N\n");
+ printf("-? This help\n");
+ exit(0);
+}
+
+
+void QuitMessage(char * fmt, ...)
+{
+ char msg[80];
+ va_list argptr;
+ va_start( argptr, fmt );
+ vsprintf( msg, fmt, argptr );
+ va_end(argptr);
+ printf(msg);
+ exit(1);
+}
+
+
+/***********************************************************************
+ * Process command line arguments
+ **********************************************************************/
+void ParseOptions( void )
+{
+ enum { kSwitchHelp, kSwitchVersion };
+ static SWITCH switches[] = {
+ { "?", kSwitchHelp, FALSE },
+ { "V", kSwitchVersion, TRUE },
+ { NULL, 0, FALSE },
+ };
+ int r;
+ while ( (r = GetOptions(switches)) != GO_EOF )
+ {
+ switch (r) {
+ case GO_INVALID:
+ case GO_FULL:
+ QuitMessage("Invalid argument: %s", OptArgument);
+ break;
+
+ case kSwitchVersion:
+ nVersion = strtol(OptArgument, NULL, 0);
+ bForceVersion = TRUE;
+ break;
+
+ case kSwitchHelp:
+ ShowUsage();
+ break;
+ }
+ }
+}
+
+
+void main( void )
+{
+ char title[256];
+
+ FillNameArray(surfNames, surfNTNames, LENGTH(surfNTNames));
+
+ ParseOptions();
+
+ gOldDisplayMode = getvmode();
+
+ sprintf(title, "Art Editor [%s] -- DO NOT DISTRIBUTE", gBuildDate);
+ tioInit();
+ tioCenterString(0, 0, tioScreenCols - 1, title, kAttrTitle);
+ tioCenterString(tioScreenRows - 1, 0, tioScreenCols - 1,
+ "Copyright (c) 1994, 1995 Q Studios Corporation", kAttrTitle);
+
+ tioWindow(1, 0, tioScreenRows - 2, tioScreenCols);
+
+ if ( _grow_handles(kRequiredFiles) < kRequiredFiles )
+ ThrowError("Not enough file handles available", ES_ERROR);
+
+ gGamma = BloodINI.GetKeyInt("View", "Gamma", 0);
+
+ tioPrint("Initializing heap and resource system");
+ Resource::heap = new QHeap(dpmiDetermineMaxRealAlloc());
+
+ tioPrint("Initializing resource archive");
+ gSysRes.Init("BLOOD.RFF", "*.*");
+ gGuiRes.Init("GUI.RFF", NULL);
+
+ tioPrint("Loading tiles");
+ if (tileInit() == 0)
+ ThrowError("ART files not found", ES_ERROR);
+
+ if ( bForceVersion )
+ {
+ artversion = nVersion;
+ tioPrint("Updating art version number");
+ tileMarkDirtyAll();
+ tileSaveArtInfo();
+ exit(0);
+ }
+
+ LoadTileNames(); //load constants
+
+ tioPrint("Initializing mouse");
+ if ( !initmouse() )
+ tioPrint("Mouse not detected");
+
+ // install our error handler
+ prevErrorHandler = errSetHandler(EditorErrorHandler);
+
+ InitEngine();
+
+ Mouse::SetRange(xdim, ydim);
+
+ tioPrint("Initializing screen");
+ scrInit();
+
+ tioPrint("Installing keyboard handler");
+ keyInstall();
+
+ scrCreateStdColors();
+
+ RESHANDLE hInverse = gSysRes.Lookup("INVERSE", ".CLU");
+ if ( !hInverse )
+ ThrowError("Missing INVERSE.CLU resource", ES_ERROR);
+
+ inverseCLU = (BYTE *)gSysRes.Lock(hInverse);
+
+ tioPrint("Installing timer");
+ timerRegisterClient(ClockStrobe, kTimerRate);
+ timerInstall();
+
+ tioPrint("Engaging graphics subsystem...");
+
+ scrSetGameMode();
+
+ scrSetGamma(gGamma);
+ scrSetDac(0);
+
+ SetOrigin(xdim / 2, ydim / 2);
+
+ do {
+ MainEditLoop();
+ } while ((tilesModified || namesModified) && !AcknowledgeTileChange());
+
+ setvmode(gOldDisplayMode);
+
+ timerRemove();
+
+ uninitengine();
+
+ errSetHandler(prevErrorHandler);
+}
diff --git a/SRC/ASSTIMER.CPP b/SRC/ASSTIMER.CPP
new file mode 100644
index 0000000..8f1862e
--- /dev/null
+++ b/SRC/ASSTIMER.CPP
@@ -0,0 +1,112 @@
+#include <stdlib.h>
+#include <dos.h>
+#include <conio.h>
+
+#include "timer.h"
+#include "globals.h"
+#include "typedefs.h"
+#include "misc.h"
+#include "task_man.h"
+#include "error.h"
+
+#define kMaxClients 16
+
+struct CLIENT_INFO
+{
+ TIMER_CLIENT function;
+ TASK *task; // TASK_MAN timer link
+};
+
+static CLIENT_INFO client[kMaxClients];
+static int nClients = 0;
+static BOOL timerActive = FALSE;
+
+typedef void (* TASK_CLIENT)( TASK * );
+
+
+void timerRemove( void )
+{
+ if ( timerActive )
+ {
+ for (int i = 0; i < nClients; i++)
+ {
+ TS_Terminate(client[i].task);
+ }
+
+ TS_Shutdown();
+
+ nClients = 0;
+
+ dpmiUnlockMemory(FP_OFF(client), sizeof(client));
+ dpmiUnlockMemory(FP_OFF(&nClients), sizeof(nClients));
+
+ timerActive = FALSE;
+ }
+}
+
+
+void timerInstall( void )
+{
+ if ( !timerActive )
+ {
+ timerActive = TRUE;
+
+ dpmiLockMemory(FP_OFF(client), sizeof(client));
+ dpmiLockMemory(FP_OFF(&nClients), sizeof(nClients));
+
+ TS_Dispatch();
+
+ atexit(timerRemove);
+ }
+}
+
+
+void timerSetRate( int /* rate */ )
+{
+ // this function not implemented for ASS interface
+ ThrowError("Call to unimplemented glue function", ES_ERROR);
+}
+
+
+void timerRegisterClient( TIMER_CLIENT timerClient, int rate )
+{
+ if ( nClients < kMaxClients )
+ {
+ client[nClients].function = timerClient;
+ client[nClients].task = TS_ScheduleTask((TASK_CLIENT)timerClient, rate, 1, NULL);
+ nClients++;
+ }
+}
+
+
+void timerRemoveClient( TIMER_CLIENT timerClient )
+{
+ for (int i = 0; i < nClients; i++)
+ {
+ if ( client[i].function == timerClient )
+ break;
+ }
+
+ if ( i < nClients )
+ {
+ _disable();
+ nClients--;
+ client[i] = client[nClients];
+ _enable();
+ }
+}
+
+
+void timerSetClientRate( TIMER_CLIENT timerClient, int rate )
+{
+ for (int i = 0; i < nClients; i++)
+ {
+ if ( client[i].function == timerClient )
+ break;
+ }
+
+ if ( i < nClients )
+ {
+ TS_SetTaskRate(client[i].task, rate);
+ }
+}
diff --git a/SRC/BLOOD.CPP b/SRC/BLOOD.CPP
new file mode 100644
index 0000000..95ed4ca
--- /dev/null
+++ b/SRC/BLOOD.CPP
@@ -0,0 +1,1450 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <io.h>
+#include <fcntl.h>
+#include <malloc.h>
+
+#include "engine.h"
+#include "multi.h"
+
+#include "typedefs.h"
+#include "misc.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "names.h"
+#include "key.h"
+#include "timer.h"
+#include "textio.h"
+#include "inifile.h"
+#include "screen.h"
+#include "view.h"
+#include "db.h"
+#include "debug4g.h"
+#include "error.h"
+#include "fire.h"
+#include "sectorfx.h"
+#include "helix.h"
+#include "getopt.h"
+#include "levels.h"
+#include "sound.h"
+#include "controls.h"
+#include "actor.h"
+#include "player.h"
+#include "resource.h"
+#include "protect.h"
+#include "triggers.h"
+#include "weapon.h"
+#include "options.h"
+#include "tile.h"
+#include "gui.h"
+#include "seq.h"
+#include "trig.h"
+#include "mirrors.h"
+#include "warp.h"
+#include "credits.h"
+#include "sfx.h"
+
+#include <memcheck.h>
+
+#define kAttrTitle (kColorRed * 16 + kColorYellow)
+
+EHF prevErrorHandler;
+static BOOL gForceMap = FALSE;
+
+short gPacketIndex = 0;
+BOOL ready2send = FALSE;
+
+static char playerreadyflag[kMaxPlayers];
+#define kNetFifoSize 256
+INPUT gNetInput[kMaxPlayers]; // input received from net players
+INPUT gFifoInput[kNetFifoSize][kMaxPlayers];
+int gNetFifoClock;
+int gNetFifoHead, gNetFifoTail;
+
+ulong gFifoSendCRC[kNetFifoSize], gFifoReceiveCRC[kNetFifoSize];
+int gSendCRCHead, gReceiveCRCHead, gCRCTail;
+BOOL bOutOfSync = FALSE;
+
+
+/***********************************************************************
+ * Function prototypes
+ **********************************************************************/
+enum {
+// kTweakDecel = 0,
+// kTweakSideDecel,
+ kTweakFrontAccel,
+ kTweakSideAccel,
+ kTweakBackAccel,
+ kTweakFrontSpeed1,
+ kTweakFrontSpeed2,
+ kTweakSideSpeed1,
+ kTweakSideSpeed2,
+ kTweakBackSpeed1,
+ kTweakBackSpeed2,
+ kTweakPace1,
+ kTweakPace2,
+ kMaxTweaks
+};
+
+struct TWEAKS
+{
+ int x;
+ int y;
+ int *pData;
+ int nDefault;
+} tweakSet[ kMaxTweaks ] =
+{
+// {2, 2, &gPosture[0].decel, 0},
+// {2, 20, &gPosture[0].sideDecel, 0},
+ {2, 38, &gPosture[0].frontAccel, 0},
+ {2, 56, &gPosture[0].sideAccel, 0},
+ {2, 74, &gPosture[0].backAccel, 0},
+ {2, 92, &gPosture[0].frontSpeed[0], 0},
+ {2, 110, &gPosture[0].frontSpeed[1], 0},
+ {2, 128, &gPosture[0].sideSpeed[0], 0},
+ {2, 146, &gPosture[0].sideSpeed[1], 0},
+ {2, 164, &gPosture[0].backSpeed[0], 0},
+ {90, 2, &gPosture[0].backSpeed[1], 0},
+ {90, 20, &gPosture[0].pace[0], 0},
+ {90, 38, &gPosture[0].pace[1], 0},
+};
+
+void TweakProfile( char *title )
+{
+ int i;
+ EditNumber *enTweaks[kMaxTweaks];
+
+ // create the dialog
+ Window dialog(0, 0, 319, 199, title);
+
+ // these will automatically be destroyed when the dialog is destroyed
+ for (i = 0; i < kMaxTweaks; i++)
+ {
+ tweakSet[i].nDefault = *tweakSet[i].pData; // initialize the defaults
+ enTweaks[i] = new EditNumber(tweakSet[i].x, tweakSet[i].y, 80, 16, tweakSet[i].nDefault);
+ dialog.Insert(enTweaks[i]);
+ }
+
+ ShowModal(&dialog);
+
+ for (i = 0; i < kMaxTweaks; i++)
+ {
+ if ( dialog.endState != mrOk)
+ *tweakSet[i].pData = tweakSet[i].nDefault; // restore the defaults
+ else
+ *tweakSet[i].pData = enTweaks[i]->value;
+ }
+}
+
+
+void PreloadCache( void )
+{
+ long i;
+
+ dprintf("Preload floor and ceiling tiles\n");
+ for (i = 0; i < numsectors; i++)
+ {
+ tilePreloadTile(sector[i].floorpicnum);
+ tilePreloadTile(sector[i].ceilingpicnum);
+ if ( sector[i].ceilingstat & kSectorParallax )
+ {
+ for (int j = 1; j < gSkyCount; j++)
+ tilePreloadTile(sector[i].ceilingpicnum + j);
+ }
+ }
+
+ dprintf("Preload wall tiles\n");
+ for (i = 0; i < numwalls; i++)
+ {
+ tilePreloadTile(wall[i].picnum);
+ if (wall[i].overpicnum >= 0)
+ tilePreloadTile(wall[i].overpicnum);
+ }
+
+ dprintf("Preload sprite tiles\n");
+ for (i = 0; i < kMaxSprites; i++)
+ {
+ if (sprite[i].statnum < kMaxStatus)
+ tilePreloadTile(sprite[i].picnum);
+ }
+}
+
+
+void prepareboard( void )
+{
+ if ( !gForceMap )
+ levelGetName(gEpisodeId, gMapId);
+
+ dbLoadMap(gLevelName, &startposx, &startposy, &startposz, &startang, &startsectnum);
+ srand(gMapCRC);
+
+ levelLoadDef();
+
+ scrLoadPLUs();
+
+ for ( int i = 0; i < kMaxPlayers; i++ )
+ {
+ gStartZone[i].x = startposx;
+ gStartZone[i].y = startposy;
+ gStartZone[i].z = sector[startsectnum].floorz;
+ gStartZone[i].sector = startsectnum;
+ gStartZone[i].angle = startang;
+ }
+
+ // make sure the player starts out on the floor
+ startposz = sector[startsectnum].floorz;
+
+ seqKillAll(); // clear the current sequence list
+
+ InitSectorFX();
+ InitPlayerStartZones();
+
+ actInit();
+
+ // initialize all the tag buckets
+ dprintf("evInit()\n");
+ evInit();
+
+ gFrameClock = gGameClock = 0;
+
+ // initialize external trigger masks for sprites, sectors, and walls
+ dprintf("trInit()\n");
+ trInit();
+
+ PreloadCache();
+
+ //Map starts out blank; you map out what you see in 3D mode
+ automapping = 1;
+
+ for (i = 0; i < numplayers; i++)
+ playerInit( i );
+
+ InitMirrors();
+
+ // start the music for the level
+ sndPlaySong(gLevelSong, TRUE);
+
+ gNetFifoClock = gFrameClock = gGameClock = 0;
+ gNetFifoHead = gNetFifoTail = 0;
+ gSendCRCHead = gReceiveCRCHead = gCRCTail = 0;
+}
+
+
+BYTE cheatAmmo[] = { KEY_Q, KEY_S, KEY_A, KEY_M, KEY_M, KEY_O };
+BYTE cheatRate[] = { KEY_Q, KEY_S, KEY_R, KEY_A, KEY_T, KEY_E };
+BYTE cheatGoto[] = { KEY_Q, KEY_S, KEY_G, KEY_O, KEY_T, KEY_O };
+BYTE cheatJump[] = { KEY_Q, KEY_S, KEY_J, KEY_U, KEY_M, KEY_P };
+BYTE cheatClip[] = { KEY_Q, KEY_S, KEY_C, KEY_L, KEY_I, KEY_P };
+BYTE cheatKeys[] = { KEY_Q, KEY_S, KEY_K, KEY_E, KEY_Y, KEY_S };
+BYTE cheatMaps[] = { KEY_Q, KEY_S, KEY_M, KEY_A, KEY_P, KEY_S };
+BYTE cheatHurt[] = { KEY_Q, KEY_S, KEY_O, KEY_U, KEY_C, KEY_H };
+BYTE cheatExplode[] = { KEY_Q, KEY_S, KEY_B, KEY_O, KEY_O, KEY_M };
+BYTE cheatBurn[] = { KEY_Q, KEY_S, KEY_B, KEY_U, KEY_R, KEY_N };
+BYTE cheatHigh[] = { KEY_Q, KEY_S, KEY_H, KEY_I, KEY_G, KEY_H };
+BYTE cheatInviso[] = { KEY_Q, KEY_S, KEY_O, KEY_N, KEY_E, KEY_R, KEY_I, KEY_N, KEY_G };
+BYTE cheatBeast[] = { KEY_Q, KEY_S, KEY_L, KEY_U, KEY_S, KEY_T };
+BYTE cheatHealth[] = { KEY_Q, KEY_S, KEY_L, KEY_A, KEY_M, KEY_E };
+BYTE cheatSeer[] = { KEY_Q, KEY_S, KEY_I, KEY_C, KEY_U };
+BYTE cheatFrag[] = { KEY_Q, KEY_S, KEY_F, KEY_R, KEY_A, KEY_G };
+BYTE cheatGod[] = { KEY_Q, KEY_S, KEY_G, KEY_O, KEY_D };
+BYTE cheatSight[] = { KEY_Q, KEY_S, KEY_O, KEY_S, KEY_C , KEY_A , KEY_R };
+BYTE cheatAim[] = { KEY_Q, KEY_S, KEY_A, KEY_I, KEY_M };
+
+void LocalKeys( void )
+{
+ char buffer[80];
+ BYTE key;
+ BYTE alt = keystatus[KEY_LALT] | keystatus[KEY_RALT];
+
+ while ( (key = keyGet()) != 0 )
+ {
+ switch( key )
+ {
+ case KEY_F1: // help/info screens
+ TweakProfile( "Player Profile" );
+ break;
+ case KEY_F2: // save game
+ break;
+ case KEY_F3: // load game
+ break;
+ case KEY_F4: // volume
+ break;
+ case KEY_F5: // detail
+ break;
+ case KEY_F6: // quicksave
+ break;
+ case KEY_F7: // end game
+ break;
+ case KEY_F8: // messages
+ break;
+ case KEY_F10: // quit game
+ break;
+
+ case KEY_F11: // gamma
+ if (gGamma == gGammaLevels - 1)
+ gGamma = 0;
+ else
+ gGamma = ClipHigh(gGamma + 1, gGammaLevels - 1);
+ BloodINI.PutKeyInt("View", "Gamma", gGamma);
+ scrSetGamma(gGamma);
+ sprintf(buffer, "Gamma correction level %i", gGamma);
+ viewSetMessage(buffer);
+ break;
+
+ case KEY_F12:
+ // setup gViewIndex for different play and power-up modes
+ if ( gNetGame && gNetPlayers > 1 )
+ {
+ if ( powerupCheck(gMe, kItemCrystalBall - kItemBase) )
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ }
+ else
+ {
+ if (gNetMode == kNetModeTeams)
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ while ( gPlayer[gViewIndex].teamID != gMe->teamID )
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ }
+ gView = &gPlayer[gViewIndex];
+ }
+ }
+ }
+ else
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ gView = &gPlayer[gViewIndex];
+ }
+ break;
+
+ case KEY_MINUS:
+ if ( keystatus[KEY_G] )
+ {
+ gGamma = ClipLow(gGamma - 1, 0);
+ BloodINI.PutKeyInt("View", "Gamma", gGamma);
+ scrSetGamma(gGamma);
+ sprintf(buffer, "Gamma correction level %i", gGamma);
+ viewSetMessage(buffer);
+ }
+ else if ( keystatus[KEY_D] )
+ {
+ visibility = ClipHigh(visibility + 16, 4096);
+ sprintf(buffer, "Depth cueing level %i", visibility);
+ viewSetMessage(buffer);
+ }
+ else if ( keystatus[KEY_S] )
+ {
+ gSpring = ClipLow(gSpring - 1, 1);
+ BloodINI.PutKeyInt("Player", "SpringCoefficient", gSpring);
+ gSpringPhaseInc = (int)(kAngle360 / (2 * 3.141592654 * (double)gSpring));
+
+ sprintf(buffer, "Spring coefficient = %i", gSpring);
+ viewSetMessage(buffer);
+ }
+ else if (gViewMode == kView3D)
+ {
+ viewResizeView(gViewSize + 1);
+ BloodINI.PutKeyInt("View", "Size", gViewSize);
+ }
+ break;
+
+ case KEY_PLUS:
+ if ( keystatus[KEY_G] )
+ {
+ gGamma = ClipHigh(gGamma + 1, gGammaLevels - 1);
+ BloodINI.PutKeyInt("View", "Gamma", gGamma);
+ scrSetGamma(gGamma);
+ sprintf(buffer, "Gamma correction level %i", gGamma);
+ viewSetMessage(buffer);
+ }
+ else if ( keystatus[KEY_D] )
+ {
+ visibility = ClipLow(visibility - 16, 128);
+ sprintf(buffer, "Depth cueing level %i", visibility);
+ viewSetMessage(buffer);
+ }
+ else if ( keystatus[KEY_S])
+ {
+ gSpring++;
+ BloodINI.PutKeyInt("Player", "SpringCoefficient", gSpring);
+ gSpringPhaseInc = (int)(kAngle360 / (2 * 3.141592654 * (double)gSpring));
+
+ sprintf(buffer, "Spring coefficient = %i", gSpring);
+ viewSetMessage(buffer);
+ }
+ else if (gViewMode == kView3D)
+ {
+ viewResizeView(gViewSize - 1);
+ BloodINI.PutKeyInt("View", "Size", gViewSize);
+ }
+ break;
+
+ case KEY_P:
+ parallaxtype = (char)IncRotate(parallaxtype, 3);
+ break;
+
+ case KEY_V:
+ if ( alt )
+ {
+ if ( gViewPos > 0 )
+ gViewPos = (VIEWPOS)((gViewPos & 7) + 1);
+ sprintf(buffer, "gViewPos = %i", gViewPos);
+ viewSetMessage(buffer);
+ }
+ else
+ {
+ if ( gViewPos > 0 )
+ gViewPos = kViewPosCenter;
+ else
+ gViewPos = kViewPosBack;
+ }
+ break;
+
+ case KEY_PRINTSCREEN:
+ screencapture("captxxxx.pcx");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (keystatus[KEY_MINUS] && (gViewMode == kView2D || gViewMode == kView2DIcon))
+ {
+ gZoom = ClipLow(gZoom - (gZoom >> 6), 64);
+ }
+
+ if (keystatus[KEY_PLUS] && (gViewMode == kView2D || gViewMode == kView2DIcon))
+ {
+ gZoom = ClipHigh(gZoom + (gZoom >> 6), 4096);
+ }
+
+ int nDelirium = powerupCheck(gView, kItemShroomDelirium - kItemBase);
+ if ( nDelirium )
+ {
+ static int timer = 0;
+ timer += kFrameTicks;
+
+ int maxTilt = kAngle30;
+ int maxTurn = kAngle30;
+ int maxPitch = 20;
+
+// this constant determine when the effect starts to wear off
+#define kWaneTime 512
+
+ if (nDelirium < kWaneTime)
+ {
+ int scale = (nDelirium << 16) / kWaneTime;
+ maxTilt = mulscale16(maxTilt, scale);
+ maxTurn = mulscale16(maxTurn, scale);
+ maxPitch = mulscale16(maxPitch, scale);
+ }
+
+ gScreenTilt = mulscale30(Sin(timer * 2) / 2 + Sin(timer * 3) / 2, maxTilt);
+ deliriumTurn = mulscale30(Sin(timer * 3) / 2 + Sin(timer * 4) / 2, maxTurn);
+ deliriumPitch = mulscale30(Sin(timer * 4) / 2 + Sin(timer * 5) / 2, maxPitch);
+ }
+ else
+ {
+ // this code needs a different calculation,
+ // and should probably be partially placed in ctrlGetInput
+
+ if ( keystatus[KEY_LBRACE] )
+ gScreenTilt += kFrameTicks * 4;
+
+ if ( keystatus[KEY_RBRACE] )
+ gScreenTilt -= kFrameTicks * 4;
+
+ // put gScreenTilt in the range -kAngle180 to kAngle180
+ gScreenTilt = ((gScreenTilt + kAngle180) & kAngleMask) - kAngle180;
+
+ // pull screen to vertical
+ if ( gScreenTilt > 0 )
+ gScreenTilt = ClipLow(gScreenTilt - kFrameTicks * 2, 0);
+ else if ( gScreenTilt < 0 )
+ gScreenTilt = ClipHigh(gScreenTilt + kFrameTicks * 2, 0);
+ }
+
+ if ( keyCompareStream( cheatRate, LENGTH(cheatRate)) )
+ {
+ keyPokeStream(0);
+ gShowFrameRate = !gShowFrameRate;
+ }
+
+ if ( keyCompareStream( cheatFrag, LENGTH(cheatFrag)) )
+ {
+ keyPokeStream(0);
+ gShowFrags = !gShowFrags;
+ }
+
+ if ( keyCompareStream( cheatAim, LENGTH(cheatAim)) )
+ {
+ keyPokeStream(0);
+ gAimReticle = !gAimReticle;
+ }
+
+ if ( gNetGame == FALSE )
+ {
+ if ( keyCompareStream( cheatAmmo, LENGTH(cheatAmmo)) )
+ {
+ keyPokeStream(0);
+ for (int i = 0; i < kWeaponMax; i++)
+ {
+ gMe->hasWeapon[i] = TRUE;
+ //gMe->weaponState[i] = 0;
+ }
+ for (i = 0; i < kAmmoMax; i++)
+ {
+ gMe->ammoCount[i] = gAmmoInfo[i].max;
+ }
+ viewSetMessage("Full armory granted!");
+ }
+
+ if ( keyCompareStream( cheatKeys, LENGTH(cheatKeys)) )
+ {
+ keyPokeStream(0);
+ for (int i = 1; i <= 6; i++)
+ gMe->hasKey[i] = (short)(kPicKey1 + i - 1);
+ viewSetMessage("Full keychain granted!");
+ }
+
+ if ( keyCompareStream( cheatMaps, LENGTH(cheatMaps)) )
+ {
+ keyPokeStream(0);
+ gFullMap = !gFullMap;
+ if ( gFullMap )
+ viewSetMessage("Full atlas granted");
+ else
+ viewSetMessage("Full atlas revoked");
+ }
+
+ if ( keyCompareStream( cheatClip, LENGTH(cheatClip)) )
+ {
+ keyPokeStream(0);
+ gNoClip = !gNoClip;
+ if ( gNoClip )
+ viewSetMessage("Non-clipped mode enabled");
+ else
+ viewSetMessage("Non-clipped mode disabled");
+ }
+
+ if ( keyCompareStream( cheatGoto, LENGTH(cheatGoto)) )
+ {
+ keyPokeStream(0);
+ long x = GetNumberBox("X=", gMe->sprite->x, gMe->sprite->x);
+ long y = GetNumberBox("Y=", gMe->sprite->y, gMe->sprite->y);
+
+ short nSector;
+ updatesector(x, y, &nSector);
+ if (nSector != -1) {
+ changespritesect((short)gMe->nSprite, nSector);
+ gMe->sprite->x = x;
+ gMe->sprite->y = y;
+ gMe->sprite->z = sector[nSector].floorz;
+ gMe->sprite->zvel = 0;
+ }
+ }
+
+ if ( keyCompareStream( cheatJump, LENGTH(cheatJump)) )
+ {
+ keyPokeStream(0);
+ long newEpisodeId = GetNumberBox("Episode (1..3)=", gEpisodeId, 1);
+ long newMapId = GetNumberBox("Map (1..15)=", gMapId, 1);
+
+ if ((newEpisodeId != 0) && (newMapId != 0)) {
+ gEpisodeId = newEpisodeId;
+ gMapId = newMapId;
+ gForceMap = FALSE;
+ ready2send = FALSE;
+ prepareboard();
+ ready2send = TRUE;
+ }
+ viewResizeView(gViewSize);
+ }
+
+ if ( keyCompareStream( cheatHurt, LENGTH(cheatHurt)) )
+ {
+ keyPokeStream(0);
+ actDamageSprite(gMe->nSprite, gMe->nSprite, kDamagePummel, 100 << 4);
+ viewSetMessage("Ouch!");
+ }
+
+ if ( keyCompareStream( cheatExplode, LENGTH(cheatExplode)) )
+ {
+ keyPokeStream(0);
+ actDamageSprite(gMe->nSprite, gMe->nSprite, kDamageExplode, 100 << 4);
+ viewSetMessage("Boom.");
+ }
+
+ if ( keyCompareStream( cheatBurn, LENGTH(cheatBurn)) )
+ {
+ keyPokeStream(0);
+ actAddBurnTime( gMe->nSprite, gMe->xsprite, kMaxBurnTime );
+ viewSetMessage("Crispy!");
+ }
+
+ if ( keyCompareStream( cheatHigh, LENGTH(cheatHigh)) )
+ {
+ keyPokeStream(0);
+ if ( powerupCheck( gMe, kItemShroomDelirium - kItemBase ) )
+ powerupDeactivate( gMe, kItemShroomDelirium - kItemBase );
+ else
+ powerupActivate( gMe, kItemShroomDelirium - kItemBase );
+ viewSetMessage("Cosmic, man!");
+ }
+
+ if ( keyCompareStream( cheatGod, LENGTH(cheatGod)) )
+ {
+ keyPokeStream(0);
+ playerSetGodMode( gMe, !gMe->godMode );
+ if (gMe->godMode)
+ viewSetMessage("Entering Broussard mode!");
+ else
+ viewSetMessage("Back to normal mode!");
+ }
+
+ if ( keyCompareStream( cheatInviso, LENGTH(cheatInviso)) )
+ {
+ keyPokeStream(0);
+ if ( powerupCheck( gMe, kItemLtdInvisibility - kItemBase ) )
+ {
+ viewSetMessage("Deactivating the One Ring");
+ powerupDeactivate( gMe, kItemLtdInvisibility - kItemBase );
+ }
+ else
+ {
+ viewSetMessage("Activating the One Ring");
+ powerupActivate( gMe, kItemLtdInvisibility - kItemBase );
+ }
+ }
+
+ if ( keyCompareStream( cheatSeer, LENGTH(cheatSeer)) )
+ {
+ keyPokeStream(0);
+ if ( powerupCheck( gMe, kItemCrystalBall - kItemBase ) )
+ {
+ viewSetMessage("Deactivating the Crystal Ball");
+ powerupDeactivate( gMe, kItemCrystalBall - kItemBase );
+ }
+ else
+ {
+ viewSetMessage("Activating the Crystal Ball");
+ powerupActivate( gMe, kItemCrystalBall - kItemBase );
+ }
+ }
+
+ if ( keyCompareStream( cheatBeast, LENGTH(cheatBeast)) )
+ {
+ keyPokeStream(0);
+ if (gMe->lifemode == kModeBeast)
+ playerSetRace( gMe, kModeHuman, gMe->sprite->type );
+ else
+ playerSetRace( gMe, kModeBeast, gMe->sprite->type );
+ }
+
+ if ( keyCompareStream( cheatHealth, LENGTH(cheatHealth)) )
+ {
+ keyPokeStream(0);
+ gMe->xsprite->health = 200 << 4;
+ viewSetMessage("Full health granted, you maggot!");
+ }
+
+ if ( keyCompareStream( cheatSight, LENGTH(cheatSight)) )
+ {
+ keyPokeStream(0);
+ if ( powerupCheck( gMe, kItemRoseGlasses - kItemBase ) )
+ {
+ viewSetMessage("Deactivating Oscar Sight");
+ powerupDeactivate( gMe, kItemRoseGlasses - kItemBase );
+ }
+ else
+ {
+ viewSetMessage("Activating Oscar Sight");
+ powerupActivate( gMe, kItemRoseGlasses - kItemBase );
+ }
+ }
+ }
+
+ if (*control[kAutomapToggle])
+ {
+ *control[kAutomapToggle] = 0;
+
+ if (gViewMode == kView3D)
+ {
+ gViewMode = kView2D;
+ }
+ else if (gViewMode == kView2D)
+ {
+ gViewMode = kView2DIcon;
+ }
+ else
+ {
+ gViewMode = kView3D;
+ viewResizeView(gViewSize);
+ }
+ }
+
+ if (gNetPlayers == 1) //Single player only keys
+ {
+ if (keystatus[KEY_INSERT]) //Insert - Insert player
+ {
+ keystatus[KEY_INSERT] = 0;
+ if (numplayers < kMaxPlayers)
+ {
+ connectpoint2[numplayers-1] = numplayers;
+ connectpoint2[numplayers] = -1;
+
+ gViewIndex = myconnectindex = numplayers++;
+ gView = gMe = &gPlayer[myconnectindex];
+ playerInit( myconnectindex );
+ }
+ }
+ if (keystatus[KEY_DELETE]) //Delete - Delete player
+ {
+ keystatus[KEY_DELETE] = 0;
+ if (numplayers > 1)
+ {
+ // actDeletePlayer( myconnectindex ); // should probably add something like this!
+
+ numplayers--;
+ connectpoint2[numplayers-1] = -1;
+
+ seqKill( SS_SPRITE, gPlayer[numplayers].sprite->extra );
+
+ actPostSprite( gPlayer[numplayers].nSprite, kStatFree );
+ gPlayer[numplayers].sprite = NULL;
+
+ if (myconnectindex >= numplayers) myconnectindex = 0;
+ gMe = &gPlayer[myconnectindex];
+ if (gViewIndex >= numplayers) gViewIndex = 0;
+ gView = &gPlayer[gViewIndex];
+ }
+ }
+ if (keystatus[KEY_SCROLLLOCK]) //Scroll Lock
+ {
+ keystatus[KEY_SCROLLLOCK] = 0;
+
+ myconnectindex = connectpoint2[myconnectindex];
+ if (myconnectindex < 0) myconnectindex = connecthead;
+ gViewIndex = myconnectindex;
+ gView = gMe = &gPlayer[myconnectindex];
+ }
+ }
+}
+
+ulong CalcGameChecksum( void )
+{
+ ulong value = 0;
+
+ value = Random(65536);
+// dprintf("Frame #%d: randCount1 = %d, randCount2 = %d, Random() = %4x\n",
+// gFrame, randCount1, randCount2, value);
+ //value ^= CRC32(gPlayer, sizeof(gPlayer));
+ for ( int i = connecthead; i >= 0; i = connectpoint2[i] )
+ value ^= CRC32(gPlayer[i].sprite, sizeof(SPRITE));
+
+ return value;
+}
+
+/*
+void CheckMasterSwitch( void )
+{
+ int i, j;
+
+ i = connecthead; j = connectpoint2[i];
+ while (j >= 0)
+ {
+ if (gPlayer[j].keyFlags.master)
+ {
+ connectpoint2[i] = connectpoint2[j];
+ connectpoint2[j] = connecthead;
+ connecthead = (short)j;
+
+ gGameClock = gFrameClock;
+ if (connecthead == myconnectindex)
+ viewSetMessage("Master");
+ else
+ viewSetMessage("Slave");
+ gPlayer[j].keyFlags.master = 0;
+ return;
+ }
+ i = j; j = connectpoint2[i];
+ }
+}
+*/
+
+
+/*******************************************************************************
+ FUNCTION: ProcessFrame()
+
+ DESCRIPTION: Main game processing function
+*******************************************************************************/
+void ProcessFrame()
+{
+ for ( int i = connecthead; i >= 0; i = connectpoint2[i] )
+ {
+ gPlayerInput[i].syncFlags = gFifoInput[gNetFifoHead][i].syncFlags;
+ gPlayerInput[i].buttonFlags = gFifoInput[gNetFifoHead][i].buttonFlags;
+
+ // keys and weapon selection are non-destructive
+ gPlayerInput[i].keyFlags.byte |= gFifoInput[gNetFifoHead][i].keyFlags.byte;
+ if (gFifoInput[gNetFifoHead][i].newWeapon != 0)
+ gPlayerInput[i].newWeapon = gFifoInput[gNetFifoHead][i].newWeapon;
+
+ gPlayerInput[i].forward = gFifoInput[gNetFifoHead][i].forward;
+ gPlayerInput[i].strafe = gFifoInput[gNetFifoHead][i].strafe;
+ gPlayerInput[i].turn = gFifoInput[gNetFifoHead][i].turn;
+ gPlayerInput[i].ticks = gFifoInput[gNetFifoHead][i].ticks;
+ }
+ gNetFifoHead = IncRotate(gNetFifoHead, kNetFifoSize);
+
+ gFifoSendCRC[gSendCRCHead] = CalcGameChecksum();
+ gSendCRCHead = IncRotate(gSendCRCHead, kNetFifoSize);
+
+ for (i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ if ( gPlayerInput[i].keyFlags.restart )
+ {
+ gPlayerInput[i].keyFlags.restart = 0;
+ gForceMap = TRUE;
+ gPaused = FALSE;
+
+ ready2send = FALSE;
+ prepareboard();
+ ready2send = TRUE;
+ }
+ if ( gPlayerInput[i].keyFlags.pause )
+ {
+ gPlayerInput[i].keyFlags.pause = 0;
+ gPaused = !gPaused;
+ }
+ }
+
+ if (gPaused)
+ return;
+
+ for (i = connecthead; i >= 0; i = connectpoint2[i])
+ viewBackupPlayerLoc(i);
+
+ trProcessBusy(); // process busy triggers
+ evProcess(gFrameClock); // process event queue
+ seqProcess(kFrameTicks);
+
+ for (i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ powerupProcess(&gPlayer[i]); // process the power-ups
+ playerMove(&gPlayer[i], &gPlayerInput[i]); // Move player
+ }
+
+ actProcessSprites(); // Actor code: projectiles, etc.
+ actPostProcess(); // clean up deleted sprites or sprites changing status
+ sndProcess();
+ sfxUpdate3DSounds();
+
+ gFrame++;
+ gFrameClock += kFrameTicks;
+}
+
+
+void UpdateNetFifo( void )
+{
+ for ( int i = connecthead; i >= 0; i = connectpoint2[i] )
+ {
+ gFifoInput[gNetFifoTail][i] = gNetInput[i];
+ }
+ gNetFifoTail = IncRotate(gNetFifoTail, kNetFifoSize);
+ // CheckMasterSwitch();
+}
+
+
+char packet[576];
+
+inline char GetPacketByte(char * &p)
+{ return *p++; }
+
+inline PutPacketByte(char * &p, int b)
+{ *p++ = (char)b; }
+
+inline short GetPacketShort(char * &p)
+{
+ short w = *(short *)p;
+ p += 2;
+ return w;
+}
+
+inline PutPacketShort(char * &p, int w)
+{
+ *(short *)p = (short)w;
+ p += 2;
+}
+
+inline long GetPacketLong(char * &p)
+{
+ long l = *(long *)p;
+ p += 4;
+ return l;
+}
+
+inline PutPacketLong(char * &p, int l)
+{
+ *(long *)p = (long)l;
+ p += 4;
+}
+
+
+int GetPackets( void )
+{
+ short nPlayer, length;
+ long i, j;
+ char *p;
+ int n = 0;
+ int nMoveCount = 0;
+
+ while ( (length = getpacket(&nPlayer, packet)) > 0)
+ {
+ n++;
+ p = packet;
+
+ switch( GetPacketByte(p) )
+ {
+ case 0: // receive master sync buffer
+
+ j = GetPacketShort(p);
+ if (j != gPacketIndex++)
+ ThrowError("Missing master packet", ES_ERROR);
+
+ for (i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ SYNCFLAGS syncFlags;
+ syncFlags.byte = GetPacketByte(p);
+ gNetInput[i].syncFlags.byte = syncFlags.byte;
+
+ if (syncFlags.buttonChange)
+ gNetInput[i].buttonFlags.byte = GetPacketByte(p);
+ if (syncFlags.keyChange)
+ gNetInput[i].keyFlags.byte = GetPacketByte(p);
+ if (syncFlags.weaponChange)
+ gNetInput[i].newWeapon = GetPacketByte(p);
+ if (syncFlags.forwardChange)
+ gNetInput[i].forward = GetPacketByte(p);
+ if (syncFlags.turnChange)
+ gNetInput[i].turn = GetPacketShort(p);
+ if (syncFlags.strafeChange)
+ gNetInput[i].strafe = GetPacketByte(p);
+ gNetInput[i].ticks = GetPacketByte(p);
+ }
+
+ while ( p < &packet[length] )
+ {
+ gFifoReceiveCRC[gReceiveCRCHead] = GetPacketLong(p);
+ gReceiveCRCHead = IncRotate(gReceiveCRCHead, kNetFifoSize);
+ }
+
+ if ( gReceiveCRCHead != gCRCTail && gSendCRCHead != gCRCTail )
+ {
+ bOutOfSync = FALSE;
+ do
+ {
+ if ( gFifoReceiveCRC[gCRCTail] != gFifoSendCRC[gCRCTail] )
+ {
+ dprintf("SYNC: gFifoReceiveCRC[%d] = %8x, gFifoSendCRC[%d] = %8x\n",
+ gCRCTail, gFifoReceiveCRC[gCRCTail], gCRCTail, gFifoSendCRC[gCRCTail]);
+ bOutOfSync = TRUE;
+ }
+
+ gCRCTail = IncRotate(gCRCTail, kNetFifoSize);
+ }
+ while ( gReceiveCRCHead != gCRCTail && gSendCRCHead != gCRCTail );
+ }
+
+ UpdateNetFifo();
+ nMoveCount++;
+ continue;
+
+ case 1: // receive slave sync buffer
+
+ SYNCFLAGS syncFlags;
+ syncFlags.byte = GetPacketByte(p);
+ gNetInput[nPlayer].syncFlags.byte = syncFlags.byte;
+
+ if (syncFlags.buttonChange)
+ gNetInput[nPlayer].buttonFlags.byte = GetPacketByte(p);
+ if (syncFlags.keyChange)
+ gNetInput[nPlayer].keyFlags.byte = GetPacketByte(p);
+ if (syncFlags.weaponChange)
+ gNetInput[nPlayer].newWeapon = GetPacketByte(p);
+ if (syncFlags.forwardChange)
+ gNetInput[nPlayer].forward = GetPacketByte(p);
+ if (syncFlags.turnChange)
+ gNetInput[nPlayer].turn = GetPacketShort(p);
+ if (syncFlags.strafeChange)
+ gNetInput[nPlayer].strafe = GetPacketByte(p);
+ gNetInput[nPlayer].ticks = GetPacketByte(p);
+
+ continue;
+
+ case 250:
+ playerreadyflag[nPlayer] = GetPacketByte(p);
+
+ // if the message came from the master, acknowledge it
+ if ( nPlayer == connecthead && playerreadyflag[connecthead] == 2 )
+ sendpacket(connecthead, packet, 2);
+ break;
+
+ case 255: //[255] (logout)
+ keystatus[1] = 1;
+ break;
+ }
+ break;
+ }
+
+ // try to prevent slave and master from getting into phase lock-step
+ if ( myconnectindex != connecthead && nMoveCount != 1 )
+ {
+ if (nMoveCount == 2)
+ dprintf("%4d: Received 2 packets from master\n", gPacketIndex);
+// if (nMoveCount & 2)
+// gNetFifoClock += kFrameTicks / 2;
+// else
+// gNetFifoClock -= kFrameTicks / 2;
+ }
+ return n;
+}
+
+
+void GetInput( void )
+{
+ if ( gNetPlayers > 1 )
+ GetPackets();
+
+ // this doesn't currently do anything but Ken might add to this later
+ if (getoutputcirclesize() >= 16)
+ return;
+
+ ctrlGetInput();
+
+ char *p = packet;
+
+ // single player or master
+ if (gNetPlayers == 1 || myconnectindex == connecthead)
+ {
+ // copy in local input
+ gNetInput[myconnectindex].syncFlags.byte = gInput.syncFlags.byte;
+
+ if (gInput.syncFlags.buttonChange)
+ gNetInput[myconnectindex].buttonFlags.byte = gInput.buttonFlags.byte;
+ if (gInput.syncFlags.keyChange)
+ gNetInput[myconnectindex].keyFlags.byte = gInput.keyFlags.byte;
+ if (gInput.syncFlags.weaponChange)
+ gNetInput[myconnectindex].newWeapon = gInput.newWeapon;
+ if (gInput.syncFlags.forwardChange)
+ gNetInput[myconnectindex].forward = gInput.forward;
+ if (gInput.syncFlags.turnChange)
+ gNetInput[myconnectindex].turn = gInput.turn;
+ if (gInput.syncFlags.strafeChange)
+ gNetInput[myconnectindex].strafe = gInput.strafe;
+ gNetInput[myconnectindex].ticks = gInput.ticks;
+
+ if ( gNetPlayers > 1 )
+ {
+ // build master packet
+ PutPacketByte(p, 0);
+ PutPacketShort(p, gPacketIndex++);
+
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ PutPacketByte(p, gNetInput[i].syncFlags.byte);
+
+ if (gNetInput[i].syncFlags.buttonChange)
+ PutPacketByte(p, gNetInput[i].buttonFlags.byte);
+ if (gNetInput[i].syncFlags.keyChange)
+ PutPacketByte(p, gNetInput[i].keyFlags.byte);
+ if (gNetInput[i].syncFlags.weaponChange)
+ PutPacketByte(p, gNetInput[i].newWeapon);
+ if (gNetInput[i].syncFlags.forwardChange)
+ PutPacketByte(p, gNetInput[i].forward);
+ if (gNetInput[i].syncFlags.turnChange)
+ PutPacketShort(p, gNetInput[i].turn);
+ if (gNetInput[i].syncFlags.strafeChange)
+ PutPacketByte(p, gNetInput[i].strafe);
+ PutPacketByte(p, gNetInput[i].ticks);
+
+ gNetInput[i].syncFlags.byte = 0;
+ }
+
+ while ( gCRCTail != gSendCRCHead )
+ {
+ PutPacketLong(p, gFifoSendCRC[gCRCTail]);
+ gCRCTail = IncRotate(gCRCTail, kNetFifoSize); // master increment SEND tail
+ }
+
+ // send packets to slaves
+ for (i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i])
+ sendpacket((short)i, packet, (short)(p - packet));
+
+ }
+ UpdateNetFifo();
+ }
+ else //I am a SLAVE
+ {
+ // build slave packet
+ PutPacketByte(p, 1);
+ PutPacketByte(p, gInput.syncFlags.byte);
+
+ if (gInput.syncFlags.buttonChange)
+ PutPacketByte(p, gInput.buttonFlags.byte);
+ if (gInput.syncFlags.keyChange)
+ PutPacketByte(p, gInput.keyFlags.byte);
+ if (gInput.syncFlags.weaponChange)
+ PutPacketByte(p, gInput.newWeapon);
+ if (gInput.syncFlags.forwardChange)
+ PutPacketByte(p, gInput.forward);
+ if (gInput.syncFlags.turnChange)
+ PutPacketShort(p, gInput.turn);
+ if (gInput.syncFlags.strafeChange)
+ PutPacketByte(p, gInput.strafe);
+ PutPacketByte(p, gInput.ticks);
+
+ // send packet to master
+ sendpacket((short)connecthead, packet, (short)(p - packet));
+ }
+}
+
+
+void faketimerhandler( void )
+{
+ if ( gGameClock >= gNetFifoClock && ready2send )
+ {
+ gNetFifoClock += kFrameTicks;
+ GetInput();
+ }
+}
+
+
+BOOL WaitForAllPlayers( void )
+{
+ long i, j, oldtotalclock;
+
+ if (numplayers < 2) return TRUE;
+
+ // if I'm the Master
+ if (myconnectindex == connecthead)
+ {
+ for (j = 1; j <= 2; j++)
+ {
+ for (i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i])
+ playerreadyflag[i] = 0;
+ oldtotalclock = gGameClock - 8;
+ do
+ {
+ if (keystatus[1]) return FALSE;
+
+ if ( gNetPlayers > 1 )
+ GetPackets();
+
+ if (gGameClock >= oldtotalclock + 8)
+ {
+ oldtotalclock = gGameClock;
+ char *p = packet;
+ PutPacketByte(p, 250);
+ PutPacketByte(p, j);
+ for(i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i])
+ if (playerreadyflag[i] != j)
+ sendpacket((short)i, packet, 2);
+ }
+ for(i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i])
+ if (playerreadyflag[i] != j) break;
+ } while (i >= 0);
+ }
+ }
+ else
+ {
+ playerreadyflag[connecthead] = 0;
+ while (playerreadyflag[connecthead] != 2)
+ {
+ if (keystatus[1]) return FALSE;
+ if ( gNetPlayers > 1 )
+ GetPackets();
+
+ if (playerreadyflag[connecthead] == 1)
+ {
+ playerreadyflag[connecthead] = 0;
+ sendpacket(connecthead, packet, 2);
+ }
+ }
+ }
+ return TRUE;
+}
+
+
+ErrorResult GameErrorHandler( const Error& error )
+{
+ // shut down the game engine
+ if ( gNetGame )
+ {
+ sendlogoff();
+ uninitmultiplayers();
+ }
+
+ sndTerm();
+ timerRemove();
+ ctrlTerm();
+ UnlockClockStrobe();
+ uninitengine();
+ setvmode(gOldDisplayMode);
+
+ // chain to the default error handler
+ return prevErrorHandler(error);
+};
+
+enum
+{
+ kSwitchNet,
+ kSwitchBloodBath,
+ kSwitchTeams,
+};
+
+SWITCH switches[] =
+{
+ { "net", kSwitchNet, TRUE },
+ { "bloodbath", kSwitchBloodBath, FALSE },
+ { "teams", kSwitchBloodBath, FALSE },
+ { NULL, 0, FALSE },
+};
+
+/***********************************************************************
+ * Process command line arguments
+ **********************************************************************/
+void ParseOptions( void )
+{
+ char buffer[256];
+ int r;
+ while ( (r = GetOptions(switches)) != GO_EOF )
+ {
+ switch (r)
+ {
+ case GO_INVALID:
+ sprintf(buffer, "Invalid argument: %s", OptArgument);
+ ThrowError(buffer, ES_ERROR);
+
+ case kSwitchNet:
+ gNetGame = TRUE;
+ dprintf("Net parameter is %s\n", OptArgument);
+ break;
+
+ case kSwitchBloodBath:
+ // NET PARAMETER MUST COME FIRST: Can you add a CheckParm?
+ gNetMode = kNetModeBloodBath;
+ dprintf("Bloodbath network mode\n");
+ break;
+
+ case kSwitchTeams:
+ // NET PARAMETER MUST COME FIRST: Can you add a CheckParm?
+ gNetMode = kNetModeTeams;
+ dprintf("Team network mode\n");
+ break;
+
+ case GO_FULL:
+ strcpy(gLevelName, OptArgument);
+ gForceMap = TRUE;
+ break;
+ }
+ }
+
+}
+
+
+/***********************************************************************
+ * main()
+ *
+ * It all starts here. Have a Bloody day.
+ *
+ **********************************************************************/
+void main( /* int argc, char *argv[] */ )
+{
+ char title[256];
+
+ gOldDisplayMode = getvmode();
+
+ sprintf(title, "BLOOD ALPHA BUILD [%s] -- DO NOT DISTRIBUTE", gBuildDate);
+ tioInit();
+ tioCenterString(0, 0, tioScreenCols - 1, title, kAttrTitle);
+ tioCenterString(tioScreenRows - 1, 0, tioScreenCols - 1,
+ "Copyright (c) 1994, 1995 Q Studios Corporation", kAttrTitle);
+
+ tioWindow(1, 0, tioScreenRows - 2, tioScreenCols);
+
+ if ( _grow_handles(kRequiredFiles) < kRequiredFiles )
+ {
+ tioPrint("Increase FILES=## value in CONFIG.SYS.");
+ ThrowError("Not enough file handles available", ES_ERROR);
+ }
+
+ ParseOptions();
+
+ tioPrint("Loading INI file");
+ optLoadINI();
+
+ tioPrint("Initializing heap and resource system");
+ Resource::heap = new QHeap(dpmiDetermineMaxRealAlloc());
+
+ tioPrint("Initializing resource archive");
+ gSysRes.Init("BLOOD.RFF", "*.*");
+ gGuiRes.Init("GUI.RFF", NULL);
+
+ CheckDemoDate(gSysRes);
+
+ // install our error handler
+ prevErrorHandler = errSetHandler(GameErrorHandler);
+
+ tioPrint("Initializing 3D engine");
+ InitEngine();
+
+ tioPrint("Creating standard color lookups");
+ scrCreateStdColors();
+
+ tioPrint("Loading tiles");
+ if (tileInit() == 0)
+ ThrowError("ART files not found", ES_ERROR);
+
+ powerupInit(); // calc zoom table for power-up animations
+
+ tioPrint("Loading cosine table");
+ trigInit(gSysRes);
+
+ tioPrint("Initializing screen");
+ scrInit();
+
+ tioPrint("Initializing view subsystem");
+ viewInit();
+
+ tioPrint("Initializing dynamic fire");
+ FireInit();
+
+ tioPrint("Initializing weapon animations");
+ WeaponInit();
+
+ tioPrint("Installing keyboard handler");
+ keyInstall();
+
+ tioPrint("Initializing network users");
+ initmultiplayers(0, 0, 0);
+ gNetPlayers = numplayers;
+
+ gMe = gView = &gPlayer[myconnectindex];
+ gViewIndex = myconnectindex;
+
+ // this must be done before initializing timer, because mouse interrupt hooks timer vector
+ tioPrint("Loading control setup");
+ ctrlInit();
+
+ tioPrint("Installing timer");
+ LockClockStrobe();
+ timerRegisterClient(ClockStrobe, kTimerRate);
+ timerInstall();
+
+ tioPrint("Initializing sound system");
+ sndInit();
+ sfxInit();
+
+ tioPrint("Waiting for network players");
+ WaitForAllPlayers();
+
+ scrSetGameMode();
+ scrSetGamma(gGamma);
+
+// if ( !gForceMap )
+// credCredits();
+
+ viewResizeView(gViewSize);
+
+ prepareboard();
+
+ gNetFifoClock = gFrameClock = gGameClock = 0;
+ gNetFifoHead = gNetFifoTail = 0;
+ gSendCRCHead = gReceiveCRCHead = gCRCTail = 0;
+ ready2send = TRUE;
+
+ //This is the whole game loop!
+ while ( keystatus[KEY_ESC] == 0 && !syncstate )
+ {
+ LocalKeys();
+
+ if ( gNetPlayers > 1 )
+ GetPackets();
+
+ faketimerhandler();
+
+ while ( gNetFifoHead != gNetFifoTail )
+ ProcessFrame();
+
+ if ( !gPaused )
+ gInterpolate = (gGameClock + kFrameTicks - gFrameClock) * (0x10000 / kFrameTicks);
+
+ viewDrawScreen();
+
+ if (bOutOfSync)
+ viewSetMessage("Out of Sync!");
+
+ if ( gEndLevelFlag )
+ {
+ // fade out the current music
+ sndFadeSong(4000);
+
+ if ( gEndLevelFlag < 0 )
+ {
+ // end episode or something special like that
+ }
+ else
+ gMapId = gEndLevelFlag;
+
+ gEndLevelFlag = 0;
+ gForceMap = FALSE;
+ ready2send = FALSE;
+ prepareboard();
+ ready2send = TRUE;
+ viewResizeView(gViewSize);
+ }
+ }
+ ready2send = FALSE;
+
+ setvmode(gOldDisplayMode);
+
+ sndTerm();
+
+ dprintf("Removing timer\n");
+ timerRemove();
+
+ ctrlTerm();
+ UnlockClockStrobe();
+
+ if ( gNetGame )
+ {
+ sendlogoff();
+ uninitmultiplayers();
+ }
+
+ dprintf("uninitengine()\n");
+ uninitengine();
+
+ BloodINI.Save();
+
+ if (syncstate)
+ printf("A packet was lost! (syncstate)\n");
+
+ dprintf("All subsystems shut down. Processing exit functions\n");
+
+ errSetHandler(prevErrorHandler);
+}
diff --git a/SRC/BSTUB.CPP b/SRC/BSTUB.CPP
new file mode 100644
index 0000000..dab4be9
--- /dev/null
+++ b/SRC/BSTUB.CPP
@@ -0,0 +1,1924 @@
+/*******************************************************************************
+ FILE: BSTUB.CPP
+
+ DESCRIPTION: This file contains enhancements to the BUILD editor, both
+ in 3d mode and 2d mode.
+
+ AUTHOR: Peter M. Freese
+ CREATED: 94/11/01
+ COPYRIGHT: Copyright (c) 1995 Q Studios Corporation
+*******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <io.h>
+#include <fcntl.h>
+#include <dos.h>
+#include <conio.h>
+
+#include "engine.h"
+#include "build.h"
+
+#include "typedefs.h"
+#include "bstub.h"
+#include "db.h"
+#include "screen.h"
+#include "misc.h"
+#include "gameutil.h"
+#include "sectorfx.h"
+#include "globals.h"
+#include "key.h"
+#include "textio.h"
+#include "inifile.h"
+#include "helix.h"
+#include "debug4g.h"
+#include "qheap.h"
+#include "resource.h"
+#include "gui.h"
+#include "error.h"
+#include "protect.h"
+#include "tile.h"
+#include "options.h"
+#include "gfx.h"
+#include "timer.h"
+#include "trig.h"
+#include "edit2d.h"
+#include "mouse.h"
+#include "eventq.h"
+
+#include "names.h"
+
+#include <memcheck.h>
+
+
+/*******************************************************************************
+ Structures and typedefs
+*******************************************************************************/
+
+struct NAMED_TYPE
+{
+ short id;
+ char *name;
+};
+
+
+/*******************************************************************************
+ Global Variables
+*******************************************************************************/
+int gHighlightThreshold;
+int gStairHeight;
+int gLBIntensity;
+int gLBAttenuation;
+int gLBReflections;
+int gLBMaxBright;
+int gLBRampDist;
+int gPalette = kPalNormal;
+BOOL gBeep;
+BOOL gOldKeyMapping;
+BOOL gPanning = TRUE;
+char *gBoolNames[2];
+char *gSpriteNames[1024];
+char *gWallNames[1024];
+char *gSectorNames[1024];
+char *gWaveNames[16];
+char *gDepthNames[4];
+char *gCommandNames[256];
+char *gRespawnNames[4];
+int gSaveTime = 0;
+int gSaveInterval;
+
+/*******************************************************************************
+These were globals that were referenced in other parts of build.c. Ken should
+make these non-static.
+*******************************************************************************/
+short grid = 4, gridlock = 1;
+long zoom = 768;
+
+
+/*******************************************************************************
+ Local Variables
+*******************************************************************************/
+static char buffer[256] = "";
+static EHF prevErrorHandler;
+
+// options
+IniFile MapEditINI("MAPEDIT.INI");
+
+
+char buildkeys[] =
+{
+ KEY_UP,
+ KEY_DOWN,
+ KEY_LEFT,
+ KEY_RIGHT,
+ KEY_LSHIFT,
+ KEY_RCTRL,
+ KEY_LCTRL,
+ KEY_SPACE,
+ KEY_A,
+ KEY_Z,
+ KEY_PAGEDN,
+ KEY_PAGEUP,
+ KEY_PAD7,
+ KEY_PAD9,
+ KEY_PADENTER,
+ KEY_ENTER,
+ KEY_PLUS,
+ KEY_MINUS,
+ KEY_TAB,
+};
+
+
+static NAMED_TYPE boolNTNames[] =
+{
+ 0, "Off",
+ 1, "On",
+};
+
+
+static NAMED_TYPE spriteNTNames[] =
+{
+ kNothing, "Decoration",
+ kMarkerPlayerStart, "Player Start",
+ kMarkerDeathStart, "Bloodbath Start",
+ kMarkerOff, "Off marker",
+ kMarkerOn, "On marker",
+ kMarkerAxis, "Axis marker",
+
+ kMarkerUpperLink, "Upper link",
+ kMarkerLowerLink, "Lower link",
+ kMarkerWarpDest, "Teleport target",
+ kMarkerRaingen, "Rain generator",
+
+ kSwitchToggle, "Toggle switch",
+ kSwitchMomentary, "Momentary switch",
+ kSwitchCombination, "Combination switch",
+ kSwitchPadlock, "Padlock (1-shot)",
+
+ kMiscTorch, "Torch",
+ kMiscHangingTorch, "Hanging Torch",
+
+ kWeaponItemRandom, gWeaponText[kWeaponItemRandom - kWeaponItemBase],
+ kWeaponItemShotgun, gWeaponText[kWeaponItemShotgun - kWeaponItemBase],
+ kWeaponItemTommyGun, gWeaponText[kWeaponItemTommyGun - kWeaponItemBase],
+ kWeaponItemFlareGun, gWeaponText[kWeaponItemFlareGun - kWeaponItemBase],
+ kWeaponItemVoodooDoll, gWeaponText[kWeaponItemVoodooDoll - kWeaponItemBase],
+ kWeaponItemSpearGun, gWeaponText[kWeaponItemSpearGun - kWeaponItemBase],
+ kWeaponItemShadowGun, gWeaponText[kWeaponItemShadowGun - kWeaponItemBase],
+ kWeaponItemPitchfork, gWeaponText[kWeaponItemPitchfork - kWeaponItemBase],
+ kWeaponItemSprayCan, gWeaponText[kWeaponItemSprayCan - kWeaponItemBase],
+ kWeaponItemTNT, gWeaponText[kWeaponItemTNT - kWeaponItemBase],
+
+ kAmmoItemSprayCan, gAmmoText[kAmmoItemSprayCan - kAmmoItemBase],
+ kAmmoItemTNTStick, gAmmoText[kAmmoItemTNTStick - kAmmoItemBase],
+ kAmmoItemTNTBundle, gAmmoText[kAmmoItemTNTBundle - kAmmoItemBase],
+ kAmmoItemTNTCase, gAmmoText[kAmmoItemTNTCase - kAmmoItemBase],
+ kAmmoItemTNTProximity, gAmmoText[kAmmoItemTNTProximity - kAmmoItemBase],
+ kAmmoItemTNTRemote, gAmmoText[kAmmoItemTNTRemote - kAmmoItemBase],
+ kAmmoItemTNTTimer, gAmmoText[kAmmoItemTNTTimer - kAmmoItemBase],
+ kAmmoItemShells, gAmmoText[kAmmoItemShells - kAmmoItemBase],
+ kAmmoItemShellBox, gAmmoText[kAmmoItemShellBox - kAmmoItemBase],
+ kAmmoItemBullets, gAmmoText[kAmmoItemBullets - kAmmoItemBase],
+ kAmmoItemBulletBox, gAmmoText[kAmmoItemBulletBox - kAmmoItemBase],
+ kAmmoItemAPBullets, gAmmoText[kAmmoItemAPBullets - kAmmoItemBase],
+ kAmmoItemTommyDrum, gAmmoText[kAmmoItemTommyDrum - kAmmoItemBase],
+ kAmmoItemSpear, gAmmoText[kAmmoItemSpear - kAmmoItemBase],
+ kAmmoItemSpearPack, gAmmoText[kAmmoItemSpearPack - kAmmoItemBase],
+ kAmmoItemHESpears, gAmmoText[kAmmoItemHESpears - kAmmoItemBase],
+ kAmmoItemFlares, gAmmoText[kAmmoItemFlares - kAmmoItemBase],
+ kAmmoItemHEFlares, gAmmoText[kAmmoItemHEFlares - kAmmoItemBase],
+ kAmmoItemStarFlares, gAmmoText[kAmmoItemStarFlares - kAmmoItemBase],
+ kAmmoItemRandom, "Random Ammo",
+
+ kItemKey1, gItemText[kItemKey1 - kItemBase],
+ kItemKey2, gItemText[kItemKey2 - kItemBase],
+ kItemKey3, gItemText[kItemKey3 - kItemBase],
+ kItemKey4, gItemText[kItemKey4 - kItemBase],
+ kItemKey5, gItemText[kItemKey5 - kItemBase],
+ kItemKey6, gItemText[kItemKey6 - kItemBase],
+ kItemKey7, gItemText[kItemKey7 - kItemBase],
+ kItemDoctorBag, gItemText[kItemDoctorBag - kItemBase],
+ kItemMedPouch, gItemText[kItemMedPouch - kItemBase],
+ kItemLifeEssence, gItemText[kItemLifeEssence - kItemBase],
+ kItemLifeSeed, gItemText[kItemLifeSeed - kItemBase],
+ kItemPotion1, gItemText[kItemPotion1 - kItemBase],
+ kItemFeatherFall, gItemText[kItemFeatherFall - kItemBase],
+ kItemLtdInvisibility, gItemText[kItemLtdInvisibility - kItemBase],
+ kItemInvulnerability, gItemText[kItemInvulnerability - kItemBase],
+ kItemJumpBoots, gItemText[kItemJumpBoots - kItemBase],
+ kItemRavenFlight, gItemText[kItemRavenFlight - kItemBase],
+ kItemGunsAkimbo, gItemText[kItemGunsAkimbo - kItemBase],
+ kItemDivingSuit, gItemText[kItemDivingSuit - kItemBase],
+ kItemGasMask, gItemText[kItemGasMask - kItemBase],
+ kItemClone, gItemText[kItemClone - kItemBase],
+ kItemCrystalBall, gItemText[kItemCrystalBall - kItemBase],
+ kItemDecoy, gItemText[kItemDecoy - kItemBase],
+ kItemDoppleganger, gItemText[kItemDoppleganger - kItemBase],
+ kItemReflectiveShots, gItemText[kItemReflectiveShots - kItemBase],
+ kItemRoseGlasses, gItemText[kItemRoseGlasses - kItemBase],
+ kItemShadowCloak, gItemText[kItemShadowCloak - kItemBase],
+ kItemShroomRage, gItemText[kItemShroomRage - kItemBase],
+ kItemShroomDelirium, gItemText[kItemShroomDelirium - kItemBase],
+ kItemShroomGrow, gItemText[kItemShroomGrow - kItemBase],
+ kItemShroomShrink, gItemText[kItemShroomShrink - kItemBase],
+ kItemDeathMask, gItemText[kItemDeathMask - kItemBase],
+ kItemWineGoblet, gItemText[kItemWineGoblet - kItemBase],
+ kItemWineBottle, gItemText[kItemWineBottle - kItemBase],
+ kItemSkullGrail, gItemText[kItemSkullGrail - kItemBase],
+ kItemSilverGrail, gItemText[kItemSilverGrail - kItemBase],
+ kItemTome, gItemText[kItemTome - kItemBase],
+ kItemBlackChest, gItemText[kItemBlackChest - kItemBase],
+ kItemWoodenChest, gItemText[kItemWoodenChest - kItemBase],
+ kItemAsbestosArmor, gItemText[kItemAsbestosArmor - kItemBase],
+ kItemRandom, "Random Item",
+
+ kDudeRandom, "Random Creature",
+ kDudeTommyCultist, "Cultist w/Tommy",
+ kDudeShotgunCultist, "Cultist w/Shotgun",
+ kDudeAxeZombie, "Axe Zombie",
+ kDudeFatZombie, "Fat Zombie",
+ kDudeEarthZombie, "Earth Zombie",
+ kDudeFleshGargoyle, "Flesh Gargoyle",
+ kDudeStoneGargoyle, "Stone Gargoyle",
+ kDudeFleshStatue, "Flesh Statue",
+ kDudeStoneStatue, "Stone Statue",
+ kDudePhantasm, "Phantasm",
+ kDudeHound, "Hound",
+ kDudeHand, "Hand",
+ kDudeBrownSpider, "Brown Spider",
+ kDudeRedSpider, "Red Spider",
+ kDudeBlackSpider, "Black Spider",
+ kDudeMotherSpider, "Mother Spider",
+ kDudeGillBeast, "GillBeast",
+ kDudeEel, "Eel",
+ kDudeBat, "Bat",
+ kDudeRat, "Rat",
+ kDudeGreenPod, "Green Pod",
+ kDudeGreenTentacle, "Green Tentacle",
+ kDudeFirePod, "Fire Pod",
+ kDudeFireTentacle, "Fire Tentacle",
+ kDudeMotherPod, "Mother Pod",
+ kDudeMotherTentacle, "Mother Tentacle",
+ kDudeCerberus, "Cerberus",
+ kDudeTchernobog, "Tchernobog",
+ kDudeRachel, "Rachel",
+
+ kThingTNTBarrel, "TNT Barrel",
+ kThingTNTProxArmed, "Armed Proximity Bomb",
+ kThingTNTRemArmed, "Armed Remote Bomb",
+ kThingBlueVase, "Blue Vase",
+ kThingBrownVase, "Brown Vase",
+ kThingCrateFace, "Crate Face",
+ kThingClearGlass, "Clear Glass",
+ kThingFluorescent, "Fluorescent Light",
+ kThingWallCrack, "Wall Crack",
+ kThingWoodBeam, "Wood Beam",
+ kThingWeb, "Spider's Web",
+ kThingMetalGrate1, "MetalGrate1",
+ kThingFlammableTree, "FlammableTree",
+ kThingMachineGun, "Machine Gun",
+
+ kTrapSpiketrap, "Spike Trap",
+ kTrapRocktrap, "Rock Trap",
+ kTrapSawBlade, "Saw Blade",
+ kTrapUnpoweredZap, "Electric Zap",
+ kTrapPoweredZap, "Switched Zap",
+ kTrapPendulum, "Pendulum",
+ kTrapGuillotine, "Guillotine",
+
+ kGenTrigger, "Trigger Gen",
+ kGenWaterDrip, "WaterDrip Gen",
+ kGenBloodDrip, "BloodDrip Gen",
+ kGenFireball, "Fireball Gen",
+ kGenEctoSkull, "EctoSkull Gen",
+ kGenDart, "Dart Gen",
+ kGenBubble, "Bubble Gen",
+ kGenBubbles, "Multi-Bubble Gen",
+ kGenSound, "Sound Gen",
+};
+
+
+static NAMED_TYPE wallNTNames[] =
+{
+ 0, "Normal",
+ kSwitchToggle, "Toggle switch",
+ kSwitchMomentary, "Momentary switch",
+ kWallForcefield, "Forcefield",
+ kWallCrushing, "Crushing",
+// kWallPendulum, "Pendulum",
+ kWallSpeartrap, "Speartrap",
+ kWallFlametrap, "Flametrap",
+ kWallRazordoor, "Razor Door",
+// kWallGuillotine, "Guillotine",
+ kWallClearGlass, "Clear Glass",
+ kWallStainedGlass, "Stained Glass",
+ kWallWoodBeams, "Wood Beams",
+ kWallWeb, "Spider's Web",
+// kWallMetalGrate1, "MetalGrate1"
+};
+
+
+static NAMED_TYPE sectorNTNames[] =
+{
+ 0, "Normal",
+ kSectorZMotion, "Z Motion",
+ kSectorZCrusher, "Z Crusher",
+ kSectorZSprite, "Z Motion SPRITE",
+ kSectorWarp, "Warp",
+ kSectorTeleport, "Teleporter",
+ kSectorUpperwater, "Upper water",
+ kSectorLowerwater, "Lower water",
+ kSectorSlideMarked, "Slide Marked",
+ kSectorRotateMarked, "Rotate Marked",
+ kSectorSlide, "Slide",
+ kSectorRotate, "Rotate",
+ kSectorSlideCrush, "Slide Crush",
+ kSectorRotateCrush, "Rotate Crush",
+};
+
+
+static NAMED_TYPE waveNTNames[] =
+{
+ kWaveNone, "None",
+ kWaveSquare, "Square",
+ kWaveSaw, "Saw",
+ kWaveRampup, "Ramp up",
+ kWaveRampdown, "Ramp down",
+ kWaveSine, "Sine",
+ kWaveFlicker1, "Flicker1",
+ kWaveFlicker2, "Flicker2",
+ kWaveFlicker3, "Flicker3",
+ kWaveFlicker4, "Flicker4",
+ kWaveStrobe, "Strobe",
+ kWaveSearch, "Search",
+};
+
+
+static NAMED_TYPE depthNTNames[] =
+{
+ kDepthWalk, "Walk",
+ kDepthTread, "Tread",
+ kDepthWade, "Wade",
+ kDepthSwim, "Swim*",
+};
+
+
+static NAMED_TYPE commandNTNames[] =
+{
+ kCommandOff, "OFF",
+ kCommandOn, "ON",
+ kCommandState, "State",
+ kCommandToggle, "Toggle",
+ kCommandNotState, "!State",
+ kCommandLink, "Link",
+ kCommandLock, "Lock",
+ kCommandUnlock, "Unlock",
+ kCommandToggleLock, "Tog-Lock",
+};
+
+
+static NAMED_TYPE respawnNTNames[] =
+{
+ 0, "Never",
+ 1, "Optional",
+ 2, "Always",
+ 3, "Permanent",
+};
+
+
+/***********************************************************************
+ * Functions
+ **********************************************************************/
+void ProcessKeys3D( void );
+void ProcessKeys2D( void );
+
+
+/***********************************************************************
+ * Wait()
+ *
+ * Delays for a period of time specified in 1/120th secs
+ **********************************************************************/
+void Wait(long time)
+{
+ long done = gGameClock + time;
+ while (gGameClock < done);
+}
+
+
+void BeepOkay( void )
+{
+ if (gBeep)
+ {
+ sound(6000);
+ Wait(2);
+ nosound();
+ Wait(2);
+ }
+ asksave = 1;
+}
+
+
+void BeepError( void )
+{
+ sound(1000);
+ Wait(4);
+ sound(800);
+ Wait(4);
+ nosound();
+}
+
+
+/***********************************************************************
+ * CloseBanner()
+ *
+ * Display credits when program exits
+ **********************************************************************/
+void CloseBanner( void )
+{
+ setvmode(gOldDisplayMode);
+ printf("\nBUILD engine by Ken Silverman\n");
+ printf("MAPEDIT version by Peter Freese\n");
+ printf("Copyright (c) 1994, 1995 Q Studios\n");
+}
+
+
+/***********************************************************************
+ * FillNameArray
+ *
+ **********************************************************************/
+void FillNameArray( char **names, NAMED_TYPE *ntNames, int length )
+{
+ for (int i = 0; i < length; i++)
+ {
+ names[ntNames->id] = ntNames->name;
+ ntNames++;
+ }
+}
+
+
+void InitializeNames( void )
+{
+ static char numbers[192][4];
+ FillNameArray(gBoolNames, boolNTNames, LENGTH(boolNTNames));
+ FillNameArray(gSpriteNames, spriteNTNames, LENGTH(spriteNTNames));
+ FillNameArray(gWallNames, wallNTNames, LENGTH(wallNTNames));
+ FillNameArray(gSectorNames, sectorNTNames, LENGTH(sectorNTNames));
+ FillNameArray(gWaveNames, waveNTNames, LENGTH(waveNTNames));
+ FillNameArray(gDepthNames, depthNTNames, LENGTH(depthNTNames));
+ FillNameArray(gCommandNames, commandNTNames, LENGTH(commandNTNames));
+ for (int i = 0; i < 192; i++)
+ {
+ sprintf(numbers[i], "%d", i);
+ gCommandNames[64 + i] = numbers[i];
+ }
+ FillNameArray(gRespawnNames, respawnNTNames, LENGTH(respawnNTNames));
+}
+
+
+/*******************************************************************************
+ FUNCTION: CleanUp()
+
+ DESCRIPTION: Call this function periodically to fix all the problems
+ Ken causes by rearranging the database with abandon.
+*******************************************************************************/
+void CleanUp( void )
+{
+ int nSprite, nSector, j;
+
+ dbXSectorClean();
+ dbXWallClean();
+ dbXSpriteClean();
+
+ // make sure proper sectors are on the effect lists
+ InitSectorFX();
+
+ // back propagate marker references
+ for (nSector = 0; nSector < numsectors; nSector++)
+ {
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ {
+ switch (sector[nSector].type)
+ {
+ case kSectorTeleport:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (nSprite < kMaxSprites && sprite[nSprite].statnum == kStatMarker && sprite[nSprite].type == kMarkerWarpDest)
+ sprite[nSprite].owner = (short)nSector;
+ else
+ {
+ xsector[nXSector].marker0 = -1;
+ dprintf("Invalid teleport sector -> target marker reference found\n");
+ }
+ }
+ break;
+
+ case kSectorSlide:
+ case kSectorSlideMarked:
+ case kSectorSlideCrush:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (nSprite < kMaxSprites && sprite[nSprite].statnum == kStatMarker && sprite[nSprite].type == kMarkerOff)
+ sprite[nSprite].owner = (short)nSector;
+ else
+ {
+ xsector[nXSector].marker0 = -1;
+ dprintf("Invalid slide sector -> off marker reference found\n");
+ }
+ }
+
+ if (xsector[nXSector].marker1 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker1;
+ if (nSprite < kMaxSprites && sprite[nSprite].statnum == kStatMarker && sprite[nSprite].type == kMarkerOn)
+ sprite[nSprite].owner = (short)nSector;
+ else
+ {
+ xsector[nXSector].marker1 = -1;
+ dprintf("Invalid slide sector -> on marker reference found\n");
+ }
+ }
+ break;
+
+ case kSectorRotate:
+ case kSectorRotateMarked:
+ case kSectorRotateCrush:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (nSprite < kMaxSprites && sprite[nSprite].statnum == kStatMarker && sprite[nSprite].type == kMarkerAxis)
+ sprite[nSprite].owner = (short)nSector;
+ else
+ {
+ xsector[nXSector].marker0 = -1;
+ dprintf("Invalid rotate sector -> axis marker reference found\n");
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // clear invalid marker references and add new markers as necessary
+ for (nSector = 0; nSector < numsectors; nSector++)
+ {
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ {
+ switch (sector[nSector].type)
+ {
+ case kSectorTeleport:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (sprite[nSprite].owner != (short)nSector)
+ {
+ // clone the marker
+ dprintf("Cloning teleport sector -> target marker\n");
+ int nSprite2 = insertsprite(sprite[nSprite].sectnum, kStatMarker);
+ sprite[nSprite2] = sprite[nSprite];
+ sprite[nSprite2].owner = (short)nSector;
+ xsector[nXSector].marker0 = nSprite2;
+ }
+ }
+
+ if (xsector[nXSector].marker0 < 0)
+ {
+ dprintf("Creating new teleport sector -> target marker\n");
+ int nSprite = insertsprite((short)nSector, kStatMarker);
+ sprite[nSprite].x = wall[sector[nSector].wallptr].x;
+ sprite[nSprite].y = wall[sector[nSector].wallptr].y;
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ sprite[nSprite].owner = (short)nSector;
+ sprite[nSprite].type = kMarkerWarpDest;
+ xsector[nXSector].marker0 = nSprite;
+ }
+ break;
+
+ case kSectorSlide:
+ case kSectorSlideMarked:
+ case kSectorSlideCrush:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (sprite[nSprite].owner != (short)nSector)
+ {
+ // clone the marker
+ dprintf("Cloning slide sector -> off marker\n");
+ int nSprite2 = insertsprite(sprite[nSprite].sectnum, kStatMarker);
+ sprite[nSprite2] = sprite[nSprite];
+ sprite[nSprite2].owner = (short)nSector;
+ xsector[nXSector].marker0 = nSprite2;
+ }
+ }
+
+ if (xsector[nXSector].marker0 < 0)
+ {
+ // create a new marker
+ dprintf("Creating new slide sector -> off marker\n");
+ int nSprite = insertsprite((short)nSector, kStatMarker);
+ sprite[nSprite].x = wall[sector[nSector].wallptr].x;
+ sprite[nSprite].y = wall[sector[nSector].wallptr].y;
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ sprite[nSprite].owner = (short)nSector;
+ sprite[nSprite].type = kMarkerOff;
+ xsector[nXSector].marker0 = nSprite;
+ }
+
+ if (xsector[nXSector].marker1 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker1;
+ if (sprite[nSprite].owner != (short)nSector)
+ {
+ // clone the marker
+ dprintf("Cloning slide sector -> on marker\n");
+ int nSprite2 = insertsprite(sprite[nSprite].sectnum, kStatMarker);
+ sprite[nSprite2] = sprite[nSprite];
+ sprite[nSprite2].owner = (short)nSector;
+ xsector[nXSector].marker1 = (short)nSprite2;
+ }
+ }
+
+ if (xsector[nXSector].marker1 < 0)
+ {
+ dprintf("Creating new slide sector -> on marker\n");
+ int nSprite = insertsprite((short)nSector, kStatMarker);
+ sprite[nSprite].x = wall[sector[nSector].wallptr].x;
+ sprite[nSprite].y = wall[sector[nSector].wallptr].y;
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ sprite[nSprite].owner = (short)nSector;
+ sprite[nSprite].type = kMarkerOn;
+ xsector[nXSector].marker1 = nSprite;
+ }
+ break;
+
+ case kSectorRotate:
+ case kSectorRotateMarked:
+ case kSectorRotateCrush:
+ if (xsector[nXSector].marker0 >= 0)
+ {
+ int nSprite = xsector[nXSector].marker0;
+ if (sprite[nSprite].owner != (short)nSector)
+ {
+ // clone the marker
+ dprintf("Cloning rotate sector -> axis marker\n");
+ int nSprite2 = insertsprite(sprite[nSprite].sectnum, kStatMarker);
+ sprite[nSprite2] = sprite[nSprite];
+ sprite[nSprite2].owner = (short)nSector;
+ xsector[nXSector].marker0 = nSprite2;
+ }
+ }
+
+ if (xsector[nXSector].marker0 < 0)
+ {
+ dprintf("Creating new rotate sector -> axis marker\n");
+ int nSprite = insertsprite((short)nSector, kStatMarker);
+ sprite[nSprite].x = wall[sector[nSector].wallptr].x;
+ sprite[nSprite].y = wall[sector[nSector].wallptr].y;
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ sprite[nSprite].owner = (short)nSector;
+ sprite[nSprite].type = kMarkerAxis;
+ xsector[nXSector].marker0 = nSprite;
+ }
+ break;
+
+ default:
+ xsector[nXSector].marker0 = -1;
+ xsector[nXSector].marker1 = -1;
+ break;
+ }
+ }
+ }
+
+ // delete all invalid marker sprites
+ for (nSprite = headspritestat[kStatMarker]; nSprite != -1; nSprite = j)
+ {
+ j = nextspritestat[nSprite];
+ sprite[nSprite].extra = -1;
+ sprite[nSprite].cstat |= kSpriteInvisible;
+ sprite[nSprite].cstat &= ~kSpriteBlocking;
+
+ int nSector = sprite[nSprite].owner;
+ int nXSector = sector[nSector].extra;
+
+ if (nSector >= 0 && nSector < numsectors && nXSector > 0 && nXSector < kMaxXSectors)
+ {
+ switch (sprite[nSprite].type)
+ {
+ case kMarkerOff:
+ if ( xsector[nXSector].marker0 == nSprite )
+ continue;
+ break;
+
+ case kMarkerOn:
+ if ( xsector[nXSector].marker1 == nSprite)
+ continue;
+ break;
+
+ case kMarkerAxis:
+ if ( xsector[nXSector].marker0 == nSprite)
+ continue;
+ break;
+
+ case kMarkerWarpDest:
+ if ( xsector[nXSector].marker0 == nSprite)
+ continue;
+ break;
+ }
+ }
+
+ dprintf("Deleting unreferenced marker sprite\n");
+ deletesprite((short)nSprite);
+ }
+}
+
+
+/***********************************************************************
+ * GetXSector()
+ *
+ * Returns the index of the xsector structure for a sector index. If
+ * the xsector structure does not exist, it is created.
+ **********************************************************************/
+int GetXSector( int nSector )
+{
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+
+ if (sector[nSector].extra != -1)
+ return sector[nSector].extra;
+
+ return dbInsertXSector(nSector);
+}
+
+
+/***********************************************************************
+ * GetXWall()
+ *
+ * Returns the index of the xwall structure for a wall index. If
+ * the xwall structure does not exist, it is created.
+ **********************************************************************/
+int GetXWall( int nWall )
+{
+ int nXWall;
+
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+
+ if (wall[nWall].extra > 0)
+ return wall[nWall].extra;
+
+ nXWall = dbInsertXWall(nWall);
+ return nXWall;
+}
+
+
+/***********************************************************************
+ * GetXSprite()
+ *
+ * Returns the index of the xsprite structure for a sprite index. If
+ * the xsprite structure does not exist, it is created.
+ **********************************************************************/
+int GetXSprite( int nSprite )
+{
+ int nXSprite;
+
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+
+ if (sprite[nSprite].extra > 0)
+ return sprite[nSprite].extra;
+
+ nXSprite = dbInsertXSprite(nSprite);
+ return nXSprite;
+}
+
+
+struct AUTODATA
+{
+ short type;
+ short picnum;
+ short xrepeat, yrepeat;
+ uchar hitBit;
+ short plu;
+};
+
+AUTODATA autoData[] =
+{
+ { kMarkerPlayerStart, -1, -1, -1, 0, kPLUNormal },
+ { kMarkerDeathStart, -1, -1, -1, 0, kPLUGray },
+ { kMarkerUpperLink, 2332, 16, 16, 0, -1 },
+ { kMarkerLowerLink, 2332, 16, 16, 0, -1 },
+
+ { kSwitchToggle, -1, -1, -1, 1, -1 },
+ { kSwitchMomentary, -1, -1, -1, 1, -1 },
+ { kSwitchCombination, -1, -1, -1, 1, -1 },
+ { kSwitchPadlock, kPicPadlock, -1, -1, 1, -1 },
+
+ { kMiscTorch, kPicTorchPot1, -1, -1, 1, -1 },
+ { kMiscTorch, kPicTorchPot2, -1, -1, 1, -1 },
+ { kMiscTorch, kPicTorchStand1, -1, -1, 1, -1 },
+ { kMiscTorch, kPicTorchStand2, -1, -1, 1, -1 },
+ { kMiscTorch, kPicTorchSconce1, -1, -1, 1, -1 },
+ { kMiscTorch, kPicTorchSconce2, -1, -1, 1, -1 },
+
+ { kMiscHangingTorch, kPicTorchBowl1, -1, -1, 1, -1 },
+
+ { kWeaponItemRandom, kPicRandomUp, 48, 48, 0, kPLUNormal },
+ { kWeaponItemShotgun, kPicShotgun, 48, 48, 0, kPLUNormal },
+ { kWeaponItemTommyGun, kPicTommyGun, 48, 48, 0, kPLUNormal },
+ { kWeaponItemFlareGun, kPicFlareGun, 48, 48, 0, kPLUNormal },
+ { kWeaponItemVoodooDoll, kPicVoodooDoll, 48, 48, 0, kPLUNormal },
+ { kWeaponItemSpearGun, kPicSpearGun, 48, 48, 0, kPLUNormal },
+ { kWeaponItemShadowGun, kPicShadowGun, 48, 48, 0, kPLUNormal },
+
+ { kAmmoItemSprayCan, kPicSprayCan, 40, 40, 1, kPLUNormal },
+ { kAmmoItemTNTStick, kPicTNTStick, 48, 48, 1, kPLUNormal },
+ { kAmmoItemTNTBundle, kPicTNTPak, 48, 48, 1, kPLUNormal },
+ { kAmmoItemTNTCase, kPicTNTBox, 48, 48, 1, kPLUNormal },
+ { kAmmoItemTNTProximity, kPicTNTProx, 48, 48, 0, kPLUNormal },
+ { kAmmoItemTNTRemote, kPicTNTRemote, 48, 48, 0, kPLUNormal },
+ { kAmmoItemTNTTimer, kPicTNTTimer, 48, 48, 0, kPLUNormal },
+ { kAmmoItemShells, kPicShotShells, 48, 48, 0, kPLUNormal },
+ { kAmmoItemShellBox, kPicShellBox, 48, 48, 0, kPLUNormal },
+ { kAmmoItemBullets, kPicBullets, 48, 48, 0, kPLUNormal },
+ { kAmmoItemBulletBox, kPicBulletBox, 48, 48, 0, kPLUNormal },
+ { kAmmoItemAPBullets, kPicBulletBoxAP, 48, 48, 0, kPLUNormal },
+ { kAmmoItemTommyDrum, kPicTommyDrum, 48, 48, 0, kPLUNormal },
+ { kAmmoItemSpear, kPicSpear, 48, 48, 0, kPLUNormal },
+ { kAmmoItemSpearPack, kPicSpears, 48, 48, 0, kPLUNormal },
+ { kAmmoItemHESpears, kPicSpearExplode, 48, 48, 1, kPLUNormal },
+ { kAmmoItemFlares, kPicFlares, 48, 48, 0, kPLUNormal },
+ { kAmmoItemHEFlares, kPicFlareHE, 48, 48, 1, kPLUNormal },
+ { kAmmoItemStarFlares, kPicFlareBurst, 48, 48, 0, kPLUNormal },
+ { kAmmoItemRandom, kPicRandomUp, 40, 40, 0, kPLUNormal },
+
+ { kItemKey1, kPicKey1, 32, 32, 0, kPLUNormal },
+ { kItemKey2, kPicKey2, 32, 32, 0, kPLUNormal },
+ { kItemKey3, kPicKey3, 32, 32, 0, kPLUNormal },
+ { kItemKey4, kPicKey4, 32, 32, 0, kPLUNormal },
+ { kItemKey5, kPicKey5, 32, 32, 0, kPLUNormal },
+ { kItemKey6, kPicKey6, 32, 32, 0, kPLUNormal },
+ { kItemKey7, -1, -1, -1, 0, kPLUNormal },
+ { kItemDoctorBag, kPicDocBag, 48, 48, 0, kPLUNormal },
+ { kItemMedPouch, kPicMedPouch, 40, 40, 0, kPLUNormal },
+ { kItemLifeEssence, kPicEssence, 40, 40, 0, kPLUNormal },
+ { kItemLifeSeed, kAnmLifeSeed, 40, 40, 0, kPLUNormal },
+ { kItemPotion1, kPicPotion, 40, 40, 0, kPLUNormal },
+ { kItemFeatherFall, kAnmFeather, 40, 40, 0, kPLUNormal },
+ { kItemLtdInvisibility,kAnmInviso, 40, 40, 0, kPLUNormal },
+ { kItemInvulnerability,kPicInvulnerable, 40, 40, 0, kPLUNormal },
+ { kItemJumpBoots, kPicJumpBoots, 40, 40, 0, kPLUNormal },
+ { kItemRavenFlight, kPicRavenFlight, 40, 40, 0, kPLUNormal },
+ { kItemGunsAkimbo, kPicGunsAkimbo, 40, 40, 0, kPLUNormal },
+ { kItemDivingSuit, kPicDivingSuit, 80, 64, 0, kPLUNormal },
+ { kItemGasMask, kPicGasMask, 40, 40, 0, kPLUNormal },
+ { kItemClone, kAnmClone, 40, 40, 0, kPLUNormal },
+ { kItemCrystalBall, kPicCrystalBall, 40, 40, 0, kPLUNormal },
+ { kItemDecoy, kPicDecoy, 40, 40, 0, kPLUNormal },
+ { kItemDoppleganger, kAnmDoppleganger, 40, 40, 0, kPLUNormal },
+ { kItemReflectiveShots,kAnmReflectShots, 40, 40, 0, kPLUNormal },
+ { kItemRoseGlasses, kPicRoseGlasses, 40, 40, 0, kPLUNormal },
+ { kItemShadowCloak, kAnmCloakNDagger, 64, 64, 0, kPLUNormal },
+ { kItemShroomRage, kPicShroom1, 48, 48, 0, kPLUNormal },
+ { kItemShroomDelirium,kPicShroom2, 48, 48, 0, kPLUNormal },
+ { kItemShroomGrow, kPicShroom3, 48, 48, 0, kPLUNormal },
+ { kItemShroomShrink, kPicShroom4, 48, 48, 0, kPLUNormal },
+ { kItemDeathMask, kPicDeathMask, 40, 40, 0, kPLUNormal },
+ { kItemWineGoblet, kPicGoblet, 40, 40, 0, kPLUNormal },
+ { kItemWineBottle, kPicBottle1, 40, 40, 0, kPLUNormal },
+ { kItemSkullGrail, kPicSkullGrail, 40, 40, 0, kPLUNormal },
+ { kItemSilverGrail, kPicSilverGrail, 40, 40, 0, kPLUNormal },
+ { kItemTome, kPicTome1, 40, 40, 0, kPLUNormal },
+ { kItemBlackChest, kPicBlackChest, 40, 40, 0, kPLUNormal },
+ { kItemWoodenChest, kPicWoodChest, 40, 40, 0, kPLUNormal },
+ { kItemAsbestosArmor, kPicAsbestosSuit, 80, 64, 0, kPLUNormal },
+ { kItemRandom, kPicRandomUp, 40, 40, 0, kPLUNormal },
+
+ { kDudeRandom, kPicRandomUp, 64, 64, 1, kPLUNormal },
+ { kDudeTommyCultist, kAnmCultistA1, 40, 40, 1, kPLUCultist2 },
+ { kDudeShotgunCultist,kPicShotgunIdle, 40, 40, 1, kPLUNormal },
+ { kDudeAxeZombie, kAnmZomb1M1, 38, 38, 1, kPLUNormal },
+ { kDudeFatZombie, kAnmZomb2M1, 40, 40, 1, kPLUNormal },
+ { kDudeEarthZombie, kPicEarthIdle, 38, 38, 1, kPLUNormal },
+ { kDudeFleshGargoyle, kAnmGargoyleM1, 40, 40, 1, kPLUNormal },
+ { kDudeStoneGargoyle, kAnmGargoyleM1, 40, 40, 1, kPLUGray },
+ { kDudeFleshStatue, kAnmGargStatue, 40, 40, 1, kPLUNormal },
+ { kDudeStoneStatue, kAnmGargStatue, 40, 40, 1, kPLUGray },
+ { kDudePhantasm, kAnmPhantasmM1, 40, 40, 1, kPLUNormal },
+ { kDudeHound, kAnmHellM2, 40, 40, 1, kPLUNormal },
+ { kDudeHand, kAnmHandM1, 32, 32, 1, kPLUNormal },
+ { kDudeBrownSpider, kAnmSpiderM1, 16, 16, 1, kPLUSpider1 },
+ { kDudeRedSpider, kAnmSpiderM1B, 24, 24, 1, kPLUSpider2 },
+ { kDudeBlackSpider, kAnmSpiderM1C, 32, 32, 1, kPLUSpider3 },
+ { kDudeMotherSpider, kAnmSpiderM1D, 40, 40, 1, kPLUNormal },
+ { kDudeGillBeast, kAnmGillM1, 48, 48, 1, kPLUNormal },
+ { kDudeEel, kAnmEelM1, 32, 32, 1, kPLUNormal },
+ { kDudeBat, kAnmBatM2, 32, 32, 1, kPLUNormal },
+ { kDudeRat, kAnmRatM1, 24, 24, 1, kPLUNormal },
+ { kDudeGreenPod, kAnmPodM1, 32, 32, 1, kPLUNormal },
+ { kDudeGreenTentacle, kAnmTentacleM1, 32, 32, 1, kPLUNormal },
+ { kDudeFirePod, kAnmPodM1, 48, 48, 1, kPLURed },
+ { kDudeFireTentacle, kAnmTentacleM1, 48, 48, 1, kPLURed },
+ { kDudeMotherPod, kAnmPodM1, 64, 64, 1, kPLUGrayish },
+ { kDudeMotherTentacle,kAnmTentacleM1, 64, 64, 1, kPLUGrayish },
+ { kDudeCerberus, kAnmCerberusM1, 32, 32, 1, kPLUNormal },
+ { kDudeTchernobog, kAnmTchernobogM1, 40, 40, 1, kPLUNormal },
+ { kDudeRachel, kAnmRachelM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer1, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer2, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer3, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer4, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer5, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer6, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer7, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer8, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudePlayer_Owned, kAnmCultistM1, 40, 40, 1, kPLUNormal },
+ { kDudeHound_Owned, kAnmHellM2, 40, 40, 1, kPLUNormal },
+ { kDudeEel_Owned, kAnmEelM1, 32, 32, 1, kPLUNormal },
+ { kDudeSpider_Owned, kAnmSpiderM1, 16, 16, 1, kPLUNormal },
+
+ { kThingTNTBarrel, kPicTNTBarrel, 64, 64, 1, kPLUNormal },
+ { kThingTNTProxArmed, kAnmTNTProxArmed, 40, 40, 1, kPLUNormal },
+ { kThingTNTRemArmed, kAnmTNTRemArmed, 40, 40, 1, kPLUNormal },
+ { kThingBlueVase, kPicVase1, -1, -1, 1, kPLUNormal },
+ { kThingBrownVase, kPicVase2, -1, -1, 1, kPLUNormal },
+ { kThingCrateFace, kPicCrateFace, -1, -1, 1, kPLUNormal },
+ { kThingClearGlass, kPicGlass, -1, -1, 1, kPLUNormal },
+ { kThingFluorescent, kPicFluorescent, -1, -1, 1, kPLUNormal },
+ { kThingWallCrack, kPicWallCrack, -1, -1, 1, kPLUNormal },
+ { kThingWoodBeam, kPicWoodBeam, -1, -1, 1, kPLUNormal },
+ { kThingWeb, kPicBlockWeb, -1, -1, 1, kPLUNormal },
+ { kThingMetalGrate1, kPicMetalGrate1, -1, -1, 1, -1 },
+ { kThingFlammableTree,-1, -1, -1, 1, -1 },
+ { kThingMachineGun, -1, 64, 64, 0, kPLUNormal },
+
+ { kTrapSpiketrap, kAnmFloorSpike, 64, 64, 0, kPLUNormal },
+ { kTrapRocktrap, -1, 64, 64, 0, kPLUNormal },
+ { kTrapSatellite, -1, -1, -1, 0, kPLUNormal },
+ { kTrapSawBlade, kAnmCircSaw1, 27, 32, 0, kPLUNormal },
+ { kTrapUnpoweredZap, kAnmElectrify, -1, -1, 0, kPLUNormal },
+ { kTrapPoweredZap, kAnmElectrify, -1, -1, 0, kPLUNormal },
+ { kTrapPendulum, kAnmPendulum, -1, -1, 0, kPLUNormal },
+ { kTrapGuillotine, kPicGuillotine, -1, -1, 0, kPLUNormal },
+};
+
+
+/***********************************************************************
+ * AutoAdjustSprites()
+ *
+ * Adjust sprite x/yrepeat values for oddly sized sprites
+ **********************************************************************/
+void AutoAdjustSprites(void)
+{
+ dprintf("Auto adjusting sprite attributes... ");
+ for ( short nSprite = 0; nSprite < kMaxSprites; nSprite++ )
+ {
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->statnum < kMaxStatus )
+ {
+ int i, iPicnum = -1, iType = -1;
+
+ // clear out types having no name (probably invalid)
+ if ( pSprite->type != 0 && gSpriteNames[sprite[nSprite].type] == NULL )
+ pSprite->type = 0;
+
+ // look up by picnum
+ for ( i = 0; i < LENGTH(autoData); i++)
+ {
+ if ( autoData[i].picnum >= 0 && autoData[i].picnum == pSprite->picnum )
+ {
+ iPicnum = i;
+ break;
+ }
+ }
+
+ // look up by type
+ for ( i = 0; i < LENGTH(autoData); i++)
+ {
+ if ( autoData[i].type == pSprite->type )
+ {
+ iType = i;
+ break;
+ }
+ }
+
+ i = -1;
+
+ // this set of rules determine which row to use from the autoData table
+ if ( iPicnum >= 0 )
+ i = iPicnum;
+ if ( iType >= 0 )
+ i = iType;
+ if ( iPicnum >= 0 && autoData[iPicnum].type == pSprite->type )
+ i = iPicnum;
+
+ // if nothing found, ignore this sprite
+ if ( i < 0 )
+ continue;
+
+ // convert spin sprite flags to face sprites
+ if ( (pSprite->cstat & kSpriteSpin) == kSpriteSpin )
+ pSprite->cstat &= ~kSpriteSpin;
+
+ short type = autoData[i].type;
+ if ( type == kMarkerPlayerStart || type == kMarkerDeathStart)
+ {
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatDefault);
+
+ int nXSprite = GetXSprite(nSprite);
+ xsprite[nXSprite].data1 &= 7; // force 0..7 player range
+ pSprite->picnum = (short)(kPicStart1 + xsprite[nXSprite].data1);
+ }
+ else if ( type == kMarkerUpperLink || type == kMarkerLowerLink )
+ {
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatDefault);
+
+ int nXSprite = GetXSprite(nSprite);
+
+ if (type == kMarkerUpperLink)
+ pSprite->cstat &= ~kSpriteFlipY;
+ else
+ pSprite->cstat |= kSpriteFlipY;
+ }
+ else if ( type >= kSwitchBase && type < kSwitchMax )
+ {
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatDefault);
+ GetXSprite(nSprite);
+ }
+ else if ( type >= kWeaponItemBase && type < kWeaponItemMax )
+ {
+ if ( (pSprite->cstat & kSpriteRMask) != kSpriteFace )
+ continue;
+
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatItem);
+ }
+ else if ( type >= kAmmoItemBase && type < kAmmoItemMax )
+ {
+ if ( (pSprite->cstat & kSpriteRMask) != kSpriteFace )
+ continue;
+
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatItem);
+ }
+ else if ( type >= kItemBase && type < kItemMax )
+ {
+ if ( (pSprite->cstat & kSpriteRMask) != kSpriteFace )
+ continue;
+
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatItem);
+ }
+ else if ( type >= kDudeBase && type < kDudeMax )
+ {
+ if ( (pSprite->cstat & kSpriteRMask) != kSpriteFace )
+ continue;
+
+ pSprite->cstat &= ~kSpriteBlocking;
+ changespritestat(nSprite, kStatDude);
+ GetXSprite(nSprite);
+ }
+ else if ( type >= kThingBase && type < kThingMax )
+ {
+// if ( (pSprite->cstat & kSpriteRMask) != kSpriteFace )
+// continue;
+
+ pSprite->cstat |= kSpriteBlocking;
+ changespritestat(nSprite, kStatThing);
+ GetXSprite(nSprite);
+ }
+ else if ( type >= kTrapBase && type < kTrapMax )
+ {
+ // these get moved onto their appropriate stat lists in trInit()
+ changespritestat(nSprite, kStatTraps);
+ GetXSprite(nSprite);
+ }
+ else
+ changespritestat(nSprite, kStatDefault);
+
+ pSprite->type = type;
+
+ if ( autoData[i].picnum >= 0 )
+ pSprite->picnum = autoData[i].picnum;
+
+ if ( autoData[i].xrepeat >= 0 )
+ pSprite->xrepeat = (uchar)autoData[i].xrepeat;
+
+ if ( autoData[i].yrepeat >= 0 )
+ pSprite->yrepeat = (uchar)autoData[i].yrepeat;
+
+ if ( autoData[i].hitBit )
+ pSprite->cstat |= kSpriteHitscan;
+ else
+ pSprite->cstat &= ~kSpriteHitscan;
+
+ if ( autoData[i].plu >= 0 )
+ pSprite->pal = (uchar)autoData[i].plu;
+
+ // make sure Things & Dudes aren't in the floor or ceiling
+ if ( pSprite->statnum == kStatThing || pSprite->statnum == kStatDude || pSprite->statnum == kStatInactive )
+ {
+ int zTop, zBot;
+ GetSpriteExtents(pSprite, &zTop, &zBot);
+ if ( !(sector[pSprite->sectnum].ceilingstat & kSectorParallax) )
+ pSprite->z += ClipLow(sector[pSprite->sectnum].ceilingz - zTop, 0);
+ if ( !(sector[pSprite->sectnum].floorstat & kSectorParallax) )
+ pSprite->z += ClipHigh(sector[pSprite->sectnum].floorz - zBot, 0);
+ }
+ }
+ }
+ dprintf("done\n");
+}
+
+
+/***********************************************************************
+ * ShowSpriteStatistics()
+ *
+ * Generate and display game sprite statistics
+ *
+ **********************************************************************/
+void ShowSpriteStatistics(short nSprite)
+{
+ short spriteCount[ 1024 ];
+
+ memset( spriteCount, 0, sizeof(spriteCount) );
+
+ // ignore nSprite parameter and use as iterator instead
+ for (nSprite = 0; nSprite < kMaxSprites; nSprite++)
+ {
+ if ( sprite[nSprite].statnum < kMaxStatus )
+ spriteCount[ sprite[nSprite].type ]++;
+ }
+}
+
+
+/***********************************************************************
+ * ExtGetSectorCaption()
+ *
+ * Build calls this to generate the sector caption displayed on the
+ * 2D map.
+ **********************************************************************/
+const char *ExtGetSectorCaption(short nSector)
+{
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ int nXSector = sector[nSector].extra;
+
+ buffer[0] = 0;
+ if (nXSector > 0)
+ {
+ char temp[256];
+ if ( xsector[nXSector].rxID > 0 )
+ {
+ sprintf(buffer, "%i:", xsector[nXSector].rxID);
+ }
+
+ strcat(buffer, gSectorNames[sector[nSector].type]);
+
+ if ( xsector[nXSector].txID > 0 )
+ {
+ sprintf(temp, ":%i", xsector[nXSector].txID);
+ strcat(buffer, temp);
+ }
+
+ if ( xsector[nXSector].panVel != 0 )
+ {
+ sprintf(temp, " PAN(%i,%i)", xsector[nXSector].panAngle, xsector[nXSector].panVel);
+ strcat(buffer, temp);
+ }
+
+ strcat(buffer, " ");
+ strcat(buffer, gBoolNames[xsector[nXSector].state]);
+ }
+ else
+ {
+ if ( sector[nSector].type != 0 || sector[nSector].hitag != 0 )
+ sprintf(buffer,"{%i:%i}", sector[nSector].hitag, sector[nSector].type);
+ }
+
+ return buffer;
+}
+
+
+/***********************************************************************
+ * ExtGetWallCaption()
+ *
+ * Build calls this to generate the wall caption displayed on the
+ * 2D map.
+ **********************************************************************/
+const char *ExtGetWallCaption(short nWall)
+{
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ int nXWall = wall[nWall].extra;
+
+ buffer[0] = 0;
+ if (nXWall > 0)
+ {
+ char temp[256];
+ if ( xwall[nXWall].rxID > 0 )
+ {
+ sprintf(temp, "%i:", xwall[nXWall].rxID);
+ strcat(buffer, temp);
+ }
+
+ strcat(buffer, gWallNames[wall[nWall].type]);
+
+ if ( xwall[nXWall].txID > 0 )
+ {
+ sprintf(temp, ":%i", xwall[nXWall].txID);
+ strcat(buffer, temp);
+ }
+
+ if ( xwall[nXWall].panXVel != 0 || xwall[nXWall].panYVel != 0 )
+ {
+ sprintf(temp, " PAN(%i,%i)", xwall[nXWall].panXVel, xwall[nXWall].panYVel);
+ strcat(buffer, temp);
+ }
+
+ strcat(buffer, " ");
+ strcat(buffer, gBoolNames[xwall[nXWall].state]);
+ }
+ else
+ {
+ if ( wall[nWall].type != 0 || wall[nWall].hitag != 0 )
+ sprintf(buffer,"{%i:%i}", wall[nWall].hitag, wall[nWall].type);
+ }
+
+ return buffer;
+}
+
+
+/***********************************************************************
+ * ExtGetSpriteCaption()
+ *
+ * Build calls this to generate the sprite caption displayed on the
+ * 2D map.
+ **********************************************************************/
+const char *ExtGetSpriteCaption( short nSprite )
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ SPRITE *pSprite = &sprite[nSprite];
+
+ if ( pSprite->type == 0 )
+ return "";
+
+ if ( pSprite->statnum == kStatMarker )
+ return "";
+
+ char *typeName = gSpriteNames[pSprite->type];
+ if ( typeName == NULL )
+ return "";
+
+ int nXSprite = pSprite->extra;
+ if ( nXSprite > 0 )
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+
+ if ( pSprite->type == kMarkerUpperLink || pSprite->type == kMarkerLowerLink )
+ {
+ sprintf(buffer, "%s [%d]", typeName, pXSprite->data1);
+ return buffer;
+ }
+
+ buffer[0] = 0;
+ char temp[256];
+ if ( pXSprite->rxID > 0 )
+ {
+ sprintf(temp, "%i:", pXSprite->rxID);
+ strcat(buffer, temp);
+ }
+
+ strcat(buffer, typeName);
+
+ if ( pXSprite->txID > 0 )
+ {
+ sprintf(temp, ":%i", pXSprite->txID);
+ strcat(buffer, temp);
+ }
+
+ if ( pSprite->type >= kSwitchBase && pSprite->type < kMiscMax )
+ {
+ strcat(buffer, " ");
+ strcat(buffer, gBoolNames[pXSprite->state]);
+ }
+
+ return buffer;
+ }
+ else
+ return typeName;
+}
+
+
+/***********************************************************************
+ * ExtShowSectorData()
+ *
+ * Build calls this when F5 is pressed.
+ **********************************************************************/
+void ExtShowSectorData( short nSector )
+{
+ if ( keystatus[KEY_LALT] | keystatus[KEY_RALT] )
+ EditSectorData(nSector);
+ else
+ ShowSectorData(nSector);
+}
+
+
+/***********************************************************************
+ * ExtShowWallData()
+ *
+ * Build calls this when F6 is pressed.
+ **********************************************************************/
+void ExtShowWallData( short nWall )
+{
+ if ( keystatus[KEY_LALT] | keystatus[KEY_RALT] )
+ EditWallData(nWall);
+ else
+ ShowWallData(nWall);
+}
+
+
+/***********************************************************************
+ * ExtShowSpriteData()
+ *
+ * Build calls this when F6 is pressed.
+ **********************************************************************/
+void ExtShowSpriteData( short nSprite )
+{
+ if ( keystatus[KEY_LCTRL] | keystatus[KEY_RCTRL] )
+ ShowSpriteStatistics(nSprite);
+ else if ( keystatus[KEY_LALT] | keystatus[KEY_RALT] )
+ EditSpriteData(nSprite);
+ else
+ ShowSpriteData(nSprite);
+}
+
+
+/***********************************************************************
+ * ExtEditSectorData()
+ *
+ * Build calls this when F7 is pressed.
+ **********************************************************************/
+void ExtEditSectorData( short /* nSector */ )
+{
+}
+
+
+/***********************************************************************
+ * ExtEditWallData()
+ *
+ * Build calls this when F8 is pressed.
+ **********************************************************************/
+void ExtEditWallData(short /* nWall */ )
+{
+}
+
+
+/***********************************************************************
+ * ExtEditSpriteData()
+ *
+ * This function allows editing of the game specific sprite data.
+ * Build calls this when F8 is pressed.
+ **********************************************************************/
+void ExtEditSpriteData( short /* nSprite */ )
+{
+}
+
+
+/***********************************************************************
+ * ExtLoadMap()
+ *
+ * Build calls this right after the loadmap() call. Use this opportunity
+ * to preload all the necessary tiles.
+ **********************************************************************/
+void ExtLoadMap( char * )
+{
+ long i;
+
+ for (i = 0; i < numsectors; i++)
+ {
+ tilePreloadTile(sector[i].ceilingpicnum);
+ tilePreloadTile(sector[i].floorpicnum);
+ }
+
+ for (i = 0; i < numwalls; i++)
+ {
+ tilePreloadTile(wall[i].picnum);
+ if (wall[i].overpicnum >= 0)
+ tilePreloadTile(wall[i].overpicnum);
+ }
+
+ for (i = 0; i < kMaxSprites; i++)
+ {
+ if (sprite[i].statnum < kMaxStatus)
+ tilePreloadTile(sprite[i].picnum);
+ }
+
+ dprintf("ExtLoadMap: highlightsectorcnt = %d\n", highlightsectorcnt);
+}
+
+
+/***********************************************************************
+ * Overridden functions
+ **********************************************************************/
+
+#if 0
+short sectorofwall(short nWall)
+{
+ int l = 0, r = numsectors - 1, mid = 0;
+
+ if ( nWall < 0 )
+ return -1;
+
+ if ( wall[nWall].nextwall >= 0 )
+ return wall[wall[nWall].nextwall].nextsector;
+
+ while ( l <= r )
+ {
+ mid = (l + r) >> 1;
+ int nStart = sector[mid].wallptr;
+ int nEnd = nStart + sector[mid].wallnum - 1;
+ if ( nWall < nStart )
+ r = mid - 1;
+ else if (nWall > nEnd )
+ l = mid + 1;
+ else
+ break;
+ }
+
+ dassert(nWall >= sector[mid].wallptr && nWall < sector[mid].wallptr + sector[mid].wallnum);
+ return (short)mid;
+}
+
+void checksectorpointer(short nWall, short nSector)
+{
+ long x1, y1, x2, y2;
+
+ if ( nWall < 0 )
+ return;
+
+ if ( nSector < 0 )
+ return;
+
+ x1 = wall[nWall].x;
+ y1 = wall[nWall].y;
+ x2 = wall[wall[nWall].point2].x;
+ y2 = wall[wall[nWall].point2].y;
+
+ if (wall[nWall].nextwall >= 0) //Check for early exit
+ {
+ short k = wall[nWall].nextwall;
+ if ((wall[k].x == x2) && (wall[k].y == y2))
+ if ((wall[wall[k].point2].x == x1) && (wall[wall[k].point2].y == y1))
+ return;
+ }
+
+ short startwall, endwall;
+ wall[nWall].nextsector = -1;
+ wall[nWall].nextwall = -1;
+ for (short j = 0; j < numsectors; j++)
+ {
+ if (j == nSector)
+ continue;
+
+ startwall = sector[j].wallptr;
+ endwall = (short)(startwall + sector[j].wallnum - 1);
+ for (short k = startwall; k <= endwall; k++)
+ {
+ if ((wall[k].x == x2) && (wall[k].y == y2))
+ if ((wall[wall[k].point2].x == x1) && (wall[wall[k].point2].y == y1))
+ {
+ wall[nWall].nextsector = j;
+ wall[nWall].nextwall = k;
+ wall[k].nextsector = nSector;
+ wall[k].nextwall = nWall;
+ }
+ }
+ }
+}
+#endif
+
+
+void setbrightness( char /* brightness */, char * /* pal */ )
+{
+ scrSetGamma(gGamma);
+ scrSetDac(0);
+}
+
+
+int loadboard( char *filename, long *x, long *y, long *z, short *ang, short *sectnum )
+{
+ // don't try to load it if it doesn't exist
+ if (access(filename, F_OK) == -1)
+ return -1;
+
+ dbLoadMap(filename, x, y, z, ang, sectnum);
+
+ CleanUp();
+
+ // fix sprite type attributes
+ AutoAdjustSprites();
+
+ if (qsetmode != 200) // in 2D mode
+ {
+ sprintf( buffer, "Map Revisions: %i", gMapRev );
+ printext16(0 * 8 + 4, 0 * 8 + 28, 11, 8, buffer, 0);
+ }
+
+ dprintf("loadboard: highlightsectorcnt = %d\n", highlightsectorcnt);
+
+ return 0;
+}
+
+// this replaces ken's timer handler
+void bstubClockStrobe( void )
+{
+ totalclock = ++gGameClock;
+ keytimerstuff();
+}
+
+
+void inittimer( void )
+{
+ timerRegisterClient(bstubClockStrobe, kTimerRate);
+ timerInstall();
+}
+
+void uninittimer( void )
+{
+ timerRemove();
+}
+
+void initkeys( void )
+{
+ keyInstall();
+}
+
+void uninitkeys( void )
+{
+ keyRemove();
+}
+
+
+void faketimerhandler( void )
+{ }
+
+
+/***********************************************************************
+ * saveboard()
+ *
+ * Saves the given board from memory into the specified filename. Returns -1
+ * if unable to save. If no extension is given, .MAP will be appended to the
+ * filename.
+ **********************************************************************/
+int saveboard(char *filename, long *x, long *y, long *z, short *ang,
+ short *sectnum)
+{
+ // fix up floor and ceiling shade values for sectors that have dynamic lighting
+ UndoSectorLighting();
+
+ CleanUp();
+
+ // fix sprite type attributes
+ AutoAdjustSprites();
+
+ dbSaveMap(filename, *x, *y, *z, *ang, *sectnum);
+
+ asksave = 0;
+ return 0;
+}
+
+
+/***********************************************************************
+ * ExtSaveMap()
+ *
+ * Build calls this right after the savemap() call. This function saves
+ * the game and object specific tag information.
+ **********************************************************************/
+void ExtSaveMap( char * )
+{
+}
+
+
+/***********************************************************************
+ * ExtPreCheckKeys()
+ *
+ * Called before drawrooms, drawmasks and nextpage in 3D mode.
+ *
+ **********************************************************************/
+void ExtPreCheckKeys(void)
+{
+ if (qsetmode == 200) // in 3D mode
+ {
+ // animate sector lighting
+ DoSectorLighting();
+
+ // we need this because Ken called nextpage() in build.c
+ switch ( vidoption )
+ {
+ case 0: // non-chained
+ if (chainnumpages > 1)
+ gPageTable[0].begin = (unsigned)chainplace;
+ break;
+
+ case 1: // VESA 2.0 linear buffer
+ gPageTable[0].begin = (unsigned)frameplace;
+
+ case 2: // memory buffered VESA/13h
+ break;
+
+ case 3: // Tseng ET4000
+ break;
+
+ case 4: // Paradise
+ break;
+
+ case 5: // S3
+ break;
+
+ case 6: // Crystal Eyes
+ break;
+
+ case 7: // Red/Blue Stereogram
+ break;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * ExtAnalyzeSprites()
+ *
+ * Called between drawrooms and drawmasks in 3D mode.
+ *
+ **********************************************************************/
+void ExtAnalyzeSprites(void)
+{
+ int i, nOctant;
+ short nSprite, nXSprite;
+ long dx, dy;
+
+ for (i = 0; i < spritesortcnt; i++)
+ {
+ int nTile = tsprite[i].picnum;
+ dassert(nTile >= 0 && nTile < kMaxTiles);
+ int nFrames = picanm[nTile].frames + 1;
+
+ nSprite = tsprite[i].owner;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ int j = nSprite;
+
+ // KEN'S CODE TO SHADE SPRITES
+ // Add relative shading code for textures, and possibly sector cstat bit to
+ // indicate where shading comes from (i.e. shadows==floorshade, sky==ceilingshade)
+ tsprite[i].shade = (schar)ClipRange( tsprite[i].shade + 6, -128, 127 );
+ if ( (sector[tsprite[i].sectnum].ceilingstat & kSectorParallax)
+ && !(sector[tsprite[i].sectnum].floorstat & kSectorFloorShade) )
+ tsprite[i].shade = (schar)ClipRange( tsprite[i].shade + sector[tsprite[i].sectnum].ceilingshade, -128, 127 );
+ else
+ tsprite[i].shade = (schar)ClipRange( tsprite[i].shade + sector[tsprite[i].sectnum].floorshade, -128, 127 );
+
+ nXSprite = tsprite[i].extra;
+
+ switch ( picanm[nTile].view )
+ {
+ case kSpriteViewSingle:
+
+ if (nXSprite > 0)
+ {
+ dassert(nXSprite < kMaxXSprites);
+ switch ( sprite[nSprite].type )
+ {
+ case kSwitchToggle:
+ case kSwitchMomentary:
+ if ( xsprite[nXSprite].state )
+ tsprite[i].picnum = (short)(nTile + nFrames);
+ break;
+
+ case kSwitchCombination:
+ tsprite[i].picnum = (short)(nTile + xsprite[nXSprite].data1 * nFrames);
+ break;
+
+ }
+ }
+ break;
+
+ case kSpriteView5Full:
+
+ // Calculate which of the 8 angles of the sprite to draw (0-7)
+ dx = posx - tsprite[i].x;
+ dy = posy - tsprite[i].y;
+ RotateVector(&dx, &dy, -tsprite[i].ang + kAngle45 / 2);
+ nOctant = GetOctant(dx, dy);
+
+ if (nOctant <= 4)
+ {
+ tsprite[i].picnum = (short)(nTile + nOctant);
+ tsprite[i].cstat &= ~4; // clear x-flipping bit
+ }
+ else
+ {
+ tsprite[i].picnum = (short)(nTile + 8 - nOctant);
+ tsprite[i].cstat |= 4; // set x-flipping bit
+ }
+ break;
+
+ case kSpriteView8Full:
+ break;
+
+ case kSpriteView5Half:
+ break;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * ExtCheckKeys()
+ *
+ * Called just before nextpage in both 2D and 3D modes.
+ *
+ **********************************************************************/
+void ExtCheckKeys( void )
+{
+ gFrameTicks = gGameClock - gFrameClock;
+ gFrameClock += gFrameTicks;
+
+ // autosave code
+ if ( gFrameClock > gSaveTime + gSaveInterval )
+ {
+ gSaveTime = gFrameClock;
+
+ if ( asksave )
+ {
+ // fix up floor and ceiling shade values for sectors that have dynamic lighting
+ UndoSectorLighting();
+
+ CleanUp();
+
+ // fix sprite type attributes
+ AutoAdjustSprites();
+
+ dbSaveMap("AUTOSAVE.MAP", posx, posy, posz, ang, cursectnum);
+ if (qsetmode == 200) // in 3D mode
+ scrSetMessage("Map autosaved to AUTOSAVE.MAP");
+ else
+ printmessage16("Map autosaved to AUTOSAVE.MAP");
+ }
+ }
+
+ CalcFrameRate();
+
+ if (qsetmode == 200) // in 3D mode
+ {
+ // animate sector lighting
+ UndoSectorLighting();
+
+ ProcessKeys3D();
+
+ sprintf(buffer, "%3i", gFrameRate);
+ printext256(xdim - 12, 0, gStdColor[kColorWhite], -1, buffer, 1);
+ scrDisplayMessage(gStdColor[kColorWhite]);
+
+ // animate panning sectors
+ if ( gPanning )
+ DoSectorPanning();
+ }
+ else // 2D mode
+ {
+ ProcessKeys2D();
+ sprintf(buffer, "%3i", gFrameRate);
+ printext16(640 - 24, pageoffset / 640, 15, -1, buffer, 0);
+ }
+}
+
+
+/***********************************************************************
+ * EditorErrorHandler()
+ *
+ * Terminate from error condition, displaying a message in text mode.
+ *
+ **********************************************************************/
+ErrorResult EditorErrorHandler( const Error& error )
+{
+ timerRemove();
+ uninitengine();
+ keyRemove();
+ setvmode(gOldDisplayMode);
+
+ // chain to the default error handler
+ return prevErrorHandler(error);
+};
+
+
+#define kAttrTitle (kColorGreen * 16 + kColorWhite)
+
+/***********************************************************************
+ * ExtInit()
+ *
+ * Called once before BUILD.EXE makes calls to loadpics()/loadboard()
+ **********************************************************************/
+void ExtInit(void)
+{
+ char title[256];
+ int i;
+
+ void *p = NULL;
+
+ atexit(CloseBanner);
+
+ gOldDisplayMode = getvmode();
+
+ sprintf(title, "MapEdit Alpha Build [%s] -- DO NOT DISTRIBUTE", gBuildDate);
+ tioInit();
+ tioCenterString(0, 0, tioScreenCols - 1, title, kAttrTitle);
+ tioCenterString(tioScreenRows - 1, 0, tioScreenCols - 1,
+ "Copyright (c) 1994, 1995 Q Studios Corporation", kAttrTitle);
+
+ tioWindow(1, 0, tioScreenRows - 2, tioScreenCols);
+
+ if ( _grow_handles(kRequiredFiles) < kRequiredFiles )
+ ThrowError("Not enough file handles available", ES_ERROR);
+
+ InitializeNames();
+
+ tioPrint("Loading preferences");
+ gGamma = BloodINI.GetKeyInt("View", "Gamma", 0);
+ gBeep = MapEditINI.GetKeyBool("Options", "Beep", TRUE);
+ gHighlightThreshold = MapEditINI.GetKeyInt("Options", "HighlightThreshold", 40);
+ gStairHeight = MapEditINI.GetKeyInt("Options", "StairHeight", 8);
+ gOldKeyMapping = MapEditINI.GetKeyBool("Options", "OldKeyMapping", FALSE);
+ gSaveInterval = kTimerRate * MapEditINI.GetKeyBool("Options", "AutoSaveInterval", 5 * 60);
+
+ gLBIntensity = MapEditINI.GetKeyInt("LightBomb", "Intensity", 16);
+ gLBAttenuation = MapEditINI.GetKeyInt("LightBomb", "Attenuation", 0x1000);
+ gLBReflections = MapEditINI.GetKeyInt("LightBomb", "Reflections", 2);
+ gLBMaxBright = MapEditINI.GetKeyInt("LightBomb", "MaxBright", -4);
+ gLBRampDist = MapEditINI.GetKeyInt("LightBomb", "RampDist", 0x10000);
+
+ tioPrint("Initializing heap and resource system");
+ Resource::heap = new QHeap(dpmiDetermineMaxRealAlloc());
+
+ tioPrint("Initializing resource archive");
+ gSysRes.Init("BLOOD.RFF", "*.*");
+ gGuiRes.Init("GUI.RFF", NULL);
+
+ tioPrint("Initializing mouse");
+ if ( !initmouse() )
+ tioPrint("Mouse not detected");
+
+ CheckDemoDate(gSysRes);
+
+ // install our error handler
+ prevErrorHandler = errSetHandler(EditorErrorHandler);
+
+ InitEngine();
+
+ Mouse::SetRange(xdim, ydim);
+
+ tioPrint("Loading tiles");
+ if (tileInit() == 0)
+ ThrowError("ART files not found", ES_ERROR);
+
+ tioPrint("Loading cosine table");
+ trigInit(gSysRes);
+
+ scrInit();
+
+ tioPrint("Creating standard color lookups");
+ scrCreateStdColors();
+
+ dbInit();
+ visibility = 800;
+
+ kensplayerheight = 56 << 8; // eyeHeight
+
+ zmode = 0;
+ defaultspritecstat = kSpriteOriginAlign;
+
+ for (i = 0; i < kMaxSectors; i++)
+ sector[i].extra = -1;
+
+ for (i = 0; i < kMaxWalls; i++)
+ wall[i].extra = -1;
+
+ for (i = 0; i < kMaxSprites; i++)
+ sprite[i].extra = -1;
+
+// qsetmode = 200; // inform the rest of the code that we're in 3D mode
+ scrSetGameMode();
+}
+
+
+/***********************************************************************
+ * ExtUnInit()
+ *
+ * Called once right before BUILD.EXE uninitengine()
+ **********************************************************************/
+void ExtUnInit(void)
+{
+ unlink("AUTOSAVE.MAP");
+}
diff --git a/SRC/BSTUB.H b/SRC/BSTUB.H
new file mode 100644
index 0000000..7cf7b6a
--- /dev/null
+++ b/SRC/BSTUB.H
@@ -0,0 +1,60 @@
+#ifndef __BSTUB_H
+#define __BSTUB_H
+
+extern int gHighlightThreshold;
+extern int gStairHeight;
+extern int gLBIntensity;
+extern int gLBAttenuation;
+extern int gLBReflections;
+extern int gLBMaxBright;
+extern int gLBRampDist;
+extern int gPalette;
+extern BOOL gBeep;
+extern BOOL gOldKeyMapping;
+extern BOOL gPanning;
+extern char *gBoolNames[2];
+extern char *gSpriteNames[1024];
+extern char *gWallNames[1024];
+extern char *gSectorNames[1024];
+extern char *gWaveNames[16];
+extern char *gDepthNames[4];
+extern char *gCommandNames[256];
+extern char *gRespawnNames[4];
+extern short grid, gridlock;
+extern long zoom;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *ExtGetSectorCaption( short nSector );
+const char *ExtGetWallCaption( short nWall );
+const char *ExtGetSpriteCaption( short nSprite );
+void ExtShowSectorData( short nSector );
+void ExtShowWallData( short nWall );
+void ExtShowSpriteData( short nSprite );
+void ExtEditSectorData( short nSector );
+void ExtEditWallData( short nWall );
+void ExtEditSpriteData( short nSprite );
+void ExtLoadMap( char *fname );
+void ExtSaveMap( char *fname );
+void ExtPreCheckKeys( void );
+void ExtCheckKeys( void );
+void ExtInit( void );
+void ExtUnInit( void );
+void ExtAnalyzeSprites( void );
+
+#ifdef __cplusplus
+}
+#endif
+
+void Wait(long time);
+void BeepOkay( void );
+void BeepError( void );
+void CleanUp( void );
+int GetXSector( int nSector );
+int GetXWall( int nWall );
+int GetXSprite( int nSprite );
+void AutoAdjustSprites( void );
+
+#endif
diff --git a/SRC/BSTUBIO.CPP b/SRC/BSTUBIO.CPP
new file mode 100644
index 0000000..798590a
--- /dev/null
+++ b/SRC/BSTUBIO.CPP
@@ -0,0 +1,245 @@
+#include <stdlib.h>
+#include <string.h>
+#include <io.h>
+#include <fcntl.h>
+
+#include "db.h"
+#include "engine.h"
+#include "error.h"
+
+#include <memcheck.h>
+
+/***********************************************************************
+ * ImportBuildMap()
+ *
+ **********************************************************************/
+int ImportBuildMap(
+ char *filename,
+ long *daposx,
+ long *daposy,
+ long *daposz,
+ short *daang,
+ short *dacursectnum )
+{
+ int fil;
+ short int i, numsprites;
+ long mapversion;
+
+ if ((fil = open(filename,O_BINARY|O_RDWR,S_IREAD)) == -1)
+ {
+ mapversion = 6L;
+ return -1;
+ }
+
+ read(fil,&mapversion,4);
+ if (mapversion != 6L)
+ return -1;
+
+ *gMapName = '\0';
+ *gMapAuthor = '\0';
+ gSkyCount = 1;
+ pskyoff[0] = 0;
+
+ dbInit(); // calls initspritelists() too!
+
+ memset(show2dsprite, 0, sizeof(show2dsprite));
+ memset(show2dwall, 0, sizeof(show2dwall));
+
+ read(fil,daposx,4);
+ read(fil,daposy,4);
+ read(fil,daposz,4);
+ read(fil,daang,2);
+ read(fil,dacursectnum,2);
+
+ read(fil,&numsectors,2);
+ read(fil,sector,sizeof(SECTOR)*numsectors);
+
+ read(fil,&numwalls,2);
+ read(fil,wall,sizeof(WALL)*numwalls);
+
+ read(fil,&numsprites,2);
+ read(fil,sprite,sizeof(SPRITE)*numsprites);
+
+ for( i=0; i<numsprites; i++)
+ insertsprite(sprite[i].sectnum,sprite[i].statnum);
+
+ //Must be after loading sectors, etc!
+ updatesector(*daposx,*daposy,dacursectnum);
+
+ close(fil);
+
+ highlightcnt = -1;
+ highlightsectorcnt = -1;
+
+ precache();
+ printmessage16("Map imported successfully.");
+
+ updatenumsprites();
+ startposx = posx; //this is same
+ startposy = posy;
+ startposz = posz;
+ startang = ang;
+ startsectnum = cursectnum;
+
+ return 0;
+}
+
+
+/***********************************************************************
+ * ExportBuildMap()
+ *
+ **********************************************************************/
+int ExportBuildMap(
+ char *filename,
+ long *daposx,
+ long *daposy,
+ long *daposz,
+ short *daang,
+ short *dacursectnum )
+{
+ int fil;
+ short int i, j, numsprites;
+ long mapversion = 6L;
+
+ if ((fil = open(filename,O_BINARY|O_TRUNC|O_CREAT|O_WRONLY,S_IWRITE)) == -1)
+ return -1;
+
+ write(fil,&mapversion,4);
+ write(fil,daposx,4);
+ write(fil,daposy,4);
+ write(fil,daposz,4);
+ write(fil,daang,2);
+ write(fil,dacursectnum,2);
+
+ write(fil,&numsectors,2);
+ write(fil,sector,sizeof(SECTOR)*numsectors);
+
+ write(fil,&numwalls,2);
+ write(fil,wall,sizeof(WALL)*numwalls);
+
+ numsprites = 0;
+ for(j=0;j<kMaxStatus;j++)
+ {
+ i = headspritestat[j];
+ while (i != -1)
+ {
+ numsprites++;
+ i = nextspritestat[i];
+ }
+ }
+ write(fil,&numsprites,2);
+
+ for(j=0;j<kMaxStatus;j++)
+ {
+ i = headspritestat[j];
+ while (i != -1)
+ {
+ write(fil,&sprite[i],sizeof(SPRITE));
+ i = nextspritestat[i];
+ }
+ }
+
+ close(fil);
+ return 0;
+}
+
+#if 0
+/***************************************************************************
+ KEN'S TAG DEFINITIONS: (Please define your own tags for your games)
+
+ sector[?].lotag = 0 Normal sector
+ sector[?].lotag = 1 If you are on a sector with this tag, then all sectors
+ with same hi tag as this are operated. Once.
+ sector[?].lotag = 2 Same as sector[?].tag = 1 but this is retriggable.
+ sector[?].lotag = 3 A really stupid sector that really does nothing now.
+ sector[?].lotag = 4 A sector where you are put closer to the floor
+ (such as the slime in DOOM1.DAT)
+ sector[?].lotag = 5 A really stupid sector that really does nothing now.
+ sector[?].lotag = 6 A normal door - instead of pressing D, you tag the
+ sector with a 6. The reason I make you edit doors
+ this way is so that can program the doors
+ yourself.
+ sector[?].lotag = 7 A door the goes down to open.
+ sector[?].lotag = 8 A door that opens horizontally in the middle.
+ sector[?].lotag = 9 A sliding door that opens vertically in the middle.
+ -Example of the advantages of not using BSP tree.
+ sector[?].lotag = 10 A warping sector with floor and walls that shade.
+ sector[?].lotag = 11 A sector with all walls that do X-panning.
+ sector[?].lotag = 12 A sector with walls using the dragging function.
+ sector[?].lotag = 13 A sector with some swinging doors in it.
+ sector[?].lotag = 14 A revolving door sector.
+ sector[?].lotag = 15 A subway track.
+ sector[?].lotag = 16 A true double-sliding door.
+ sector[?].lotag = 17 A true double-sliding door for subways only.
+
+ wall[?].lotag = 0 Normal wall
+ wall[?].lotag = 1 Y-panning wall
+ wall[?].lotag = 2 Switch - If you flip it, then all sectors with same hi
+ tag as this are operated.
+ wall[?].lotag = 3 Marked wall to detemine starting dir. (sector tag 12)
+ wall[?].lotag = 4 Mark on the shorter wall closest to the pivot point
+ of a swinging door. (sector tag 13)
+ wall[?].lotag = 5 Mark where a subway should stop. (sector tag 15)
+ wall[?].lotag = 6 Mark for true double-sliding doors (sector tag 16)
+ wall[?].lotag = 7 Water fountain
+
+ sprite[?].lotag = 0 Normal sprite
+ sprite[?].lotag = 1 If you press space bar on an AL, and the AL is tagged
+ with a 1, he will turn evil.
+ sprite[?].lotag = 2 When this sprite is operated, a bomb is shot at its
+ position.
+ sprite[?].lotag = 3 Rotating sprite.
+ sprite[?].lotag = 4 Sprite switch.
+ sprite[?].lotag = 5 Basketball hoop score.
+**************************************************************************/
+
+/***********************************************************************
+ * ConvertKenTriggers()
+ *
+ * Called in loadboard and saveboard. Converts Ken's triggers
+ * to XSECTOR data, leaving the sector lotag and hitag intact.
+ **********************************************************************/
+
+void ConvertKenTriggers( void )
+{
+ int nSector, nXSector;
+
+ for ( nSector = 0; nSector < numsectors; nSector++ )
+ {
+ switch (sector[nSector].lotag)
+ {
+ case 1: // kSectorTriggerOnEnterOnce
+ case 2: // kSectorTriggerOnEnter
+ case 4: // kSectorWadeWater
+ case 6: // kSectorRaiseDoor
+ case 7: // kSectorLowerDoor
+ case 8: // kSectorHorizontalDoors
+ case 9: // kSectorVerticalDoors
+ case 10: // kSectorTeleport
+ case 11: // kSectorXPanWalls
+ case 12: // kSectorDragWalls
+ case 13: // kSectorContainsSwingingDoors
+ case 14: // kSectorRevolveWalls
+ case 16: // kSectorPeggedVerticalDoors
+ case 100: // kSectorMovesUp
+ case 101: // kSectorMovesDown
+ break;
+
+ default:
+ break;
+ }
+
+ // get or create an extra structure
+ if ( sector[nSector].extra > 0 ) {
+ nXSector = sector[nSector].extra;
+ dassert(nXSector < kMaxXSectors);
+ } else {
+ nXSector = GetXSector(nSector);
+ dassert(nXSector < kMaxXSectors);
+ }
+ // fill the extra here...
+ }
+}
+#endif
+
+
diff --git a/SRC/BUILD.H b/SRC/BUILD.H
new file mode 100644
index 0000000..96e7e42
--- /dev/null
+++ b/SRC/BUILD.H
@@ -0,0 +1,188 @@
+#ifndef __BUILD_H
+#define __BUILD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "engine.h"
+
+/***********************************************************************
+ * Build global variables
+ **********************************************************************/
+
+extern short ang;
+
+extern long vel, svel, angvel;
+
+extern short asksave; // set to 1 to indicate changes have been made
+
+//static char boardfilename[13];
+
+extern char buildkeys[];
+
+//static short brightness;
+
+extern short cursectnum;
+
+//static long fillist[640];
+
+//static int gettilezoom;
+
+//static short grid;
+
+//static short gridlock;
+
+extern short highlight[kMaxWalls];
+
+extern short highlightsector[kMaxSectors];
+
+extern short highlightsectorcnt;
+
+extern long horiz;
+
+//static long hvel;
+
+//static short localartfreq[kMaxTiles];
+
+//static short localartlookup[kMaxTiles];
+
+//static short localartlookupnum;
+
+//static long lockclock, long lockspeed;
+
+//static char menuname[MAXMENUFILES][17], curpath[80], menupath[80];
+//static long menunamecnt, menuhighlight;
+
+extern char names[kMaxTiles][17];
+
+//static long numsprites;
+
+//static short oldmousebstatus;
+
+extern long posx, posy, posz;
+
+//static char pskysearch[kMaxSectors];
+
+//static long repeatcountx, repeatcounty;
+
+//static char scantoasc[128];
+
+//static char scantoascwithshift[128];
+
+//static short showtags;
+
+//static char somethingintab = 255;
+
+//static long synctics = 0, lockclock = 0;
+
+extern short temppicnum, tempcstat, templotag, temphitag, tempextra;
+extern char tempshade, temppal, tempxrepeat, tempyrepeat;
+extern char somethingintab;
+
+extern char totalclockinreal; // unused
+
+extern long whitecol;
+
+extern long zlock, zmode, kensplayerheight;
+extern short defaultspritecstat;
+
+//static long zoom;
+
+/***********************************************************************
+ * Build function prototypes
+ **********************************************************************/
+
+void adjustmark( long, long, long );
+
+char changechar( char dachar, long dadir, char smooshyalign, char boundcheck );
+
+int checkautoinsert( long, long, long );
+
+void checksectorpointer( short nWall, short nSector );
+
+void clearmidstatbar16( void ); // Clear middle of status bar
+
+int clockdir( long );
+
+void copysector( short nSector1, short nSector2, short nWall, char);
+
+void deletepoint( short nWall );
+
+void deletesector( short nSector );
+
+void drawtilescreen( long, long );
+
+void editinput( void );
+
+void fillsector( short nSector, char );
+
+void fixrepeats( short wall );
+
+void fixspritesectors( void );
+
+void flipwalls( short, short );
+
+int getcard( void );
+
+void getfilenames( char * );
+
+int getlinehighlight( int x, int y );
+
+short getnumber16(char namestart[80], short num, long maxnumber);
+
+short getnumber256(char namestart[80], short num, long maxnumber);
+
+void getpoint( int x, int y, int *a, int *b );
+/* converts a screen x,y into a map x,y in 2D mode */
+
+int getpointhighlight( int x, int y );
+/* returns closest wall vertice or sprite | 0x4000 to the specified point */
+
+long gettile( long );
+
+void initmenupaths( char * );
+
+void inittimer( void );
+
+void insertpoint( short nWall, long x, long y );
+
+void keytimerstuff( void );
+
+void loadnames( void );
+
+int loopinside( long x, long y, short nWall );
+
+short loopnumofsector( short, short, short nWall );
+
+int menuselect( void );
+
+void movewalls( short nWall, int );
+
+int numloopsofsector( short nSector );
+
+void overheadeditor( void );
+
+void printcoords16( long x, long y, short ang );
+
+void showmouse( void );
+
+void showsectordata( short nSector );
+
+void showspritedata( short nSprite );
+
+void showwalldata( short nWall );
+
+void sortfilenames( void );
+
+void uninittimer( void );
+
+void updatenumsprites( void );
+
+int whitelinescan( short nWall );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/SRC/C.TAG b/SRC/C.TAG
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/SRC/C.TAG
diff --git a/SRC/CONTROLS.CPP b/SRC/CONTROLS.CPP
new file mode 100644
index 0000000..e24da02
--- /dev/null
+++ b/SRC/CONTROLS.CPP
@@ -0,0 +1,358 @@
+#include <stdlib.h>
+#include <i86.h>
+#include <string.h>
+
+#include "controls.h"
+#include "engine.h"
+#include "globals.h"
+#include "key.h"
+#include "debug4g.h"
+#include "misc.h"
+#include "timer.h"
+#include "mouse.h"
+
+#include <memcheck.h>
+
+
+// virtual key scan codes
+#define MOUSE_LBUTTON 0xE0
+#define MOUSE_RBUTTON 0xE1
+#define MOUSE_MBUTTON 0xE2
+
+#define kAngAccel1 12
+#define kAngAccel2 256 //12
+
+// number of ticks at which turning switches to fast turn
+#define kTurnThresh 30
+
+//#define KEYS DOOM2
+#define DOOM1KEYS
+
+#ifdef DOOM2KEYS
+#define kAngSpeed1 92 // Doom II speed
+#define kAngSpeed2 184 // Doom II speed
+#endif
+
+#ifdef DOOM1KEYS
+#define kAngSpeed1 93 // Doom I speed
+#define kAngSpeed2 186 // Doom I speed
+#endif
+
+
+INPUT gInput;
+
+// these variables store the keyboard analog input accelerations
+static volatile schar iForward, iStrafe;
+static volatile uchar iTicks;
+static volatile uchar iTurnL = 0, iTurnR = 0;
+static volatile int iTurnCount = 0;
+
+static BOOL useMouse = FALSE;
+
+struct {
+ char *iniKeyName;
+ BYTE defScanCode;
+} controlInfo[] = {
+ "Forward1", KEY_UP,
+ "Forward2", KEY_PADUP,
+ "Backward1", KEY_DOWN,
+ "Backward2", KEY_PADDOWN,
+ "Left1", KEY_LEFT,
+ "Left2", KEY_PADLEFT,
+ "Right1", KEY_RIGHT,
+ "Right2", KEY_PADRIGHT,
+ "Jump1", KEY_X,
+ "Jump2", 0,
+ "Crouch1", KEY_C,
+ "Crouch2", 0,
+ "StrafeOn1", KEY_LALT,
+ "StrafeOn2", KEY_RALT,
+ "StrafeLeft1", KEY_COMMA,
+ "StrafeLeft2", 0,
+ "StrafeRight1", KEY_PERIOD,
+ "StrafeRight2", 0,
+ "LookUp1", KEY_PAGEUP,
+ "LookUp2", KEY_PADPAGEUP,
+ "LookDown1", KEY_PAGEDN,
+ "LookDown2", KEY_PADPAGEDN,
+ "AimUp1", KEY_HOME,
+ "AimUp2", KEY_PADHOME,
+ "AimDown1", KEY_END,
+ "AimDown2", KEY_PADEND,
+ "LookCenter1", KEY_PAD5,
+ "LookCenter2", 0,
+ "Action1", KEY_SPACE,
+ "Action2", 0,
+ "Fire1", KEY_LCTRL,
+ "Fire2", KEY_RCTRL,
+ "AltFire1", KEY_Z,
+ "AltFire2", 0,
+ "RunOn1", KEY_LSHIFT,
+ "RunOn2", KEY_RSHIFT,
+ "RunToggle1", KEY_SCROLLLOCK,
+ "RunToggle2", 0,
+ "Weapon1", KEY_1,
+ "Weapon2", KEY_2,
+ "Weapon3", KEY_3,
+ "Weapon4", KEY_4,
+ "Weapon5", KEY_5,
+ "Weapon6", KEY_6,
+ "Weapon7", KEY_7,
+ "Weapon8", KEY_8,
+ "Weapon9", KEY_9,
+ "Weapon0", KEY_0,
+ "AutomapToggle", KEY_TAB,
+ "ItemLeft1", KEY_LBRACE,
+ "ItemLeft2", 0,
+ "ItemRight1", KEY_RBRACE,
+ "ItemRight2", 0,
+ "ItemDrop1", KEY_BACKSPACE,
+ "ItemDrop2", 0,
+ "ItemUse1", KEY_PADENTER,
+ "ItemUse2", KEY_ENTER,
+ "Pause", KEY_PAUSE,
+};
+
+volatile BYTE *control[ kMaxControls ];
+
+void ctrlStrobeKey( void )
+{
+ BYTE shift = *control[kRunOn1] || *control[kRunOn2];
+ iTicks++;
+ iTurnL = iTurnR = 0;
+
+ if ( *control[kStrafeOn1] || *control[kStrafeOn2] )
+ {
+ if ( *control[kLeft1] || *control[kLeft2] )
+ iStrafe += 1 + shift;
+
+ if ( *control[kRight1] || *control[kRight2] )
+ iStrafe -= 1 + shift;
+ }
+ else
+ {
+ if ( *control[kStrafeLeft1] || *control[kStrafeLeft2] )
+ iStrafe += 1 + shift;
+
+ if ( *control[kStrafeRight1] || *control[kStrafeRight2] )
+ iStrafe -= 1 + shift;
+
+ if ( *control[kLeft1] || *control[kLeft2] )
+ iTurnL = 1;
+
+ if ( *control[kRight1] || *control[kRight2] )
+ iTurnR = 1;
+ }
+
+ if ( *control[kLeft1] || *control[kLeft2] || *control[kRight1] || *control[kRight2] )
+ iTurnCount++;
+ else
+ iTurnCount = 0;
+
+ if ( *control[kForward1] || *control[kForward2] )
+ iForward += 1 + shift;
+
+ if ( *control[kBackward1] || *control[kBackward2] )
+ iForward -= 1 + shift;
+}
+
+void ctrlStrobeKey_END( void ) {};
+
+
+void ctrlInit( void )
+{
+ char inputSettings[128];
+ strcpy(inputSettings, BloodINI.GetKeyString("Options", "InputSettings", "KeyboardKeys"));
+
+ for (int i = 0; i < kMaxControls; i++)
+ {
+ int scanId = BloodINI.GetKeyInt(inputSettings, controlInfo[i].iniKeyName, -1);
+ if ( scanId < 0 )
+ control[i] = (BYTE *)&keystatus[controlInfo[i].defScanCode];
+ else
+ {
+ if (scanId < 0 || scanId > 255)
+ scanId = 0;
+ control[i] = (BYTE *)&keystatus[scanId];
+ }
+ }
+
+ dpmiLockMemory(FP_OFF(&iForward), sizeof(schar));
+ dpmiLockMemory(FP_OFF(&iTurnL), sizeof(uchar));
+ dpmiLockMemory(FP_OFF(&iTurnR), sizeof(uchar));
+ dpmiLockMemory(FP_OFF(&iTurnCount), sizeof(int));
+ dpmiLockMemory(FP_OFF(&iStrafe), sizeof(schar));
+ dpmiLockMemory(FP_OFF(&iTicks), sizeof(uchar));
+ dpmiLockMemory(FP_OFF(control), sizeof(control));
+ dpmiLockMemory(FP_OFF(&ctrlStrobeKey), FP_OFF(&ctrlStrobeKey_END) - FP_OFF(&ctrlStrobeKey));
+
+ timerRegisterClient(ctrlStrobeKey, kTimerRate);
+
+ useMouse = mouseInit() != 0;
+
+ Mouse::speedX = BloodINI.GetKeyInt("Mouse", "HSensitivity", 30 );
+ Mouse::speedY = BloodINI.GetKeyInt("Mouse", "VSensitivity", 10 );
+}
+
+
+void ctrlTerm( void )
+{
+ timerRemoveClient(ctrlStrobeKey);
+
+ dpmiUnlockMemory(FP_OFF(&iForward), sizeof(schar));
+ dpmiUnlockMemory(FP_OFF(&iTurnL), sizeof(uchar));
+ dpmiUnlockMemory(FP_OFF(&iTurnR), sizeof(uchar));
+ dpmiUnlockMemory(FP_OFF(&iTurnCount), sizeof(int));
+ dpmiUnlockMemory(FP_OFF(&iStrafe), sizeof(schar));
+ dpmiUnlockMemory(FP_OFF(&iTicks), sizeof(uchar));
+ dpmiUnlockMemory(FP_OFF(control), sizeof(control));
+ dpmiUnlockMemory(FP_OFF(&ctrlStrobeKey), FP_OFF(&ctrlStrobeKey_END) - FP_OFF(&ctrlStrobeKey));
+}
+
+
+
+void ctrlGetInput( void )
+{
+ gInput.syncFlags.byte = 0;
+ gInput.buttonFlags.byte = 0;
+ gInput.keyFlags.byte = 0;
+ gInput.newWeapon = 0;
+
+ if ( useMouse )
+ {
+ Mouse::Read(iTicks);
+
+ keystatus[MOUSE_LBUTTON] = (BYTE)(Mouse::buttons & 1);
+ keystatus[MOUSE_RBUTTON] = (BYTE)(Mouse::buttons & 2);
+ keystatus[MOUSE_MBUTTON] = (BYTE)(Mouse::buttons & 4);
+ }
+
+ // check buttons
+ gInput.buttonFlags.jump = (*control[kJump1] || *control[kJump2]) ? 1 : 0;
+ gInput.buttonFlags.crouch = (*control[kCrouch1] || *control[kCrouch2]) ? 1 : 0;
+ gInput.buttonFlags.shoot = ( *control[kFire1] || *control[kFire2] ) ? 1 : 0;
+ gInput.buttonFlags.shoot2 = ( *control[kAltFire1] || *control[kAltFire2] ) ? 1 : 0;
+
+// if (gInput.buttonFlags.byte != gMe->buttonFlags.byte)
+ gInput.syncFlags.buttonChange = 1;
+
+ if (keystatus[KEY_CAPSLOCK])
+ {
+ keystatus[KEY_CAPSLOCK] = 0;
+ gInput.keyFlags.master = 1;
+ }
+
+ if ( *control[kAction1] || *control[kAction2] )
+ {
+ *control[kAction1] = 0;
+ *control[kAction2] = 0;
+ gInput.keyFlags.action = 1;
+ }
+
+ gInput.buttonFlags.lookup = 0;
+ gInput.buttonFlags.lookdown = 0;
+ if ( *control[kAimUp1] || *control[kAimUp2] )
+ {
+ gInput.buttonFlags.lookup = 1;
+ }
+
+ if ( *control[kAimDown1] || *control[kAimDown2] )
+ {
+ gInput.buttonFlags.lookdown = 1;
+ }
+
+ if ( *control[kLookUp1] || *control[kLookUp2] )
+ {
+ gInput.buttonFlags.lookup = 1;
+ gInput.keyFlags.lookcenter = 1;
+ }
+
+ if ( *control[kLookDown1] || *control[kLookDown2] )
+ {
+ gInput.buttonFlags.lookdown = 1;
+ gInput.keyFlags.lookcenter = 1;
+ }
+
+ if ( *control[kLookCenter1] || *control[kLookCenter2] )
+ {
+ *control[kLookCenter1] = 0;
+ *control[kLookCenter2] = 0;
+ gInput.keyFlags.lookcenter = 1;
+ }
+
+ if ( keystatus[KEY_F9] )
+ {
+ gInput.keyFlags.restart = 1;
+ keystatus[KEY_F9] = 0;
+ }
+
+ if ( keystatus[KEY_PAUSE] )
+ {
+ dprintf("PAUSE PRESSED\n");
+ gInput.keyFlags.pause = 1;
+ keystatus[KEY_PAUSE] = 0;
+ }
+
+// if ( gInput.keyFlags.byte )
+ gInput.syncFlags.keyChange = 1;
+
+ // check weapon change
+ for (int i = KEY_1; i <= KEY_0; i++)
+ {
+ if (keystatus[i])
+ {
+ keystatus[i] = 0;
+ gInput.newWeapon = (uchar)(i - KEY_1 + 1);
+ gInput.syncFlags.weaponChange = 1;
+ }
+ }
+ gInput.syncFlags.weaponChange = 1;
+
+ _disable(); // prevent the values from changing while we're reading them
+
+ gInput.forward = iForward;
+// if ( gInput.forward != gMe->forward )
+ gInput.syncFlags.forwardChange = 1;
+
+ gInput.turn = 0;
+ BYTE shift = *control[kRunOn1] || *control[kRunOn2];
+
+ if ( iTurnL )
+ {
+ if ( shift && iTurnCount > kTurnThresh )
+ gInput.turn -= (sshort)ClipRange(iTurnCount * kAngAccel2, -kAngSpeed2, kAngSpeed2);
+ else
+ gInput.turn -= (sshort)ClipRange(iTurnCount * kAngAccel1, -kAngSpeed1, kAngSpeed1);
+ }
+
+ if ( iTurnR )
+ {
+ if ( shift && iTurnCount > kTurnThresh )
+ gInput.turn += (sshort)ClipRange(iTurnCount * kAngAccel2, -kAngSpeed2, kAngSpeed2);
+ else
+ gInput.turn += (sshort)ClipRange(iTurnCount * kAngAccel1, -kAngSpeed1, kAngSpeed1);
+ }
+
+// if ( gInput.turn != gMe->turn )
+ gInput.syncFlags.turnChange = 1;
+
+ gInput.strafe = iStrafe;
+// if ( gInput.strafe != gMe->strafe )
+ gInput.syncFlags.strafeChange = 1;
+
+ gInput.ticks = iTicks;
+
+ iForward = iStrafe = iTicks = 0;
+ _enable();
+
+ if ( useMouse )
+ {
+ if ( *control[kStrafeOn1] || *control[kStrafeOn2] )
+ gInput.strafe = (schar)ClipRange(gInput.strafe - Mouse::dX2, -16, 16);
+ else
+ gInput.turn = (sshort)ClipRange(gInput.turn + Mouse::dX2, -256, 256);
+
+ gInput.forward = (schar)ClipRange(gInput.forward - Mouse::dY2, -16, 16);
+ }
+
+}
diff --git a/SRC/CONTROLS.H b/SRC/CONTROLS.H
new file mode 100644
index 0000000..a4d3899
--- /dev/null
+++ b/SRC/CONTROLS.H
@@ -0,0 +1,80 @@
+#ifndef __CONTROLS_H
+#define __CONTROLS_H
+
+#include "typedefs.h"
+#include "player.h"
+
+enum {
+ kForward1 = 0,
+ kForward2,
+ kBackward1,
+ kBackward2,
+ kLeft1,
+ kLeft2,
+ kRight1,
+ kRight2,
+ kJump1,
+ kJump2,
+ kCrouch1,
+ kCrouch2,
+ kStrafeOn1,
+ kStrafeOn2,
+ kStrafeLeft1,
+ kStrafeLeft2,
+ kStrafeRight1,
+ kStrafeRight2,
+ kLookUp1,
+ kLookUp2,
+ kLookDown1,
+ kLookDown2,
+ kAimUp1,
+ kAimUp2,
+ kAimDown1,
+ kAimDown2,
+ kLookCenter1,
+ kLookCenter2,
+ kAction1,
+ kAction2,
+ kFire1,
+ kFire2,
+ kAltFire1,
+ kAltFire2,
+ kRunOn1,
+ kRunOn2,
+ kRunToggle1,
+ kRunToggle2,
+ kSelectWeapon1,
+ kSelectWeapon2,
+ kSelectWeapon3,
+ kSelectWeapon4,
+ kSelectWeapon5,
+ kSelectWeapon6,
+ kSelectWeapon7,
+ kSelectWeapon8,
+ kSelectWeapon9,
+ kSelectWeapon0,
+ kAutomapToggle,
+ kItemLeft1,
+ kItemLeft2,
+ kItemRight1,
+ kItemRight2,
+ kItemDrop1,
+ kItemDrop2,
+ kItemUse1,
+ kItemUse2,
+ kPause,
+ kMaxControls
+};
+
+
+extern INPUT gInput;
+
+extern volatile BYTE *control[ kMaxControls ];
+
+void ctrlStrobeKey( void );
+void ctrlGetInput( void );
+void ctrlInit( void );
+void ctrlTerm( void );
+
+#endif //__CONTROLS_H
+
diff --git a/SRC/CONVDB3.CPP b/SRC/CONVDB3.CPP
new file mode 100644
index 0000000..73357d9
--- /dev/null
+++ b/SRC/CONVDB3.CPP
@@ -0,0 +1,628 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <dos.h>
+#include <io.h>
+#include <fcntl.h>
+
+#include "db.h"
+#include "getopt.h"
+#include "engine.h"
+#include "error.h"
+#include "misc.h"
+
+#include <memcheck.h>
+
+#define kBloodMapSig "BLM\x1A"
+#define kBloodMapVersion 0x0300
+
+struct FNODE
+{
+ FNODE *next;
+ char name[1];
+};
+
+FNODE head = { &head, "" };
+FNODE *tail = &head;
+
+SECTOR sector[kMaxSectors];
+XSECTOR xsector[kMaxXSectors];
+WALL wall[kMaxWalls];
+XWALL xwall[kMaxXWalls];
+SPRITE sprite[kMaxSprites];
+XSPRITE xsprite[kMaxXSprites];
+uchar spriteSurf[kMaxSprites];
+uchar wallSurf[kMaxWalls];
+uchar floorSurf[kMaxSectors];
+uchar ceilingSurf[kMaxSectors];
+short pskyoff[kMaxSkyTiles];
+
+short pskybits;
+char gMapAuthor[64] = "";
+char gMapName[64] = "";
+ulong gMapCRC;
+int gSkyCount;
+short numsectors, numwalls;
+
+struct IOBuffer
+{
+ int remain;
+ char *p;
+ IOBuffer( char *buffer, int l ) : p(buffer), remain(l) {};
+ void Read(void *, int, int);
+ void Write(void *, int, int);
+};
+
+void IOBuffer::Read(void *s, int size, int nError)
+{
+ if (size <= remain)
+ {
+ memcpy(s, p, size);
+ remain -= size;
+ p += size;
+ }
+ else
+ ThrowError("IOB", nError, "Read buffer overflow", ES_ERROR);
+}
+
+void IOBuffer::Write(void *s, int size, int nError)
+{
+ if (size <= remain)
+ {
+ memcpy(p, s, size);
+ remain -= size;
+ p += size;
+ }
+ else
+ ThrowError("IOB", nError, "Write buffer overflow", ES_ERROR);
+}
+
+
+struct OLDHEADER
+{
+ long version;
+ long x, y, z;
+ short angle;
+ short sectnum;
+};
+
+struct MPXHEADER
+{
+ char signature[4];
+ short version;
+ short dummy;
+};
+
+struct HEADER
+{
+ char signature[4];
+ short version;
+};
+
+// version 2.x header
+struct INFO2
+{
+ short xspriteSize;
+ short xwallSize;
+ short xsectorSize;
+ char visibility;
+ short xspriteCount;
+ short xwallCount;
+ short xsectorCount;
+ short pskybits;
+ char lenAuthor;
+ char lenName;
+};
+
+// version 3.x header
+struct INFO3
+{
+ long x, y, z;
+ ushort angle;
+ ushort sector;
+ ushort pskybits;
+ uchar visibility;
+ int songId;
+ uchar parallax;
+ int mapRevisions;
+ uchar lenName;
+ uchar lenAuthor;
+ ushort numsectors;
+ ushort numwalls;
+ ushort numsprites;
+};
+
+struct XSPRITE2
+{
+ signed reference : 16;
+ unsigned rxID : 10;
+ unsigned difficulty : 2;
+ unsigned detail : 2;
+ unsigned map : 2;
+ unsigned txID : 10;
+ unsigned key : 3;
+ unsigned command : 3;
+ unsigned type : 10;
+ unsigned state: 1;
+ unsigned triggerOn : 1;
+ unsigned triggerOff : 1;
+ unsigned triggerOnce : 1;
+ unsigned view : 2; // changed to : 3
+ unsigned soundKit : 8;
+ unsigned data : 16;
+};
+
+struct XWALL2
+{
+ signed reference : 16;
+ unsigned rxID : 10;
+ unsigned surfType : 6;
+ unsigned txID : 10;
+ unsigned key : 3;
+ unsigned command : 3;
+ unsigned type : 10;
+ unsigned map : 2;
+ unsigned state: 1;
+ unsigned triggerOn : 1;
+ unsigned triggerOff : 1;
+ unsigned data : 16;
+};
+
+struct XSECTOR2
+{
+ signed reference : 16;
+ unsigned rxID : 10;
+ unsigned command : 3;
+ unsigned triggerOn : 1;
+ unsigned triggerOff : 1;
+ unsigned triggerOnce : 1;
+ signed ceilingShade0 : 8; // deleted
+ signed floorShade0 : 8; // deleted
+ signed amplitude : 8;
+ unsigned freq : 8;
+ unsigned wave : 8;
+ unsigned phase : 8;
+ unsigned state: 1;
+ unsigned shadeFloor : 1;
+ unsigned shadeCeiling : 1;
+ unsigned shadeWalls : 1;
+ unsigned panFloor : 1;
+ unsigned panCeiling : 1;
+ unsigned panDrag : 1;
+ unsigned panWalls : 1;
+ unsigned panAngle : 12;
+ signed panSpeed : 8;
+ signed shade : 8;
+ unsigned surfType : 6; // deleted
+ unsigned ceilSurf : 6;
+ unsigned floorSurf : 6;
+ unsigned txID : 10;
+ unsigned key : 3;
+ unsigned type : 10;
+ unsigned underwater : 1;
+ unsigned data : 16;
+
+
+};
+
+
+void ShowUsage(void)
+{
+ printf("Usage:\n");
+ printf(" CONVDB3 map1 map2 (wild cards ok)\n");
+ printf("\nTECHNICAL INFO:\n sizeof(XSPRITE)=%d\n sizeof(XSECTOR)=%d\n sizeof(XWALL)=%d",
+ sizeof(XSPRITE), sizeof(XSECTOR), sizeof(XWALL));
+ exit(0);
+}
+
+void QuitMessage(char * fmt, ...