foxygit / doom Log in
commit d9520b6415f8e373cf759f316094b986e57f0f5a
Merge: 57011e78 52f81b4e
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Sat Feb 6 03:14:20 2010 +0000
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Sat Feb 6 03:14:20 2010 +0000

    Merge from trunk.

    Subversion-branch: /branches/strife-branch
    Subversion-revision: 1849

 .gitignore                                       |    3 +
 INSTALL                                          |    2 +-
 Makefile.am                                      |    9 +-
 NEWS                                             |   98 ++-
 TODO                                             |   21 +-
 autogen.sh                                       |    2 +-
 configure.in                                     |   34 +-
 data/README                                      |    7 +
 data/doom.ico                                    |  Bin 2238 -> 36726 bytes
 data/doom8.ico                                   |  Bin 0 -> 2238 bytes
 data/setup.ico                                   |  Bin 2238 -> 36726 bytes
 data/setup8.ico                                  |  Bin 0 -> 2238 bytes
 man/Makefile.am                                  |    9 +-
 pcsound/.gitignore                               |    2 +
 pkg/.gitignore                                   |    3 +
 pkg/Makefile.am                                  |   34 +-
 pkg/config.make.in                               |   29 +
 pkg/osx/.gitignore                               |    7 +
 src/strife/deh_main.h => pkg/osx/AppController.h |   49 +-
 pkg/osx/AppController.m                          |  124 +++
 pkg/osx/Execute.h                                |   31 +
 pkg/osx/Execute.m                                |  208 +++++
 pkg/osx/GNUmakefile                              |  105 +++
 pkg/osx/IWADController.h                         |   54 ++
 pkg/osx/IWADController.m                         |  347 ++++++++
 src/strife/deh_io.h => pkg/osx/IWADLocation.h    |   36 +-
 pkg/osx/IWADLocation.m                           |   74 ++
 pkg/osx/Info-gnustep.plist.in                    |   36 +
 pkg/osx/Info.plist.in                            |   60 ++
 pkg/osx/LauncherManager.h                        |   52 ++
 pkg/osx/LauncherManager.m                        |  338 +++++++
 pkg/osx/PkgInfo                                  |    1 +
 pkg/osx/Resources/128x128.png                    |  Bin 0 -> 23029 bytes
 pkg/osx/Resources/app.icns                       |  Bin 0 -> 51625 bytes
 pkg/osx/Resources/app.png                        |  Bin 0 -> 4597 bytes
 pkg/osx/Resources/launcher.nib/classes.nib       |   47 +
 pkg/osx/Resources/launcher.nib/info.nib          |   23 +
 pkg/osx/Resources/launcher.nib/keyedobjects.nib  |  Bin 0 -> 19616 bytes
 pkg/osx/Resources/wadfile.icns                   |  Bin 0 -> 44092 bytes
 pkg/osx/Resources/wadfile.png                    |  Bin 0 -> 2249 bytes
 pkg/osx/cp-with-libs                             |  100 +++
 pkg/osx/main.m                                   |   32 +
 pkg/win32/GNUmakefile                            |   34 +
 pkg/win32/GNUmakefile.am                         |   37 -
 pkg/wince/{GNUmakefile.am => GNUmakefile}        |   25 +-
 rpm.spec.in                                      |   59 ++
 src/.gitignore                                   |    2 +
 src/Makefile.am                                  |   24 +-
 src/d_iwad.c                                     |   91 +-
 src/{doom => }/deh_defs.h                        |    0
 src/{doom => }/deh_io.c                          |    0
 src/{doom => }/deh_io.h                          |    0
 src/{strife => }/deh_main.c                      |   63 +-
 src/{doom => }/deh_main.h                        |    0
 src/{doom => }/deh_mapping.c                     |    0
 src/{doom => }/deh_mapping.h                     |    0
 src/{doom => }/deh_text.c                        |    0
 src/doom/.gitignore                              |    2 +
 src/doom/Makefile.am                             |    6 +-
 src/doom/deh_doom.c                              |   74 ++
 src/doom/deh_main.c                              |  423 ---------
 src/doom/g_game.c                                |   14 +-
 src/doom/hu_stuff.c                              |   30 +-
 src/doom/p_map.c                                 |   21 +-
 src/doom/p_setup.c                               |   27 +-
 src/doom/p_sight.c                               |   10 +-
 src/doom/st_stuff.c                              |   18 +-
 src/i_video.c                                    |    1 +
 src/mus2mid.c                                    | 1019 ++++++++++++----------
 src/resource.rc.in                               |    2 +-
 src/setup/.gitignore                             |    7 +-
 src/setup/Makefile.am                            |    7 +
 src/setup/execute.c                              |   33 +-
 src/strife/.gitignore                            |    2 +
 src/strife/Makefile.am                           |    6 +-
 src/strife/deh_defs.h                            |   68 --
 src/strife/deh_io.c                              |  211 -----
 src/strife/deh_mapping.c                         |  133 ---
 src/strife/deh_mapping.h                         |   90 --
 src/strife/deh_strife.c                          |   74 ++
 src/strife/deh_text.c                            |  127 ---
 src/strife/g_game.c                              |   14 +-
 src/strife/hu_stuff.c                            |   30 +-
 src/strife/p_map.c                               |   21 +-
 src/strife/p_setup.c                             |   27 +-
 src/strife/p_sight.c                             |   10 +-
 src/strife/st_stuff.c                            |   18 +-
 textscreen/.gitignore                            |    2 +
 textscreen/examples/.gitignore                   |    2 +
 textscreen/txt_sdl.c                             |    8 +-
 wince/.gitignore                                 |    5 +
 wince/Makefile.am                                |    1 +
 92 files changed, 3019 insertions(+), 1836 deletions(-)

diff --cc configure.in
index f8482d53,e3287d00..de537f7c
--- a/configure.in
+++ b/configure.in
@@@ -133,15 -149,12 +149,13 @@@ src/doom-screensaver.deskto
  src/doom/Makefile
  src/heretic/Makefile
  src/hexen/Makefile
- src/setup/Makefile
- src/strife/Makefile
- pcsound/Makefile
- pkg/Makefile
- pkg/wince/GNUmakefile
- pkg/win32/GNUmakefile
  src/resource.rc
  src/setup-res.rc
+ src/setup/Makefile
  src/setup/setup-manifest.xml
- src/doom-screensaver.desktop
++src/strife/Makefile
+ textscreen/Makefile
+ textscreen/examples/Makefile
+ wince/Makefile
  ])

diff --cc src/Makefile.am
index 82a7697f,9e550e15..51baa567
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@@ -152,14 -159,6 +160,14 @@@ endi

  @PROGRAM_PREFIX@hexen_LDADD = hexen/libhexen.a $(EXTRA_LIBS)

 +if HAVE_WINDRES
- @PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES) resource.rc
++@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 +else
- @PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES)
++@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH)
 +endif
 +
 +@PROGRAM_PREFIX@strife_LDADD = strife/libstrife.a $(EXTRA_LIBS)
 +
  # Source files needed for chocolate-setup:

  SETUP_FILES=                               \
diff --cc src/strife/.gitignore
index d7e732ad,00000000..973a0073
mode 100644,000000..100644
--- a/src/strife/.gitignore
+++ b/src/strife/.gitignore
@@@ -1,7 -1,0 +1,9 @@@
 +Makefile
 +Makefile.in
 +.deps
 +*.rc
 +chocolate-doom
 +chocolate-server
 +*.exe
++tags
++TAGS
diff --cc src/strife/Makefile.am
index 85f8b764,00000000..c0e930cb
mode 100644,000000..100644
--- a/src/strife/Makefile.am
+++ b/src/strife/Makefile.am
@@@ -1,80 -1,0 +1,76 @@@
 +AM_CFLAGS = -I.. @SDL_CFLAGS@ @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@
 +
 +noinst_LIBRARIES=libstrife.a
 +
 +SOURCE_FILES=                   \
 +am_map.c           am_map.h     \
 +                   d_englsh.h   \
 +d_items.c          d_items.h    \
 +d_main.c           d_main.h     \
 +d_net.c            d_net.h      \
 +                   doomdata.h   \
 +doomdef.c          doomdef.h    \
 +doomstat.c         doomstat.h   \
 +                   d_player.h   \
 +dstrings.c         dstrings.h   \
 +                   d_textur.h   \
 +                   d_think.h    \
 +f_finale.c         f_finale.h   \
 +f_wipe.c           f_wipe.h     \
 +g_game.c           g_game.h     \
 +hu_lib.c           hu_lib.h     \
 +hu_stuff.c         hu_stuff.h   \
 +info.c             info.h       \
 +m_menu.c           m_menu.h     \
 +m_random.c         m_random.h   \
 +p_ceilng.c                      \
 +p_doors.c                       \
 +p_enemy.c                       \
 +p_floor.c                       \
 +p_inter.c          p_inter.h    \
 +p_lights.c                      \
 +                   p_local.h    \
 +p_map.c                         \
 +p_maputl.c                      \
 +p_mobj.c           p_mobj.h     \
 +p_plats.c                       \
 +p_pspr.c           p_pspr.h     \
 +p_saveg.c          p_saveg.h    \
 +p_setup.c          p_setup.h    \
 +p_sight.c                       \
 +p_spec.c           p_spec.h     \
 +p_switch.c                      \
 +p_telept.c                      \
 +p_tick.c           p_tick.h     \
 +p_user.c                        \
 +r_bsp.c            r_bsp.h      \
 +r_data.c           r_data.h     \
 +                   r_defs.h     \
 +r_draw.c           r_draw.h     \
 +                   r_local.h    \
 +r_main.c           r_main.h     \
 +r_plane.c          r_plane.h    \
 +r_segs.c           r_segs.h     \
 +r_sky.c            r_sky.h      \
 +                   r_state.h    \
 +r_things.c         r_things.h   \
 +s_sound.c          s_sound.h    \
 +sounds.c           sounds.h     \
 +st_lib.c           st_lib.h     \
 +st_stuff.c         st_stuff.h   \
 +wi_stuff.c         wi_stuff.h
 +
 +FEATURE_DEHACKED_SOURCE_FILES =            \
 +deh_ammo.c                                 \
 +deh_cheat.c                                \
- deh_defs.h                                 \
++deh_strife.c                               \
 +deh_frame.c                                \
- deh_io.c             deh_io.h              \
- deh_main.c           deh_main.h            \
- deh_mapping.c        deh_mapping.h         \
 +deh_misc.c           deh_misc.h            \
 +deh_ptr.c                                  \
 +deh_sound.c                                \
- deh_text.c                                 \
 +deh_thing.c                                \
 +deh_weapon.c
 +
 +libstrife_a_SOURCES = $(SOURCE_FILES)                   \
 +                      $(FEATURE_DEHACKED_SOURCE_FILES)
 +
diff --cc src/strife/deh_strife.c
index 00000000,00000000..c9a7a73b
new file mode 100644
--- /dev/null
+++ b/src/strife/deh_strife.c
@@@ -1,0 -1,0 +1,74 @@@
++// Emacs style mode select   -*- C++ -*-
++//-----------------------------------------------------------------------------
++//
++// Copyright(C) 2005 Simon Howard
++//
++// This program is free software; you can redistribute it and/or
++// modify it under the terms of the GNU General Public License
++// as published by the Free Software Foundation; either version 2
++// of the License, or (at your option) any later version.
++//
++// This program is distributed in the hope that it will be useful,
++// but WITHOUT ANY WARRANTY; without even the implied warranty of
++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++// GNU General Public License for more details.
++//
++// You should have received a copy of the GNU General Public License
++// along with this program; if not, write to the Free Software
++// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
++// 02111-1307, USA.
++//
++//-----------------------------------------------------------------------------
++//
++// Top-level dehacked definitions for Strife dehacked (sehacked)
++//
++//-----------------------------------------------------------------------------
++
++#include <stdlib.h>
++#include "deh_defs.h"
++#include "deh_main.h"
++
++char *deh_signatures[] =
++{
++    "Patch File for DeHackEd v2.3",
++    "Patch File for DeHackEd v3.0",
++    NULL
++};
++
++// deh_ammo.c:
++extern deh_section_t deh_section_ammo;
++// deh_cheat.c:
++extern deh_section_t deh_section_cheat;
++// deh_frame.c:
++extern deh_section_t deh_section_frame;
++// deh_misc.c:
++extern deh_section_t deh_section_misc;
++// deh_ptr.c:
++extern deh_section_t deh_section_pointer;
++// deh_sound.c
++extern deh_section_t deh_section_sound;
++// deh_text.c:
++extern deh_section_t deh_section_text;
++// deh_thing.c:
++extern deh_section_t deh_section_thing;
++// deh_weapon.c:
++extern deh_section_t deh_section_weapon;
++
++//
++// List of section types:
++//
++
++deh_section_t *deh_section_types[] =
++{
++    &deh_section_ammo,
++    &deh_section_cheat,
++    &deh_section_frame,
++    &deh_section_misc,
++    &deh_section_pointer,
++    &deh_section_sound,
++    &deh_section_text,
++    &deh_section_thing,
++    &deh_section_weapon,
++    NULL
++};
++
diff --cc src/strife/g_game.c
index 35bf4d74,00000000..8d0e4503
mode 100644,000000..100644
--- a/src/strife/g_game.c
+++ b/src/strife/g_game.c
@@@ -1,2098 -1,0 +1,2108 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:  none
 +//
 +//-----------------------------------------------------------------------------
 +
 +
 +
 +#include <string.h>
 +#include <stdlib.h>
 +#include <math.h>
 +
 +#include "doomdef.h"
 +#include "doomkeys.h"
 +#include "doomstat.h"
 +
 +#include "deh_main.h"
 +#include "deh_misc.h"
 +
 +#include "z_zone.h"
 +#include "f_finale.h"
 +#include "m_argv.h"
 +#include "m_controls.h"
 +#include "m_misc.h"
 +#include "m_menu.h"
 +#include "m_random.h"
 +#include "i_system.h"
 +#include "i_timer.h"
 +#include "i_video.h"
 +
 +#include "p_setup.h"
 +#include "p_saveg.h"
 +#include "p_tick.h"
 +
 +#include "d_main.h"
 +
 +#include "wi_stuff.h"
 +#include "hu_stuff.h"
 +#include "st_stuff.h"
 +#include "am_map.h"
 +
 +// Needs access to LFB.
 +#include "v_video.h"
 +
 +#include "w_wad.h"
 +
 +#include "p_local.h"
 +
 +#include "s_sound.h"
 +
 +// Data.
 +#include "dstrings.h"
 +#include "sounds.h"
 +
 +// SKY handling - still the wrong place.
 +#include "r_data.h"
 +#include "r_sky.h"
 +
 +
 +
 +#include "g_game.h"
 +
 +
 +#define SAVEGAMESIZE	0x2c000
 +
 +boolean	G_CheckDemoStatus (void);
 +void	G_ReadDemoTiccmd (ticcmd_t* cmd);
 +void	G_WriteDemoTiccmd (ticcmd_t* cmd);
 +void	G_PlayerReborn (int player);
 +void	G_InitNew (skill_t skill, int episode, int map);
 +
 +void	G_DoReborn (int playernum);
 +
 +void	G_DoLoadLevel (void);
 +void	G_DoNewGame (void);
 +void	G_DoLoadGame (void);
 +void	G_DoPlayDemo (void);
 +void	G_DoCompleted (void);
 +void	G_DoVictory (void);
 +void	G_DoWorldDone (void);
 +void	G_DoSaveGame (void);
 +
 +// Gamestate the last time G_Ticker was called.
 +
 +gamestate_t     oldgamestate;
 +
 +gameaction_t    gameaction;
 +gamestate_t     gamestate;
 +skill_t         gameskill;
 +boolean		respawnmonsters;
 +int             gameepisode;
 +int             gamemap;
 +
 +// If non-zero, exit the level after this number of minutes.
 +
 +int             timelimit;
 +
 +boolean         paused;
 +boolean         sendpause;             	// send a pause event next tic
 +boolean         sendsave;             	// send a save event next tic
 +boolean         usergame;               // ok to save / end game
 +
 +boolean         timingdemo;             // if true, exit with report on completion
 +boolean         nodrawers;              // for comparative timing purposes
 +int             starttime;          	// for comparative timing purposes
 +
 +boolean         viewactive;
 +
 +boolean         deathmatch;           	// only if started as net death
 +boolean         netgame;                // only true if packets are broadcast
 +boolean         playeringame[MAXPLAYERS];
 +player_t        players[MAXPLAYERS];
 +
 +boolean         turbodetected[MAXPLAYERS];
 +
 +int             consoleplayer;          // player taking events and displaying
 +int             displayplayer;          // view being displayed
 +int             gametic;
 +int             levelstarttic;          // gametic at level start
 +int             totalkills, totalitems, totalsecret;    // for intermission
 +
 +char            demoname[32];
 +boolean         demorecording;
 +boolean         longtics;               // cph's doom 1.91 longtics hack
 +boolean         lowres_turn;            // low resolution turning for longtics
 +boolean         demoplayback;
 +boolean		netdemo;
 +byte*		demobuffer;
 +byte*		demo_p;
 +byte*		demoend;
 +boolean         singledemo;            	// quit after playing a demo from cmdline
 +
 +boolean         precache = true;        // if true, load all graphics at start
 +
 +boolean         testcontrols = false;    // Invoked by setup to test controls
 +
 +wbstartstruct_t wminfo;               	// parms for world map / intermission
 +
 +byte		consistancy[MAXPLAYERS][BACKUPTICS];
 +
 +#define MAXPLMOVE		(forwardmove[1])
 +
 +#define TURBOTHRESHOLD	0x32
 +
 +fixed_t         forwardmove[2] = {0x19, 0x32};
 +fixed_t         sidemove[2] = {0x18, 0x28};
 +fixed_t         angleturn[3] = {640, 1280, 320};    // + slow turn
 +
 +static int *weapon_keys[] = {
 +    &key_weapon1,
 +    &key_weapon2,
 +    &key_weapon3,
 +    &key_weapon4,
 +    &key_weapon5,
 +    &key_weapon6,
 +    &key_weapon7,
 +    &key_weapon8
 +};
 +
 +#define SLOWTURNTICS	6
 +
 +#define NUMKEYS		256
 +
 +static boolean  gamekeydown[NUMKEYS];
 +static int      turnheld;		// for accelerative turning
 +
 +static boolean  mousearray[4];
 +static boolean *mousebuttons = &mousearray[1];  // allow [-1]
 +
 +// mouse values are used once
 +int             mousex;
 +int             mousey;
 +
 +static int      dclicktime;
 +static boolean  dclickstate;
 +static int      dclicks;
 +static int      dclicktime2;
 +static boolean  dclickstate2;
 +static int      dclicks2;
 +
 +#define MAX_JOY_BUTTONS 20
 +
 +// joystick values are repeated
 +static int      joyxmove;
 +static int      joyymove;
 +static boolean  joyarray[MAX_JOY_BUTTONS + 1];
 +static boolean *joybuttons = &joyarray[1];		// allow [-1]
 +
 +static int      savegameslot;
 +static char     savedescription[32];
 +
 +static int      testcontrols_mousespeed;
 +
 +#define	BODYQUESIZE	32
 +
 +mobj_t*		bodyque[BODYQUESIZE];
 +int		bodyqueslot;
 +
 +int             vanilla_savegame_limit = 1;
 +int             vanilla_demo_limit = 1;
 +
 +
 +#define MOUSE_SPEED_BOX_WIDTH 16
 +#define COLOR_RED    0xb0
 +#define COLOR_BLACK  0x00
 +#define COLOR_WHITE  0x04
 +#define COLOR_YELLOW 0xe7
 +
 +void G_DrawMouseSpeedBox(void)
 +{
 +    extern int usemouse;
 +    int i;
 +    int box_x, box_y;
 +    int original_speed;
 +    int x, y;
 +    int redline_x;
 +    int linelen;
 +    char *lumpname;
 +    int color;
 +
 +    // If the mouse is turned off or acceleration is turned off, don't
 +    // draw the box at all.
 +
 +    if (!usemouse || fabs(mouse_acceleration - 1) < 0.01)
 +    {
 +        return;
 +    }
 +
 +    // Calculate box position
 +
 +    box_x = SCREENWIDTH - MOUSE_SPEED_BOX_WIDTH * 8;
 +    box_y = SCREENHEIGHT - 9;
 +
 +    // Draw the box.
 +
 +    x = box_x;
 +
 +    for (i=0; i<MOUSE_SPEED_BOX_WIDTH; ++i)
 +    {
 +        if (i == 0)
 +        {
 +            lumpname = "M_LSLEFT";
 +        }
 +        else if (i == MOUSE_SPEED_BOX_WIDTH - 1)
 +        {
 +            lumpname = "M_LSRGHT";
 +        }
 +        else
 +        {
 +            lumpname = "M_LSCNTR";
 +        }
 +
 +        V_DrawPatchDirect(x, box_y,
 +                          W_CacheLumpName(DEH_String(lumpname), PU_CACHE));
 +        x += 8;
 +    }
 +
 +    // Calculate the position of the red line.  This is 1/3 of the way
 +    // along the box.
 +
 +    redline_x = (MOUSE_SPEED_BOX_WIDTH / 3) * 8;
 +
 +    // Undo acceleration and get back the original mouse speed
 +
 +    if (testcontrols_mousespeed < mouse_threshold)
 +    {
 +        original_speed = testcontrols_mousespeed;
 +    }
 +    else
 +    {
 +        original_speed = testcontrols_mousespeed - mouse_threshold;
 +        original_speed = (int) (original_speed / mouse_acceleration);
 +        original_speed += mouse_threshold;
 +    }
 +
 +    // Calculate line length
 +
 +    linelen = (original_speed * redline_x) / mouse_threshold;
 +
 +    // Draw horizontal "thermometer"
 +
 +    for (x=0; x<(MOUSE_SPEED_BOX_WIDTH - 1) * 8; ++x)
 +    {
 +        if (x < linelen)
 +        {
 +            if (x < redline_x)
 +            {
 +                color = COLOR_WHITE;
 +            }
 +            else
 +            {
 +                color = COLOR_YELLOW;
 +            }
 +        }
 +        else
 +        {
 +            color = COLOR_BLACK;
 +        }
 +
 +        I_VideoBuffer[(box_y - 4) * SCREENWIDTH + box_x + x + 1] = color;
 +    }
 +
 +    // Draw red line
 +
 +    for (y=box_y - 8; y<box_y; ++y)
 +    {
 +        I_VideoBuffer[y * SCREENWIDTH + box_x + redline_x] = COLOR_RED;
 +    }
 +}
 +
 +int G_CmdChecksum (ticcmd_t* cmd)
 +{
 +    size_t		i;
 +    int		sum = 0;
 +
 +    for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++)
 +	sum += ((int *)cmd)[i];
 +
 +    return sum;
 +}
 +
 +
 +//
 +// G_BuildTiccmd
 +// Builds a ticcmd from all of the available inputs
 +// or reads it from the demo buffer.
 +// If recording a demo, write it out
 +//
 +void G_BuildTiccmd (ticcmd_t* cmd)
 +{
 +    int		i;
 +    boolean	strafe;
 +    boolean	bstrafe;
 +    int		speed;
 +    int		tspeed;
 +    int		forward;
 +    int		side;
 +
 +    memset(cmd, 0, sizeof(ticcmd_t));
 +
 +    cmd->consistancy =
 +	consistancy[consoleplayer][maketic%BACKUPTICS];
 +
 +    strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe]
 +	|| joybuttons[joybstrafe];
 +
 +    // fraggle: support the old "joyb_speed = 31" hack which
 +    // allowed an autorun effect
 +
 +    speed = key_speed >= NUMKEYS
 +         || joybspeed >= MAX_JOY_BUTTONS
 +         || gamekeydown[key_speed]
 +         || joybuttons[joybspeed];
 +
 +    forward = side = 0;
 +
 +    // use two stage accelerative turning
 +    // on the keyboard and joystick
 +    if (joyxmove < 0
 +	|| joyxmove > 0
 +	|| gamekeydown[key_right]
 +	|| gamekeydown[key_left])
 +	turnheld += ticdup;
 +    else
 +	turnheld = 0;
 +
 +    if (turnheld < SLOWTURNTICS)
 +	tspeed = 2;             // slow turn
 +    else
 +	tspeed = speed;
 +
 +    // let movement keys cancel each other out
 +    if (strafe)
 +    {
 +	if (gamekeydown[key_right])
 +	{
 +	    // fprintf(stderr, "strafe right\n");
 +	    side += sidemove[speed];
 +	}
 +	if (gamekeydown[key_left])
 +	{
 +	    //	fprintf(stderr, "strafe left\n");
 +	    side -= sidemove[speed];
 +	}
 +	if (joyxmove > 0)
 +	    side += sidemove[speed];
 +	if (joyxmove < 0)
 +	    side -= sidemove[speed];
 +
 +    }
 +    else
 +    {
 +	if (gamekeydown[key_right])
 +	    cmd->angleturn -= angleturn[tspeed];
 +	if (gamekeydown[key_left])
 +	    cmd->angleturn += angleturn[tspeed];
 +	if (joyxmove > 0)
 +	    cmd->angleturn -= angleturn[tspeed];
 +	if (joyxmove < 0)
 +	    cmd->angleturn += angleturn[tspeed];
 +    }
 +
 +    if (gamekeydown[key_up])
 +    {
 +	// fprintf(stderr, "up\n");
 +	forward += forwardmove[speed];
 +    }
 +    if (gamekeydown[key_down])
 +    {
 +	// fprintf(stderr, "down\n");
 +	forward -= forwardmove[speed];
 +    }
 +
 +    if (joyymove < 0)
 +        forward += forwardmove[speed];
 +    if (joyymove > 0)
 +        forward -= forwardmove[speed];
 +
 +    if (gamekeydown[key_strafeleft]
 +     || joybuttons[joybstrafeleft]
 +     || mousebuttons[mousebstrafeleft])
 +    {
 +        side -= sidemove[speed];
 +    }
 +
 +    if (gamekeydown[key_straferight]
 +     || joybuttons[joybstraferight]
 +     || mousebuttons[mousebstraferight])
 +    {
 +        side += sidemove[speed];
 +    }
 +
 +    // buttons
 +    cmd->chatchar = HU_dequeueChatChar();
 +
 +    if (gamekeydown[key_fire] || mousebuttons[mousebfire]
 +	|| joybuttons[joybfire])
 +	cmd->buttons |= BT_ATTACK;
 +
 +    if (gamekeydown[key_use]
 +     || joybuttons[joybuse]
 +     || mousebuttons[mousebuse])
 +    {
 +	cmd->buttons |= BT_USE;
 +	// clear double clicks if hit use button
 +	dclicks = 0;
 +    }
 +
 +    // chainsaw overrides
 +
 +    for (i=0; i<arrlen(weapon_keys); ++i)
 +    {
 +        int key = *weapon_keys[i];
 +
 +        if (gamekeydown[key])
 +        {
 +	    cmd->buttons |= BT_CHANGE;
 +	    cmd->buttons |= i<<BT_WEAPONSHIFT;
 +	    break;
 +        }
 +    }
 +
 +    // mouse
 +    if (mousebuttons[mousebforward])
 +    {
 +	forward += forwardmove[speed];
 +    }
 +    if (mousebuttons[mousebbackward])
 +    {
 +        forward -= forwardmove[speed];
 +    }
 +
 +    if (dclick_use)
 +    {
 +        // forward double click
 +        if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1 )
 +        {
 +            dclickstate = mousebuttons[mousebforward];
 +            if (dclickstate)
 +                dclicks++;
 +            if (dclicks == 2)
 +            {
 +                cmd->buttons |= BT_USE;
 +                dclicks = 0;
 +            }
 +            else
 +                dclicktime = 0;
 +        }
 +        else
 +        {
 +            dclicktime += ticdup;
 +            if (dclicktime > 20)
 +            {
 +                dclicks = 0;
 +                dclickstate = 0;
 +            }
 +        }
 +
 +        // strafe double click
 +        bstrafe =
 +            mousebuttons[mousebstrafe]
 +            || joybuttons[joybstrafe];
 +        if (bstrafe != dclickstate2 && dclicktime2 > 1 )
 +        {
 +            dclickstate2 = bstrafe;
 +            if (dclickstate2)
 +                dclicks2++;
 +            if (dclicks2 == 2)
 +            {
 +                cmd->buttons |= BT_USE;
 +                dclicks2 = 0;
 +            }
 +            else
 +                dclicktime2 = 0;
 +        }
 +        else
 +        {
 +            dclicktime2 += ticdup;
 +            if (dclicktime2 > 20)
 +            {
 +                dclicks2 = 0;
 +                dclickstate2 = 0;
 +            }
 +        }
 +    }
 +
 +    forward += mousey;
 +
 +    if (strafe)
 +	side += mousex*2;
 +    else
 +	cmd->angleturn -= mousex*0x8;
 +
 +    if (mousex == 0)
 +    {
 +        // No movement in the previous frame
 +
 +        testcontrols_mousespeed = 0;
 +    }
 +
 +    mousex = mousey = 0;
 +
 +    if (forward > MAXPLMOVE)
 +	forward = MAXPLMOVE;
 +    else if (forward < -MAXPLMOVE)
 +	forward = -MAXPLMOVE;
 +    if (side > MAXPLMOVE)
 +	side = MAXPLMOVE;
 +    else if (side < -MAXPLMOVE)
 +	side = -MAXPLMOVE;
 +
 +    cmd->forwardmove += forward;
 +    cmd->sidemove += side;
 +
 +    // special buttons
 +    if (sendpause)
 +    {
 +	sendpause = false;
 +	cmd->buttons = BT_SPECIAL | BTS_PAUSE;
 +    }
 +
 +    if (sendsave)
 +    {
 +	sendsave = false;
 +	cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (savegameslot<<BTS_SAVESHIFT);
 +    }
 +
 +    // low-res turning
 +
 +    if (lowres_turn)
 +    {
-         // round angleturn to the nearest 256 boundary
++        static signed short carry = 0;
++        signed short desired_angleturn;
++
++        desired_angleturn = cmd->angleturn + carry;
++
++        // round angleturn to the nearest 256 unit boundary
 +        // for recording demos with single byte values for turn
 +
-         cmd->angleturn = (cmd->angleturn + 128) & 0xff00;
++        cmd->angleturn = (desired_angleturn + 128) & 0xff00;
++
++        // Carry forward the error from the reduced resolution to the
++        // next tic, so that successive small movements can accumulate.
++
++        carry = desired_angleturn - cmd->angleturn;
 +    }
 +}
 +
 +
 +//
 +// G_DoLoadLevel
 +//
 +extern  gamestate_t     wipegamestate;
 +
 +void G_DoLoadLevel (void)
 +{
 +    int             i;
 +
 +    // Set the sky map.
 +    // First thing, we have a dummy sky texture name,
 +    //  a flat. The data is in the WAD only because
 +    //  we look for an actual index, instead of simply
 +    //  setting one.
 +
 +    skyflatnum = R_FlatNumForName(DEH_String(SKYFLATNAME));
 +
 +    levelstarttic = gametic;        // for time calculation
 +
 +    if (wipegamestate == GS_LEVEL)
 +	wipegamestate = -1;             // force a wipe
 +
 +    gamestate = GS_LEVEL;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	turbodetected[i] = false;
 +	if (playeringame[i] && players[i].playerstate == PST_DEAD)
 +	    players[i].playerstate = PST_REBORN;
 +	memset (players[i].frags,0,sizeof(players[i].frags));
 +    }
 +
 +    P_SetupLevel (gameepisode, gamemap, 0, gameskill);
 +    displayplayer = consoleplayer;		// view the guy you are playing
 +    gameaction = ga_nothing;
 +    Z_CheckHeap ();
 +
 +    // clear cmd building stuff
 +
 +    memset (gamekeydown, 0, sizeof(gamekeydown));
 +    joyxmove = joyymove = 0;
 +    mousex = mousey = 0;
 +    sendpause = sendsave = paused = false;
 +    memset (mousebuttons, 0, sizeof(mousebuttons));
 +    memset (joybuttons, 0, sizeof(joybuttons));
 +
 +    if (testcontrols)
 +    {
 +        players[consoleplayer].message = "Press escape to quit.";
 +    }
 +}
 +
 +
 +static void SetJoyButtons(unsigned int buttons_mask)
 +{
 +    int i;
 +
 +    for (i=0; i<MAX_JOY_BUTTONS; ++i)
 +    {
 +        joybuttons[i] = (buttons_mask & (1 << i)) != 0;
 +    }
 +}
 +
 +//
 +// G_Responder
 +// Get info needed to make ticcmd_ts for the players.
 +//
 +boolean G_Responder (event_t* ev)
 +{
 +    // allow spy mode changes even during the demo
 +    if (gamestate == GS_LEVEL && ev->type == ev_keydown
 +     && ev->data1 == KEY_F12 && (singledemo || !deathmatch) )
 +    {
 +	// spy mode
 +	do
 +	{
 +	    displayplayer++;
 +	    if (displayplayer == MAXPLAYERS)
 +		displayplayer = 0;
 +	} while (!playeringame[displayplayer] && displayplayer != consoleplayer);
 +	return true;
 +    }
 +
 +    // any other key pops up menu if in demos
 +    if (gameaction == ga_nothing && !singledemo &&
 +	(demoplayback || gamestate == GS_DEMOSCREEN)
 +	)
 +    {
 +	if (ev->type == ev_keydown ||
 +	    (ev->type == ev_mouse && ev->data1) ||
 +	    (ev->type == ev_joystick && ev->data1) )
 +	{
 +	    M_StartControlPanel ();
 +	    return true;
 +	}
 +	return false;
 +    }
 +
 +    if (gamestate == GS_LEVEL)
 +    {
 +#if 0
 +	if (devparm && ev->type == ev_keydown && ev->data1 == ';')
 +	{
 +	    G_DeathMatchSpawnPlayer (0);
 +	    return true;
 +	}
 +#endif
 +	if (HU_Responder (ev))
 +	    return true;	// chat ate the event
 +	if (ST_Responder (ev))
 +	    return true;	// status window ate it
 +	if (AM_Responder (ev))
 +	    return true;	// automap ate it
 +    }
 +
 +    if (gamestate == GS_FINALE)
 +    {
 +	if (F_Responder (ev))
 +	    return true;	// finale ate the event
 +    }
 +
 +    if (testcontrols && ev->type == ev_mouse)
 +    {
 +        // If we are invoked by setup to test the controls, save the
 +        // mouse speed so that we can display it on-screen.
 +        // Perform a low pass filter on this so that the thermometer
 +        // appears to move smoothly.
 +
 +        testcontrols_mousespeed = abs(ev->data2);
 +    }
 +
 +    switch (ev->type)
 +    {
 +      case ev_keydown:
 +	if (ev->data1 == key_pause)
 +	{
 +	    sendpause = true;
 +	}
 +        else if (ev->data1 <NUMKEYS)
 +        {
 +	    gamekeydown[ev->data1] = true;
 +        }
 +
 +	return true;    // eat key down events
 +
 +      case ev_keyup:
 +	if (ev->data1 <NUMKEYS)
 +	    gamekeydown[ev->data1] = false;
 +	return false;   // always let key up events filter down
 +
 +      case ev_mouse:
 +	mousebuttons[0] = ev->data1 & 1;
 +	mousebuttons[1] = ev->data1 & 2;
 +	mousebuttons[2] = ev->data1 & 4;
 +	mousex = ev->data2*(mouseSensitivity+5)/10;
 +	mousey = ev->data3*(mouseSensitivity+5)/10;
 +	return true;    // eat events
 +
 +      case ev_joystick:
 +        SetJoyButtons(ev->data1);
 +	joyxmove = ev->data2;
 +	joyymove = ev->data3;
 +	return true;    // eat events
 +
 +      default:
 +	break;
 +    }
 +
 +    return false;
 +}
 +
 +
 +
 +//
 +// G_Ticker
 +// Make ticcmd_ts for the players.
 +//
 +void G_Ticker (void)
 +{
 +    int		i;
 +    int		buf;
 +    ticcmd_t*	cmd;
 +
 +    // do player reborns if needed
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	if (playeringame[i] && players[i].playerstate == PST_REBORN)
 +	    G_DoReborn (i);
 +
 +    // do things to change the game state
 +    while (gameaction != ga_nothing)
 +    {
 +	switch (gameaction)
 +	{
 +	  case ga_loadlevel:
 +	    G_DoLoadLevel ();
 +	    break;
 +	  case ga_newgame:
 +	    G_DoNewGame ();
 +	    break;
 +	  case ga_loadgame:
 +	    G_DoLoadGame ();
 +	    break;
 +	  case ga_savegame:
 +	    G_DoSaveGame ();
 +	    break;
 +	  case ga_playdemo:
 +	    G_DoPlayDemo ();
 +	    break;
 +	  case ga_completed:
 +	    G_DoCompleted ();
 +	    break;
 +	  case ga_victory:
 +	    F_StartFinale ();
 +	    break;
 +	  case ga_worlddone:
 +	    G_DoWorldDone ();
 +	    break;
 +	  case ga_screenshot:
 +	    V_ScreenShot("DOOM%02i.pcx");
 +            players[consoleplayer].message = DEH_String("screen shot");
 +	    gameaction = ga_nothing;
 +	    break;
 +	  case ga_nothing:
 +	    break;
 +	}
 +    }
 +
 +    // get commands, check consistancy,
 +    // and build new consistancy check
 +    buf = (gametic/ticdup)%BACKUPTICS;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	if (playeringame[i])
 +	{
 +	    cmd = &players[i].cmd;
 +
 +	    memcpy (cmd, &netcmds[i][buf], sizeof(ticcmd_t));
 +
 +	    if (demoplayback)
 +		G_ReadDemoTiccmd (cmd);
 +	    if (demorecording)
 +		G_WriteDemoTiccmd (cmd);
 +
 +	    // check for turbo cheats
 +
 +            // check ~ 4 seconds whether to display the turbo message.
 +            // store if the turbo threshold was exceeded in any tics
 +            // over the past 4 seconds.  offset the checking period
 +            // for each player so messages are not displayed at the
 +            // same time.
 +
 +            if (cmd->forwardmove > TURBOTHRESHOLD)
 +            {
 +                turbodetected[i] = true;
 +            }
 +
 +	    if ((gametic & 31) == 0
 +             && ((gametic >> 5) % MAXPLAYERS) == i
 +             && turbodetected[i])
 +	    {
 +		static char turbomessage[80];
 +		extern char *player_names[4];
 +		sprintf (turbomessage, "%s is turbo!",player_names[i]);
 +		players[consoleplayer].message = turbomessage;
 +                turbodetected[i] = false;
 +	    }
 +
 +	    if (netgame && !netdemo && !(gametic%ticdup) )
 +	    {
 +		if (gametic > BACKUPTICS
 +		    && consistancy[i][buf] != cmd->consistancy)
 +		{
 +		    I_Error ("consistency failure (%i should be %i)",
 +			     cmd->consistancy, consistancy[i][buf]);
 +		}
 +		if (players[i].mo)
 +		    consistancy[i][buf] = players[i].mo->x;
 +		else
 +		    consistancy[i][buf] = rndindex;
 +	    }
 +	}
 +    }
 +
 +    // check for special buttons
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	if (playeringame[i])
 +	{
 +	    if (players[i].cmd.buttons & BT_SPECIAL)
 +	    {
 +		switch (players[i].cmd.buttons & BT_SPECIALMASK)
 +		{
 +		  case BTS_PAUSE:
 +		    paused ^= 1;
 +		    if (paused)
 +			S_PauseSound ();
 +		    else
 +			S_ResumeSound ();
 +		    break;
 +
 +		  case BTS_SAVEGAME:
 +		    if (!savedescription[0])
 +			strcpy (savedescription, "NET GAME");
 +		    savegameslot =
 +			(players[i].cmd.buttons & BTS_SAVEMASK)>>BTS_SAVESHIFT;
 +		    gameaction = ga_savegame;
 +		    break;
 +		}
 +	    }
 +	}
 +    }
 +
 +    // Have we just finished displaying an intermission screen?
 +
 +    if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION)
 +    {
 +        WI_End();
 +    }
 +
 +    oldgamestate = gamestate;
 +
 +    // do main actions
 +    switch (gamestate)
 +    {
 +      case GS_LEVEL:
 +	P_Ticker ();
 +	ST_Ticker ();
 +	AM_Ticker ();
 +	HU_Ticker ();
 +	break;
 +
 +      case GS_INTERMISSION:
 +	WI_Ticker ();
 +	break;
 +
 +      case GS_FINALE:
 +	F_Ticker ();
 +	break;
 +
 +      case GS_DEMOSCREEN:
 +	D_PageTicker ();
 +	break;
 +    }
 +}
 +
 +
 +//
 +// PLAYER STRUCTURE FUNCTIONS
 +// also see P_SpawnPlayer in P_Things
 +//
 +
 +//
 +// G_InitPlayer
 +// Called at the start.
 +// Called by the game initialization functions.
 +//
 +void G_InitPlayer (int player)
 +{
 +    player_t*	p;
 +
 +    // set up the saved info
 +    p = &players[player];
 +
 +    // clear everything else to defaults
 +    G_PlayerReborn (player);
 +
 +}
 +
 +
 +
 +//
 +// G_PlayerFinishLevel
 +// Can when a player completes a level.
 +//
 +void G_PlayerFinishLevel (int player)
 +{
 +    player_t*	p;
 +
 +    p = &players[player];
 +
 +    memset (p->powers, 0, sizeof (p->powers));
 +    memset (p->cards, 0, sizeof (p->cards));
 +    p->mo->flags &= ~MF_SHADOW;		// cancel invisibility
 +    p->extralight = 0;			// cancel gun flashes
 +    p->fixedcolormap = 0;		// cancel ir gogles
 +    p->damagecount = 0;			// no palette changes
 +    p->bonuscount = 0;
 +}
 +
 +
 +//
 +// G_PlayerReborn
 +// Called after a player dies
 +// almost everything is cleared and initialized
 +//
 +void G_PlayerReborn (int player)
 +{
 +    player_t*	p;
 +    int		i;
 +    int		frags[MAXPLAYERS];
 +    int		killcount;
 +    int		itemcount;
 +    int		secretcount;
 +
 +    memcpy (frags,players[player].frags,sizeof(frags));
 +    killcount = players[player].killcount;
 +    itemcount = players[player].itemcount;
 +    secretcount = players[player].secretcount;
 +
 +    p = &players[player];
 +    memset (p, 0, sizeof(*p));
 +
 +    memcpy (players[player].frags, frags, sizeof(players[player].frags));
 +    players[player].killcount = killcount;
 +    players[player].itemcount = itemcount;
 +    players[player].secretcount = secretcount;
 +
 +    p->usedown = p->attackdown = true;	// don't do anything immediately
 +    p->playerstate = PST_LIVE;
 +    p->health = deh_initial_health;     // Use dehacked value
 +    p->readyweapon = p->pendingweapon = wp_pistol;
 +    p->weaponowned[wp_fist] = true;
 +    p->weaponowned[wp_pistol] = true;
 +    p->ammo[am_clip] = deh_initial_bullets;
 +
 +    for (i=0 ; i<NUMAMMO ; i++)
 +	p->maxammo[i] = maxammo[i];
 +
 +}
 +
 +//
 +// G_CheckSpot
 +// Returns false if the player cannot be respawned
 +// at the given mapthing_t spot
 +// because something is occupying it
 +//
 +void P_SpawnPlayer (mapthing_t* mthing);
 +
 +boolean
 +G_CheckSpot
 +( int		playernum,
 +  mapthing_t*	mthing )
 +{
 +    fixed_t		x;
 +    fixed_t		y;
 +    subsector_t*	ss;
 +    unsigned		an;
 +    mobj_t*		mo;
 +    int			i;
 +
 +    if (!players[playernum].mo)
 +    {
 +	// first spawn of level, before corpses
 +	for (i=0 ; i<playernum ; i++)
 +	    if (players[i].mo->x == mthing->x << FRACBITS
 +		&& players[i].mo->y == mthing->y << FRACBITS)
 +		return false;
 +	return true;
 +    }
 +
 +    x = mthing->x << FRACBITS;
 +    y = mthing->y << FRACBITS;
 +
 +    if (!P_CheckPosition (players[playernum].mo, x, y) )
 +	return false;
 +
 +    // flush an old corpse if needed
 +    if (bodyqueslot >= BODYQUESIZE)
 +	P_RemoveMobj (bodyque[bodyqueslot%BODYQUESIZE]);
 +    bodyque[bodyqueslot%BODYQUESIZE] = players[playernum].mo;
 +    bodyqueslot++;
 +
 +    // spawn a teleport fog
 +    ss = R_PointInSubsector (x,y);
 +    an = ( ANG45 * (((unsigned int) mthing->angle)/45) ) >> ANGLETOFINESHIFT;
 +
 +    mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an]
 +		      , ss->sector->floorheight
 +		      , MT_TFOG);
 +
 +    if (players[consoleplayer].viewz != 1)
 +	S_StartSound (mo, sfx_telept);	// don't start sound on first frame
 +
 +    return true;
 +}
 +
 +
 +//
 +// G_DeathMatchSpawnPlayer
 +// Spawns a player at one of the random death match spots
 +// called at level load and each death
 +//
 +void G_DeathMatchSpawnPlayer (int playernum)
 +{
 +    int             i,j;
 +    int				selections;
 +
 +    selections = deathmatch_p - deathmatchstarts;
 +    if (selections < 4)
 +	I_Error ("Only %i deathmatch spots, 4 required", selections);
 +
 +    for (j=0 ; j<20 ; j++)
 +    {
 +	i = P_Random() % selections;
 +	if (G_CheckSpot (playernum, &deathmatchstarts[i]) )
 +	{
 +	    deathmatchstarts[i].type = playernum+1;
 +	    P_SpawnPlayer (&deathmatchstarts[i]);
 +	    return;
 +	}
 +    }
 +
 +    // no good spot, so the player will probably get stuck
 +    P_SpawnPlayer (&playerstarts[playernum]);
 +}
 +
 +//
 +// G_DoReborn
 +//
 +void G_DoReborn (int playernum)
 +{
 +    int                             i;
 +
 +    if (!netgame)
 +    {
 +	// reload the level from scratch
 +	gameaction = ga_loadlevel;
 +    }
 +    else
 +    {
 +	// respawn at the start
 +
 +	// first dissasociate the corpse
 +	players[playernum].mo->player = NULL;
 +
 +	// spawn at random spot if in death match
 +	if (deathmatch)
 +	{
 +	    G_DeathMatchSpawnPlayer (playernum);
 +	    return;
 +	}
 +
 +	if (G_CheckSpot (playernum, &playerstarts[playernum]) )
 +	{
 +	    P_SpawnPlayer (&playerstarts[playernum]);
 +	    return;
 +	}
 +
 +	// try to spawn at one of the other players spots
 +	for (i=0 ; i<MAXPLAYERS ; i++)
 +	{
 +	    if (G_CheckSpot (playernum, &playerstarts[i]) )
 +	    {
 +		playerstarts[i].type = playernum+1;	// fake as other player
 +		P_SpawnPlayer (&playerstarts[i]);
 +		playerstarts[i].type = i+1;		// restore
 +		return;
 +	    }
 +	    // he's going to be inside something.  Too bad.
 +	}
 +	P_SpawnPlayer (&playerstarts[playernum]);
 +    }
 +}
 +
 +
 +void G_ScreenShot (void)
 +{
 +    gameaction = ga_screenshot;
 +}
 +
 +
 +
 +// DOOM Par Times
 +int pars[4][10] =
 +{
 +    {0},
 +    {0,30,75,120,90,165,180,180,30,165},
 +    {0,90,90,90,120,90,360,240,30,170},
 +    {0,90,45,90,150,90,90,165,30,135}
 +};
 +
 +// DOOM II Par Times
 +int cpars[32] =
 +{
 +    30,90,120,120,90,150,120,120,270,90,	//  1-10
 +    210,150,150,150,210,150,420,150,210,150,	// 11-20
 +    240,150,180,150,150,300,330,420,300,180,	// 21-30
 +    120,30					// 31-32
 +};
 +
 +
 +//
 +// G_DoCompleted
 +//
 +boolean		secretexit;
 +extern char*	pagename;
 +
 +void G_ExitLevel (void)
 +{
 +    secretexit = false;
 +    gameaction = ga_completed;
 +}
 +
 +// Here's for the german edition.
 +void G_SecretExitLevel (void)
 +{
 +    // IF NO WOLF3D LEVELS, NO SECRET EXIT!
 +    if ( (gamemode == commercial)
 +      && (W_CheckNumForName("map31")<0))
 +	secretexit = false;
 +    else
 +	secretexit = true;
 +    gameaction = ga_completed;
 +}
 +
 +void G_DoCompleted (void)
 +{
 +    int             i;
 +
 +    gameaction = ga_nothing;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	if (playeringame[i])
 +	    G_PlayerFinishLevel (i);        // take away cards and stuff
 +
 +    if (automapactive)
 +	AM_Stop ();
 +
 +    if (gamemode != commercial)
 +    {
 +        // Chex Quest ends after 5 levels, rather than 8.
 +
 +        if (gameversion == exe_chex)
 +        {
 +            if (gamemap == 5)
 +            {
 +                gameaction = ga_victory;
 +                return;
 +            }
 +        }
 +        else
 +        {
 +            switch(gamemap)
 +            {
 +              case 8:
 +                gameaction = ga_victory;
 +                return;
 +              case 9:
 +                for (i=0 ; i<MAXPLAYERS ; i++)
 +                    players[i].didsecret = true;
 +                break;
 +            }
 +        }
 +    }
 +
 +//#if 0  Hmmm - why?
 +    if ( (gamemap == 8)
 +	 && (gamemode != commercial) )
 +    {
 +	// victory
 +	gameaction = ga_victory;
 +	return;
 +    }
 +
 +    if ( (gamemap == 9)
 +	 && (gamemode != commercial) )
 +    {
 +	// exit secret level
 +	for (i=0 ; i<MAXPLAYERS ; i++)
 +	    players[i].didsecret = true;
 +    }
 +//#endif
 +
 +
 +    wminfo.didsecret = players[consoleplayer].didsecret;
 +    wminfo.epsd = gameepisode -1;
 +    wminfo.last = gamemap -1;
 +
 +    // wminfo.next is 0 biased, unlike gamemap
 +    if ( gamemode == commercial)
 +    {
 +	if (secretexit)
 +	    switch(gamemap)
 +	    {
 +	      case 15: wminfo.next = 30; break;
 +	      case 31: wminfo.next = 31; break;
 +	    }
 +	else
 +	    switch(gamemap)
 +	    {
 +	      case 31:
 +	      case 32: wminfo.next = 15; break;
 +	      default: wminfo.next = gamemap;
 +	    }
 +    }
 +    else
 +    {
 +	if (secretexit)
 +	    wminfo.next = 8; 	// go to secret level
 +	else if (gamemap == 9)
 +	{
 +	    // returning from secret level
 +	    switch (gameepisode)
 +	    {
 +	      case 1:
 +		wminfo.next = 3;
 +		break;
 +	      case 2:
 +		wminfo.next = 5;
 +		break;
 +	      case 3:
 +		wminfo.next = 6;
 +		break;
 +	      case 4:
 +		wminfo.next = 2;
 +		break;
 +	    }
 +	}
 +	else
 +	    wminfo.next = gamemap;          // go to next level
 +    }
 +
 +    wminfo.maxkills = totalkills;
 +    wminfo.maxitems = totalitems;
 +    wminfo.maxsecret = totalsecret;
 +    wminfo.maxfrags = 0;
 +    if ( gamemode == commercial )
 +	wminfo.partime = TICRATE*cpars[gamemap-1];
 +    else
 +	wminfo.partime = TICRATE*pars[gameepisode][gamemap];
 +    wminfo.pnum = consoleplayer;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	wminfo.plyr[i].in = playeringame[i];
 +	wminfo.plyr[i].skills = players[i].killcount;
 +	wminfo.plyr[i].sitems = players[i].itemcount;
 +	wminfo.plyr[i].ssecret = players[i].secretcount;
 +	wminfo.plyr[i].stime = leveltime;
 +	memcpy (wminfo.plyr[i].frags, players[i].frags
 +		, sizeof(wminfo.plyr[i].frags));
 +    }
 +
 +    gamestate = GS_INTERMISSION;
 +    viewactive = false;
 +    automapactive = false;
 +
 +    WI_Start (&wminfo);
 +}
 +
 +
 +//
 +// G_WorldDone
 +//
 +void G_WorldDone (void)
 +{
 +    gameaction = ga_worlddone;
 +
 +    if (secretexit)
 +	players[consoleplayer].didsecret = true;
 +
 +    if ( gamemode == commercial )
 +    {
 +	switch (gamemap)
 +	{
 +	  case 15:
 +	  case 31:
 +	    if (!secretexit)
 +		break;
 +	  case 6:
 +	  case 11:
 +	  case 20:
 +	  case 30:
 +	    F_StartFinale ();
 +	    break;
 +	}
 +    }
 +}
 +
 +void G_DoWorldDone (void)
 +{
 +    gamestate = GS_LEVEL;
 +    gamemap = wminfo.next+1;
 +    G_DoLoadLevel ();
 +    gameaction = ga_nothing;
 +    viewactive = true;
 +}
 +
 +
 +
 +//
 +// G_InitFromSavegame
 +// Can be called by the startup code or the menu task.
 +//
 +extern boolean setsizeneeded;
 +void R_ExecuteSetViewSize (void);
 +
 +char	savename[256];
 +
 +void G_LoadGame (char* name)
 +{
 +    strcpy (savename, name);
 +    gameaction = ga_loadgame;
 +}
 +
 +#define VERSIONSIZE		16
 +
 +
 +void G_DoLoadGame (void)
 +{
 +    int savedleveltime;
 +
 +    gameaction = ga_nothing;
 +
 +    save_stream = fopen(savename, "rb");
 +
 +    if (save_stream == NULL)
 +    {
 +        return;
 +    }
 +
 +    if (!P_ReadSaveGameHeader())
 +    {
 +        fclose(save_stream);
 +        return;
 +    }
 +
 +    savedleveltime = leveltime;
 +
 +    // load a base level
 +    G_InitNew (gameskill, gameepisode, gamemap);
 +
 +    leveltime = savedleveltime;
 +
 +    // dearchive all the modifications
 +    P_UnArchivePlayers ();
 +    P_UnArchiveWorld ();
 +    P_UnArchiveThinkers ();
 +    P_UnArchiveSpecials ();
 +
 +    if (!P_ReadSaveGameEOF())
 +	I_Error ("Bad savegame");
 +
 +    fclose(save_stream);
 +
 +    if (setsizeneeded)
 +	R_ExecuteSetViewSize ();
 +
 +    // draw the pattern into the back screen
 +    R_FillBackScreen ();
 +}
 +
 +
 +//
 +// G_SaveGame
 +// Called by the menu task.
 +// Description is a 24 byte text string
 +//
 +void
 +G_SaveGame
 +( int	slot,
 +  char*	description )
 +{
 +    savegameslot = slot;
 +    strcpy (savedescription, description);
 +    sendsave = true;
 +}
 +
 +void G_DoSaveGame (void)
 +{
 +    char *savegame_file;
 +    char *temp_savegame_file;
 +
 +    temp_savegame_file = P_TempSaveGameFile();
 +    savegame_file = P_SaveGameFile(savegameslot);
 +
 +    // Open the savegame file for writing.  We write to a temporary file
 +    // and then rename it at the end if it was successfully written.
 +    // This prevents an existing savegame from being overwritten by
 +    // a corrupted one, or if a savegame buffer overrun occurs.
 +
 +    save_stream = fopen(temp_savegame_file, "wb");
 +
 +    if (save_stream == NULL)
 +    {
 +        return;
 +    }
 +
 +    P_WriteSaveGameHeader(savedescription);
 +
 +    P_ArchivePlayers ();
 +    P_ArchiveWorld ();
 +    P_ArchiveThinkers ();
 +    P_ArchiveSpecials ();
 +
 +    P_WriteSaveGameEOF();
 +
 +    // Enforce the same savegame size limit as in Vanilla Doom,
 +    // except if the vanilla_savegame_limit setting is turned off.
 +
 +    if (vanilla_savegame_limit && ftell(save_stream) > SAVEGAMESIZE)
 +    {
 +        I_Error ("Savegame buffer overrun");
 +    }
 +
 +    // Finish up, close the savegame file.
 +
 +    fclose(save_stream);
 +
 +    // Now rename the temporary savegame file to the actual savegame
 +    // file, overwriting the old savegame if there was one there.
 +
 +    remove(savegame_file);
 +    rename(temp_savegame_file, savegame_file);
 +
 +    gameaction = ga_nothing;
 +    strcpy(savedescription, "");
 +
 +    players[consoleplayer].message = DEH_String(GGSAVED);
 +
 +    // draw the pattern into the back screen
 +    R_FillBackScreen ();
 +}
 +
 +
 +//
 +// G_InitNew
 +// Can be called by the startup code or the menu task,
 +// consoleplayer, displayplayer, playeringame[] should be set.
 +//
 +skill_t	d_skill;
 +int     d_episode;
 +int     d_map;
 +
 +void
 +G_DeferedInitNew
 +( skill_t	skill,
 +  int		episode,
 +  int		map)
 +{
 +    d_skill = skill;
 +    d_episode = episode;
 +    d_map = map;
 +    gameaction = ga_newgame;
 +}
 +
 +
 +void G_DoNewGame (void)
 +{
 +    demoplayback = false;
 +    netdemo = false;
 +    netgame = false;
 +    deathmatch = false;
 +    playeringame[1] = playeringame[2] = playeringame[3] = 0;
 +    respawnparm = false;
 +    fastparm = false;
 +    nomonsters = false;
 +    consoleplayer = 0;
 +    G_InitNew (d_skill, d_episode, d_map);
 +    gameaction = ga_nothing;
 +}
 +
 +// The sky texture to be used instead of the F_SKY1 dummy.
 +extern  int	skytexture;
 +
 +
 +void
 +G_InitNew
 +( skill_t	skill,
 +  int		episode,
 +  int		map )
 +{
 +    char *skytexturename;
 +    int             i;
 +
 +    if (paused)
 +    {
 +	paused = false;
 +	S_ResumeSound ();
 +    }
 +
 +
 +    if (skill > sk_nightmare)
 +	skill = sk_nightmare;
 +
 +
 +    // This was quite messy with SPECIAL and commented parts.
 +    // Supposedly hacks to make the latest edition work.
 +    // It might not work properly.
 +    if (episode < 1)
 +      episode = 1;
 +
 +    if ( gamemode == retail )
 +    {
 +      if (episode > 4)
 +	episode = 4;
 +    }
 +    else if ( gamemode == shareware )
 +    {
 +      if (episode > 1)
 +	   episode = 1;	// only start episode 1 on shareware
 +    }
 +    else
 +    {
 +      if (episode > 3)
 +	episode = 3;
 +    }
 +
 +
 +
 +    if (map < 1)
 +	map = 1;
 +
 +    if ( (map > 9)
 +	 && ( gamemode != commercial) )
 +      map = 9;
 +
 +    M_ClearRandom ();
 +
 +    if (skill == sk_nightmare || respawnparm )
 +	respawnmonsters = true;
 +    else
 +	respawnmonsters = false;
 +
 +    if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare) )
 +    {
 +	for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++)
 +	    states[i].tics >>= 1;
 +	mobjinfo[MT_BRUISERSHOT].speed = 20*FRACUNIT;
 +	mobjinfo[MT_HEADSHOT].speed = 20*FRACUNIT;
 +	mobjinfo[MT_TROOPSHOT].speed = 20*FRACUNIT;
 +    }
 +    else if (skill != sk_nightmare && gameskill == sk_nightmare)
 +    {
 +	for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++)
 +	    states[i].tics <<= 1;
 +	mobjinfo[MT_BRUISERSHOT].speed = 15*FRACUNIT;
 +	mobjinfo[MT_HEADSHOT].speed = 10*FRACUNIT;
 +	mobjinfo[MT_TROOPSHOT].speed = 10*FRACUNIT;
 +    }
 +
 +
 +    // force players to be initialized upon first level load
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	players[i].playerstate = PST_REBORN;
 +
 +    usergame = true;                // will be set false if a demo
 +    paused = false;
 +    demoplayback = false;
 +    automapactive = false;
 +    viewactive = true;
 +    gameepisode = episode;
 +    gamemap = map;
 +    gameskill = skill;
 +
 +    viewactive = true;
 +
 +    // Set the sky to use.
 +    //
 +    // Note: This IS broken, but it is how Vanilla Doom behaves.
 +    // See http://doom.wikia.com/wiki/Sky_never_changes_in_Doom_II.
 +    //
 +    // Because we set the sky here at the start of a game, not at the
 +    // start of a level, the sky texture never changes unless we
 +    // restore from a saved game.  This was fixed before the Doom
 +    // source release, but this IS the way Vanilla DOS Doom behaves.
 +
 +    if (gamemode == commercial)
 +    {
 +        if (gamemap < 12)
 +            skytexturename = "SKY1";
 +        else if (gamemap < 21)
 +            skytexturename = "SKY2";
 +        else
 +            skytexturename = "SKY3";
 +    }
 +    else
 +    {
 +        switch (gameepisode)
 +        {
 +          default:
 +          case 1:
 +            skytexturename = "SKY1";
 +            break;
 +          case 2:
 +            skytexturename = "SKY2";
 +            break;
 +          case 3:
 +            skytexturename = "SKY3";
 +            break;
 +          case 4:        // Special Edition sky
 +            skytexturename = "SKY4";
 +            break;
 +        }
 +    }
 +
 +    skytexturename = DEH_String(skytexturename);
 +
 +    skytexture = R_TextureNumForName(skytexturename);
 +
 +
 +    G_DoLoadLevel ();
 +}
 +
 +
 +//
 +// DEMO RECORDING
 +//
 +#define DEMOMARKER		0x80
 +
 +
 +void G_ReadDemoTiccmd (ticcmd_t* cmd)
 +{
 +    if (*demo_p == DEMOMARKER)
 +    {
 +	// end of demo data stream
 +	G_CheckDemoStatus ();
 +	return;
 +    }
 +    cmd->forwardmove = ((signed char)*demo_p++);
 +    cmd->sidemove = ((signed char)*demo_p++);
 +
 +    // If this is a longtics demo, read back in higher resolution
 +
 +    if (longtics)
 +    {
 +        cmd->angleturn = *demo_p++;
 +        cmd->angleturn |= (*demo_p++) << 8;
 +    }
 +    else
 +    {
 +        cmd->angleturn = ((unsigned char) *demo_p++)<<8;
 +    }
 +
 +    cmd->buttons = (unsigned char)*demo_p++;
 +}
 +
 +// Increase the size of the demo buffer to allow unlimited demos
 +
 +static void IncreaseDemoBuffer(void)
 +{
 +    int current_length;
 +    byte *new_demobuffer;
 +    byte *new_demop;
 +    int new_length;
 +
 +    // Find the current size
 +
 +    current_length = demoend - demobuffer;
 +
 +    // Generate a new buffer twice the size
 +    new_length = current_length * 2;
 +
 +    new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0);
 +    new_demop = new_demobuffer + (demo_p - demobuffer);
 +
 +    // Copy over the old data
 +
 +    memcpy(new_demobuffer, demobuffer, current_length);
 +
 +    // Free the old buffer and point the demo pointers at the new buffer.
 +
 +    Z_Free(demobuffer);
 +
 +    demobuffer = new_demobuffer;
 +    demo_p = new_demop;
 +    demoend = demobuffer + new_length;
 +}
 +
 +void G_WriteDemoTiccmd (ticcmd_t* cmd)
 +{
 +    byte *demo_start;
 +
 +    if (gamekeydown['q'])           // press q to end demo recording
 +	G_CheckDemoStatus ();
 +
 +    demo_start = demo_p;
 +
 +    *demo_p++ = cmd->forwardmove;
 +    *demo_p++ = cmd->sidemove;
 +
 +    // If this is a longtics demo, record in higher resolution
 +
 +    if (longtics)
 +    {
 +        *demo_p++ = (cmd->angleturn & 0xff);
 +        *demo_p++ = (cmd->angleturn >> 8) & 0xff;
 +    }
 +    else
 +    {
 +        *demo_p++ = cmd->angleturn >> 8;
 +    }
 +
 +    *demo_p++ = cmd->buttons;
 +
 +    // reset demo pointer back
 +    demo_p = demo_start;
 +
 +    if (demo_p > demoend - 16)
 +    {
 +        if (vanilla_demo_limit)
 +        {
 +            // no more space
 +            G_CheckDemoStatus ();
 +            return;
 +        }
 +        else
 +        {
 +            // Vanilla demo limit disabled: unlimited
 +            // demo lengths!
 +
 +            IncreaseDemoBuffer();
 +        }
 +    }
 +
 +    G_ReadDemoTiccmd (cmd);         // make SURE it is exactly the same
 +}
 +
 +
 +
 +//
 +// G_RecordDemo
 +//
 +void G_RecordDemo (char* name)
 +{
 +    int             i;
 +    int				maxsize;
 +
 +    usergame = false;
 +    strcpy (demoname, name);
 +    strcat (demoname, ".lmp");
 +    maxsize = 0x20000;
 +
 +    //!
 +    // @arg <size>
 +    // @category demo
 +    // @vanilla
 +    //
 +    // Specify the demo buffer size (KiB)
 +    //
 +
 +    i = M_CheckParm ("-maxdemo");
 +    if (i && i<myargc-1)
 +	maxsize = atoi(myargv[i+1])*1024;
 +    demobuffer = Z_Malloc (maxsize,PU_STATIC,NULL);
 +    demoend = demobuffer + maxsize;
 +
 +    demorecording = true;
 +}
 +
 +
 +void G_BeginRecording (void)
 +{
 +    int             i;
 +
 +    //!
 +    // @category demo
 +    //
 +    // Record a high resolution "Doom 1.91" demo.
 +    //
 +
 +    longtics = M_CheckParm("-longtics") != 0;
 +
 +    // If not recording a longtics demo, record in low res
 +
 +    lowres_turn = !longtics;
 +
 +    demo_p = demobuffer;
 +
 +    // Save the right version code for this demo
 +
 +    if (longtics)
 +    {
 +        *demo_p++ = DOOM_191_VERSION;
 +    }
 +    else
 +    {
 +        *demo_p++ = DOOM_VERSION;
 +    }
 +
 +    *demo_p++ = gameskill;
 +    *demo_p++ = gameepisode;
 +    *demo_p++ = gamemap;
 +    *demo_p++ = deathmatch;
 +    *demo_p++ = respawnparm;
 +    *demo_p++ = fastparm;
 +    *demo_p++ = nomonsters;
 +    *demo_p++ = consoleplayer;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	*demo_p++ = playeringame[i];
 +}
 +
 +
 +//
 +// G_PlayDemo
 +//
 +
 +char*	defdemoname;
 +
 +void G_DeferedPlayDemo (char* name)
 +{
 +    defdemoname = name;
 +    gameaction = ga_playdemo;
 +}
 +
 +// Generate a string describing a demo version
 +
 +static char *DemoVersionDescription(int version)
 +{
 +    static char resultbuf[16];
 +
 +    switch (version)
 +    {
 +        case 104:
 +            return "v1.4";
 +        case 105:
 +            return "v1.5";
 +        case 106:
 +            return "v1.6/v1.666";
 +        case 107:
 +            return "v1.7/v1.7a";
 +        case 108:
 +            return "v1.8";
 +        case 109:
 +            return "v1.9";
 +        default:
 +            break;
 +    }
 +
 +    // Unknown version.  Perhaps this is a pre-v1.4 IWAD?  If the version
 +    // byte is in the range 0-4 then it can be a v1.0-v1.2 demo.
 +
 +    if (version >= 0 && version <= 4)
 +    {
 +        return "v1.0/v1.1/v1.2";
 +    }
 +    else
 +    {
 +        sprintf(resultbuf, "%i.%i (unknown)", version / 100, version % 100);
 +        return resultbuf;
 +    }
 +}
 +
 +void G_DoPlayDemo (void)
 +{
 +    skill_t skill;
 +    int             i, episode, map;
 +    int demoversion;
 +
 +    gameaction = ga_nothing;
 +    demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC);
 +
 +    demoversion = *demo_p++;
 +
 +    if (demoversion == DOOM_VERSION)
 +    {
 +        longtics = false;
 +    }
 +    else if (demoversion == DOOM_191_VERSION)
 +    {
 +        // demo recorded with cph's modified "v1.91" doom exe
 +        longtics = true;
 +    }
 +    else
 +    {
 +        char *message = "Demo is from a different game version!\n"
 +                        "(read %i, should be %i)\n"
 +                        "\n"
 +                        "*** You may need to upgrade your version "
 +                            "of Doom to v1.9. ***\n"
 +                        "    See: http://doomworld.com/files/patches.shtml\n"
 +                        "    This appears to be %s.";
 +
 +        I_Error(message, demoversion, DOOM_VERSION,
 +                         DemoVersionDescription(demoversion));
 +    }
 +
 +    skill = *demo_p++;
 +    episode = *demo_p++;
 +    map = *demo_p++;
 +    deathmatch = *demo_p++;
 +    respawnparm = *demo_p++;
 +    fastparm = *demo_p++;
 +    nomonsters = *demo_p++;
 +    consoleplayer = *demo_p++;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	playeringame[i] = *demo_p++;
 +
 +    //!
 +    // @category demo
 +    //
 +    // Play back a demo recorded in a netgame with a single player.
 +    //
 +
 +    if (playeringame[1] || M_CheckParm("-netdemo") > 0)
 +    {
 +	netgame = true;
 +	netdemo = true;
 +    }
 +
 +    // don't spend a lot of time in loadlevel
 +    precache = false;
 +    G_InitNew (skill, episode, map);
 +    precache = true;
 +    starttime = I_GetTime ();
 +
 +    usergame = false;
 +    demoplayback = true;
 +}
 +
 +//
 +// G_TimeDemo
 +//
 +void G_TimeDemo (char* name)
 +{
 +    //!
 +    // @vanilla
 +    //
 +    // Disable rendering the screen entirely.
 +    //
 +
 +    nodrawers = M_CheckParm ("-nodraw");
 +
 +    timingdemo = true;
 +    singletics = true;
 +
 +    defdemoname = name;
 +    gameaction = ga_playdemo;
 +}
 +
 +
 +/*
 +===================
 +=
 += G_CheckDemoStatus
 +=
 += Called after a death or level completion to allow demos to be cleaned up
 += Returns true if a new demo loop action will take place
 +===================
 +*/
 +
 +boolean G_CheckDemoStatus (void)
 +{
 +    int             endtime;
 +
 +    if (timingdemo)
 +    {
 +        float fps;
 +        int realtics;
 +
 +	endtime = I_GetTime ();
 +        realtics = endtime - starttime;
 +        fps = ((float) gametic * TICRATE) / realtics;
 +
 +        // Prevent recursive calls
 +        timingdemo = false;
 +        demoplayback = false;
 +
 +	I_Error ("timed %i gametics in %i realtics (%f fps)",
 +                 gametic, realtics, fps);
 +    }
 +
 +    if (demoplayback)
 +    {
 +        W_ReleaseLumpName(defdemoname);
 +	demoplayback = false;
 +	netdemo = false;
 +	netgame = false;
 +	deathmatch = false;
 +	playeringame[1] = playeringame[2] = playeringame[3] = 0;
 +	respawnparm = false;
 +	fastparm = false;
 +	nomonsters = false;
 +	consoleplayer = 0;
 +
 +        if (singledemo)
 +            I_Quit ();
 +        else
 +            D_AdvanceDemo ();
 +
 +	return true;
 +    }
 +
 +    if (demorecording)
 +    {
 +	*demo_p++ = DEMOMARKER;
 +	M_WriteFile (demoname, demobuffer, demo_p - demobuffer);
 +	Z_Free (demobuffer);
 +	demorecording = false;
 +	I_Error ("Demo %s recorded",demoname);
 +    }
 +
 +    return false;
 +}
 +
 +
 +
diff --cc src/strife/hu_stuff.c
index a106978e,00000000..ca74ce92
mode 100644,000000..100644
--- a/src/strife/hu_stuff.c
+++ b/src/strife/hu_stuff.c
@@@ -1,654 -1,0 +1,658 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:  Heads-up displays
 +//
 +//-----------------------------------------------------------------------------
 +
 +
 +#include <ctype.h>
 +
 +#include "doomdef.h"
 +#include "doomkeys.h"
 +
 +#include "z_zone.h"
 +
 +#include "deh_main.h"
 +#include "i_swap.h"
 +#include "i_video.h"
 +
 +#include "hu_stuff.h"
 +#include "hu_lib.h"
 +#include "m_controls.h"
 +#include "w_wad.h"
 +
 +#include "s_sound.h"
 +
 +#include "doomstat.h"
 +
 +// Data.
 +#include "dstrings.h"
 +#include "sounds.h"
 +
 +//
 +// Locally used constants, shortcuts.
 +//
 +#define HU_TITLE	(mapnames[(gameepisode-1)*9+gamemap-1])
- #define HU_TITLE2	(mapnames2[gamemap-1])
- #define HU_TITLEP	(mapnamesp[gamemap-1])
- #define HU_TITLET	(mapnamest[gamemap-1])
++#define HU_TITLE2	(mapnames_commercial[gamemap-1])
++#define HU_TITLEP	(mapnames_commercial[gamemap-1 + 32])
++#define HU_TITLET	(mapnames_commercial[gamemap-1 + 64])
 +#define HU_TITLE_CHEX   (mapnames[gamemap - 1])
 +#define HU_TITLEHEIGHT	1
 +#define HU_TITLEX	0
 +#define HU_TITLEY	(167 - SHORT(hu_font[0]->height))
 +
 +#define HU_INPUTTOGGLE	't'
 +#define HU_INPUTX	HU_MSGX
 +#define HU_INPUTY	(HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1))
 +#define HU_INPUTWIDTH	64
 +#define HU_INPUTHEIGHT	1
 +
 +
 +
 +char *chat_macros[10] =
 +{
 +    HUSTR_CHATMACRO0,
 +    HUSTR_CHATMACRO1,
 +    HUSTR_CHATMACRO2,
 +    HUSTR_CHATMACRO3,
 +    HUSTR_CHATMACRO4,
 +    HUSTR_CHATMACRO5,
 +    HUSTR_CHATMACRO6,
 +    HUSTR_CHATMACRO7,
 +    HUSTR_CHATMACRO8,
 +    HUSTR_CHATMACRO9
 +};
 +
 +char*	player_names[] =
 +{
 +    HUSTR_PLRGREEN,
 +    HUSTR_PLRINDIGO,
 +    HUSTR_PLRBROWN,
 +    HUSTR_PLRRED
 +};
 +
 +char			chat_char; // remove later.
 +static player_t*	plr;
 +patch_t*		hu_font[HU_FONTSIZE];
 +static hu_textline_t	w_title;
 +boolean			chat_on;
 +static hu_itext_t	w_chat;
 +static boolean		always_off = false;
 +static char		chat_dest[MAXPLAYERS];
 +static hu_itext_t w_inputbuffer[MAXPLAYERS];
 +
 +static boolean		message_on;
 +boolean			message_dontfuckwithme;
 +static boolean		message_nottobefuckedwith;
 +
 +static hu_stext_t	w_message;
 +static int		message_counter;
 +
 +extern int		showMessages;
 +extern boolean		automapactive;
 +
 +static boolean		headsupactive = false;
 +
 +//
 +// Builtin map names.
 +// The actual names can be found in DStrings.h.
 +//
 +
 +char*	mapnames[] =	// DOOM shareware/registered/retail (Ultimate) names.
 +{
 +
 +    HUSTR_E1M1,
 +    HUSTR_E1M2,
 +    HUSTR_E1M3,
 +    HUSTR_E1M4,
 +    HUSTR_E1M5,
 +    HUSTR_E1M6,
 +    HUSTR_E1M7,
 +    HUSTR_E1M8,
 +    HUSTR_E1M9,
 +
 +    HUSTR_E2M1,
 +    HUSTR_E2M2,
 +    HUSTR_E2M3,
 +    HUSTR_E2M4,
 +    HUSTR_E2M5,
 +    HUSTR_E2M6,
 +    HUSTR_E2M7,
 +    HUSTR_E2M8,
 +    HUSTR_E2M9,
 +
 +    HUSTR_E3M1,
 +    HUSTR_E3M2,
 +    HUSTR_E3M3,
 +    HUSTR_E3M4,
 +    HUSTR_E3M5,
 +    HUSTR_E3M6,
 +    HUSTR_E3M7,
 +    HUSTR_E3M8,
 +    HUSTR_E3M9,
 +
 +    HUSTR_E4M1,
 +    HUSTR_E4M2,
 +    HUSTR_E4M3,
 +    HUSTR_E4M4,
 +    HUSTR_E4M5,
 +    HUSTR_E4M6,
 +    HUSTR_E4M7,
 +    HUSTR_E4M8,
 +    HUSTR_E4M9,
 +
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL",
 +    "NEWLEVEL"
 +};
 +
- char*	mapnames2[] =	// DOOM 2 map names.
++// List of names for levels in commercial IWADs
++// (doom2.wad, plutonia.wad, tnt.wad).  These are stored in a
++// single large array; WADs like pl2.wad have a MAP33, and rely on
++// the layout in the Vanilla executable, where it is possible to
++// overflow the end of one array into the next.
++
++char *mapnames_commercial[] =
 +{
++    // DOOM 2 map names.
++
 +    HUSTR_1,
 +    HUSTR_2,
 +    HUSTR_3,
 +    HUSTR_4,
 +    HUSTR_5,
 +    HUSTR_6,
 +    HUSTR_7,
 +    HUSTR_8,
 +    HUSTR_9,
 +    HUSTR_10,
 +    HUSTR_11,
 +
 +    HUSTR_12,
 +    HUSTR_13,
 +    HUSTR_14,
 +    HUSTR_15,
 +    HUSTR_16,
 +    HUSTR_17,
 +    HUSTR_18,
 +    HUSTR_19,
 +    HUSTR_20,
 +
 +    HUSTR_21,
 +    HUSTR_22,
 +    HUSTR_23,
 +    HUSTR_24,
 +    HUSTR_25,
 +    HUSTR_26,
 +    HUSTR_27,
 +    HUSTR_28,
 +    HUSTR_29,
 +    HUSTR_30,
 +    HUSTR_31,
-     HUSTR_32
- };
++    HUSTR_32,
 +
++    // Plutonia WAD map names.
 +
- char*	mapnamesp[] =	// Plutonia WAD map names.
- {
 +    PHUSTR_1,
 +    PHUSTR_2,
 +    PHUSTR_3,
 +    PHUSTR_4,
 +    PHUSTR_5,
 +    PHUSTR_6,
 +    PHUSTR_7,
 +    PHUSTR_8,
 +    PHUSTR_9,
 +    PHUSTR_10,
 +    PHUSTR_11,
 +
 +    PHUSTR_12,
 +    PHUSTR_13,
 +    PHUSTR_14,
 +    PHUSTR_15,
 +    PHUSTR_16,
 +    PHUSTR_17,
 +    PHUSTR_18,
 +    PHUSTR_19,
 +    PHUSTR_20,
 +
 +    PHUSTR_21,
 +    PHUSTR_22,
 +    PHUSTR_23,
 +    PHUSTR_24,
 +    PHUSTR_25,
 +    PHUSTR_26,
 +    PHUSTR_27,
 +    PHUSTR_28,
 +    PHUSTR_29,
 +    PHUSTR_30,
 +    PHUSTR_31,
-     PHUSTR_32
- };
-
++    PHUSTR_32,
++
++    // TNT WAD map names.
 +
- char *mapnamest[] =	// TNT WAD map names.
- {
 +    THUSTR_1,
 +    THUSTR_2,
 +    THUSTR_3,
 +    THUSTR_4,
 +    THUSTR_5,
 +    THUSTR_6,
 +    THUSTR_7,
 +    THUSTR_8,
 +    THUSTR_9,
 +    THUSTR_10,
 +    THUSTR_11,
 +
 +    THUSTR_12,
 +    THUSTR_13,
 +    THUSTR_14,
 +    THUSTR_15,
 +    THUSTR_16,
 +    THUSTR_17,
 +    THUSTR_18,
 +    THUSTR_19,
 +    THUSTR_20,
 +
 +    THUSTR_21,
 +    THUSTR_22,
 +    THUSTR_23,
 +    THUSTR_24,
 +    THUSTR_25,
 +    THUSTR_26,
 +    THUSTR_27,
 +    THUSTR_28,
 +    THUSTR_29,
 +    THUSTR_30,
 +    THUSTR_31,
 +    THUSTR_32
 +};
 +
 +void HU_Init(void)
 +{
 +
 +    int		i;
 +    int		j;
 +    char	buffer[9];
 +
 +    // load the heads-up font
 +    j = HU_FONTSTART;
 +    for (i=0;i<HU_FONTSIZE;i++)
 +    {
 +	sprintf(buffer, DEH_String("STCFN%.3d"), j++);
 +	hu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
 +    }
 +
 +}
 +
 +void HU_Stop(void)
 +{
 +    headsupactive = false;
 +}
 +
 +void HU_Start(void)
 +{
 +
 +    int		i;
 +    char*	s;
 +
 +    if (headsupactive)
 +	HU_Stop();
 +
 +    plr = &players[consoleplayer];
 +    message_on = false;
 +    message_dontfuckwithme = false;
 +    message_nottobefuckedwith = false;
 +    chat_on = false;
 +
 +    // create the message widget
 +    HUlib_initSText(&w_message,
 +		    HU_MSGX, HU_MSGY, HU_MSGHEIGHT,
 +		    hu_font,
 +		    HU_FONTSTART, &message_on);
 +
 +    // create the map title widget
 +    HUlib_initTextLine(&w_title,
 +		       HU_TITLEX, HU_TITLEY,
 +		       hu_font,
 +		       HU_FONTSTART);
 +
 +    switch ( gamemission )
 +    {
 +      case doom:
 +	s = HU_TITLE;
 +	break;
 +      case doom2:
 +	 s = HU_TITLE2;
 +	 break;
 +      case pack_plut:
 +	s = HU_TITLEP;
 +	break;
 +      case pack_tnt:
 +	s = HU_TITLET;
 +	break;
 +      default:
 +         s = "Unknown level";
 +         break;
 +    }
 +
 +    // Chex.exe always uses the episode 1 level title
 +    // eg. E2M1 gives the title for E1M1
 +
 +    if (gameversion == exe_chex)
 +    {
 +        s = HU_TITLE_CHEX;
 +    }
 +
 +    // dehacked substitution to get modified level name
 +
 +    s = DEH_String(s);
 +
 +    while (*s)
 +	HUlib_addCharToTextLine(&w_title, *(s++));
 +
 +    // create the chat widget
 +    HUlib_initIText(&w_chat,
 +		    HU_INPUTX, HU_INPUTY,
 +		    hu_font,
 +		    HU_FONTSTART, &chat_on);
 +
 +    // create the inputbuffer widgets
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	HUlib_initIText(&w_inputbuffer[i], 0, 0, 0, 0, &always_off);
 +
 +    headsupactive = true;
 +
 +}
 +
 +void HU_Drawer(void)
 +{
 +
 +    HUlib_drawSText(&w_message);
 +    HUlib_drawIText(&w_chat);
 +    if (automapactive)
 +	HUlib_drawTextLine(&w_title, false);
 +
 +}
 +
 +void HU_Erase(void)
 +{
 +
 +    HUlib_eraseSText(&w_message);
 +    HUlib_eraseIText(&w_chat);
 +    HUlib_eraseTextLine(&w_title);
 +
 +}
 +
 +void HU_Ticker(void)
 +{
 +
 +    int i, rc;
 +    char c;
 +
 +    // tick down message counter if message is up
 +    if (message_counter && !--message_counter)
 +    {
 +	message_on = false;
 +	message_nottobefuckedwith = false;
 +    }
 +
 +    if (showMessages || message_dontfuckwithme)
 +    {
 +
 +	// display message if necessary
 +	if ((plr->message && !message_nottobefuckedwith)
 +	    || (plr->message && message_dontfuckwithme))
 +	{
 +	    HUlib_addMessageToSText(&w_message, 0, plr->message);
 +	    plr->message = 0;
 +	    message_on = true;
 +	    message_counter = HU_MSGTIMEOUT;
 +	    message_nottobefuckedwith = message_dontfuckwithme;
 +	    message_dontfuckwithme = 0;
 +	}
 +
 +    } // else message_on = false;
 +
 +    // check for incoming chat characters
 +    if (netgame)
 +    {
 +	for (i=0 ; i<MAXPLAYERS; i++)
 +	{
 +	    if (!playeringame[i])
 +		continue;
 +	    if (i != consoleplayer
 +		&& (c = players[i].cmd.chatchar))
 +	    {
 +		if (c <= HU_BROADCAST)
 +		    chat_dest[i] = c;
 +		else
 +		{
 +		    rc = HUlib_keyInIText(&w_inputbuffer[i], c);
 +		    if (rc && c == KEY_ENTER)
 +		    {
 +			if (w_inputbuffer[i].l.len
 +			    && (chat_dest[i] == consoleplayer+1
 +				|| chat_dest[i] == HU_BROADCAST))
 +			{
 +			    HUlib_addMessageToSText(&w_message,
 +						    DEH_String(player_names[i]),
 +						    w_inputbuffer[i].l.l);
 +
 +			    message_nottobefuckedwith = true;
 +			    message_on = true;
 +			    message_counter = HU_MSGTIMEOUT;
 +			    if ( gamemode == commercial )
 +			      S_StartSound(0, sfx_radio);
 +			    else
 +			      S_StartSound(0, sfx_tink);
 +			}
 +			HUlib_resetIText(&w_inputbuffer[i]);
 +		    }
 +		}
 +		players[i].cmd.chatchar = 0;
 +	    }
 +	}
 +    }
 +
 +}
 +
 +#define QUEUESIZE		128
 +
 +static char	chatchars[QUEUESIZE];
 +static int	head = 0;
 +static int	tail = 0;
 +
 +
 +void HU_queueChatChar(char c)
 +{
 +    if (((head + 1) & (QUEUESIZE-1)) == tail)
 +    {
 +	plr->message = DEH_String(HUSTR_MSGU);
 +    }
 +    else
 +    {
 +	chatchars[head] = c;
 +	head = (head + 1) & (QUEUESIZE-1);
 +    }
 +}
 +
 +char HU_dequeueChatChar(void)
 +{
 +    char c;
 +
 +    if (head != tail)
 +    {
 +	c = chatchars[tail];
 +	tail = (tail + 1) & (QUEUESIZE-1);
 +    }
 +    else
 +    {
 +	c = 0;
 +    }
 +
 +    return c;
 +}
 +
 +boolean HU_Responder(event_t *ev)
 +{
 +
 +    static char		lastmessage[HU_MAXLINELENGTH+1];
 +    char*		macromessage;
 +    boolean		eatkey = false;
 +    static boolean	shiftdown = false;
 +    static boolean	altdown = false;
 +    unsigned char 	c;
 +    int			i;
 +    int			numplayers;
 +
 +    static char		destination_keys[MAXPLAYERS] =
 +    {
 +	HUSTR_KEYGREEN,
 +	HUSTR_KEYINDIGO,
 +	HUSTR_KEYBROWN,
 +	HUSTR_KEYRED
 +    };
 +
 +    static int		num_nobrainers = 0;
 +
 +    numplayers = 0;
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +	numplayers += playeringame[i];
 +
 +    if (ev->data1 == KEY_RSHIFT)
 +    {
 +	shiftdown = ev->type == ev_keydown;
 +	return false;
 +    }
 +    else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT)
 +    {
 +	altdown = ev->type == ev_keydown;
 +	return false;
 +    }
 +
 +    if (ev->type != ev_keydown)
 +	return false;
 +
 +    if (!chat_on)
 +    {
 +	if (ev->data1 == key_message_refresh)
 +	{
 +	    message_on = true;
 +	    message_counter = HU_MSGTIMEOUT;
 +	    eatkey = true;
 +	}
 +	else if (netgame && ev->data2 == HU_INPUTTOGGLE)
 +	{
 +	    eatkey = chat_on = true;
 +	    HUlib_resetIText(&w_chat);
 +	    HU_queueChatChar(HU_BROADCAST);
 +	}
 +	else if (netgame && numplayers > 2)
 +	{
 +	    for (i=0; i<MAXPLAYERS ; i++)
 +	    {
 +		if (ev->data2 == destination_keys[i])
 +		{
 +		    if (playeringame[i] && i!=consoleplayer)
 +		    {
 +			eatkey = chat_on = true;
 +			HUlib_resetIText(&w_chat);
 +			HU_queueChatChar(i+1);
 +			break;
 +		    }
 +		    else if (i == consoleplayer)
 +		    {
 +			num_nobrainers++;
 +			if (num_nobrainers < 3)
 +			    plr->message = DEH_String(HUSTR_TALKTOSELF1);
 +			else if (num_nobrainers < 6)
 +			    plr->message = DEH_String(HUSTR_TALKTOSELF2);
 +			else if (num_nobrainers < 9)
 +			    plr->message = DEH_String(HUSTR_TALKTOSELF3);
 +			else if (num_nobrainers < 32)
 +			    plr->message = DEH_String(HUSTR_TALKTOSELF4);
 +			else
 +			    plr->message = DEH_String(HUSTR_TALKTOSELF5);
 +		    }
 +		}
 +	    }
 +	}
 +    }
 +    else
 +    {
 +	c = ev->data2;
 +	// send a macro
 +	if (altdown)
 +	{
 +	    c = c - '0';
 +	    if (c > 9)
 +		return false;
 +	    // fprintf(stderr, "got here\n");
 +	    macromessage = chat_macros[c];
 +
 +	    // kill last message with a '\n'
 +	    HU_queueChatChar(KEY_ENTER); // DEBUG!!!
 +
 +	    // send the macro message
 +	    while (*macromessage)
 +		HU_queueChatChar(*macromessage++);
 +	    HU_queueChatChar(KEY_ENTER);
 +
 +	    // leave chat mode and notify that it was sent
 +	    chat_on = false;
 +	    strcpy(lastmessage, chat_macros[c]);
 +	    plr->message = lastmessage;
 +	    eatkey = true;
 +	}
 +	else
 +	{
 +	    eatkey = HUlib_keyInIText(&w_chat, c);
 +	    if (eatkey)
 +	    {
 +		// static unsigned char buf[20]; // DEBUG
 +		HU_queueChatChar(c);
 +
 +		// sprintf(buf, "KEY: %d => %d", ev->data1, c);
 +		//      plr->message = buf;
 +	    }
 +	    if (c == KEY_ENTER)
 +	    {
 +		chat_on = false;
 +		if (w_chat.l.len)
 +		{
 +		    strcpy(lastmessage, w_chat.l.l);
 +		    plr->message = lastmessage;
 +		}
 +	    }
 +	    else if (c == KEY_ESCAPE)
 +		chat_on = false;
 +	}
 +    }
 +
 +    return eatkey;
 +
 +}
diff --cc src/strife/p_map.c
index 3f88aabc,00000000..925e4398
mode 100644,000000..100644
--- a/src/strife/p_map.c
+++ b/src/strife/p_map.c
@@@ -1,1449 -1,0 +1,1466 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard, Andrey Budko
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:
 +//	Movement, collision handling.
 +//	Shooting and aiming.
 +//
 +//-----------------------------------------------------------------------------
 +
 +#include <stdio.h>
 +#include <stdlib.h>
 +
 +#include "deh_misc.h"
 +
 +#include "m_bbox.h"
 +#include "m_random.h"
 +#include "i_system.h"
 +
 +#include "doomdef.h"
 +#include "m_argv.h"
 +#include "m_misc.h"
 +#include "p_local.h"
 +
 +#include "s_sound.h"
 +
 +// State.
 +#include "doomstat.h"
 +#include "r_state.h"
 +// Data.
 +#include "sounds.h"
 +
 +// Spechit overrun magic value.
 +//
 +// This is the value used by PrBoom-plus.  I think the value below is
 +// actually better and works with more demos.  However, I think
 +// it's better for the spechits emulation to be compatible with
 +// PrBoom-plus, at least so that the big spechits emulation list
 +// on Doomworld can also be used with Chocolate Doom.
 +
 +#define DEFAULT_SPECHIT_MAGIC 0x01C09C98
 +
 +// This is from a post by myk on the Doomworld forums,
 +// outputted from entryway's spechit_magic generator for
 +// s205n546.lmp.  The _exact_ value of this isn't too
 +// important; as long as it is in the right general
 +// range, it will usually work.  Otherwise, we can use
 +// the generator (hacked doom2.exe) and provide it
 +// with -spechit.
 +
 +//#define DEFAULT_SPECHIT_MAGIC 0x84f968e8
 +
 +
 +fixed_t		tmbbox[4];
 +mobj_t*		tmthing;
 +int		tmflags;
 +fixed_t		tmx;
 +fixed_t		tmy;
 +
 +
 +// If "floatok" true, move would be ok
 +// if within "tmfloorz - tmceilingz".
 +boolean		floatok;
 +
 +fixed_t		tmfloorz;
 +fixed_t		tmceilingz;
 +fixed_t		tmdropoffz;
 +
 +// keep track of the line that lowers the ceiling,
 +// so missiles don't explode against sky hack walls
 +line_t*		ceilingline;
 +
 +// keep track of special lines as they are hit,
 +// but don't process them until the move is proven valid
 +
 +// fraggle: I have increased the size of this buffer.  In the original Doom,
 +// overrunning past this limit caused other bits of memory to be overwritten,
 +// affecting demo playback.  However, in doing so, the limit was still
 +// exceeded.  So we have to support more than 8 specials.
 +//
 +// We keep the original limit, to detect what variables in memory were
 +// overwritten (see SpechitOverrun())
 +
 +#define MAXSPECIALCROSS 		20
 +#define MAXSPECIALCROSS_ORIGINAL	8
 +
 +line_t*		spechit[MAXSPECIALCROSS];
 +int		numspechit;
 +
 +
 +
 +//
 +// TELEPORT MOVE
 +//
 +
 +//
 +// PIT_StompThing
 +//
 +boolean PIT_StompThing (mobj_t* thing)
 +{
 +    fixed_t	blockdist;
 +
 +    if (!(thing->flags & MF_SHOOTABLE) )
 +	return true;
 +
 +    blockdist = thing->radius + tmthing->radius;
 +
 +    if ( abs(thing->x - tmx) >= blockdist
 +	 || abs(thing->y - tmy) >= blockdist )
 +    {
 +	// didn't hit it
 +	return true;
 +    }
 +
 +    // don't clip against self
 +    if (thing == tmthing)
 +	return true;
 +
 +    // monsters don't stomp things except on boss level
 +    if ( !tmthing->player && gamemap != 30)
 +	return false;
 +
 +    P_DamageMobj (thing, tmthing, tmthing, 10000);
 +
 +    return true;
 +}
 +
 +
 +//
 +// P_TeleportMove
 +//
 +boolean
 +P_TeleportMove
 +( mobj_t*	thing,
 +  fixed_t	x,
 +  fixed_t	y )
 +{
 +    int			xl;
 +    int			xh;
 +    int			yl;
 +    int			yh;
 +    int			bx;
 +    int			by;
 +
 +    subsector_t*	newsubsec;
 +
 +    // kill anything occupying the position
 +    tmthing = thing;
 +    tmflags = thing->flags;
 +
 +    tmx = x;
 +    tmy = y;
 +
 +    tmbbox[BOXTOP] = y + tmthing->radius;
 +    tmbbox[BOXBOTTOM] = y - tmthing->radius;
 +    tmbbox[BOXRIGHT] = x + tmthing->radius;
 +    tmbbox[BOXLEFT] = x - tmthing->radius;
 +
 +    newsubsec = R_PointInSubsector (x,y);
 +    ceilingline = NULL;
 +
 +    // The base floor/ceiling is from the subsector
 +    // that contains the point.
 +    // Any contacted lines the step closer together
 +    // will adjust them.
 +    tmfloorz = tmdropoffz = newsubsec->sector->floorheight;
 +    tmceilingz = newsubsec->sector->ceilingheight;
 +
 +    validcount++;
 +    numspechit = 0;
 +
 +    // stomp on any things contacted
 +    xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
 +    xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
 +    yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
 +    yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
 +
 +    for (bx=xl ; bx<=xh ; bx++)
 +	for (by=yl ; by<=yh ; by++)
 +	    if (!P_BlockThingsIterator(bx,by,PIT_StompThing))
 +		return false;
 +
 +    // the move is ok,
 +    // so link the thing into its new position
 +    P_UnsetThingPosition (thing);
 +
 +    thing->floorz = tmfloorz;
 +    thing->ceilingz = tmceilingz;
 +    thing->x = x;
 +    thing->y = y;
 +
 +    P_SetThingPosition (thing);
 +
 +    return true;
 +}
 +
 +
 +//
 +// MOVEMENT ITERATOR FUNCTIONS
 +//
 +
 +static void SpechitOverrun(line_t *ld);
 +
 +//
 +// PIT_CheckLine
 +// Adjusts tmfloorz and tmceilingz as lines are contacted
 +//
 +boolean PIT_CheckLine (line_t* ld)
 +{
 +    if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
 +	|| tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
 +	|| tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
 +	|| tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] )
 +	return true;
 +
 +    if (P_BoxOnLineSide (tmbbox, ld) != -1)
 +	return true;
 +
 +    // A line has been hit
 +
 +    // The moving thing's destination position will cross
 +    // the given line.
 +    // If this should not be allowed, return false.
 +    // If the line is special, keep track of it
 +    // to process later if the move is proven ok.
 +    // NOTE: specials are NOT sorted by order,
 +    // so two special lines that are only 8 pixels apart
 +    // could be crossed in either order.
 +
 +    if (!ld->backsector)
 +	return false;		// one sided line
 +
 +    if (!(tmthing->flags & MF_MISSILE) )
 +    {
 +	if ( ld->flags & ML_BLOCKING )
 +	    return false;	// explicitly blocking everything
 +
 +	if ( !tmthing->player && ld->flags & ML_BLOCKMONSTERS )
 +	    return false;	// block monsters only
 +    }
 +
 +    // set openrange, opentop, openbottom
 +    P_LineOpening (ld);
 +
 +    // adjust floor / ceiling heights
 +    if (opentop < tmceilingz)
 +    {
 +	tmceilingz = opentop;
 +	ceilingline = ld;
 +    }
 +
 +    if (openbottom > tmfloorz)
 +	tmfloorz = openbottom;
 +
 +    if (lowfloor < tmdropoffz)
 +	tmdropoffz = lowfloor;
 +
 +    // if contacted a special line, add it to the list
 +    if (ld->special)
 +    {
 +        spechit[numspechit] = ld;
 +	numspechit++;
 +
 +        // fraggle: spechits overrun emulation code from prboom-plus
 +        if (numspechit > MAXSPECIALCROSS_ORIGINAL)
 +        {
 +            SpechitOverrun(ld);
 +        }
 +    }
 +
 +    return true;
 +}
 +
 +//
 +// PIT_CheckThing
 +//
 +boolean PIT_CheckThing (mobj_t* thing)
 +{
 +    fixed_t		blockdist;
 +    boolean		solid;
 +    int			damage;
 +
 +    if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE) ))
 +	return true;
 +
 +    blockdist = thing->radius + tmthing->radius;
 +
 +    if ( abs(thing->x - tmx) >= blockdist
 +	 || abs(thing->y - tmy) >= blockdist )
 +    {
 +	// didn't hit it
 +	return true;
 +    }
 +
 +    // don't clip against self
 +    if (thing == tmthing)
 +	return true;
 +
 +    // check for skulls slamming into things
 +    if (tmthing->flags & MF_SKULLFLY)
 +    {
 +	damage = ((P_Random()%8)+1)*tmthing->info->damage;
 +
 +	P_DamageMobj (thing, tmthing, tmthing, damage);
 +
 +	tmthing->flags &= ~MF_SKULLFLY;
 +	tmthing->momx = tmthing->momy = tmthing->momz = 0;
 +
 +	P_SetMobjState (tmthing, tmthing->info->spawnstate);
 +
 +	return false;		// stop moving
 +    }
 +
 +
 +    // missiles can hit other things
 +    if (tmthing->flags & MF_MISSILE)
 +    {
 +	// see if it went over / under
 +	if (tmthing->z > thing->z + thing->height)
 +	    return true;		// overhead
 +	if (tmthing->z+tmthing->height < thing->z)
 +	    return true;		// underneath
 +
 +	if (tmthing->target
 +         && (tmthing->target->type == thing->type ||
 +	    (tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER)||
 +	    (tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT) ) )
 +	{
 +	    // Don't hit same species as originator.
 +	    if (thing == tmthing->target)
 +		return true;
 +
 +            // sdh: Add deh_species_infighting here.  We can override the
 +            // "monsters of the same species cant hurt each other" behavior
 +            // through dehacked patches
 +
 +	    if (thing->type != MT_PLAYER && !deh_species_infighting)
 +	    {
 +		// Explode, but do no damage.
 +		// Let players missile other players.
 +		return false;
 +	    }
 +	}
 +
 +	if (! (thing->flags & MF_SHOOTABLE) )
 +	{
 +	    // didn't do any damage
 +	    return !(thing->flags & MF_SOLID);
 +	}
 +
 +	// damage / explode
 +	damage = ((P_Random()%8)+1)*tmthing->info->damage;
 +	P_DamageMobj (thing, tmthing, tmthing->target, damage);
 +
 +	// don't traverse any more
 +	return false;
 +    }
 +
 +    // check for special pickup
 +    if (thing->flags & MF_SPECIAL)
 +    {
 +	solid = thing->flags&MF_SOLID;
 +	if (tmflags&MF_PICKUP)
 +	{
 +	    // can remove thing
 +	    P_TouchSpecialThing (thing, tmthing);
 +	}
 +	return !solid;
 +    }
 +
 +    return !(thing->flags & MF_SOLID);
 +}
 +
 +
 +//
 +// MOVEMENT CLIPPING
 +//
 +
 +//
 +// P_CheckPosition
 +// This is purely informative, nothing is modified
 +// (except things picked up).
 +//
 +// in:
 +//  a mobj_t (can be valid or invalid)
 +//  a position to be checked
 +//   (doesn't need to be related to the mobj_t->x,y)
 +//
 +// during:
 +//  special things are touched if MF_PICKUP
 +//  early out on solid lines?
 +//
 +// out:
 +//  newsubsec
 +//  floorz
 +//  ceilingz
 +//  tmdropoffz
 +//   the lowest point contacted
 +//   (monsters won't move to a dropoff)
 +//  speciallines[]
 +//  numspeciallines
 +//
 +boolean
 +P_CheckPosition
 +( mobj_t*	thing,
 +  fixed_t	x,
 +  fixed_t	y )
 +{
 +    int			xl;
 +    int			xh;
 +    int			yl;
 +    int			yh;
 +    int			bx;
 +    int			by;
 +    subsector_t*	newsubsec;
 +
 +    tmthing = thing;
 +    tmflags = thing->flags;
 +
 +    tmx = x;
 +    tmy = y;
 +
 +    tmbbox[BOXTOP] = y + tmthing->radius;
 +    tmbbox[BOXBOTTOM] = y - tmthing->radius;
 +    tmbbox[BOXRIGHT] = x + tmthing->radius;
 +    tmbbox[BOXLEFT] = x - tmthing->radius;
 +
 +    newsubsec = R_PointInSubsector (x,y);
 +    ceilingline = NULL;
 +
 +    // The base floor / ceiling is from the subsector
 +    // that contains the point.
 +    // Any contacted lines the step closer together
 +    // will adjust them.
 +    tmfloorz = tmdropoffz = newsubsec->sector->floorheight;
 +    tmceilingz = newsubsec->sector->ceilingheight;
 +
 +    validcount++;
 +    numspechit = 0;
 +
 +    if ( tmflags & MF_NOCLIP )
 +	return true;
 +
 +    // Check things first, possibly picking things up.
 +    // The bounding box is extended by MAXRADIUS
 +    // because mobj_ts are grouped into mapblocks
 +    // based on their origin point, and can overlap
 +    // into adjacent blocks by up to MAXRADIUS units.
 +    xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
 +    xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
 +    yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
 +    yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
 +
 +    for (bx=xl ; bx<=xh ; bx++)
 +	for (by=yl ; by<=yh ; by++)
 +	    if (!P_BlockThingsIterator(bx,by,PIT_CheckThing))
 +		return false;
 +
 +    // check lines
 +    xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
 +    xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
 +    yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
 +    yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
 +
 +    for (bx=xl ; bx<=xh ; bx++)
 +	for (by=yl ; by<=yh ; by++)
 +	    if (!P_BlockLinesIterator (bx,by,PIT_CheckLine))
 +		return false;
 +
 +    return true;
 +}
 +
 +
 +//
 +// P_TryMove
 +// Attempt to move to a new position,
 +// crossing special lines unless MF_TELEPORT is set.
 +//
 +boolean
 +P_TryMove
 +( mobj_t*	thing,
 +  fixed_t	x,
 +  fixed_t	y )
 +{
 +    fixed_t	oldx;
 +    fixed_t	oldy;
 +    int		side;
 +    int		oldside;
 +    line_t*	ld;
 +
 +    floatok = false;
 +    if (!P_CheckPosition (thing, x, y))
 +	return false;		// solid wall or thing
 +
 +    if ( !(thing->flags & MF_NOCLIP) )
 +    {
 +	if (tmceilingz - tmfloorz < thing->height)
 +	    return false;	// doesn't fit
 +
 +	floatok = true;
 +
 +	if ( !(thing->flags&MF_TELEPORT)
 +	     &&tmceilingz - thing->z < thing->height)
 +	    return false;	// mobj must lower itself to fit
 +
 +	if ( !(thing->flags&MF_TELEPORT)
 +	     && tmfloorz - thing->z > 24*FRACUNIT )
 +	    return false;	// too big a step up
 +
 +	if ( !(thing->flags&(MF_DROPOFF|MF_FLOAT))
 +	     && tmfloorz - tmdropoffz > 24*FRACUNIT )
 +	    return false;	// don't stand over a dropoff
 +    }
 +
 +    // the move is ok,
 +    // so link the thing into its new position
 +    P_UnsetThingPosition (thing);
 +
 +    oldx = thing->x;
 +    oldy = thing->y;
 +    thing->floorz = tmfloorz;
 +    thing->ceilingz = tmceilingz;
 +    thing->x = x;
 +    thing->y = y;
 +
 +    P_SetThingPosition (thing);
 +
 +    // if any special lines were hit, do the effect
 +    if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) )
 +    {
 +	while (numspechit--)
 +	{
 +	    // see if the line was crossed
 +	    ld = spechit[numspechit];
 +	    side = P_PointOnLineSide (thing->x, thing->y, ld);
 +	    oldside = P_PointOnLineSide (oldx, oldy, ld);
 +	    if (side != oldside)
 +	    {
 +		if (ld->special)
 +		    P_CrossSpecialLine (ld-lines, oldside, thing);
 +	    }
 +	}
 +    }
 +
 +    return true;
 +}
 +
 +
 +//
 +// P_ThingHeightClip
 +// Takes a valid thing and adjusts the thing->floorz,
 +// thing->ceilingz, and possibly thing->z.
 +// This is called for all nearby monsters
 +// whenever a sector changes height.
 +// If the thing doesn't fit,
 +// the z will be set to the lowest value
 +// and false will be returned.
 +//
 +boolean P_ThingHeightClip (mobj_t* thing)
 +{
 +    boolean		onfloor;
 +
 +    onfloor = (thing->z == thing->floorz);
 +
 +    P_CheckPosition (thing, thing->x, thing->y);
 +    // what about stranding a monster partially off an edge?
 +
 +    thing->floorz = tmfloorz;
 +    thing->ceilingz = tmceilingz;
 +
 +    if (onfloor)
 +    {
 +	// walking monsters rise and fall with the floor
 +	thing->z = thing->floorz;
 +    }
 +    else
 +    {
 +	// don't adjust a floating monster unless forced to
 +	if (thing->z+thing->height > thing->ceilingz)
 +	    thing->z = thing->ceilingz - thing->height;
 +    }
 +
 +    if (thing->ceilingz - thing->floorz < thing->height)
 +	return false;
 +
 +    return true;
 +}
 +
 +
 +
 +//
 +// SLIDE MOVE
 +// Allows the player to slide along any angled walls.
 +//
 +fixed_t		bestslidefrac;
 +fixed_t		secondslidefrac;
 +
 +line_t*		bestslideline;
 +line_t*		secondslideline;
 +
 +mobj_t*		slidemo;
 +
 +fixed_t		tmxmove;
 +fixed_t		tmymove;
 +
 +
 +
 +//
 +// P_HitSlideLine
 +// Adjusts the xmove / ymove
 +// so that the next move will slide along the wall.
 +//
 +void P_HitSlideLine (line_t* ld)
 +{
 +    int			side;
 +
 +    angle_t		lineangle;
 +    angle_t		moveangle;
 +    angle_t		deltaangle;
 +
 +    fixed_t		movelen;
 +    fixed_t		newlen;
 +
 +
 +    if (ld->slopetype == ST_HORIZONTAL)
 +    {
 +	tmymove = 0;
 +	return;
 +    }
 +
 +    if (ld->slopetype == ST_VERTICAL)
 +    {
 +	tmxmove = 0;
 +	return;
 +    }
 +
 +    side = P_PointOnLineSide (slidemo->x, slidemo->y, ld);
 +
 +    lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy);
 +
 +    if (side == 1)
 +	lineangle += ANG180;
 +
 +    moveangle = R_PointToAngle2 (0,0, tmxmove, tmymove);
 +    deltaangle = moveangle-lineangle;
 +
 +    if (deltaangle > ANG180)
 +	deltaangle += ANG180;
 +    //	I_Error ("SlideLine: ang>ANG180");
 +
 +    lineangle >>= ANGLETOFINESHIFT;
 +    deltaangle >>= ANGLETOFINESHIFT;
 +
 +    movelen = P_AproxDistance (tmxmove, tmymove);
 +    newlen = FixedMul (movelen, finecosine[deltaangle]);
 +
 +    tmxmove = FixedMul (newlen, finecosine[lineangle]);
 +    tmymove = FixedMul (newlen, finesine[lineangle]);
 +}
 +
 +
 +//
 +// PTR_SlideTraverse
 +//
 +boolean PTR_SlideTraverse (intercept_t* in)
 +{
 +    line_t*	li;
 +
 +    if (!in->isaline)
 +	I_Error ("PTR_SlideTraverse: not a line?");
 +
 +    li = in->d.line;
 +
 +    if ( ! (li->flags & ML_TWOSIDED) )
 +    {
 +	if (P_PointOnLineSide (slidemo->x, slidemo->y, li))
 +	{
 +	    // don't hit the back side
 +	    return true;
 +	}
 +	goto isblocking;
 +    }
 +
 +    // set openrange, opentop, openbottom
 +    P_LineOpening (li);
 +
 +    if (openrange < slidemo->height)
 +	goto isblocking;		// doesn't fit
 +
 +    if (opentop - slidemo->z < slidemo->height)
 +	goto isblocking;		// mobj is too high
 +
 +    if (openbottom - slidemo->z > 24*FRACUNIT )
 +	goto isblocking;		// too big a step up
 +
 +    // this line doesn't block movement
 +    return true;
 +
 +    // the line does block movement,
 +    // see if it is closer than best so far
 +  isblocking:
 +    if (in->frac < bestslidefrac)
 +    {
 +	secondslidefrac = bestslidefrac;
 +	secondslideline = bestslideline;
 +	bestslidefrac = in->frac;
 +	bestslideline = li;
 +    }
 +
 +    return false;	// stop
 +}
 +
 +
 +
 +//
 +// P_SlideMove
 +// The momx / momy move is bad, so try to slide
 +// along a wall.
 +// Find the first line hit, move flush to it,
 +// and slide along it
 +//
 +// This is a kludgy mess.
 +//
 +void P_SlideMove (mobj_t* mo)
 +{
 +    fixed_t		leadx;
 +    fixed_t		leady;
 +    fixed_t		trailx;
 +    fixed_t		traily;
 +    fixed_t		newx;
 +    fixed_t		newy;
 +    int			hitcount;
 +
 +    slidemo = mo;
 +    hitcount = 0;
 +
 +  retry:
 +    if (++hitcount == 3)
 +	goto stairstep;		// don't loop forever
 +
 +
 +    // trace along the three leading corners
 +    if (mo->momx > 0)
 +    {
 +	leadx = mo->x + mo->radius;
 +	trailx = mo->x - mo->radius;
 +    }
 +    else
 +    {
 +	leadx = mo->x - mo->radius;
 +	trailx = mo->x + mo->radius;
 +    }
 +
 +    if (mo->momy > 0)
 +    {
 +	leady = mo->y + mo->radius;
 +	traily = mo->y - mo->radius;
 +    }
 +    else
 +    {
 +	leady = mo->y - mo->radius;
 +	traily = mo->y + mo->radius;
 +    }
 +
 +    bestslidefrac = FRACUNIT+1;
 +
 +    P_PathTraverse ( leadx, leady, leadx+mo->momx, leady+mo->momy,
 +		     PT_ADDLINES, PTR_SlideTraverse );
 +    P_PathTraverse ( trailx, leady, trailx+mo->momx, leady+mo->momy,
 +		     PT_ADDLINES, PTR_SlideTraverse );
 +    P_PathTraverse ( leadx, traily, leadx+mo->momx, traily+mo->momy,
 +		     PT_ADDLINES, PTR_SlideTraverse );
 +
 +    // move up to the wall
 +    if (bestslidefrac == FRACUNIT+1)
 +    {
 +	// the move most have hit the middle, so stairstep
 +      stairstep:
 +	if (!P_TryMove (mo, mo->x, mo->y + mo->momy))
 +	    P_TryMove (mo, mo->x + mo->momx, mo->y);
 +	return;
 +    }
 +
 +    // fudge a bit to make sure it doesn't hit
 +    bestslidefrac -= 0x800;
 +    if (bestslidefrac > 0)
 +    {
 +	newx = FixedMul (mo->momx, bestslidefrac);
 +	newy = FixedMul (mo->momy, bestslidefrac);
 +
 +	if (!P_TryMove (mo, mo->x+newx, mo->y+newy))
 +	    goto stairstep;
 +    }
 +
 +    // Now continue along the wall.
 +    // First calculate remainder.
 +    bestslidefrac = FRACUNIT-(bestslidefrac+0x800);
 +
 +    if (bestslidefrac > FRACUNIT)
 +	bestslidefrac = FRACUNIT;
 +
 +    if (bestslidefrac <= 0)
 +	return;
 +
 +    tmxmove = FixedMul (mo->momx, bestslidefrac);
 +    tmymove = FixedMul (mo->momy, bestslidefrac);
 +
 +    P_HitSlideLine (bestslideline);	// clip the moves
 +
 +    mo->momx = tmxmove;
 +    mo->momy = tmymove;
 +
 +    if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove))
 +    {
 +	goto retry;
 +    }
 +}
 +
 +
 +//
 +// P_LineAttack
 +//
 +mobj_t*		linetarget;	// who got hit (or NULL)
 +mobj_t*		shootthing;
 +
 +// Height if not aiming up or down
 +// ???: use slope for monsters?
 +fixed_t		shootz;
 +
 +int		la_damage;
 +fixed_t		attackrange;
 +
 +fixed_t		aimslope;
 +
 +// slopes to top and bottom of target
 +extern fixed_t	topslope;
 +extern fixed_t	bottomslope;
 +
 +
 +//
 +// PTR_AimTraverse
 +// Sets linetaget and aimslope when a target is aimed at.
 +//
 +boolean
 +PTR_AimTraverse (intercept_t* in)
 +{
 +    line_t*		li;
 +    mobj_t*		th;
 +    fixed_t		slope;
 +    fixed_t		thingtopslope;
 +    fixed_t		thingbottomslope;
 +    fixed_t		dist;
 +
 +    if (in->isaline)
 +    {
 +	li = in->d.line;
 +
 +	if ( !(li->flags & ML_TWOSIDED) )
 +	    return false;		// stop
 +
 +	// Crosses a two sided line.
 +	// A two sided line will restrict
 +	// the possible target ranges.
 +	P_LineOpening (li);
 +
 +	if (openbottom >= opentop)
 +	    return false;		// stop
 +
 +	dist = FixedMul (attackrange, in->frac);
 +
- 	if (li->frontsector->floorheight != li->backsector->floorheight)
++        // Return false if there is no back sector.  This should never
++        // be the case if the line is two-sided; however, some WADs
++        // (eg. ottawau.wad) use this as an "impassible glass" trick
++        // and rely on Vanilla Doom's (unintentional) support for this.
++
++        if (li->backsector == NULL)
++        {
++            return false;
++        }
++
++        if (li->frontsector->floorheight != li->backsector->floorheight)
 +	{
 +	    slope = FixedDiv (openbottom - shootz , dist);
 +	    if (slope > bottomslope)
 +		bottomslope = slope;
 +	}
 +
 +	if (li->frontsector->ceilingheight != li->backsector->ceilingheight)
 +	{
 +	    slope = FixedDiv (opentop - shootz , dist);
 +	    if (slope < topslope)
 +		topslope = slope;
 +	}
 +
 +	if (topslope <= bottomslope)
 +	    return false;		// stop
 +
 +	return true;			// shot continues
 +    }
 +
 +    // shoot a thing
 +    th = in->d.thing;
 +    if (th == shootthing)
 +	return true;			// can't shoot self
 +
 +    if (!(th->flags&MF_SHOOTABLE))
 +	return true;			// corpse or something
 +
 +    // check angles to see if the thing can be aimed at
 +    dist = FixedMul (attackrange, in->frac);
 +    thingtopslope = FixedDiv (th->z+th->height - shootz , dist);
 +
 +    if (thingtopslope < bottomslope)
 +	return true;			// shot over the thing
 +
 +    thingbottomslope = FixedDiv (th->z - shootz, dist);
 +
 +    if (thingbottomslope > topslope)
 +	return true;			// shot under the thing
 +
 +    // this thing can be hit!
 +    if (thingtopslope > topslope)
 +	thingtopslope = topslope;
 +
 +    if (thingbottomslope < bottomslope)
 +	thingbottomslope = bottomslope;
 +
 +    aimslope = (thingtopslope+thingbottomslope)/2;
 +    linetarget = th;
 +
 +    return false;			// don't go any farther
 +}
 +
 +
 +//
 +// PTR_ShootTraverse
 +//
 +boolean PTR_ShootTraverse (intercept_t* in)
 +{
 +    fixed_t		x;
 +    fixed_t		y;
 +    fixed_t		z;
 +    fixed_t		frac;
 +
 +    line_t*		li;
 +
 +    mobj_t*		th;
 +
 +    fixed_t		slope;
 +    fixed_t		dist;
 +    fixed_t		thingtopslope;
 +    fixed_t		thingbottomslope;
 +
 +    if (in->isaline)
 +    {
 +	li = in->d.line;
 +
 +	if (li->special)
 +	    P_ShootSpecialLine (shootthing, li);
 +
 +	if ( !(li->flags & ML_TWOSIDED) )
 +	    goto hitline;
 +
 +	// crosses a two sided line
 +	P_LineOpening (li);
 +
 +	dist = FixedMul (attackrange, in->frac);
 +
- 	if (li->frontsector->floorheight != li->backsector->floorheight)
++        // Check if backsector is NULL.  See comment in PTR_AimTraverse.
++
++	if (li->backsector == NULL)
++        {
++            goto hitline;
++        }
++
++        if (li->frontsector->floorheight != li->backsector->floorheight)
 +	{
 +	    slope = FixedDiv (openbottom - shootz , dist);
 +	    if (slope > aimslope)
 +		goto hitline;
 +	}
 +
 +	if (li->frontsector->ceilingheight != li->backsector->ceilingheight)
 +	{
 +	    slope = FixedDiv (opentop - shootz , dist);
 +	    if (slope < aimslope)
 +		goto hitline;
 +	}
 +
 +	// shot continues
 +	return true;
 +
 +
 +	// hit line
 +      hitline:
 +	// position a bit closer
 +	frac = in->frac - FixedDiv (4*FRACUNIT,attackrange);
 +	x = trace.x + FixedMul (trace.dx, frac);
 +	y = trace.y + FixedMul (trace.dy, frac);
 +	z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange));
 +
 +	if (li->frontsector->ceilingpic == skyflatnum)
 +	{
 +	    // don't shoot the sky!
 +	    if (z > li->frontsector->ceilingheight)
 +		return false;
 +
 +	    // it's a sky hack wall
 +	    if	(li->backsector && li->backsector->ceilingpic == skyflatnum)
 +		return false;
 +	}
 +
 +	// Spawn bullet puffs.
 +	P_SpawnPuff (x,y,z);
 +
 +	// don't go any farther
 +	return false;
 +    }
 +
 +    // shoot a thing
 +    th = in->d.thing;
 +    if (th == shootthing)
 +	return true;		// can't shoot self
 +
 +    if (!(th->flags&MF_SHOOTABLE))
 +	return true;		// corpse or something
 +
 +    // check angles to see if the thing can be aimed at
 +    dist = FixedMul (attackrange, in->frac);
 +    thingtopslope = FixedDiv (th->z+th->height - shootz , dist);
 +
 +    if (thingtopslope < aimslope)
 +	return true;		// shot over the thing
 +
 +    thingbottomslope = FixedDiv (th->z - shootz, dist);
 +
 +    if (thingbottomslope > aimslope)
 +	return true;		// shot under the thing
 +
 +
 +    // hit thing
 +    // position a bit closer
 +    frac = in->frac - FixedDiv (10*FRACUNIT,attackrange);
 +
 +    x = trace.x + FixedMul (trace.dx, frac);
 +    y = trace.y + FixedMul (trace.dy, frac);
 +    z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange));
 +
 +    // Spawn bullet puffs or blod spots,
 +    // depending on target type.
 +    if (in->d.thing->flags & MF_NOBLOOD)
 +	P_SpawnPuff (x,y,z);
 +    else
 +	P_SpawnBlood (x,y,z, la_damage);
 +
 +    if (la_damage)
 +	P_DamageMobj (th, shootthing, shootthing, la_damage);
 +
 +    // don't go any farther
 +    return false;
 +
 +}
 +
 +
 +//
 +// P_AimLineAttack
 +//
 +fixed_t
 +P_AimLineAttack
 +( mobj_t*	t1,
 +  angle_t	angle,
 +  fixed_t	distance )
 +{
 +    fixed_t	x2;
 +    fixed_t	y2;
 +
 +    t1 = P_SubstNullMobj(t1);
 +
 +    angle >>= ANGLETOFINESHIFT;
 +    shootthing = t1;
 +
 +    x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
 +    y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
 +    shootz = t1->z + (t1->height>>1) + 8*FRACUNIT;
 +
 +    // can't shoot outside view angles
 +    topslope = 100*FRACUNIT/160;
 +    bottomslope = -100*FRACUNIT/160;
 +
 +    attackrange = distance;
 +    linetarget = NULL;
 +
 +    P_PathTraverse ( t1->x, t1->y,
 +		     x2, y2,
 +		     PT_ADDLINES|PT_ADDTHINGS,
 +		     PTR_AimTraverse );
 +
 +    if (linetarget)
 +	return aimslope;
 +
 +    return 0;
 +}
 +
 +
 +//
 +// P_LineAttack
 +// If damage == 0, it is just a test trace
 +// that will leave linetarget set.
 +//
 +void
 +P_LineAttack
 +( mobj_t*	t1,
 +  angle_t	angle,
 +  fixed_t	distance,
 +  fixed_t	slope,
 +  int		damage )
 +{
 +    fixed_t	x2;
 +    fixed_t	y2;
 +
 +    angle >>= ANGLETOFINESHIFT;
 +    shootthing = t1;
 +    la_damage = damage;
 +    x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
 +    y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
 +    shootz = t1->z + (t1->height>>1) + 8*FRACUNIT;
 +    attackrange = distance;
 +    aimslope = slope;
 +
 +    P_PathTraverse ( t1->x, t1->y,
 +		     x2, y2,
 +		     PT_ADDLINES|PT_ADDTHINGS,
 +		     PTR_ShootTraverse );
 +}
 +
 +
 +
 +//
 +// USE LINES
 +//
 +mobj_t*		usething;
 +
 +boolean	PTR_UseTraverse (intercept_t* in)
 +{
 +    int		side;
 +
 +    if (!in->d.line->special)
 +    {
 +	P_LineOpening (in->d.line);
 +	if (openrange <= 0)
 +	{
 +	    S_StartSound (usething, sfx_noway);
 +
 +	    // can't use through a wall
 +	    return false;
 +	}
 +	// not a special line, but keep checking
 +	return true ;
 +    }
 +
 +    side = 0;
 +    if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1)
 +	side = 1;
 +
 +    //	return false;		// don't use back side
 +
 +    P_UseSpecialLine (usething, in->d.line, side);
 +
 +    // can't use for than one special line in a row
 +    return false;
 +}
 +
 +
 +//
 +// P_UseLines
 +// Looks for special lines in front of the player to activate.
 +//
 +void P_UseLines (player_t*	player)
 +{
 +    int		angle;
 +    fixed_t	x1;
 +    fixed_t	y1;
 +    fixed_t	x2;
 +    fixed_t	y2;
 +
 +    usething = player->mo;
 +
 +    angle = player->mo->angle >> ANGLETOFINESHIFT;
 +
 +    x1 = player->mo->x;
 +    y1 = player->mo->y;
 +    x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle];
 +    y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle];
 +
 +    P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse );
 +}
 +
 +
 +//
 +// RADIUS ATTACK
 +//
 +mobj_t*		bombsource;
 +mobj_t*		bombspot;
 +int		bombdamage;
 +
 +
 +//
 +// PIT_RadiusAttack
 +// "bombsource" is the creature
 +// that caused the explosion at "bombspot".
 +//
 +boolean PIT_RadiusAttack (mobj_t* thing)
 +{
 +    fixed_t	dx;
 +    fixed_t	dy;
 +    fixed_t	dist;
 +
 +    if (!(thing->flags & MF_SHOOTABLE) )
 +	return true;
 +
 +    // Boss spider and cyborg
 +    // take no damage from concussion.
 +    if (thing->type == MT_CYBORG
 +	|| thing->type == MT_SPIDER)
 +	return true;
 +
 +    dx = abs(thing->x - bombspot->x);
 +    dy = abs(thing->y - bombspot->y);
 +
 +    dist = dx>dy ? dx : dy;
 +    dist = (dist - thing->radius) >> FRACBITS;
 +
 +    if (dist < 0)
 +	dist = 0;
 +
 +    if (dist >= bombdamage)
 +	return true;	// out of range
 +
 +    if ( P_CheckSight (thing, bombspot) )
 +    {
 +	// must be in direct path
 +	P_DamageMobj (thing, bombspot, bombsource, bombdamage - dist);
 +    }
 +
 +    return true;
 +}
 +
 +
 +//
 +// P_RadiusAttack
 +// Source is the creature that caused the explosion at spot.
 +//
 +void
 +P_RadiusAttack
 +( mobj_t*	spot,
 +  mobj_t*	source,
 +  int		damage )
 +{
 +    int		x;
 +    int		y;
 +
 +    int		xl;
 +    int		xh;
 +    int		yl;
 +    int		yh;
 +
 +    fixed_t	dist;
 +
 +    dist = (damage+MAXRADIUS)<<FRACBITS;
 +    yh = (spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT;
 +    yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT;
 +    xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT;
 +    xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT;
 +    bombspot = spot;
 +    bombsource = source;
 +    bombdamage = damage;
 +
 +    for (y=yl ; y<=yh ; y++)
 +	for (x=xl ; x<=xh ; x++)
 +	    P_BlockThingsIterator (x, y, PIT_RadiusAttack );
 +}
 +
 +
 +
 +//
 +// SECTOR HEIGHT CHANGING
 +// After modifying a sectors floor or ceiling height,
 +// call this routine to adjust the positions
 +// of all things that touch the sector.
 +//
 +// If anything doesn't fit anymore, true will be returned.
 +// If crunch is true, they will take damage
 +//  as they are being crushed.
 +// If Crunch is false, you should set the sector height back
 +//  the way it was and call P_ChangeSector again
 +//  to undo the changes.
 +//
 +boolean		crushchange;
 +boolean		nofit;
 +
 +
 +//
 +// PIT_ChangeSector
 +//
 +boolean PIT_ChangeSector (mobj_t*	thing)
 +{
 +    mobj_t*	mo;
 +
 +    if (P_ThingHeightClip (thing))
 +    {
 +	// keep checking
 +	return true;
 +    }
 +
 +
 +    // crunch bodies to giblets
 +    if (thing->health <= 0)
 +    {
 +	P_SetMobjState (thing, S_GIBS);
 +
 +	thing->flags &= ~MF_SOLID;
 +	thing->height = 0;
 +	thing->radius = 0;
 +
 +	// keep checking
 +	return true;
 +    }
 +
 +    // crunch dropped items
 +    if (thing->flags & MF_DROPPED)
 +    {
 +	P_RemoveMobj (thing);
 +
 +	// keep checking
 +	return true;
 +    }
 +
 +    if (! (thing->flags & MF_SHOOTABLE) )
 +    {
 +	// assume it is bloody gibs or something
 +	return true;
 +    }
 +
 +    nofit = true;
 +
 +    if (crushchange && !(leveltime&3) )
 +    {
 +	P_DamageMobj(thing,NULL,NULL,10);
 +
 +	// spray blood in a random direction
 +	mo = P_SpawnMobj (thing->x,
 +			  thing->y,
 +			  thing->z + thing->height/2, MT_BLOOD);
 +
 +	mo->momx = (P_Random() - P_Random ())<<12;
 +	mo->momy = (P_Random() - P_Random ())<<12;
 +    }
 +
 +    // keep checking (crush other things)
 +    return true;
 +}
 +
 +
 +
 +//
 +// P_ChangeSector
 +//
 +boolean
 +P_ChangeSector
 +( sector_t*	sector,
 +  boolean	crunch )
 +{
 +    int		x;
 +    int		y;
 +
 +    nofit = false;
 +    crushchange = crunch;
 +
 +    // re-check heights for all things near the moving sector
 +    for (x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++)
 +	for (y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++)
 +	    P_BlockThingsIterator (x, y, PIT_ChangeSector);
 +
 +
 +    return nofit;
 +}
 +
 +// Code to emulate the behavior of Vanilla Doom when encountering an overrun
 +// of the spechit array.  This is by Andrey Budko (e6y) and comes from his
 +// PrBoom plus port.  A big thanks to Andrey for this.
 +
 +static void SpechitOverrun(line_t *ld)
 +{
 +    static unsigned int baseaddr = 0;
 +    unsigned int addr;
 +
 +    if (baseaddr == 0)
 +    {
 +        int p;
 +
 +        // This is the first time we have had an overrun.  Work out
 +        // what base address we are going to use.
 +        // Allow a spechit value to be specified on the command line.
 +
 +        //!
 +        // @category compat
 +        // @arg <n>
 +        //
 +        // Use the specified magic value when emulating spechit overruns.
 +        //
 +
 +        p = M_CheckParm("-spechit");
 +
 +        if (p > 0)
 +        {
 +            M_StrToInt(myargv[p+1], (int *) &baseaddr);
 +        }
 +        else
 +        {
 +            baseaddr = DEFAULT_SPECHIT_MAGIC;
 +        }
 +    }
 +
 +    // Calculate address used in doom2.exe
 +
 +    addr = baseaddr + (ld - lines) * 0x3E;
 +
 +    switch(numspechit)
 +    {
 +        case 9:
 +        case 10:
 +        case 11:
 +        case 12:
 +            tmbbox[numspechit-9] = addr;
 +            break;
 +        case 13:
 +            crushchange = addr;
 +            break;
 +        case 14:
 +            nofit = addr;
 +            break;
 +        default:
 +            fprintf(stderr, "SpechitOverrun: Warning: unable to emulate"
 +                            "an overrun where numspechit=%i\n",
 +                            numspechit);
 +            break;
 +    }
 +}
 +
diff --cc src/strife/p_setup.c
index 5cf7a628,00000000..2a3a8f85
mode 100644,000000..100644
--- a/src/strife/p_setup.c
+++ b/src/strife/p_setup.c
@@@ -1,753 -1,0 +1,772 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:
 +//	Do all the WAD I/O, get map description,
 +//	set up initial state and misc. LUTs.
 +//
 +//-----------------------------------------------------------------------------
 +
 +
 +
 +#include <math.h>
 +
 +#include "z_zone.h"
 +
 +#include "deh_main.h"
 +#include "i_swap.h"
 +#include "m_bbox.h"
 +
 +#include "g_game.h"
 +
 +#include "i_system.h"
 +#include "w_wad.h"
 +
 +#include "doomdef.h"
 +#include "p_local.h"
 +
 +#include "s_sound.h"
 +
 +#include "doomstat.h"
 +
 +
 +void	P_SpawnMapThing (mapthing_t*	mthing);
 +
 +
 +//
 +// MAP related Lookup tables.
 +// Store VERTEXES, LINEDEFS, SIDEDEFS, etc.
 +//
 +int		numvertexes;
 +vertex_t*	vertexes;
 +
 +int		numsegs;
 +seg_t*		segs;
 +
 +int		numsectors;
 +sector_t*	sectors;
 +
 +int		numsubsectors;
 +subsector_t*	subsectors;
 +
 +int		numnodes;
 +node_t*		nodes;
 +
 +int		numlines;
 +line_t*		lines;
 +
 +int		numsides;
 +side_t*		sides;
 +
 +
 +// BLOCKMAP
 +// Created from axis aligned bounding box
 +// of the map, a rectangular array of
 +// blocks of size ...
 +// Used to speed up collision detection
 +// by spatial subdivision in 2D.
 +//
 +// Blockmap size.
 +int		bmapwidth;
 +int		bmapheight;	// size in mapblocks
 +short*		blockmap;	// int for larger maps
 +// offsets in blockmap are from here
 +short*		blockmaplump;
 +// origin of block map
 +fixed_t		bmaporgx;
 +fixed_t		bmaporgy;
 +// for thing chains
 +mobj_t**	blocklinks;
 +
 +
 +// REJECT
 +// For fast sight rejection.
 +// Speeds up enemy AI by skipping detailed
 +//  LineOf Sight calculation.
 +// Without special effect, this could be
 +//  used as a PVS lookup as well.
 +//
 +byte*		rejectmatrix;
 +
 +
 +// Maintain single and multi player starting spots.
 +#define MAX_DEATHMATCH_STARTS	10
 +
 +mapthing_t	deathmatchstarts[MAX_DEATHMATCH_STARTS];
 +mapthing_t*	deathmatch_p;
 +mapthing_t	playerstarts[MAXPLAYERS];
 +
 +
 +
 +
 +
 +//
 +// P_LoadVertexes
 +//
 +void P_LoadVertexes (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    mapvertex_t*	ml;
 +    vertex_t*		li;
 +
 +    // Determine number of lumps:
 +    //  total lump length / vertex record length.
 +    numvertexes = W_LumpLength (lump) / sizeof(mapvertex_t);
 +
 +    // Allocate zone memory for buffer.
 +    vertexes = Z_Malloc (numvertexes*sizeof(vertex_t),PU_LEVEL,0);
 +
 +    // Load data into cache.
 +    data = W_CacheLumpNum (lump, PU_STATIC);
 +
 +    ml = (mapvertex_t *)data;
 +    li = vertexes;
 +
 +    // Copy and convert vertex coordinates,
 +    // internal representation as fixed.
 +    for (i=0 ; i<numvertexes ; i++, li++, ml++)
 +    {
 +	li->x = SHORT(ml->x)<<FRACBITS;
 +	li->y = SHORT(ml->y)<<FRACBITS;
 +    }
 +
 +    // Free buffer memory.
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +
 +//
 +// P_LoadSegs
 +//
 +void P_LoadSegs (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    mapseg_t*		ml;
 +    seg_t*		li;
 +    line_t*		ldef;
 +    int			linedef;
 +    int			side;
++    int                 sidenum;
 +
 +    numsegs = W_LumpLength (lump) / sizeof(mapseg_t);
 +    segs = Z_Malloc (numsegs*sizeof(seg_t),PU_LEVEL,0);
 +    memset (segs, 0, numsegs*sizeof(seg_t));
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    ml = (mapseg_t *)data;
 +    li = segs;
 +    for (i=0 ; i<numsegs ; i++, li++, ml++)
 +    {
 +	li->v1 = &vertexes[SHORT(ml->v1)];
 +	li->v2 = &vertexes[SHORT(ml->v2)];
-
++
 +	li->angle = (SHORT(ml->angle))<<16;
 +	li->offset = (SHORT(ml->offset))<<16;
 +	linedef = SHORT(ml->linedef);
 +	ldef = &lines[linedef];
 +	li->linedef = ldef;
 +	side = SHORT(ml->side);
 +	li->sidedef = &sides[ldef->sidenum[side]];
 +	li->frontsector = sides[ldef->sidenum[side]].sector;
- 	if (ldef-> flags & ML_TWOSIDED)
- 	    li->backsector = sides[ldef->sidenum[side^1]].sector;
- 	else
++
++        if (ldef-> flags & ML_TWOSIDED)
++        {
++            sidenum = ldef->sidenum[side ^ 1];
++
++            // If the sidenum is out of range, this may be a "glass hack"
++            // impassible window.  Point at side #0 (this may not be
++            // the correct Vanilla behavior; however, it seems to work for
++            // OTTAWAU.WAD, which is the one place I've seen this trick
++            // used).
++
++            if (sidenum < 0 || sidenum >= numsides)
++            {
++                sidenum = 0;
++            }
++
++            li->backsector = sides[sidenum].sector;
++        }
++        else
++        {
 +	    li->backsector = 0;
++        }
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadSubsectors
 +//
 +void P_LoadSubsectors (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    mapsubsector_t*	ms;
 +    subsector_t*	ss;
 +
 +    numsubsectors = W_LumpLength (lump) / sizeof(mapsubsector_t);
 +    subsectors = Z_Malloc (numsubsectors*sizeof(subsector_t),PU_LEVEL,0);
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    ms = (mapsubsector_t *)data;
 +    memset (subsectors,0, numsubsectors*sizeof(subsector_t));
 +    ss = subsectors;
 +
 +    for (i=0 ; i<numsubsectors ; i++, ss++, ms++)
 +    {
 +	ss->numlines = SHORT(ms->numsegs);
 +	ss->firstline = SHORT(ms->firstseg);
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +
 +//
 +// P_LoadSectors
 +//
 +void P_LoadSectors (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    mapsector_t*	ms;
 +    sector_t*		ss;
 +
 +    numsectors = W_LumpLength (lump) / sizeof(mapsector_t);
 +    sectors = Z_Malloc (numsectors*sizeof(sector_t),PU_LEVEL,0);
 +    memset (sectors, 0, numsectors*sizeof(sector_t));
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    ms = (mapsector_t *)data;
 +    ss = sectors;
 +    for (i=0 ; i<numsectors ; i++, ss++, ms++)
 +    {
 +	ss->floorheight = SHORT(ms->floorheight)<<FRACBITS;
 +	ss->ceilingheight = SHORT(ms->ceilingheight)<<FRACBITS;
 +	ss->floorpic = R_FlatNumForName(ms->floorpic);
 +	ss->ceilingpic = R_FlatNumForName(ms->ceilingpic);
 +	ss->lightlevel = SHORT(ms->lightlevel);
 +	ss->special = SHORT(ms->special);
 +	ss->tag = SHORT(ms->tag);
 +	ss->thinglist = NULL;
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadNodes
 +//
 +void P_LoadNodes (int lump)
 +{
 +    byte*	data;
 +    int		i;
 +    int		j;
 +    int		k;
 +    mapnode_t*	mn;
 +    node_t*	no;
 +
 +    numnodes = W_LumpLength (lump) / sizeof(mapnode_t);
 +    nodes = Z_Malloc (numnodes*sizeof(node_t),PU_LEVEL,0);
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    mn = (mapnode_t *)data;
 +    no = nodes;
 +
 +    for (i=0 ; i<numnodes ; i++, no++, mn++)
 +    {
 +	no->x = SHORT(mn->x)<<FRACBITS;
 +	no->y = SHORT(mn->y)<<FRACBITS;
 +	no->dx = SHORT(mn->dx)<<FRACBITS;
 +	no->dy = SHORT(mn->dy)<<FRACBITS;
 +	for (j=0 ; j<2 ; j++)
 +	{
 +	    no->children[j] = SHORT(mn->children[j]);
 +	    for (k=0 ; k<4 ; k++)
 +		no->bbox[j][k] = SHORT(mn->bbox[j][k])<<FRACBITS;
 +	}
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadThings
 +//
 +void P_LoadThings (int lump)
 +{
 +    byte               *data;
 +    int			i;
 +    mapthing_t         *mt;
 +    mapthing_t          spawnthing;
 +    int			numthings;
 +    boolean		spawn;
 +
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +    numthings = W_LumpLength (lump) / sizeof(mapthing_t);
 +
 +    mt = (mapthing_t *)data;
 +    for (i=0 ; i<numthings ; i++, mt++)
 +    {
 +	spawn = true;
 +
 +	// Do not spawn cool, new monsters if !commercial
 +	if (gamemode != commercial)
 +	{
 +	    switch (SHORT(mt->type))
 +	    {
 +	      case 68:	// Arachnotron
 +	      case 64:	// Archvile
 +	      case 88:	// Boss Brain
 +	      case 89:	// Boss Shooter
 +	      case 69:	// Hell Knight
 +	      case 67:	// Mancubus
 +	      case 71:	// Pain Elemental
 +	      case 65:	// Former Human Commando
 +	      case 66:	// Revenant
 +	      case 84:	// Wolf SS
 +		spawn = false;
 +		break;
 +	    }
 +	}
 +	if (spawn == false)
 +	    break;
 +
 +	// Do spawn all other stuff.
 +	spawnthing.x = SHORT(mt->x);
 +	spawnthing.y = SHORT(mt->y);
 +	spawnthing.angle = SHORT(mt->angle);
 +	spawnthing.type = SHORT(mt->type);
 +	spawnthing.options = SHORT(mt->options);
 +
 +	P_SpawnMapThing(&spawnthing);
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadLineDefs
 +// Also counts secret lines for intermissions.
 +//
 +void P_LoadLineDefs (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    maplinedef_t*	mld;
 +    line_t*		ld;
 +    vertex_t*		v1;
 +    vertex_t*		v2;
 +
 +    numlines = W_LumpLength (lump) / sizeof(maplinedef_t);
 +    lines = Z_Malloc (numlines*sizeof(line_t),PU_LEVEL,0);
 +    memset (lines, 0, numlines*sizeof(line_t));
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    mld = (maplinedef_t *)data;
 +    ld = lines;
 +    for (i=0 ; i<numlines ; i++, mld++, ld++)
 +    {
 +	ld->flags = SHORT(mld->flags);
 +	ld->special = SHORT(mld->special);
 +	ld->tag = SHORT(mld->tag);
 +	v1 = ld->v1 = &vertexes[SHORT(mld->v1)];
 +	v2 = ld->v2 = &vertexes[SHORT(mld->v2)];
 +	ld->dx = v2->x - v1->x;
 +	ld->dy = v2->y - v1->y;
 +
 +	if (!ld->dx)
 +	    ld->slopetype = ST_VERTICAL;
 +	else if (!ld->dy)
 +	    ld->slopetype = ST_HORIZONTAL;
 +	else
 +	{
 +	    if (FixedDiv (ld->dy , ld->dx) > 0)
 +		ld->slopetype = ST_POSITIVE;
 +	    else
 +		ld->slopetype = ST_NEGATIVE;
 +	}
 +
 +	if (v1->x < v2->x)
 +	{
 +	    ld->bbox[BOXLEFT] = v1->x;
 +	    ld->bbox[BOXRIGHT] = v2->x;
 +	}
 +	else
 +	{
 +	    ld->bbox[BOXLEFT] = v2->x;
 +	    ld->bbox[BOXRIGHT] = v1->x;
 +	}
 +
 +	if (v1->y < v2->y)
 +	{
 +	    ld->bbox[BOXBOTTOM] = v1->y;
 +	    ld->bbox[BOXTOP] = v2->y;
 +	}
 +	else
 +	{
 +	    ld->bbox[BOXBOTTOM] = v2->y;
 +	    ld->bbox[BOXTOP] = v1->y;
 +	}
 +
 +	ld->sidenum[0] = SHORT(mld->sidenum[0]);
 +	ld->sidenum[1] = SHORT(mld->sidenum[1]);
 +
 +	if (ld->sidenum[0] != -1)
 +	    ld->frontsector = sides[ld->sidenum[0]].sector;
 +	else
 +	    ld->frontsector = 0;
 +
 +	if (ld->sidenum[1] != -1)
 +	    ld->backsector = sides[ld->sidenum[1]].sector;
 +	else
 +	    ld->backsector = 0;
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadSideDefs
 +//
 +void P_LoadSideDefs (int lump)
 +{
 +    byte*		data;
 +    int			i;
 +    mapsidedef_t*	msd;
 +    side_t*		sd;
 +
 +    numsides = W_LumpLength (lump) / sizeof(mapsidedef_t);
 +    sides = Z_Malloc (numsides*sizeof(side_t),PU_LEVEL,0);
 +    memset (sides, 0, numsides*sizeof(side_t));
 +    data = W_CacheLumpNum (lump,PU_STATIC);
 +
 +    msd = (mapsidedef_t *)data;
 +    sd = sides;
 +    for (i=0 ; i<numsides ; i++, msd++, sd++)
 +    {
 +	sd->textureoffset = SHORT(msd->textureoffset)<<FRACBITS;
 +	sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 +	sd->toptexture = R_TextureNumForName(msd->toptexture);
 +	sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
 +	sd->midtexture = R_TextureNumForName(msd->midtexture);
 +	sd->sector = &sectors[SHORT(msd->sector)];
 +    }
 +
 +    W_ReleaseLumpNum(lump);
 +}
 +
 +
 +//
 +// P_LoadBlockMap
 +//
 +void P_LoadBlockMap (int lump)
 +{
 +    int i;
 +    int count;
 +    int lumplen;
 +
 +    lumplen = W_LumpLength(lump);
 +    count = lumplen / 2;
 +
 +    blockmaplump = Z_Malloc(lumplen, PU_LEVEL, NULL);
 +    W_ReadLump(lump, blockmaplump);
 +    blockmap = blockmaplump + 4;
 +
 +    // Swap all short integers to native byte ordering.
 +
 +    for (i=0; i<count; i++)
 +    {
 +	blockmaplump[i] = SHORT(blockmaplump[i]);
 +    }
 +
 +    // Read the header
 +
 +    bmaporgx = blockmaplump[0]<<FRACBITS;
 +    bmaporgy = blockmaplump[1]<<FRACBITS;
 +    bmapwidth = blockmaplump[2];
 +    bmapheight = blockmaplump[3];
 +
 +    // Clear out mobj chains
 +
 +    count = sizeof(*blocklinks) * bmapwidth * bmapheight;
 +    blocklinks = Z_Malloc(count, PU_LEVEL, 0);
 +    memset(blocklinks, 0, count);
 +}
 +
 +
 +
 +//
 +// P_GroupLines
 +// Builds sector line lists and subsector sector numbers.
 +// Finds block bounding boxes for sectors.
 +//
 +void P_GroupLines (void)
 +{
 +    line_t**		linebuffer;
 +    int			i;
 +    int			j;
 +    int			total;
 +    line_t*		li;
 +    sector_t*		sector;
 +    subsector_t*	ss;
 +    seg_t*		seg;
 +    fixed_t		bbox[4];
 +    int			block;
 +
 +    // look up sector number for each subsector
 +    ss = subsectors;
 +    for (i=0 ; i<numsubsectors ; i++, ss++)
 +    {
 +	seg = &segs[ss->firstline];
 +	ss->sector = seg->sidedef->sector;
 +    }
 +
 +    // count number of lines in each sector
 +    li = lines;
 +    total = 0;
 +    for (i=0 ; i<numlines ; i++, li++)
 +    {
 +	total++;
 +	li->frontsector->linecount++;
 +
 +	if (li->backsector && li->backsector != li->frontsector)
 +	{
 +	    li->backsector->linecount++;
 +	    total++;
 +	}
 +    }
 +
 +    // build line tables for each sector
 +    linebuffer = Z_Malloc (total*sizeof(line_t *), PU_LEVEL, 0);
 +
 +    for (i=0; i<numsectors; ++i)
 +    {
 +        // Assign the line buffer for this sector
 +
 +        sectors[i].lines = linebuffer;
 +        linebuffer += sectors[i].linecount;
 +
 +        // Reset linecount to zero so in the next stage we can count
 +        // lines into the list.
 +
 +        sectors[i].linecount = 0;
 +    }
 +
 +    // Assign lines to sectors
 +
 +    for (i=0; i<numlines; ++i)
 +    {
 +        li = &lines[i];
 +
 +        if (li->frontsector != NULL)
 +        {
 +            sector = li->frontsector;
 +
 +            sector->lines[sector->linecount] = li;
 +            ++sector->linecount;
 +        }
 +
 +        if (li->backsector != NULL && li->frontsector != li->backsector)
 +        {
 +            sector = li->backsector;
 +
 +            sector->lines[sector->linecount] = li;
 +            ++sector->linecount;
 +        }
 +    }
 +
 +    // Generate bounding boxes for sectors
 +
 +    sector = sectors;
 +    for (i=0 ; i<numsectors ; i++, sector++)
 +    {
 +	M_ClearBox (bbox);
 +
 +	for (j=0 ; j<sector->linecount; j++)
 +	{
 +            li = sector->lines[j];
 +
 +            M_AddToBox (bbox, li->v1->x, li->v1->y);
 +            M_AddToBox (bbox, li->v2->x, li->v2->y);
 +	}
 +
 +	// set the degenmobj_t to the middle of the bounding box
 +	sector->soundorg.x = (bbox[BOXRIGHT]+bbox[BOXLEFT])/2;
 +	sector->soundorg.y = (bbox[BOXTOP]+bbox[BOXBOTTOM])/2;
 +
 +	// adjust bounding box to map blocks
 +	block = (bbox[BOXTOP]-bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT;
 +	block = block >= bmapheight ? bmapheight-1 : block;
 +	sector->blockbox[BOXTOP]=block;
 +
 +	block = (bbox[BOXBOTTOM]-bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT;
 +	block = block < 0 ? 0 : block;
 +	sector->blockbox[BOXBOTTOM]=block;
 +
 +	block = (bbox[BOXRIGHT]-bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT;
 +	block = block >= bmapwidth ? bmapwidth-1 : block;
 +	sector->blockbox[BOXRIGHT]=block;
 +
 +	block = (bbox[BOXLEFT]-bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT;
 +	block = block < 0 ? 0 : block;
 +	sector->blockbox[BOXLEFT]=block;
 +    }
 +
 +}
 +
 +
 +//
 +// P_SetupLevel
 +//
 +void
 +P_SetupLevel
 +( int		episode,
 +  int		map,
 +  int		playermask,
 +  skill_t	skill)
 +{
 +    int		i;
 +    char	lumpname[9];
 +    int		lumpnum;
 +
 +    totalkills = totalitems = totalsecret = wminfo.maxfrags = 0;
 +    wminfo.partime = 180;
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	players[i].killcount = players[i].secretcount
 +	    = players[i].itemcount = 0;
 +    }
 +
 +    // Initial height of PointOfView
 +    // will be set by player think.
 +    players[consoleplayer].viewz = 1;
 +
 +    // Make sure all sounds are stopped before Z_FreeTags.
 +    S_Start ();
 +
 +
 +#if 0 // UNUSED
 +    if (debugfile)
 +    {
 +	Z_FreeTags (PU_LEVEL, INT_MAX);
 +	Z_FileDumpHeap (debugfile);
 +    }
 +    else
 +#endif
 +	Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1);
 +
 +
 +    // UNUSED W_Profile ();
 +    P_InitThinkers ();
 +
 +    // find map name
 +    if ( gamemode == commercial)
 +    {
 +	if (map<10)
 +	    sprintf (lumpname, DEH_String("map0%i"), map);
 +	else
 +	    sprintf (lumpname, DEH_String("map%i"), map);
 +    }
 +    else
 +    {
 +	lumpname[0] = 'E';
 +	lumpname[1] = '0' + episode;
 +	lumpname[2] = 'M';
 +	lumpname[3] = '0' + map;
 +	lumpname[4] = 0;
 +    }
 +
 +    lumpnum = W_GetNumForName (lumpname);
 +
 +    leveltime = 0;
 +
 +    // note: most of this ordering is important
 +    P_LoadBlockMap (lumpnum+ML_BLOCKMAP);
 +    P_LoadVertexes (lumpnum+ML_VERTEXES);
 +    P_LoadSectors (lumpnum+ML_SECTORS);
 +    P_LoadSideDefs (lumpnum+ML_SIDEDEFS);
 +
 +    P_LoadLineDefs (lumpnum+ML_LINEDEFS);
 +    P_LoadSubsectors (lumpnum+ML_SSECTORS);
 +    P_LoadNodes (lumpnum+ML_NODES);
 +    P_LoadSegs (lumpnum+ML_SEGS);
 +
 +    rejectmatrix = W_CacheLumpNum (lumpnum+ML_REJECT,PU_LEVEL);
 +    P_GroupLines ();
 +
 +    bodyqueslot = 0;
 +    deathmatch_p = deathmatchstarts;
 +    P_LoadThings (lumpnum+ML_THINGS);
 +
 +    // if deathmatch, randomly spawn the active players
 +    if (deathmatch)
 +    {
 +	for (i=0 ; i<MAXPLAYERS ; i++)
 +	    if (playeringame[i])
 +	    {
 +		players[i].mo = NULL;
 +		G_DeathMatchSpawnPlayer (i);
 +	    }
 +
 +    }
 +
 +    // clear special respawning que
 +    iquehead = iquetail = 0;
 +
 +    // set up world state
 +    P_SpawnSpecials ();
 +
 +    // build subsector connect matrix
 +    //	UNUSED P_ConnectSubsectors ();
 +
 +    // preload graphics
 +    if (precache)
 +	R_PrecacheLevel ();
 +
 +    //printf ("free memory: 0x%x\n", Z_FreeMemory());
 +
 +}
 +
 +
 +
 +//
 +// P_Init
 +//
 +void P_Init (void)
 +{
 +    P_InitSwitchList ();
 +    P_InitPicAnims ();
 +    R_InitSprites (sprnames);
 +}
 +
 +
 +
diff --cc src/strife/p_sight.c
index e192567b,00000000..79c1bb1d
mode 100644,000000..100644
--- a/src/strife/p_sight.c
+++ b/src/strife/p_sight.c
@@@ -1,350 -1,0 +1,358 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:
 +//	LineOfSight/Visibility checks, uses REJECT Lookup Table.
 +//
 +//-----------------------------------------------------------------------------
 +
 +
 +
 +#include "doomdef.h"
 +
 +#include "i_system.h"
 +#include "p_local.h"
 +
 +// State.
 +#include "r_state.h"
 +
 +//
 +// P_CheckSight
 +//
 +fixed_t		sightzstart;		// eye z of looker
 +fixed_t		topslope;
 +fixed_t		bottomslope;		// slopes to top and bottom of target
 +
 +divline_t	strace;			// from t1 to t2
 +fixed_t		t2x;
 +fixed_t		t2y;
 +
 +int		sightcounts[2];
 +
 +
 +//
 +// P_DivlineSide
 +// Returns side 0 (front), 1 (back), or 2 (on).
 +//
 +int
 +P_DivlineSide
 +( fixed_t	x,
 +  fixed_t	y,
 +  divline_t*	node )
 +{
 +    fixed_t	dx;
 +    fixed_t	dy;
 +    fixed_t	left;
 +    fixed_t	right;
 +
 +    if (!node->dx)
 +    {
 +	if (x==node->x)
 +	    return 2;
 +
 +	if (x <= node->x)
 +	    return node->dy > 0;
 +
 +	return node->dy < 0;
 +    }
 +
 +    if (!node->dy)
 +    {
 +	if (x==node->y)
 +	    return 2;
 +
 +	if (y <= node->y)
 +	    return node->dx < 0;
 +
 +	return node->dx > 0;
 +    }
 +
 +    dx = (x - node->x);
 +    dy = (y - node->y);
 +
 +    left =  (node->dy>>FRACBITS) * (dx>>FRACBITS);
 +    right = (dy>>FRACBITS) * (node->dx>>FRACBITS);
 +
 +    if (right < left)
 +	return 0;	// front side
 +
 +    if (left == right)
 +	return 2;
 +    return 1;		// back side
 +}
 +
 +
 +//
 +// P_InterceptVector2
 +// Returns the fractional intercept point
 +// along the first divline.
 +// This is only called by the addthings and addlines traversers.
 +//
 +fixed_t
 +P_InterceptVector2
 +( divline_t*	v2,
 +  divline_t*	v1 )
 +{
 +    fixed_t	frac;
 +    fixed_t	num;
 +    fixed_t	den;
 +
 +    den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy);
 +
 +    if (den == 0)
 +	return 0;
 +    //	I_Error ("P_InterceptVector: parallel");
 +
 +    num = FixedMul ( (v1->x - v2->x)>>8 ,v1->dy) +
 +	FixedMul ( (v2->y - v1->y)>>8 , v1->dx);
 +    frac = FixedDiv (num , den);
 +
 +    return frac;
 +}
 +
 +//
 +// P_CrossSubsector
 +// Returns true
 +//  if strace crosses the given subsector successfully.
 +//
 +boolean P_CrossSubsector (int num)
 +{
 +    seg_t*		seg;
 +    line_t*		line;
 +    int			s1;
 +    int			s2;
 +    int			count;
 +    subsector_t*	sub;
 +    sector_t*		front;
 +    sector_t*		back;
 +    fixed_t		opentop;
 +    fixed_t		openbottom;
 +    divline_t		divl;
 +    vertex_t*		v1;
 +    vertex_t*		v2;
 +    fixed_t		frac;
 +    fixed_t		slope;
 +
 +#ifdef RANGECHECK
 +    if (num>=numsubsectors)
 +	I_Error ("P_CrossSubsector: ss %i with numss = %i",
 +		 num,
 +		 numsubsectors);
 +#endif
 +
 +    sub = &subsectors[num];
 +
 +    // check lines
 +    count = sub->numlines;
 +    seg = &segs[sub->firstline];
 +
 +    for ( ; count ; seg++, count--)
 +    {
 +	line = seg->linedef;
 +
 +	// allready checked other side?
 +	if (line->validcount == validcount)
 +	    continue;
 +
 +	line->validcount = validcount;
-
++
 +	v1 = line->v1;
 +	v2 = line->v2;
 +	s1 = P_DivlineSide (v1->x,v1->y, &strace);
 +	s2 = P_DivlineSide (v2->x, v2->y, &strace);
 +
 +	// line isn't crossed?
 +	if (s1 == s2)
 +	    continue;
 +
 +	divl.x = v1->x;
 +	divl.y = v1->y;
 +	divl.dx = v2->x - v1->x;
 +	divl.dy = v2->y - v1->y;
 +	s1 = P_DivlineSide (strace.x, strace.y, &divl);
 +	s2 = P_DivlineSide (t2x, t2y, &divl);
 +
 +	// line isn't crossed?
 +	if (s1 == s2)
 +	    continue;
 +
++        // Backsector may be NULL if this is an "impassible
++        // glass" hack line.
++
++        if (line->backsector == NULL)
++        {
++            return false;
++        }
++
 +	// stop because it is not two sided anyway
 +	// might do this after updating validcount?
 +	if ( !(line->flags & ML_TWOSIDED) )
 +	    return false;
 +
 +	// crosses a two sided line
 +	front = seg->frontsector;
 +	back = seg->backsector;
 +
 +	// no wall to block sight with?
 +	if (front->floorheight == back->floorheight
 +	    && front->ceilingheight == back->ceilingheight)
 +	    continue;
 +
 +	// possible occluder
 +	// because of ceiling height differences
 +	if (front->ceilingheight < back->ceilingheight)
 +	    opentop = front->ceilingheight;
 +	else
 +	    opentop = back->ceilingheight;
 +
 +	// because of ceiling height differences
 +	if (front->floorheight > back->floorheight)
 +	    openbottom = front->floorheight;
 +	else
 +	    openbottom = back->floorheight;
 +
 +	// quick test for totally closed doors
 +	if (openbottom >= opentop)
 +	    return false;		// stop
 +
 +	frac = P_InterceptVector2 (&strace, &divl);
 +
 +	if (front->floorheight != back->floorheight)
 +	{
 +	    slope = FixedDiv (openbottom - sightzstart , frac);
 +	    if (slope > bottomslope)
 +		bottomslope = slope;
 +	}
 +
 +	if (front->ceilingheight != back->ceilingheight)
 +	{
 +	    slope = FixedDiv (opentop - sightzstart , frac);
 +	    if (slope < topslope)
 +		topslope = slope;
 +	}
 +
 +	if (topslope <= bottomslope)
 +	    return false;		// stop
 +    }
 +    // passed the subsector ok
 +    return true;
 +}
 +
 +
 +
 +//
 +// P_CrossBSPNode
 +// Returns true
 +//  if strace crosses the given node successfully.
 +//
 +boolean P_CrossBSPNode (int bspnum)
 +{
 +    node_t*	bsp;
 +    int		side;
 +
 +    if (bspnum & NF_SUBSECTOR)
 +    {
 +	if (bspnum == -1)
 +	    return P_CrossSubsector (0);
 +	else
 +	    return P_CrossSubsector (bspnum&(~NF_SUBSECTOR));
 +    }
 +
 +    bsp = &nodes[bspnum];
 +
 +    // decide which side the start point is on
 +    side = P_DivlineSide (strace.x, strace.y, (divline_t *)bsp);
 +    if (side == 2)
 +	side = 0;	// an "on" should cross both sides
 +
 +    // cross the starting side
 +    if (!P_CrossBSPNode (bsp->children[side]) )
 +	return false;
 +
 +    // the partition plane is crossed here
 +    if (side == P_DivlineSide (t2x, t2y,(divline_t *)bsp))
 +    {
 +	// the line doesn't touch the other side
 +	return true;
 +    }
 +
 +    // cross the ending side
 +    return P_CrossBSPNode (bsp->children[side^1]);
 +}
 +
 +
 +//
 +// P_CheckSight
 +// Returns true
 +//  if a straight line between t1 and t2 is unobstructed.
 +// Uses REJECT.
 +//
 +boolean
 +P_CheckSight
 +( mobj_t*	t1,
 +  mobj_t*	t2 )
 +{
 +    int		s1;
 +    int		s2;
 +    int		pnum;
 +    int		bytenum;
 +    int		bitnum;
 +
 +    // First check for trivial rejection.
 +
 +    // Determine subsector entries in REJECT table.
 +    s1 = (t1->subsector->sector - sectors);
 +    s2 = (t2->subsector->sector - sectors);
 +    pnum = s1*numsectors + s2;
 +    bytenum = pnum>>3;
 +    bitnum = 1 << (pnum&7);
 +
 +    // Check in REJECT table.
 +    if (rejectmatrix[bytenum]&bitnum)
 +    {
 +	sightcounts[0]++;
 +
 +	// can't possibly be connected
 +	return false;
 +    }
 +
 +    // An unobstructed LOS is possible.
 +    // Now look from eyes of t1 to any part of t2.
 +    sightcounts[1]++;
 +
 +    validcount++;
 +
 +    sightzstart = t1->z + t1->height - (t1->height>>2);
 +    topslope = (t2->z+t2->height) - sightzstart;
 +    bottomslope = (t2->z) - sightzstart;
 +
 +    strace.x = t1->x;
 +    strace.y = t1->y;
 +    t2x = t2->x;
 +    t2y = t2->y;
 +    strace.dx = t2->x - t1->x;
 +    strace.dy = t2->y - t1->y;
 +
 +    // the head node is the last node output
 +    return P_CrossBSPNode (numnodes-1);
 +}
 +
 +
diff --cc src/strife/st_stuff.c
index e6ef0828,00000000..160244ce
mode 100644,000000..100644
--- a/src/strife/st_stuff.c
+++ b/src/strife/st_stuff.c
@@@ -1,1412 -1,0 +1,1416 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:
 +//	Status bar code.
 +//	Does the face/direction indicator animatin.
 +//	Does palette indicators as well (red pain/berserk, bright pickup)
 +//
 +//-----------------------------------------------------------------------------
 +
 +
 +
 +#include <stdio.h>
 +
 +#include "i_system.h"
 +#include "i_video.h"
 +#include "z_zone.h"
 +#include "m_random.h"
 +#include "w_wad.h"
 +
 +#include "deh_main.h"
 +#include "deh_misc.h"
 +#include "doomdef.h"
 +#include "doomkeys.h"
 +
 +#include "g_game.h"
 +
 +#include "st_stuff.h"
 +#include "st_lib.h"
 +#include "r_local.h"
 +
 +#include "p_local.h"
 +#include "p_inter.h"
 +
 +#include "am_map.h"
 +#include "m_cheat.h"
 +
 +#include "s_sound.h"
 +
 +// Needs access to LFB.
 +#include "v_video.h"
 +
 +// State.
 +#include "doomstat.h"
 +
 +// Data.
 +#include "dstrings.h"
 +#include "sounds.h"
 +
 +//
 +// STATUS BAR DATA
 +//
 +
 +
 +// Palette indices.
 +// For damage/bonus red-/gold-shifts
 +#define STARTREDPALS		1
 +#define STARTBONUSPALS		9
 +#define NUMREDPALS			8
 +#define NUMBONUSPALS		4
 +// Radiation suit, green shift.
 +#define RADIATIONPAL		13
 +
 +// N/256*100% probability
 +//  that the normal face state will change
 +#define ST_FACEPROBABILITY		96
 +
 +// For Responder
 +#define ST_TOGGLECHAT		KEY_ENTER
 +
 +// Location of status bar
 +#define ST_X				0
 +#define ST_X2				104
 +
 +#define ST_FX  			143
 +#define ST_FY  			169
 +
 +// Should be set to patch width
 +//  for tall numbers later on
 +#define ST_TALLNUMWIDTH		(tallnum[0]->width)
 +
 +// Number of status faces.
 +#define ST_NUMPAINFACES		5
 +#define ST_NUMSTRAIGHTFACES	3
 +#define ST_NUMTURNFACES		2
 +#define ST_NUMSPECIALFACES		3
 +
 +#define ST_FACESTRIDE \
 +          (ST_NUMSTRAIGHTFACES+ST_NUMTURNFACES+ST_NUMSPECIALFACES)
 +
 +#define ST_NUMEXTRAFACES		2
 +
 +#define ST_NUMFACES \
 +          (ST_FACESTRIDE*ST_NUMPAINFACES+ST_NUMEXTRAFACES)
 +
 +#define ST_TURNOFFSET		(ST_NUMSTRAIGHTFACES)
 +#define ST_OUCHOFFSET		(ST_TURNOFFSET + ST_NUMTURNFACES)
 +#define ST_EVILGRINOFFSET		(ST_OUCHOFFSET + 1)
 +#define ST_RAMPAGEOFFSET		(ST_EVILGRINOFFSET + 1)
 +#define ST_GODFACE			(ST_NUMPAINFACES*ST_FACESTRIDE)
 +#define ST_DEADFACE			(ST_GODFACE+1)
 +
 +#define ST_FACESX			143
 +#define ST_FACESY			168
 +
 +#define ST_EVILGRINCOUNT		(2*TICRATE)
 +#define ST_STRAIGHTFACECOUNT	(TICRATE/2)
 +#define ST_TURNCOUNT		(1*TICRATE)
 +#define ST_OUCHCOUNT		(1*TICRATE)
 +#define ST_RAMPAGEDELAY		(2*TICRATE)
 +
 +#define ST_MUCHPAIN			20
 +
 +
 +// Location and size of statistics,
 +//  justified according to widget type.
 +// Problem is, within which space? STbar? Screen?
 +// Note: this could be read in by a lump.
 +//       Problem is, is the stuff rendered
 +//       into a buffer,
 +//       or into the frame buffer?
 +
 +// AMMO number pos.
 +#define ST_AMMOWIDTH		3
 +#define ST_AMMOX			44
 +#define ST_AMMOY			171
 +
 +// HEALTH number pos.
 +#define ST_HEALTHWIDTH		3
 +#define ST_HEALTHX			90
 +#define ST_HEALTHY			171
 +
 +// Weapon pos.
 +#define ST_ARMSX			111
 +#define ST_ARMSY			172
 +#define ST_ARMSBGX			104
 +#define ST_ARMSBGY			168
 +#define ST_ARMSXSPACE		12
 +#define ST_ARMSYSPACE		10
 +
 +// Frags pos.
 +#define ST_FRAGSX			138
 +#define ST_FRAGSY			171
 +#define ST_FRAGSWIDTH		2
 +
 +// ARMOR number pos.
 +#define ST_ARMORWIDTH		3
 +#define ST_ARMORX			221
 +#define ST_ARMORY			171
 +
 +// Key icon positions.
 +#define ST_KEY0WIDTH		8
 +#define ST_KEY0HEIGHT		5
 +#define ST_KEY0X			239
 +#define ST_KEY0Y			171
 +#define ST_KEY1WIDTH		ST_KEY0WIDTH
 +#define ST_KEY1X			239
 +#define ST_KEY1Y			181
 +#define ST_KEY2WIDTH		ST_KEY0WIDTH
 +#define ST_KEY2X			239
 +#define ST_KEY2Y			191
 +
 +// Ammunition counter.
 +#define ST_AMMO0WIDTH		3
 +#define ST_AMMO0HEIGHT		6
 +#define ST_AMMO0X			288
 +#define ST_AMMO0Y			173
 +#define ST_AMMO1WIDTH		ST_AMMO0WIDTH
 +#define ST_AMMO1X			288
 +#define ST_AMMO1Y			179
 +#define ST_AMMO2WIDTH		ST_AMMO0WIDTH
 +#define ST_AMMO2X			288
 +#define ST_AMMO2Y			191
 +#define ST_AMMO3WIDTH		ST_AMMO0WIDTH
 +#define ST_AMMO3X			288
 +#define ST_AMMO3Y			185
 +
 +// Indicate maximum ammunition.
 +// Only needed because backpack exists.
 +#define ST_MAXAMMO0WIDTH		3
 +#define ST_MAXAMMO0HEIGHT		5
 +#define ST_MAXAMMO0X		314
 +#define ST_MAXAMMO0Y		173
 +#define ST_MAXAMMO1WIDTH		ST_MAXAMMO0WIDTH
 +#define ST_MAXAMMO1X		314
 +#define ST_MAXAMMO1Y		179
 +#define ST_MAXAMMO2WIDTH		ST_MAXAMMO0WIDTH
 +#define ST_MAXAMMO2X		314
 +#define ST_MAXAMMO2Y		191
 +#define ST_MAXAMMO3WIDTH		ST_MAXAMMO0WIDTH
 +#define ST_MAXAMMO3X		314
 +#define ST_MAXAMMO3Y		185
 +
 +// pistol
 +#define ST_WEAPON0X			110
 +#define ST_WEAPON0Y			172
 +
 +// shotgun
 +#define ST_WEAPON1X			122
 +#define ST_WEAPON1Y			172
 +
 +// chain gun
 +#define ST_WEAPON2X			134
 +#define ST_WEAPON2Y			172
 +
 +// missile launcher
 +#define ST_WEAPON3X			110
 +#define ST_WEAPON3Y			181
 +
 +// plasma gun
 +#define ST_WEAPON4X			122
 +#define ST_WEAPON4Y			181
 +
 + // bfg
 +#define ST_WEAPON5X			134
 +#define ST_WEAPON5Y			181
 +
 +// WPNS title
 +#define ST_WPNSX			109
 +#define ST_WPNSY			191
 +
 + // DETH title
 +#define ST_DETHX			109
 +#define ST_DETHY			191
 +
 +//Incoming messages window location
 +//UNUSED
 +// #define ST_MSGTEXTX	   (viewwindowx)
 +// #define ST_MSGTEXTY	   (viewwindowy+viewheight-18)
 +#define ST_MSGTEXTX			0
 +#define ST_MSGTEXTY			0
 +// Dimensions given in characters.
 +#define ST_MSGWIDTH			52
 +// Or shall I say, in lines?
 +#define ST_MSGHEIGHT		1
 +
 +#define ST_OUTTEXTX			0
 +#define ST_OUTTEXTY			6
 +
 +// Width, in characters again.
 +#define ST_OUTWIDTH			52
 + // Height, in lines.
 +#define ST_OUTHEIGHT		1
 +
- #define ST_MAPWIDTH	\
-     (strlen(mapnames[(gameepisode-1)*9+(gamemap-1)]))
-
 +#define ST_MAPTITLEX \
 +    (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH)
 +
 +#define ST_MAPTITLEY		0
 +#define ST_MAPHEIGHT		1
 +
 +// graphics are drawn to a backing screen and blitted to the real screen
 +byte                   *st_backing_screen;
 +
 +// main player in game
 +static player_t*	plyr;
 +
 +// ST_Start() has just been called
 +static boolean		st_firsttime;
 +
 +// lump number for PLAYPAL
 +static int		lu_palette;
 +
 +// used for timing
 +static unsigned int	st_clock;
 +
 +// used for making messages go away
 +static int		st_msgcounter=0;
 +
 +// used when in chat
 +static st_chatstateenum_t	st_chatstate;
 +
 +// whether in automap or first-person
 +static st_stateenum_t	st_gamestate;
 +
 +// whether left-side main status bar is active
 +static boolean		st_statusbaron;
 +
 +// whether status bar chat is active
 +static boolean		st_chat;
 +
 +// value of st_chat before message popped up
 +static boolean		st_oldchat;
 +
 +// whether chat window has the cursor on
 +static boolean		st_cursoron;
 +
 +// !deathmatch
 +static boolean		st_notdeathmatch;
 +
 +// !deathmatch && st_statusbaron
 +static boolean		st_armson;
 +
 +// !deathmatch
 +static boolean		st_fragson;
 +
 +// main bar left
 +static patch_t*		sbar;
 +
 +// 0-9, tall numbers
 +static patch_t*		tallnum[10];
 +
 +// tall % sign
 +static patch_t*		tallpercent;
 +
 +// 0-9, short, yellow (,different!) numbers
 +static patch_t*		shortnum[10];
 +
 +// 3 key-cards, 3 skulls
 +static patch_t*		keys[NUMCARDS];
 +
 +// face status patches
 +static patch_t*		faces[ST_NUMFACES];
 +
 +// face background
 +static patch_t*		faceback;
 +
 + // main bar right
 +static patch_t*		armsbg;
 +
 +// weapon ownership patches
 +static patch_t*		arms[6][2];
 +
 +// ready-weapon widget
 +static st_number_t	w_ready;
 +
 + // in deathmatch only, summary of frags stats
 +static st_number_t	w_frags;
 +
 +// health widget
 +static st_percent_t	w_health;
 +
 +// arms background
 +static st_binicon_t	w_armsbg;
 +
 +
 +// weapon ownership widgets
 +static st_multicon_t	w_arms[6];
 +
 +// face status widget
 +static st_multicon_t	w_faces;
 +
 +// keycard widgets
 +static st_multicon_t	w_keyboxes[3];
 +
 +// armor widget
 +static st_percent_t	w_armor;
 +
 +// ammo widgets
 +static st_number_t	w_ammo[4];
 +
 +// max ammo widgets
 +static st_number_t	w_maxammo[4];
 +
 +
 +
 + // number of frags so far in deathmatch
 +static int	st_fragscount;
 +
 +// used to use appopriately pained face
 +static int	st_oldhealth = -1;
 +
 +// used for evil grin
 +static boolean	oldweaponsowned[NUMWEAPONS];
 +
 + // count until face changes
 +static int	st_facecount = 0;
 +
 +// current face index, used by w_faces
 +static int	st_faceindex = 0;
 +
 +// holds key-type for each key box on bar
 +static int	keyboxes[3];
 +
 +// a random number per tick
 +static int	st_randomnumber;
 +
 +cheatseq_t cheat_mus = CHEAT("idmus", 2);
 +cheatseq_t cheat_god = CHEAT("iddqd", 0);
 +cheatseq_t cheat_ammo = CHEAT("idkfa", 0);
 +cheatseq_t cheat_ammonokey = CHEAT("idfa", 0);
 +cheatseq_t cheat_noclip = CHEAT("idspispopd", 0);
 +cheatseq_t cheat_commercial_noclip = CHEAT("idclip", 0);
 +
 +cheatseq_t	cheat_powerup[7] =
 +{
 +    CHEAT("idbeholdv", 0),
 +    CHEAT("idbeholds", 0),
 +    CHEAT("idbeholdi", 0),
 +    CHEAT("idbeholdr", 0),
 +    CHEAT("idbeholda", 0),
 +    CHEAT("idbeholdl", 0),
 +    CHEAT("idbehold", 0),
 +};
 +
 +cheatseq_t cheat_choppers = CHEAT("idchoppers", 0);
 +cheatseq_t cheat_clev = CHEAT("idclev", 2);
 +cheatseq_t cheat_mypos = CHEAT("idmypos", 0);
 +
 +
- //
- extern char*	mapnames[];
-
-
 +//
 +// STATUS BAR CODE
 +//
 +void ST_Stop(void);
 +
 +void ST_refreshBackground(void)
 +{
 +
 +    if (st_statusbaron)
 +    {
 +        V_UseBuffer(st_backing_screen);
 +
 +	V_DrawPatch(ST_X, 0, sbar);
 +
 +	if (netgame)
 +	    V_DrawPatch(ST_FX, 0, faceback);
 +
 +        V_RestoreBuffer();
 +
 +	V_CopyRect(ST_X, 0, st_backing_screen, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y);
 +    }
 +
 +}
 +
 +
 +// Respond to keyboard input events,
 +//  intercept cheats.
 +boolean
 +ST_Responder (event_t* ev)
 +{
 +  int		i;
 +
 +  // Filter automap on/off.
 +  if (ev->type == ev_keyup
 +      && ((ev->data1 & 0xffff0000) == AM_MSGHEADER))
 +  {
 +    switch(ev->data1)
 +    {
 +      case AM_MSGENTERED:
 +	st_gamestate = AutomapState;
 +	st_firsttime = true;
 +	break;
 +
 +      case AM_MSGEXITED:
 +	//	fprintf(stderr, "AM exited\n");
 +	st_gamestate = FirstPersonState;
 +	break;
 +    }
 +  }
 +
 +  // if a user keypress...
 +  else if (ev->type == ev_keydown)
 +  {
 +    if (!netgame && gameskill != sk_nightmare)
 +    {
 +      // 'dqd' cheat for toggleable god mode
 +      if (cht_CheckCheat(&cheat_god, ev->data2))
 +      {
 +	plyr->cheats ^= CF_GODMODE;
 +	if (plyr->cheats & CF_GODMODE)
 +	{
 +	  if (plyr->mo)
 +	    plyr->mo->health = 100;
 +
 +	  plyr->health = deh_god_mode_health;
 +	  plyr->message = DEH_String(STSTR_DQDON);
 +	}
 +	else
 +	  plyr->message = DEH_String(STSTR_DQDOFF);
 +      }
 +      // 'fa' cheat for killer fucking arsenal
 +      else if (cht_CheckCheat(&cheat_ammonokey, ev->data2))
 +      {
 +	plyr->armorpoints = deh_idfa_armor;
 +	plyr->armortype = deh_idfa_armor_class;
 +
 +	for (i=0;i<NUMWEAPONS;i++)
 +	  plyr->weaponowned[i] = true;
 +
 +	for (i=0;i<NUMAMMO;i++)
 +	  plyr->ammo[i] = plyr->maxammo[i];
 +
 +	plyr->message = DEH_String(STSTR_FAADDED);
 +      }
 +      // 'kfa' cheat for key full ammo
 +      else if (cht_CheckCheat(&cheat_ammo, ev->data2))
 +      {
 +	plyr->armorpoints = deh_idkfa_armor;
 +	plyr->armortype = deh_idkfa_armor_class;
 +
 +	for (i=0;i<NUMWEAPONS;i++)
 +	  plyr->weaponowned[i] = true;
 +
 +	for (i=0;i<NUMAMMO;i++)
 +	  plyr->ammo[i] = plyr->maxammo[i];
 +
 +	for (i=0;i<NUMCARDS;i++)
 +	  plyr->cards[i] = true;
 +
 +	plyr->message = DEH_String(STSTR_KFAADDED);
 +      }
 +      // 'mus' cheat for changing music
 +      else if (cht_CheckCheat(&cheat_mus, ev->data2))
 +      {
 +
 +	char	buf[3];
 +	int		musnum;
 +
 +	plyr->message = DEH_String(STSTR_MUS);
 +	cht_GetParam(&cheat_mus, buf);
 +
 +	if (gamemode == commercial)
 +	{
 +	  musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1;
 +
 +	  if (((buf[0]-'0')*10 + buf[1]-'0') > 35)
 +	    plyr->message = DEH_String(STSTR_NOMUS);
 +	  else
 +	    S_ChangeMusic(musnum, 1);
 +	}
 +	else
 +	{
 +	  musnum = mus_e1m1 + (buf[0]-'1')*9 + (buf[1]-'1');
 +
 +	  if (((buf[0]-'1')*9 + buf[1]-'1') > 31)
 +	    plyr->message = DEH_String(STSTR_NOMUS);
 +	  else
 +	    S_ChangeMusic(musnum, 1);
 +	}
 +      }
 +      else if ( (gamemission == doom
 +                 && cht_CheckCheat(&cheat_noclip, ev->data2))
 +             || (gamemission != doom
 +                 && cht_CheckCheat(&cheat_commercial_noclip,ev->data2)))
 +      {
 +        // Noclip cheat.
 +        // For Doom 1, use the idspipsopd cheat; for all others, use
 +        // idclip
 +
 +	plyr->cheats ^= CF_NOCLIP;
 +
 +	if (plyr->cheats & CF_NOCLIP)
 +	  plyr->message = DEH_String(STSTR_NCON);
 +	else
 +	  plyr->message = DEH_String(STSTR_NCOFF);
 +      }
 +      // 'behold?' power-up cheats
 +      for (i=0;i<6;i++)
 +      {
 +	if (cht_CheckCheat(&cheat_powerup[i], ev->data2))
 +	{
 +	  if (!plyr->powers[i])
 +	    P_GivePower( plyr, i);
 +	  else if (i!=pw_strength)
 +	    plyr->powers[i] = 1;
 +	  else
 +	    plyr->powers[i] = 0;
 +
 +	  plyr->message = DEH_String(STSTR_BEHOLDX);
 +	}
 +      }
 +
 +      // 'behold' power-up menu
 +      if (cht_CheckCheat(&cheat_powerup[6], ev->data2))
 +      {
 +	plyr->message = DEH_String(STSTR_BEHOLD);
 +      }
 +      // 'choppers' invulnerability & chainsaw
 +      else if (cht_CheckCheat(&cheat_choppers, ev->data2))
 +      {
 +	plyr->weaponowned[wp_chainsaw] = true;
 +	plyr->powers[pw_invulnerability] = true;
 +	plyr->message = DEH_String(STSTR_CHOPPERS);
 +      }
 +      // 'mypos' for player position
 +      else if (cht_CheckCheat(&cheat_mypos, ev->data2))
 +      {
 +	static char	buf[ST_MSGWIDTH];
 +	sprintf(buf, "ang=0x%x;x,y=(0x%x,0x%x)",
 +		players[consoleplayer].mo->angle,
 +		players[consoleplayer].mo->x,
 +		players[consoleplayer].mo->y);
 +	plyr->message = buf;
 +      }
 +    }
 +
 +    // 'clev' change-level cheat
 +    if (!netgame && cht_CheckCheat(&cheat_clev, ev->data2))
 +    {
 +      char		buf[3];
 +      int		epsd;
 +      int		map;
 +
 +      cht_GetParam(&cheat_clev, buf);
 +
 +      if (gamemode == commercial)
 +      {
 +	epsd = 1;
 +	map = (buf[0] - '0')*10 + buf[1] - '0';
 +      }
 +      else
 +      {
 +	epsd = buf[0] - '0';
 +	map = buf[1] - '0';
 +      }
 +
 +      // Chex.exe always warps to episode 1.
 +
 +      if (gameversion == exe_chex)
 +      {
 +        epsd = 1;
 +      }
 +
 +      // Catch invalid maps.
 +      if (epsd < 1)
 +	return false;
 +
 +      if (map < 1)
 +	return false;
 +
 +      // Ohmygod - this is not going to work.
 +      if ((gamemode == retail)
 +	  && ((epsd > 4) || (map > 9)))
 +	return false;
 +
 +      if ((gamemode == registered)
 +	  && ((epsd > 3) || (map > 9)))
 +	return false;
 +
 +      if ((gamemode == shareware)
 +	  && ((epsd > 1) || (map > 9)))
 +	return false;
 +
 +      if ((gamemode == commercial)
 +	&& (( epsd > 1) || (map > 34)))
 +	return false;
 +
 +      // So be it.
 +      plyr->message = DEH_String(STSTR_CLEV);
 +      G_DeferedInitNew(gameskill, epsd, map);
 +    }
 +  }
 +  return false;
 +}
 +
 +
 +
 +int ST_calcPainOffset(void)
 +{
 +    int		health;
 +    static int	lastcalc;
 +    static int	oldhealth = -1;
 +
 +    health = plyr->health > 100 ? 100 : plyr->health;
 +
 +    if (health != oldhealth)
 +    {
 +	lastcalc = ST_FACESTRIDE * (((100 - health) * ST_NUMPAINFACES) / 101);
 +	oldhealth = health;
 +    }
 +    return lastcalc;
 +}
 +
 +
 +//
 +// This is a not-very-pretty routine which handles
 +//  the face states and their timing.
 +// the precedence of expressions is:
 +//  dead > evil grin > turned head > straight ahead
 +//
 +void ST_updateFaceWidget(void)
 +{
 +    int		i;
 +    angle_t	badguyangle;
 +    angle_t	diffang;
 +    static int	lastattackdown = -1;
 +    static int	priority = 0;
 +    boolean	doevilgrin;
 +
 +    if (priority < 10)
 +    {
 +	// dead
 +	if (!plyr->health)
 +	{
 +	    priority = 9;
 +	    st_faceindex = ST_DEADFACE;
 +	    st_facecount = 1;
 +	}
 +    }
 +
 +    if (priority < 9)
 +    {
 +	if (plyr->bonuscount)
 +	{
 +	    // picking up bonus
 +	    doevilgrin = false;
 +
 +	    for (i=0;i<NUMWEAPONS;i++)
 +	    {
 +		if (oldweaponsowned[i] != plyr->weaponowned[i])
 +		{
 +		    doevilgrin = true;
 +		    oldweaponsowned[i] = plyr->weaponowned[i];
 +		}
 +	    }
 +	    if (doevilgrin)
 +	    {
 +		// evil grin if just picked up weapon
 +		priority = 8;
 +		st_facecount = ST_EVILGRINCOUNT;
 +		st_faceindex = ST_calcPainOffset() + ST_EVILGRINOFFSET;
 +	    }
 +	}
 +
 +    }
 +
 +    if (priority < 8)
 +    {
 +	if (plyr->damagecount
 +	    && plyr->attacker
 +	    && plyr->attacker != plyr->mo)
 +	{
 +	    // being attacked
 +	    priority = 7;
 +
 +	    if (plyr->health - st_oldhealth > ST_MUCHPAIN)
 +	    {
 +		st_facecount = ST_TURNCOUNT;
 +		st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET;
 +	    }
 +	    else
 +	    {
 +		badguyangle = R_PointToAngle2(plyr->mo->x,
 +					      plyr->mo->y,
 +					      plyr->attacker->x,
 +					      plyr->attacker->y);
 +
 +		if (badguyangle > plyr->mo->angle)
 +		{
 +		    // whether right or left
 +		    diffang = badguyangle - plyr->mo->angle;
 +		    i = diffang > ANG180;
 +		}
 +		else
 +		{
 +		    // whether left or right
 +		    diffang = plyr->mo->angle - badguyangle;
 +		    i = diffang <= ANG180;
 +		} // confusing, aint it?
 +
 +
 +		st_facecount = ST_TURNCOUNT;
 +		st_faceindex = ST_calcPainOffset();
 +
 +		if (diffang < ANG45)
 +		{
 +		    // head-on
 +		    st_faceindex += ST_RAMPAGEOFFSET;
 +		}
 +		else if (i)
 +		{
 +		    // turn face right
 +		    st_faceindex += ST_TURNOFFSET;
 +		}
 +		else
 +		{
 +		    // turn face left
 +		    st_faceindex += ST_TURNOFFSET+1;
 +		}
 +	    }
 +	}
 +    }
 +
 +    if (priority < 7)
 +    {
 +	// getting hurt because of your own damn stupidity
 +	if (plyr->damagecount)
 +	{
 +	    if (plyr->health - st_oldhealth > ST_MUCHPAIN)
 +	    {
 +		priority = 7;
 +		st_facecount = ST_TURNCOUNT;
 +		st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET;
 +	    }
 +	    else
 +	    {
 +		priority = 6;
 +		st_facecount = ST_TURNCOUNT;
 +		st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET;
 +	    }
 +
 +	}
 +
 +    }
 +
 +    if (priority < 6)
 +    {
 +	// rapid firing
 +	if (plyr->attackdown)
 +	{
 +	    if (lastattackdown==-1)
 +		lastattackdown = ST_RAMPAGEDELAY;
 +	    else if (!--lastattackdown)
 +	    {
 +		priority = 5;
 +		st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET;
 +		st_facecount = 1;
 +		lastattackdown = 1;
 +	    }
 +	}
 +	else
 +	    lastattackdown = -1;
 +
 +    }
 +
 +    if (priority < 5)
 +    {
 +	// invulnerability
 +	if ((plyr->cheats & CF_GODMODE)
 +	    || plyr->powers[pw_invulnerability])
 +	{
 +	    priority = 4;
 +
 +	    st_faceindex = ST_GODFACE;
 +	    st_facecount = 1;
 +
 +	}
 +
 +    }
 +
 +    // look left or look right if the facecount has timed out
 +    if (!st_facecount)
 +    {
 +	st_faceindex = ST_calcPainOffset() + (st_randomnumber % 3);
 +	st_facecount = ST_STRAIGHTFACECOUNT;
 +	priority = 0;
 +    }
 +
 +    st_facecount--;
 +
 +}
 +
 +void ST_updateWidgets(void)
 +{
 +    static int	largeammo = 1994; // means "n/a"
 +    int		i;
 +
 +    // must redirect the pointer if the ready weapon has changed.
 +    //  if (w_ready.data != plyr->readyweapon)
 +    //  {
 +    if (weaponinfo[plyr->readyweapon].ammo == am_noammo)
 +	w_ready.num = &largeammo;
 +    else
 +	w_ready.num = &plyr->ammo[weaponinfo[plyr->readyweapon].ammo];
 +    //{
 +    // static int tic=0;
 +    // static int dir=-1;
 +    // if (!(tic&15))
 +    //   plyr->ammo[weaponinfo[plyr->readyweapon].ammo]+=dir;
 +    // if (plyr->ammo[weaponinfo[plyr->readyweapon].ammo] == -100)
 +    //   dir = 1;
 +    // tic++;
 +    // }
 +    w_ready.data = plyr->readyweapon;
 +
 +    // if (*w_ready.on)
 +    //  STlib_updateNum(&w_ready, true);
 +    // refresh weapon change
 +    //  }
 +
 +    // update keycard multiple widgets
 +    for (i=0;i<3;i++)
 +    {
 +	keyboxes[i] = plyr->cards[i] ? i : -1;
 +
 +	if (plyr->cards[i+3])
 +	    keyboxes[i] = i+3;
 +    }
 +
 +    // refresh everything if this is him coming back to life
 +    ST_updateFaceWidget();
 +
 +    // used by the w_armsbg widget
 +    st_notdeathmatch = !deathmatch;
 +
 +    // used by w_arms[] widgets
 +    st_armson = st_statusbaron && !deathmatch;
 +
 +    // used by w_frags widget
 +    st_fragson = deathmatch && st_statusbaron;
 +    st_fragscount = 0;
 +
 +    for (i=0 ; i<MAXPLAYERS ; i++)
 +    {
 +	if (i != consoleplayer)
 +	    st_fragscount += plyr->frags[i];
 +	else
 +	    st_fragscount -= plyr->frags[i];
 +    }
 +
 +    // get rid of chat window if up because of message
 +    if (!--st_msgcounter)
 +	st_chat = st_oldchat;
 +
 +}
 +
 +void ST_Ticker (void)
 +{
 +
 +    st_clock++;
 +    st_randomnumber = M_Random();
 +    ST_updateWidgets();
 +    st_oldhealth = plyr->health;
 +
 +}
 +
 +static int st_palette = 0;
 +
 +void ST_doPaletteStuff(void)
 +{
 +
 +    int		palette;
 +    byte*	pal;
 +    int		cnt;
 +    int		bzc;
 +
 +    cnt = plyr->damagecount;
 +
 +    if (plyr->powers[pw_strength])
 +    {
 +	// slowly fade the berzerk out
 +  	bzc = 12 - (plyr->powers[pw_strength]>>6);
 +
 +	if (bzc > cnt)
 +	    cnt = bzc;
 +    }
 +
 +    if (cnt)
 +    {
 +	palette = (cnt+7)>>3;
 +
 +	if (palette >= NUMREDPALS)
 +	    palette = NUMREDPALS-1;
 +
 +	palette += STARTREDPALS;
 +    }
 +
 +    else if (plyr->bonuscount)
 +    {
 +	palette = (plyr->bonuscount+7)>>3;
 +
 +	if (palette >= NUMBONUSPALS)
 +	    palette = NUMBONUSPALS-1;
 +
 +	palette += STARTBONUSPALS;
 +    }
 +
 +    else if ( plyr->powers[pw_ironfeet] > 4*32
 +	      || plyr->powers[pw_ironfeet]&8)
 +	palette = RADIATIONPAL;
 +    else
 +	palette = 0;
 +
++    // In Chex Quest, the player never sees red.  Instead, the
++    // radiation suit palette is used to tint the screen green,
++    // as though the player is being covered in goo by an
++    // attacking flemoid.
++
++    if (gameversion == exe_chex
++     && palette >= STARTREDPALS && palette < STARTREDPALS + NUMREDPALS)
++    {
++        palette = RADIATIONPAL;
++    }
++
 +    if (palette != st_palette)
 +    {
 +	st_palette = palette;
 +	pal = (byte *) W_CacheLumpNum (lu_palette, PU_CACHE)+palette*768;
 +	I_SetPalette (pal);
 +    }
 +
 +}
 +
 +void ST_drawWidgets(boolean refresh)
 +{
 +    int		i;
 +
 +    // used by w_arms[] widgets
 +    st_armson = st_statusbaron && !deathmatch;
 +
 +    // used by w_frags widget
 +    st_fragson = deathmatch && st_statusbaron;
 +
 +    STlib_updateNum(&w_ready, refresh);
 +
 +    for (i=0;i<4;i++)
 +    {
 +	STlib_updateNum(&w_ammo[i], refresh);
 +	STlib_updateNum(&w_maxammo[i], refresh);
 +    }
 +
 +    STlib_updatePercent(&w_health, refresh);
 +    STlib_updatePercent(&w_armor, refresh);
 +
 +    STlib_updateBinIcon(&w_armsbg, refresh);
 +
 +    for (i=0;i<6;i++)
 +	STlib_updateMultIcon(&w_arms[i], refresh);
 +
 +    STlib_updateMultIcon(&w_faces, refresh);
 +
 +    for (i=0;i<3;i++)
 +	STlib_updateMultIcon(&w_keyboxes[i], refresh);
 +
 +    STlib_updateNum(&w_frags, refresh);
 +
 +}
 +
 +void ST_doRefresh(void)
 +{
 +
 +    st_firsttime = false;
 +
 +    // draw status bar background to off-screen buff
 +    ST_refreshBackground();
 +
 +    // and refresh all widgets
 +    ST_drawWidgets(true);
 +
 +}
 +
 +void ST_diffDraw(void)
 +{
 +    // update all widgets
 +    ST_drawWidgets(false);
 +}
 +
 +void ST_Drawer (boolean fullscreen, boolean refresh)
 +{
 +
 +    st_statusbaron = (!fullscreen) || automapactive;
 +    st_firsttime = st_firsttime || refresh;
 +
 +    // Do red-/gold-shifts from damage/items
 +    ST_doPaletteStuff();
 +
 +    // If just after ST_Start(), refresh all
 +    if (st_firsttime) ST_doRefresh();
 +    // Otherwise, update as little as possible
 +    else ST_diffDraw();
 +
 +}
 +
 +typedef void (*load_callback_t)(char *lumpname, patch_t **variable);
 +
 +// Iterates through all graphics to be loaded or unloaded, along with
 +// the variable they use, invoking the specified callback function.
 +
 +static void ST_loadUnloadGraphics(load_callback_t callback)
 +{
 +
 +    int		i;
 +    int		j;
 +    int		facenum;
 +
 +    char	namebuf[9];
 +
 +    // Load the numbers, tall and short
 +    for (i=0;i<10;i++)
 +    {
 +	sprintf(namebuf, DEH_String("STTNUM%d"), i);
 +        callback(namebuf, &tallnum[i]);
 +
 +	sprintf(namebuf, DEH_String("STYSNUM%d"), i);
 +        callback(namebuf, &shortnum[i]);
 +    }
 +
 +    // Load percent key.
 +    //Note: why not load STMINUS here, too?
 +
 +    callback(DEH_String("STTPRCNT"), &tallpercent);
 +
 +    // key cards
 +    for (i=0;i<NUMCARDS;i++)
 +    {
 +	sprintf(namebuf, DEH_String("STKEYS%d"), i);
 +        callback(namebuf, &keys[i]);
 +    }
 +
 +    // arms background
 +    callback(DEH_String("STARMS"), &armsbg);
 +
 +    // arms ownership widgets
 +    for (i=0; i<6; i++)
 +    {
 +	sprintf(namebuf, DEH_String("STGNUM%d"), i+2);
 +
 +	// gray #
 +        callback(namebuf, &arms[i][0]);
 +
 +	// yellow #
 +	arms[i][1] = shortnum[i+2];
 +    }
 +
 +    // face backgrounds for different color players
 +    sprintf(namebuf, DEH_String("STFB%d"), consoleplayer);
 +    callback(namebuf, &faceback);
 +
 +    // status bar background bits
 +    callback(DEH_String("STBAR"), &sbar);
 +
 +    // face states
 +    facenum = 0;
 +    for (i=0; i<ST_NUMPAINFACES; i++)
 +    {
 +	for (j=0; j<ST_NUMSTRAIGHTFACES; j++)
 +	{
 +	    sprintf(namebuf, DEH_String("STFST%d%d"), i, j);
 +            callback(namebuf, &faces[facenum]);
 +            ++facenum;
 +	}
 +	sprintf(namebuf, DEH_String("STFTR%d0"), i);	// turn right
 +        callback(namebuf, &faces[facenum]);
 +        ++facenum;
 +	sprintf(namebuf, DEH_String("STFTL%d0"), i);	// turn left
 +        callback(namebuf, &faces[facenum]);
 +        ++facenum;
 +	sprintf(namebuf, DEH_String("STFOUCH%d"), i);	// ouch!
 +        callback(namebuf, &faces[facenum]);
 +        ++facenum;
 +	sprintf(namebuf, DEH_String("STFEVL%d"), i);	// evil grin ;)
 +        callback(namebuf, &faces[facenum]);
 +        ++facenum;
 +	sprintf(namebuf, DEH_String("STFKILL%d"), i);	// pissed off
 +        callback(namebuf, &faces[facenum]);
 +        ++facenum;
 +    }
 +
 +    callback(DEH_String("STFGOD0"), &faces[facenum]);
 +    ++facenum;
 +    callback(DEH_String("STFDEAD0"), &faces[facenum]);
 +    ++facenum;
 +}
 +
 +static void ST_loadCallback(char *lumpname, patch_t **variable)
 +{
 +    *variable = W_CacheLumpName(lumpname, PU_STATIC);
 +}
 +
 +void ST_loadGraphics(void)
 +{
 +    ST_loadUnloadGraphics(ST_loadCallback);
 +}
 +
 +void ST_loadData(void)
 +{
 +    lu_palette = W_GetNumForName (DEH_String("PLAYPAL"));
 +    ST_loadGraphics();
 +}
 +
 +static void ST_unloadCallback(char *lumpname, patch_t **variable)
 +{
 +    W_ReleaseLumpName(lumpname);
 +    *variable = NULL;
 +}
 +
 +void ST_unloadGraphics(void)
 +{
 +    ST_loadUnloadGraphics(ST_unloadCallback);
 +}
 +
 +void ST_unloadData(void)
 +{
 +    ST_unloadGraphics();
 +}
 +
 +void ST_initData(void)
 +{
 +
 +    int		i;
 +
 +    st_firsttime = true;
 +    plyr = &players[consoleplayer];
 +
 +    st_clock = 0;
 +    st_chatstate = StartChatState;
 +    st_gamestate = FirstPersonState;
 +
 +    st_statusbaron = true;
 +    st_oldchat = st_chat = false;
 +    st_cursoron = false;
 +
 +    st_faceindex = 0;
 +    st_palette = -1;
 +
 +    st_oldhealth = -1;
 +
 +    for (i=0;i<NUMWEAPONS;i++)
 +	oldweaponsowned[i] = plyr->weaponowned[i];
 +
 +    for (i=0;i<3;i++)
 +	keyboxes[i] = -1;
 +
 +    STlib_init();
 +
 +}
 +
 +
 +
 +void ST_createWidgets(void)
 +{
 +
 +    int i;
 +
 +    // ready weapon ammo
 +    STlib_initNum(&w_ready,
 +		  ST_AMMOX,
 +		  ST_AMMOY,
 +		  tallnum,
 +		  &plyr->ammo[weaponinfo[plyr->readyweapon].ammo],
 +		  &st_statusbaron,
 +		  ST_AMMOWIDTH );
 +
 +    // the last weapon type
 +    w_ready.data = plyr->readyweapon;
 +
 +    // health percentage
 +    STlib_initPercent(&w_health,
 +		      ST_HEALTHX,
 +		      ST_HEALTHY,
 +		      tallnum,
 +		      &plyr->health,
 +		      &st_statusbaron,
 +		      tallpercent);
 +
 +    // arms background
 +    STlib_initBinIcon(&w_armsbg,
 +		      ST_ARMSBGX,
 +		      ST_ARMSBGY,
 +		      armsbg,
 +		      &st_notdeathmatch,
 +		      &st_statusbaron);
 +
 +    // weapons owned
 +    for(i=0;i<6;i++)
 +    {
 +	STlib_initMultIcon(&w_arms[i],
 +			   ST_ARMSX+(i%3)*ST_ARMSXSPACE,
 +			   ST_ARMSY+(i/3)*ST_ARMSYSPACE,
 +			   arms[i], (int *) &plyr->weaponowned[i+1],
 +			   &st_armson);
 +    }
 +
 +    // frags sum
 +    STlib_initNum(&w_frags,
 +		  ST_FRAGSX,
 +		  ST_FRAGSY,
 +		  tallnum,
 +		  &st_fragscount,
 +		  &st_fragson,
 +		  ST_FRAGSWIDTH);
 +
 +    // faces
 +    STlib_initMultIcon(&w_faces,
 +		       ST_FACESX,
 +		       ST_FACESY,
 +		       faces,
 +		       &st_faceindex,
 +		       &st_statusbaron);
 +
 +    // armor percentage - should be colored later
 +    STlib_initPercent(&w_armor,
 +		      ST_ARMORX,
 +		      ST_ARMORY,
 +		      tallnum,
 +		      &plyr->armorpoints,
 +		      &st_statusbaron, tallpercent);
 +
 +    // keyboxes 0-2
 +    STlib_initMultIcon(&w_keyboxes[0],
 +		       ST_KEY0X,
 +		       ST_KEY0Y,
 +		       keys,
 +		       &keyboxes[0],
 +		       &st_statusbaron);
 +
 +    STlib_initMultIcon(&w_keyboxes[1],
 +		       ST_KEY1X,
 +		       ST_KEY1Y,
 +		       keys,
 +		       &keyboxes[1],
 +		       &st_statusbaron);
 +
 +    STlib_initMultIcon(&w_keyboxes[2],
 +		       ST_KEY2X,
 +		       ST_KEY2Y,
 +		       keys,
 +		       &keyboxes[2],
 +		       &st_statusbaron);
 +
 +    // ammo count (all four kinds)
 +    STlib_initNum(&w_ammo[0],
 +		  ST_AMMO0X,
 +		  ST_AMMO0Y,
 +		  shortnum,
 +		  &plyr->ammo[0],
 +		  &st_statusbaron,
 +		  ST_AMMO0WIDTH);
 +
 +    STlib_initNum(&w_ammo[1],
 +		  ST_AMMO1X,
 +		  ST_AMMO1Y,
 +		  shortnum,
 +		  &plyr->ammo[1],
 +		  &st_statusbaron,
 +		  ST_AMMO1WIDTH);
 +
 +    STlib_initNum(&w_ammo[2],
 +		  ST_AMMO2X,
 +		  ST_AMMO2Y,
 +		  shortnum,
 +		  &plyr->ammo[2],
 +		  &st_statusbaron,
 +		  ST_AMMO2WIDTH);
 +
 +    STlib_initNum(&w_ammo[3],
 +		  ST_AMMO3X,
 +		  ST_AMMO3Y,
 +		  shortnum,
 +		  &plyr->ammo[3],
 +		  &st_statusbaron,
 +		  ST_AMMO3WIDTH);
 +
 +    // max ammo count (all four kinds)
 +    STlib_initNum(&w_maxammo[0],
 +		  ST_MAXAMMO0X,
 +		  ST_MAXAMMO0Y,
 +		  shortnum,
 +		  &plyr->maxammo[0],
 +		  &st_statusbaron,
 +		  ST_MAXAMMO0WIDTH);
 +
 +    STlib_initNum(&w_maxammo[1],
 +		  ST_MAXAMMO1X,
 +		  ST_MAXAMMO1Y,
 +		  shortnum,
 +		  &plyr->maxammo[1],
 +		  &st_statusbaron,
 +		  ST_MAXAMMO1WIDTH);
 +
 +    STlib_initNum(&w_maxammo[2],
 +		  ST_MAXAMMO2X,
 +		  ST_MAXAMMO2Y,
 +		  shortnum,
 +		  &plyr->maxammo[2],
 +		  &st_statusbaron,
 +		  ST_MAXAMMO2WIDTH);
 +
 +    STlib_initNum(&w_maxammo[3],
 +		  ST_MAXAMMO3X,
 +		  ST_MAXAMMO3Y,
 +		  shortnum,
 +		  &plyr->maxammo[3],
 +		  &st_statusbaron,
 +		  ST_MAXAMMO3WIDTH);
 +
 +}
 +
 +static boolean	st_stopped = true;
 +
 +
 +void ST_Start (void)
 +{
 +
 +    if (!st_stopped)
 +	ST_Stop();
 +
 +    ST_initData();
 +    ST_createWidgets();
 +    st_stopped = false;
 +
 +}
 +
 +void ST_Stop (void)
 +{
 +    if (st_stopped)
 +	return;
 +
 +    I_SetPalette (W_CacheLumpNum (lu_palette, PU_CACHE));
 +
 +    st_stopped = true;
 +}
 +
 +void ST_Init (void)
 +{
 +    ST_loadData();
 +    st_backing_screen = (byte *) Z_Malloc(ST_WIDTH * ST_HEIGHT, PU_STATIC, 0);
 +}
 +