shithub: fork

Download patch

ref: aebec5673978f3b4cf6e84d7578a3bd3b5916421
parent: 7e5e8d1747f5aca37e423b4149c961b0dab930e8
author: qwx <qwx@sciops.net>
date: Mon Feb 24 13:15:06 EST 2025

add games/doom with integrated experimental patches

--- /dev/null
+++ b/sys/src/games/doom/d_main.c
@@ -1,0 +1,1136 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+//	DOOM main program (D_DoomMain) and game loop (D_DoomLoop),
+//	plus functions to determine game mode (shareware, registered),
+//	parse command line parameters, configure game parameters (turbo),
+//	and call the startup functions.
+//
+//-----------------------------------------------------------------------------
+
+
+static const char rcsid[] = "$Id: d_main.c,v 1.8 1997/02/03 22:45:09 b1 Exp $";
+
+#define	BGCOLOR		7
+#define	FGCOLOR		8
+
+#include "doomdef.h"
+#include "doomstat.h"
+
+#include "dstrings.h"
+#include "sounds.h"
+
+
+#include "z_zone.h"
+#include "w_wad.h"
+#include "s_sound.h"
+#include "v_video.h"
+
+#include "f_finale.h"
+#include "f_wipe.h"
+
+#include "m_argv.h"
+#include "m_misc.h"
+#include "m_menu.h"
+
+#include "i_system.h"
+#include "i_sound.h"
+#include "i_video.h"
+
+#include "g_game.h"
+
+#include "hu_stuff.h"
+#include "wi_stuff.h"
+#include "st_stuff.h"
+#include "am_map.h"
+
+#include "p_setup.h"
+#include "r_local.h"
+
+
+#include "d_main.h"
+
+//
+// D-DoomLoop()
+// Not a globally visible function,
+//  just included for source reference,
+//  called by D_DoomMain, never exits.
+// Manages timing and IO,
+//  calls all ?_Responder, ?_Ticker, and ?_Drawer,
+//  calls I_GetTime, I_StartFrame, and I_StartTic
+//
+void D_DoomLoop (void);
+
+
+char*		wadfiles[MAXWADFILES];
+
+
+boolean		devparm;	// started game with -devparm
+boolean         nomonsters;	// checkparm of -nomonsters
+boolean         respawnparm;	// checkparm of -respawn
+boolean         fastparm;	// checkparm of -fast
+
+boolean         drone;
+
+boolean		singletics = false; // debug flag to cancel adaptiveness
+
+/* bug compatibility with various versions of doom */
+boolean		noztele;
+boolean		nobounce;
+
+/* demo breaking bug fixes */
+boolean		noskyabs;
+
+//extern int soundVolume;
+//extern  int	sfxVolume;
+//extern  int	musicVolume;
+
+extern  boolean	inhelpscreens;
+
+skill_t		startskill;
+int             startepisode;
+int		startmap;
+boolean		autostart;
+
+FILE*		debugfile;
+
+boolean		advancedemo;
+
+
+
+
+char		wadfile[1024];		// primary wad file
+char		mapdir[1024];           // directory of development maps
+char		basedefault[1024];      // default file
+
+
+void D_CheckNetGame (void);
+void D_ProcessEvents (void);
+void G_BuildTiccmd (ticcmd_t* cmd);
+void D_DoAdvanceDemo (void);
+
+
+//
+// EVENT HANDLING
+//
+// Events are asynchronous inputs generally generated by the game user.
+// Events can be discarded if no responder claims them
+//
+event_t         events[MAXEVENTS];
+int             eventhead;
+int 		eventtail;
+QLock		eventlock;
+
+
+//
+// D_PostEvent
+// Called by the I/O functions when input is detected
+//
+void D_PostEvent (event_t* ev)
+{
+    int next;
+
+retry:
+    qlock(&eventlock);
+    next = (eventhead+1)&(MAXEVENTS-1);
+    if(next == eventtail){
+        qunlock(&eventlock);
+        if(ev->type != ev_keydown && ev->type != ev_keyup)
+            return;
+        sleep(1);
+        goto retry;
+    }
+    events[eventhead] = *ev;
+    eventhead = next;
+    qunlock(&eventlock);
+}
+
+
+//
+// D_ProcessEvents
+// Send all the events of the given timestamp down the responder chain
+//
+void D_ProcessEvents (void)
+{
+    event_t*	ev;
+	
+    // IF STORE DEMO, DO NOT ACCEPT INPUT
+    if ( ( gamemode == commercial )
+	 && (W_CheckNumForName("map01")<0) )
+      return;
+	
+    for ( ; eventtail != eventhead ; eventtail = (eventtail+1)&(MAXEVENTS-1))
+    {
+	ev = &events[eventtail];
+	if (M_Responder (ev))
+	    continue;               // menu ate the event
+	G_Responder (ev);
+    }
+}
+
+
+
+
+//
+// D_Display
+//  draw current display, possibly wiping it from the previous
+//
+
+// wipegamestate can be set to -1 to force a wipe on the next draw
+gamestate_t     wipegamestate = GS_DEMOSCREEN;
+extern  boolean setsizeneeded;
+extern  int             showMessages;
+void R_ExecuteSetViewSize (void);
+
+void D_Display (void)
+{
+    static  boolean		viewactivestate = false;
+    static  boolean		menuactivestate = false;
+    static  boolean		inhelpscreensstate = false;
+    static  boolean		fullscreen = false;
+    static  gamestate_t		oldgamestate = -1;
+    static  int			borderdrawcount;
+    int				nowtime;
+    int				tics;
+    int				wipestart;
+    int				y;
+    boolean			done;
+    boolean			wipe;
+    boolean			redrawsbar;
+
+    if (nodrawers)
+	return;                    // for comparative timing / profiling
+		
+    redrawsbar = false;
+    
+    // change the view size if needed
+    if (setsizeneeded)
+    {
+	R_ExecuteSetViewSize ();
+	oldgamestate = -1;                      // force background redraw
+	borderdrawcount = 3;
+    }
+
+    // save the current screen if about to wipe
+    if (gamestate != wipegamestate)
+    {
+	wipe = true;
+	wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);
+    }
+    else
+	wipe = false;
+
+    if (gamestate == GS_LEVEL && gametic)
+	HU_Erase();
+    
+    // do buffered drawing
+    switch (gamestate)
+    {
+      case GS_LEVEL:
+	if (!gametic)
+	    break;
+	if (automapactive)
+	    AM_Drawer ();
+	if (wipe || (viewheight != 200 && fullscreen) )
+	    redrawsbar = true;
+	if (inhelpscreensstate && !inhelpscreens)
+	    redrawsbar = true;              // just put away the help screen
+	ST_Drawer (viewheight == 200, redrawsbar );
+	fullscreen = viewheight == 200;
+	break;
+
+      case GS_INTERMISSION:
+	WI_Drawer ();
+	break;
+
+      case GS_FINALE:
+	F_Drawer ();
+	break;
+
+      case GS_DEMOSCREEN:
+	D_PageDrawer ();
+	break;
+    }
+    
+    // draw buffered stuff to screen
+    I_UpdateNoBlit ();
+    
+    // draw the view directly
+    if (gamestate == GS_LEVEL && !automapactive && gametic)
+	R_RenderPlayerView (&players[displayplayer]);
+
+    if (gamestate == GS_LEVEL && gametic)
+	HU_Drawer ();
+    
+    // clean up border stuff
+    if (gamestate != oldgamestate && gamestate != GS_LEVEL)
+	I_SetPalette (W_CacheLumpName ("PLAYPAL",PU_CACHE));
+
+    // see if the border needs to be initially drawn
+    if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL)
+    {
+	viewactivestate = false;        // view was not active
+	R_FillBackScreen ();    // draw the pattern into the back screen
+    }
+
+    // see if the border needs to be updated to the screen
+    if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != 320)
+    {
+	if (menuactive || menuactivestate || !viewactivestate)
+	    borderdrawcount = 3;
+	if (borderdrawcount)
+	{
+	    R_DrawViewBorder ();    // erase old menu stuff
+	    borderdrawcount--;
+	}
+
+    }
+
+    menuactivestate = menuactive;
+    viewactivestate = viewactive;
+    inhelpscreensstate = inhelpscreens;
+    oldgamestate = wipegamestate = gamestate;
+    
+    // draw pause pic
+    if (paused)
+    {
+	if (automapactive)
+	    y = 4;
+	else
+	    y = viewwindowy+4;
+	V_DrawPatchDirect(viewwindowx+(scaledviewwidth-68)/2,
+			  y,0,W_CacheLumpName ("M_PAUSE", PU_CACHE));
+    }
+
+
+    // menus go directly to the screen
+    M_Drawer ();          // menu is drawn even on top of everything
+    NetUpdate ();         // send out any new accumulation
+
+
+    // normal update
+    if (!wipe)
+    {
+	I_FinishUpdate ();              // page flip or blit buffer
+	I_UpdateSound ();
+	return;
+    }
+    
+    // wipe update
+    wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);
+
+    wipestart = I_GetTime () - 1;
+
+    do
+    {
+	do
+	{
+	    nowtime = I_GetTime ();
+	    tics = nowtime - wipestart;
+	} while (!tics);
+	wipestart = nowtime;
+	done = wipe_ScreenWipe(wipe_Melt
+			       , 0, 0, SCREENWIDTH, SCREENHEIGHT, tics);
+	I_UpdateNoBlit ();
+	M_Drawer ();                            // menu is drawn even on top of wipes
+	I_FinishUpdate ();                      // page flip or blit buffer
+	if (!done)
+		I_UpdateSound ();
+    } while (!done);
+}
+
+
+
+//
+//  D_DoomLoop
+//
+extern  boolean         demorecording;
+
+void D_DoomLoop (void)
+{
+    if (demorecording)
+	G_BeginRecording ();
+		
+    if (M_CheckParm ("-debugfile"))
+    {
+	char    filename[20];
+	sprintf (filename,"debug%i.txt",consoleplayer);
+	printf ("debug output to: %s\n",filename);
+	debugfile = fopen (filename,"w");
+    }
+	
+    for(;;)
+    {
+	// frame syncronous IO operations
+	// I_StartFrame ();                
+	
+	// process one or more tics
+	if (singletics)
+	{
+	    I_StartTic ();
+	    D_ProcessEvents ();
+	    G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
+	    if (advancedemo)
+		D_DoAdvanceDemo ();
+	    M_Ticker ();
+	    G_Ticker ();
+	    gametic++;
+	    maketic++;
+	}
+	else
+	{
+	    TryRunTics (); // will run at least one tic
+	}
+		
+	S_UpdateSounds (players[consoleplayer].mo);// move positional sounds
+
+	// Update display, next frame, with current state.
+	D_Display ();
+    }
+}
+
+
+
+//
+//  DEMO LOOP
+//
+int             demosequence;
+int             pagetic;
+char                    *pagename;
+
+
+//
+// D_PageTicker
+// Handles timing for warped projection
+//
+void D_PageTicker (void)
+{
+    if (--pagetic < 0)
+	D_AdvanceDemo ();
+}
+
+
+
+//
+// D_PageDrawer
+//
+void D_PageDrawer (void)
+{
+    V_DrawPatch (0,0, 0, W_CacheLumpName(pagename, PU_CACHE));
+}
+
+
+//
+// D_AdvanceDemo
+// Called after each demo or intro demosequence finishes
+//
+void D_AdvanceDemo (void)
+{
+    advancedemo = true;
+}
+
+
+//
+// This cycles through the demo sequences.
+// FIXME - version dependend demo numbers?
+//
+void D_DoAdvanceDemo (void)
+{
+    players[consoleplayer].playerstate = PST_LIVE;  // not reborn
+    advancedemo = false;
+    usergame = false;               // no save / end game here
+    paused = false;
+    gameaction = ga_nothing;
+
+    if ( gamemode == retail )
+      demosequence = (demosequence+1)%7;
+    else
+      demosequence = (demosequence+1)%6;
+    
+    switch (demosequence)
+    {
+      case 0:
+	if ( gamemode == commercial )
+	    pagetic = 35 * 11;
+	else
+	    pagetic = 170;
+	gamestate = GS_DEMOSCREEN;
+	pagename = "TITLEPIC";
+	if ( gamemode == commercial )
+	  S_StartMusic(mus_dm2ttl);
+	else
+	  S_StartMusic (mus_intro);
+	break;
+      case 1:
+	G_DeferedPlayDemo ("demo1");
+	break;
+      case 2:
+	pagetic = 200;
+	gamestate = GS_DEMOSCREEN;
+	pagename = "CREDIT";
+	break;
+      case 3:
+	G_DeferedPlayDemo ("demo2");
+	break;
+      case 4:
+	gamestate = GS_DEMOSCREEN;
+	if ( gamemode == commercial)
+	{
+	    pagetic = 35 * 11;
+	    pagename = "TITLEPIC";
+	    S_StartMusic(mus_dm2ttl);
+	}
+	else
+	{
+	    pagetic = 200;
+
+	    if ( gamemode == retail )
+	      pagename = "CREDIT";
+	    else
+	      pagename = "HELP2";
+	}
+	break;
+      case 5:
+	G_DeferedPlayDemo ("demo3");
+	break;
+        // THE DEFINITIVE DOOM Special Edition demo
+      case 6:
+	G_DeferedPlayDemo ("demo4");
+	break;
+    }
+}
+
+
+
+//
+// D_StartTitle
+//
+void D_StartTitle (void)
+{
+    gameaction = ga_nothing;
+    demosequence = -1;
+    D_AdvanceDemo ();
+}
+
+
+
+
+//      print title for every printed line
+char            title[128];
+
+
+
+//
+// D_AddFile
+//
+void D_AddFile (char *file)
+{
+	int n, ext;
+    int     numwadfiles;
+    char    *newfile, mnt[16];
+	
+	for(numwadfiles=0; wadfiles[numwadfiles]; numwadfiles++)
+		;
+
+	ext = cistrstr(file, ".wad") != nil || cistrstr(file, ".lmp") != nil;
+	n = strlen(file) + 1;
+	if(!ext)
+		n += 4;
+	if((newfile = malloc(n)) == NULL)
+		sysfatal("malloc: %r");
+	snprintf(newfile, n, "%s%s", file, ext ? "" : ".wad");
+    wadfiles[numwadfiles] = newfile;
+
+	/* all wads are piled on top of each other at /mnt/wad, iwad first;
+	 * binds are deferred to later to leave enough time for each wadfs
+	 * to properly initialize */
+	snprintf(mnt, sizeof mnt, "/mnt/wad%d", numwadfiles);
+	switch(rfork(RFPROC|RFFDG)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		close(2);
+		execl("/bin/games/wadfs", "wadfs", "-m", mnt, newfile, nil);
+		sysfatal("execl: %r");
+	}
+}
+
+
+//
+// IdentifyVersion
+// Checks availability of IWAD files by name,
+// to determine whether registered/commercial features
+// should be executed (notably loading PWAD's).
+//
+void IdentifyVersion (void)
+{
+	int p;
+	char *wadfile, *slash;
+
+	if (M_CheckParm ("-shdev"))
+	{
+		I_Error("PORTME d_main.c IdentifyVersion -shdev");
+/*
+		gamemode = shareware;
+		devparm = true;
+		D_AddFile (DEVDATA"doom1.wad");
+		D_AddFile (DEVMAPS"data_se/texture1.lmp");
+		D_AddFile (DEVMAPS"data_se/pnames.lmp");
+		strcpy (basedefault,DEVDATA"default.cfg");
+		return;
+*/
+	}
+
+	if (M_CheckParm ("-regdev"))
+	{
+		I_Error("PORTME d_main.c IdentifyVersion -regdev");
+/*
+		gamemode = registered;
+		devparm = true;
+		D_AddFile (DEVDATA"doom.wad");
+		D_AddFile (DEVMAPS"data_se/texture1.lmp");
+		D_AddFile (DEVMAPS"data_se/texture2.lmp");
+		D_AddFile (DEVMAPS"data_se/pnames.lmp");
+		strcpy (basedefault,DEVDATA"default.cfg");
+		return;
+*/
+	}
+
+	if (M_CheckParm ("-comdev"))
+	{
+		I_Error("PORTME d_main.c IdentifyVersion -comdev");
+/*
+		gamemode = commercial;
+		devparm = true;
+		D_AddFile (DEVDATA"doom2.wad");
+		D_AddFile (DEVMAPS"cdata/texture1.lmp");
+		D_AddFile (DEVMAPS"cdata/pnames.lmp");
+		strcpy (basedefault,DEVDATA"default.cfg");
+		return;
+*/
+	}
+
+	p = M_CheckParm ("-iwad");
+    if (p && p < myargc-1 && (wadfile = I_IdentifyWAD(myargv[p+1])) )
+    {
+ 		p = M_CheckParm ("-gamemode");
+ 		gamemode = commercial;
+		if (p && p < myargc-1)
+		{
+			if (!strcmp(myargv[p+1], "ultimate"))
+				gamemode = retail;
+			else if (!strcmp(myargv[p+1], "registered"))
+				gamemode = registered;
+			else if (!strcmp(myargv[p+1], "shareware"))
+				gamemode = shareware;
+		}
+		D_AddFile (wadfile);
+    } else if ( (wadfile = I_IdentifyWAD("doom2f.wad")) ) {
+		gamemode = commercial;
+		// C'est ridicule!
+		// Let's handle languages in config files, okay?
+		language = french;
+		printf("French version\n");
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("doom2.wad")) ) {
+		gamemode = commercial;
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("plutonia.wad")) ) {
+		gamemode = commercial;
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("tnt.wad")) ) {
+		gamemode = commercial;
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("doomu.wad")) ) {
+		gamemode = retail;
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("doom.wad")) ) {
+		gamemode = registered;
+		D_AddFile (wadfile);
+	} else if ( (wadfile = I_IdentifyWAD("doom1.wad")) ) {
+		gamemode = shareware;
+		D_AddFile (wadfile);
+	} else {
+		printf("Game mode indeterminate.\n");
+		gamemode = indetermined;
+		return;
+	}
+	strncpy(basedefault, wadfile, sizeof(basedefault)-5);
+	basedefault[sizeof(basedefault)-5] = '\0';
+	slash = strrchr(basedefault, '/');
+	if (slash++ == 0)
+		slash = basedefault;
+	strcpy(slash, "cfg");
+}
+
+//
+// Find a Response File
+//
+void FindResponseFile (void)
+{
+    int             i;
+#define MAXARGVS        100
+	
+    for (i = 1;i < myargc;i++)
+	if (myargv[i][0] == '@')
+	{
+	    FILE *          handle;
+	    int             size;
+	    int             k;
+	    int             index;
+	    int             indexinfile;
+	    char    *infile;
+	    char    *file;
+	    char    *moreargs[20];
+	    char    *firstargv;
+			
+	    // READ THE RESPONSE FILE INTO MEMORY
+	    handle = fopen (&myargv[i][1],"rb");
+	    if (!handle)
+	    {
+		fprint(2, "No such response file!\n");
+		exits("open");
+	    }
+	    printf("Found response file %s!\n",&myargv[i][1]);
+	    fseek (handle,0,SEEK_END);
+	    size = ftell(handle);
+	    fseek (handle,0,SEEK_SET);
+	    file = malloc (size);
+	    fread (file,size,1,handle);
+	    fclose (handle);
+			
+	    // KEEP ALL CMDLINE ARGS FOLLOWING @RESPONSEFILE ARG
+	    for (index = 0,k = i+1; k < myargc; k++)
+		moreargs[index++] = myargv[k];
+			
+	    firstargv = myargv[0];
+	    myargv = malloc(sizeof(char *)*MAXARGVS);
+	    memset(myargv,0,sizeof(char *)*MAXARGVS);
+	    myargv[0] = firstargv;
+			
+	    infile = file;
+	    indexinfile = k = 0;
+	    indexinfile++;  // SKIP PAST ARGV[0] (KEEP IT)
+	    do
+	    {
+		myargv[indexinfile++] = infile+k;
+		while(k < size &&
+		      ((*(infile+k)>= ' '+1) && (*(infile+k)<='z')))
+		    k++;
+		*(infile+k) = 0;
+		while(k < size &&
+		      ((*(infile+k)<= ' ') || (*(infile+k)>'z')))
+		    k++;
+	    } while(k < size);
+			
+	    for (k = 0;k < index;k++)
+		myargv[indexinfile++] = moreargs[k];
+	    myargc = indexinfile;
+	
+	    // DISPLAY ARGS
+	    printf("%d command-line args:\n",myargc);
+	    for (k=1;k<myargc;k++)
+		printf("%s\n",myargv[k]);
+
+	    break;
+	}
+}
+
+
+//
+// D_DoomMain
+//
+void D_DoomMain (void)
+{
+    int		i, p;
+    char	file[256], mnt[16];
+
+	rfork(RFNAMEG);
+
+    FindResponseFile ();
+	
+    IdentifyVersion ();
+	
+    setbuf (stdout, NULL);
+    modifiedgame = false;
+	
+    nomonsters = M_CheckParm ("-nomonsters");
+    respawnparm = M_CheckParm ("-respawn");
+    fastparm = M_CheckParm ("-fast");
+    devparm = M_CheckParm ("-devparm");
+    if (M_CheckParm ("-noztele") && gamemode == commercial)
+	noztele = 1;
+    if (M_CheckParm ("-nobounce") && (gamemode == commercial || gamemode == registered))
+	nobounce = 1;
+    if (M_CheckParm ("-noskyabs"))
+	noskyabs = 1;
+    if (M_CheckParm ("-altdeath"))
+	deathmatch = 2;
+    else if (M_CheckParm ("-deathmatch"))
+	deathmatch = 1;
+
+    switch ( gamemode )
+    {
+      case retail:
+	sprintf (title,
+		 "                         "
+		 "The Ultimate DOOM Startup v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+      case shareware:
+	sprintf (title,
+		 "                            "
+		 "DOOM Shareware Startup v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+      case registered:
+	sprintf (title,
+		 "                            "
+		 "DOOM Registered Startup v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+      case commercial:
+	sprintf (title,
+		 "                         "
+		 "DOOM 2: Hell on Earth v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+/*FIXME
+       case pack_plut:
+	sprintf (title,
+		 "                   "
+		 "DOOM 2: Plutonia Experiment v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+      case pack_tnt:
+	sprintf (title,
+		 "                     "
+		 "DOOM 2: TNT - Evilution v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+*/
+      default:
+	sprintf (title,
+		 "                     "
+		 "Public DOOM - v%i.%i"
+		 "                           ",
+		 VERSION/100,VERSION%100);
+	break;
+    }
+    
+    printf ("%s\n",title);
+
+    if (devparm)
+	printf(D_DEVSTR);
+    
+    // turbo option
+    if ( (p=M_CheckParm ("-turbo")) )
+    {
+	int     scale = 200;
+	extern int forwardmove[2];
+	extern int sidemove[2];
+	
+	if (p<myargc-1)
+	    scale = atoi (myargv[p+1]);
+	if (scale < 10)
+	    scale = 10;
+	if (scale > 400)
+	    scale = 400;
+	printf ("turbo scale: %i%%\n",scale);
+	forwardmove[0] = forwardmove[0]*scale/100;
+	forwardmove[1] = forwardmove[1]*scale/100;
+	sidemove[0] = sidemove[0]*scale/100;
+	sidemove[1] = sidemove[1]*scale/100;
+    }
+    
+    // add any files specified on the command line with -file wadfile
+    // to the wad list
+    //
+    // convenience hack to allow -wart e m to add a wad file
+    // prepend a tilde to the filename so wadfile will be reloadable
+    p = M_CheckParm ("-wart");
+    if (p)
+    {
+	myargv[p][4] = 'p';     // big hack, change to -warp
+
+	// Map name handling.
+	switch (gamemode )
+	{
+	  case shareware:
+	  case retail:
+	  case registered:
+	    sprintf (file,"~"DEVMAPS"E%cM%c.wad",
+		     myargv[p+1][0], myargv[p+2][0]);
+	    printf("Warping to Episode %s, Map %s.\n",
+		   myargv[p+1],myargv[p+2]);
+	    break;
+	    
+	  case commercial:
+	  default:
+	    p = atoi (myargv[p+1]);
+	    if (p<10)
+	      sprintf (file,"~"DEVMAPS"cdata/map0%i.wad", p);
+	    else
+	      sprintf (file,"~"DEVMAPS"cdata/map%i.wad", p);
+	    break;
+	}
+	D_AddFile (file);
+    }
+	
+    p = M_CheckParm ("-file");
+    if (p)
+    {
+	// the parms after p are wadfile/lump names,
+	// until end of parms or another - preceded parm
+	modifiedgame = true;            // homebrew levels
+	while (++p != myargc && myargv[p][0] != '-')
+	    D_AddFile (myargv[p]);
+    }
+
+    p = M_CheckParm ("-playdemo");
+
+    if (!p)
+	p = M_CheckParm ("-timedemo");
+
+    if (p && p < myargc-1)
+    {
+	sprintf (file,"%s.lmp", myargv[p+1]);
+	D_AddFile (file);
+	printf("Playing demo %s.lmp.\n",myargv[p+1]);
+    }
+    
+    // get skill / episode / map from parms
+    startskill = sk_medium;
+    startepisode = 1;
+    startmap = 1;
+    autostart = false;
+
+		
+    p = M_CheckParm ("-skill");
+    if (p && p < myargc-1)
+    {
+	startskill = myargv[p+1][0]-'1';
+	autostart = true;
+    }
+
+    p = M_CheckParm ("-episode");
+    if (p && p < myargc-1)
+    {
+	startepisode = myargv[p+1][0]-'0';
+	startmap = 1;
+	autostart = true;
+    }
+	
+    p = M_CheckParm ("-timer");
+    if (p && p < myargc-1 && deathmatch)
+    {
+	int     time;
+	time = atoi(myargv[p+1]);
+	printf("Levels will end after %d minute",time);
+	if (time>1)
+	    printf("s");
+	printf(".\n");
+    }
+
+    p = M_CheckParm ("-avg");
+    if (p && p < myargc-1 && deathmatch)
+	printf("Austin Virtual Gaming: Levels will end after 20 minutes\n");
+
+    p = M_CheckParm ("-warp");
+    if (p && p < myargc-1)
+    {
+	startmap = atoi (myargv[p+1]);
+	if (gamemode != commercial){
+            startepisode = startmap / 10;
+            startmap %= 10;
+        }
+	autostart = true;
+    }
+    
+    // init subsystems
+    printf ("V_Init: allocate screens.\n");
+    V_Init ();
+
+    printf ("M_LoadDefaults: Load system defaults.\n");
+    M_LoadDefaults ();              // load before initing other systems
+
+    printf ("Z_Init: Init zone memory allocation daemon. \n");
+    Z_Init ();
+
+    printf ("W_Init: Init WADfiles.\n");
+    W_InitMultipleFiles (wadfiles);
+    
+
+    // Check for -file in shareware
+    if (modifiedgame)
+    {
+	// These are the lumps that will be checked in IWAD,
+	// if any one is not present, execution will be aborted.
+	char name[23][8]=
+	{
+	    "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9",
+	    "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9",
+	    "dphoof","bfgga0","heada1","cybra1","spida1d1"
+	};
+	int i;
+	
+	if ( gamemode == shareware)
+	    I_Error("\nYou cannot -file with the shareware "
+		    "version. Register!");
+
+	// Check for fake IWAD with right name,
+	// but w/o all the lumps of the registered version. 
+	if (gamemode == registered)
+	    for (i = 0;i < 23; i++)
+		if (W_CheckNumForName(name[i])<0)
+		    I_Error("\nThis is not the registered version.");
+    }
+    
+    // Iff additonal PWAD files are used, print modified banner
+    if (modifiedgame)
+    {
+	/*m*/printf (
+	    "===========================================================================\n"
+	    "ATTENTION:  This version of DOOM has been modified.  If you would like to\n"
+	    "get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n"
+	    "        You will not receive technical support for modified games.\n"
+	    "                      press enter to continue\n"
+	    "===========================================================================\n"
+	    );
+	USED( getchar () );
+    }
+	
+
+    // Check and print which version is executed.
+    switch ( gamemode )
+    {
+      case shareware:
+      case indetermined:
+	printf (
+	    "===========================================================================\n"
+	    "                                Shareware!\n"
+	    "===========================================================================\n"
+	);
+	break;
+      case registered:
+      case retail:
+      case commercial:
+	printf (
+	    "===========================================================================\n"
+	    "                 Commercial product - do not distribute!\n"
+	    "         Please report software piracy to the SPA: 1-800-388-PIR8\n"
+	    "===========================================================================\n"
+	);
+	break;
+	
+      default:
+	// Ouch.
+	break;
+    }
+
+    printf ("M_Init: Init miscellaneous info.\n");
+    M_Init ();
+
+    printf ("R_Init: Init DOOM refresh daemon - ");
+    R_Init ();
+
+    printf ("\nP_Init: Init Playloop state.\n");
+    P_Init ();
+
+    printf ("I_Init: Setting up machine state.\n");
+    I_Init ();
+
+    printf ("D_CheckNetGame: Checking network game status.\n");
+    D_CheckNetGame ();
+
+    printf ("S_Init: Setting up sound.\n");
+    S_Init (snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/ );
+
+    printf ("HU_Init: Setting up heads up display.\n");
+    HU_Init ();
+
+    printf ("ST_Init: Init status bar.\n");
+    ST_Init ();
+
+    // check for a driver that wants intermission stats
+    p = M_CheckParm ("-statcopy");
+    if (p && p<myargc-1)
+    {
+	// for statistics driver
+	extern  void*	statcopy;                            
+
+	statcopy = (void*)atoi(myargv[p+1]);
+	printf ("External statistics registered.\n");
+    }
+    
+    // start the apropriate game based on parms
+    p = M_CheckParm ("-record");
+
+    if (p && p < myargc-1)
+    {
+	G_RecordDemo (myargv[p+1]);
+	autostart = true;
+    }
+	
+	for(i=0; wadfiles[i]; i++){
+		snprintf(mnt, sizeof mnt, "/mnt/wad%d", i);
+		if(bind(mnt, "/mnt/wad", MBEFORE) < 0)
+			sysfatal("bind: %r");
+	}
+    
+    p = M_CheckParm ("-playdemo");
+    if (p && p < myargc-1)
+    {
+	singledemo = true;              // quit after one demo
+	G_DeferedPlayDemo (myargv[p+1]);
+	D_DoomLoop ();  // never returns
+    }
+	
+    p = M_CheckParm ("-timedemo");
+    if (p && p < myargc-1)
+    {
+	G_TimeDemo (myargv[p+1]);
+	D_DoomLoop ();  // never returns
+    }
+	
+    p = M_CheckParm ("-loadgame");
+    if (p && p < myargc-1)
+    {
+	sprintf(file, SAVEGAMENAME"%c.dsg",myargv[p+1][0]);
+	G_LoadGame (file);
+    }
+	
+
+    if ( gameaction != ga_loadgame )
+    {
+	if (autostart || netgame)
+	    G_InitNew (startskill, startepisode, startmap);
+	else
+	    D_StartTitle ();                // start up intro loop
+
+    }
+
+    D_DoomLoop ();  // never returns
+}
--- /dev/null
+++ b/sys/src/games/doom/g_game.c
@@ -1,0 +1,1623 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:  none
+//
+//-----------------------------------------------------------------------------
+
+
+static const char
+rcsid[] = "$Id: g_game.c,v 1.8 1997/02/03 22:45:09 b1 Exp $";
+
+#include "doomdef.h" 
+#include "doomstat.h"
+
+#include "z_zone.h"
+#include "f_finale.h"
+#include "m_argv.h"
+#include "m_misc.h"
+#include "m_menu.h"
+#include "m_random.h"
+#include "i_system.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	0x200000
+#define SAVESTRINGSIZE	24
+
+
+
+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); 
+ 
+ 
+gameaction_t    gameaction; 
+gamestate_t     gamestate; 
+skill_t         gameskill; 
+boolean		respawnmonsters;
+int             gameepisode; 
+int             gamemap; 
+ 
+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 
+boolean         noblit;                 // 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]; 
+ 
+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         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 
+ 
+wbstartstruct_t wminfo;               	// parms for world map / intermission 
+ 
+short		consistancy[MAXPLAYERS][BACKUPTICS]; 
+ 
+byte*		savebuffer;
+ 
+ 
+// 
+// controls (have defaults) 
+// 
+int             key_right;
+int		key_left;
+
+int		key_up;
+int		key_down; 
+int             key_strafeleft;
+int		key_straferight; 
+int             key_fire;
+int		key_use;
+int		key_strafe;
+int		key_speed; 
+int		autorun;
+ 
+int             mousebfire; 
+int             mousebstrafe; 
+int             mousebuse; 
+int             m_forward;
+ 
+int             joybfire; 
+int             joybstrafe; 
+int             joybuse; 
+int             joybspeed; 
+ 
+ 
+ 
+#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 
+
+#define SLOWTURNTICS	6 
+ 
+#define NUMKEYS		256 
+
+boolean         gamekeydown[NUMKEYS]; 
+int             turnheld;				// for accelerative turning 
+ 
+boolean		mousearray[4]; 
+boolean*	mousebuttons = &mousearray[1];		// allow [-1]
+
+// mouse values are used once 
+int             mousex;
+int		mousey;         
+extern int usemouse;
+
+// joystick values are repeated 
+int             joyxmove;
+int		joyymove;
+boolean         joyarray[5]; 
+boolean*	joybuttons = &joyarray[1];		// allow [-1] 
+ 
+int		savegameslot; 
+char		savedescription[32]; 
+ 
+ 
+#define	BODYQUESIZE	32
+
+mobj_t*		bodyque[BODYQUESIZE]; 
+int		bodyqueslot; 
+ 
+void*		statcopy;				// for statistics driver
+ 
+ 
+ 
+int G_CmdChecksum (ticcmd_t* cmd) 
+{ 
+    int		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;
+    int		speed;
+    int		tspeed; 
+    int		forward;
+    int		side;
+    
+    ticcmd_t*	base;
+
+    base = I_BaseTiccmd ();		// empty, or external driver
+    memcpy (cmd,base,sizeof(*cmd)); 
+	
+    cmd->consistancy = 
+	consistancy[consoleplayer][maketic%BACKUPTICS]; 
+
+ 
+    strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] 
+	|| joybuttons[joybstrafe]; 
+    speed = autorun || 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_straferight]) 
+	side += sidemove[speed]; 
+    if (gamekeydown[key_strafeleft]) 
+	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] ) 
+	cmd->buttons |= BT_USE;
+
+    // chainsaw overrides 
+    for (i=0 ; i<NUMWEAPONS-1 ; i++)        
+	if (gamekeydown['1'+i]) 
+	{ 
+	    cmd->buttons |= BT_CHANGE; 
+	    cmd->buttons |= i<<BT_WEAPONSHIFT; 
+	    break; 
+	}
+    
+    // mouse
+    if (mousebuttons[mousebuse])
+	    cmd->buttons |= BT_USE; 
+    if (m_forward)
+        forward += mousey; 
+    if (strafe) 
+	side += mousex*2; 
+    else 
+	cmd->angleturn -= mousex*0x8; 
+
+    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); 
+    } 
+} 
+ 
+
+//
+// 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 ( SKYFLATNAME );
+
+    // DOOM determines the sky texture to be used
+    // depending on the current episode, and the game version.
+    if ( (gamemode == commercial)
+	 || ( gamemode == pack_tnt )
+	 || ( gamemode == pack_plut ) )
+    {
+	skytexture = R_TextureNumForName ("SKY3");
+	if (gamemap < 12)
+	    skytexture = R_TextureNumForName ("SKY1");
+	else
+	    if (gamemap < 21)
+		skytexture = R_TextureNumForName ("SKY2");
+    }
+
+    levelstarttic = gametic;        // for time calculation
+    
+    if (wipegamestate == GS_LEVEL) 
+	wipegamestate = -1;             // force a wipe 
+
+    gamestate = GS_LEVEL; 
+
+    for (i=0 ; i<MAXPLAYERS ; i++) 
+    { 
+	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);
+    displayplayer = consoleplayer;		// view the guy you are playing    
+    starttime = I_GetTime (); 
+    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)); 
+} 
+ 
+ 
+//
+// 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; 
+		/* FIXME */
+		extern player_t *plyr;
+		plyr = &players[displayplayer];
+	} 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 (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 
+    } 
+	 
+    switch (ev->type) 
+    { 
+      case ev_keydown: 
+	if (ev->data1 == KEY_PAUSE) 
+	{ 
+	    sendpause = true; 
+	    return true; 
+	} 
+	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: 
+	if (!usemouse)
+	    return true;
+	mousebuttons[0] = ev->data1 & 1; 
+	mousebuttons[1] = ev->data1 & 2; 
+	mousebuttons[2] = ev->data1 & 4; 
+	mousex += ev->data2*0.5*(mouseSensitivity+1); 
+	mousey += ev->data3*0.5*(mouseSensitivity+1); 
+	return true;    // eat events 
+ 
+      case ev_joystick: 
+	joybuttons[0] = ev->data1 & 1; 
+	joybuttons[1] = ev->data1 & 2; 
+	joybuttons[2] = ev->data1 & 4; 
+	joybuttons[3] = ev->data1 & 8; 
+	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: 
+	    M_ScreenShot (); 
+	    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
+	    if (cmd->forwardmove > TURBOTHRESHOLD 
+		&& !(gametic&31) && ((gametic>>5)&3) == i )
+	    {
+		static char turbomessage[80];
+		extern char *player_names[4];
+		sprintf (turbomessage, "%s is turbo!",player_names[i]);
+		players[consoleplayer].message = turbomessage;
+	    }
+			
+	    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; 
+		} 
+	    } 
+	}
+    }
+    
+    // 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) 
+{
+    // 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 = MAXHEALTH; 
+    p->readyweapon = p->pendingweapon = wp_pistol; 
+    p->weaponowned[wp_fist] = true; 
+    p->weaponowned[wp_pistol] = true; 
+    p->ammo[am_clip] = 50; 
+	 
+    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)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)
+	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 = 35*cpars[gamemap-1]; 
+    else
+	wminfo.partime = 35*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; 
+ 
+    if (statcopy)
+	memcpy (statcopy, &wminfo, sizeof(wminfo));
+	
+    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		i; 
+    int		a,b,c; 
+    char	vcheck[VERSIONSIZE]; 
+	 
+    gameaction = ga_nothing; 
+	 
+    M_ReadFile (savename, &savebuffer); 
+    save_p = savebuffer + SAVESTRINGSIZE;
+    
+    // skip the description field 
+    memset (vcheck,0,sizeof(vcheck)); 
+    sprintf (vcheck,"version %i",VERSION); 
+    if (strcmp ((char*)save_p, vcheck)) 
+	return;				// bad version 
+    save_p += VERSIONSIZE; 
+			 
+    gameskill = *save_p++; 
+    gameepisode = *save_p++; 
+    gamemap = *save_p++; 
+    for (i=0 ; i<MAXPLAYERS ; i++) 
+	playeringame[i] = *save_p++; 
+
+    // load a base level 
+    G_InitNew (gameskill, gameepisode, gamemap); 
+ 
+    // get the times 
+    a = *save_p++; 
+    b = *save_p++; 
+    c = *save_p++; 
+    leveltime = (a<<16) + (b<<8) + c; 
+	 
+    // dearchive all the modifications
+    P_UnArchivePlayers (); 
+    P_UnArchiveWorld (); 
+    P_UnArchiveThinkers (); 
+    P_UnArchiveSpecials (); 
+ 
+    if (*save_p != 0x1d) 
+	I_Error ("Bad savegame");
+    
+    // done 
+    Z_Free (savebuffer); 
+ 
+    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	name[256]; 
+    char	name2[VERSIONSIZE]; 
+    char*	description; 
+    int		length; 
+    int		i; 
+	
+    sprintf (name,SAVEGAMENAME"%d.dsg",savegameslot); 
+    description = savedescription; 
+	 
+    savebuffer = Z_Malloc(SAVEGAMESIZE, PU_STATIC, 0);
+    save_p = savebuffer;
+	 
+    memcpy (save_p, description, SAVESTRINGSIZE); 
+    save_p += SAVESTRINGSIZE; 
+    memset (name2,0,sizeof(name2)); 
+    sprintf (name2,"version %i",VERSION); 
+    memcpy (save_p, name2, VERSIONSIZE); 
+    save_p += VERSIONSIZE; 
+	 
+    *save_p++ = gameskill; 
+    *save_p++ = gameepisode; 
+    *save_p++ = gamemap; 
+    for (i=0 ; i<MAXPLAYERS ; i++) 
+	*save_p++ = playeringame[i]; 
+    *save_p++ = leveltime>>16; 
+    *save_p++ = leveltime>>8; 
+    *save_p++ = leveltime; 
+ 
+    P_ArchivePlayers (); 
+    P_ArchiveWorld (); 
+    P_ArchiveThinkers (); 
+    P_ArchiveSpecials (); 
+	 
+    *save_p++ = 0x1d;		// consistancy marker 
+	 
+    length = save_p - savebuffer; 
+    if (length > SAVEGAMESIZE) 
+	I_Error ("Savegame buffer overrun"); 
+    M_WriteFile (name, savebuffer, length); 
+    gameaction = ga_nothing; 
+    savedescription[0] = 0;		 
+	 
+    players[consoleplayer].message = GGSAVED; 
+
+    // draw the pattern into the back screen
+    R_FillBackScreen ();	
+    Z_Free(savebuffer);	
+} 
+ 
+
+//
+// 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 ) 
+{ 
+    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; 
+ 
+    
+    // set the sky map for the episode
+    if ( gamemode == commercial)
+    {
+	skytexture = R_TextureNumForName ("SKY3");
+	if (gamemap < 12)
+	    skytexture = R_TextureNumForName ("SKY1");
+	else
+	    if (gamemap < 21)
+		skytexture = R_TextureNumForName ("SKY2");
+    }
+    else
+	switch (episode) 
+	{ 
+	  case 1: 
+	    skytexture = R_TextureNumForName ("SKY1"); 
+	    break; 
+	  case 2: 
+	    skytexture = R_TextureNumForName ("SKY2"); 
+	    break; 
+	  case 3: 
+	    skytexture = R_TextureNumForName ("SKY3"); 
+	    break; 
+	  case 4:	// Special Edition sky
+	    skytexture = R_TextureNumForName ("SKY4");
+	    break;
+	} 
+ 
+    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++); 
+    cmd->angleturn = ((unsigned char)*demo_p++)<<8; 
+    cmd->buttons = (unsigned char)*demo_p++; 
+} 
+
+
+void G_WriteDemoTiccmd (ticcmd_t* cmd) 
+{ 
+    if (gamekeydown['q'])           // press q to end demo recording 
+	G_CheckDemoStatus (); 
+    *demo_p++ = cmd->forwardmove; 
+    *demo_p++ = cmd->sidemove; 
+    *demo_p++ = (cmd->angleturn+128)>>8; 
+    *demo_p++ = cmd->buttons; 
+    demo_p -= 4; 
+    if (demo_p > demoend - 16)
+    {
+	// no more space 
+	G_CheckDemoStatus (); 
+	return; 
+    } 
+	
+    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;
+    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; 
+		
+    demo_p = demobuffer;
+	
+    *demo_p++ = 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; 
+} 
+ 
+void G_DoPlayDemo (void) 
+{ 
+    skill_t skill; 
+    int             i, episode, map; 
+	 
+    gameaction = ga_nothing; 
+    demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC); 
+    switch (*demo_p) {
+    case VERSION:
+        break;
+    default:
+        fprintf(stderr, "Demo is from a different game version!\n");
+        fprintf(stderr, "VERSION=%d demover=%d\n", VERSION, *demo_p);
+        break;
+/*
+        gameaction = ga_nothing;
+        return;
+*/
+    }
+    demo_p++;
+    skill = *demo_p++; 
+    episode = *demo_p++; 
+    map = *demo_p++; 
+    deathmatch = *demo_p++;
+    respawnparm = *demo_p++;
+    fastparm = *demo_p++;
+    nomonsters = *demo_p++;
+    consoleplayer = *demo_p++;
+
+    // PORTME TODO : MAXPLAYERS assumes 4 in demo format
+    for (i=0 ; i<MAXPLAYERS ; i++) 
+	playeringame[i] = *demo_p++; 
+    if (playeringame[1]) 
+    { 
+	netgame = true; 
+	netdemo = true; 
+    }
+
+    // don't spend a lot of time in loadlevel 
+    precache = false;
+    G_InitNew (skill, episode, map); 
+    precache = true; 
+
+    usergame = false; 
+    demoplayback = true; 
+}
+
+//
+// G_TimeDemo 
+//
+void G_TimeDemo (char* name) 
+{ 	 
+    nodrawers = M_CheckParm ("-nodraw"); 
+    noblit = M_CheckParm ("-noblit"); 
+    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) 
+    { 
+	endtime = I_GetTime (); 
+	I_Error ("timed %i gametics in %i realtics",gametic 
+		 , endtime-starttime); 
+    } 
+	 
+    if (demoplayback) 
+    { 
+	if (singledemo) 
+	    I_Quit (); 
+			 
+	Z_ChangeTag (demobuffer, PU_CACHE); 
+	demoplayback = false; 
+	netdemo = false;
+	netgame = false;
+	deathmatch = false;
+	playeringame[1] = playeringame[2] = playeringame[3] = 0;
+	respawnparm = false;
+	fastparm = false;
+	nomonsters = false;
+	consoleplayer = 0;
+	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; 
+} 
+ 
+ 
+ 
--- /dev/null
+++ b/sys/src/games/doom/hu_lib.c
@@ -1,0 +1,354 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:  heads-up text and input code
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: hu_lib.c,v 1.3 1997/01/26 07:44:58 b1 Exp $";
+
+#include <ctype.h>
+
+#include "doomdef.h"
+
+#include "v_video.h"
+#include "m_swap.h"
+
+#include "hu_lib.h"
+#include "r_local.h"
+#include "r_draw.h"
+
+// boolean : whether the screen is always erased
+#define noterased viewwindowx
+
+extern boolean	automapactive;	// in AM_map.c
+
+void HUlib_init(void)
+{
+}
+
+void HUlib_clearTextLine(hu_textline_t* t)
+{
+    t->len = 0;
+    t->l[0] = 0;
+    t->needsupdate = true;
+}
+
+void
+HUlib_initTextLine
+( hu_textline_t*	t,
+  int			x,
+  int			y,
+  patch_t**		f,
+  int			sc )
+{
+    t->x = x;
+    t->y = y;
+    t->f = f;
+    t->sc = sc;
+    HUlib_clearTextLine(t);
+}
+
+boolean
+HUlib_addCharToTextLine
+( hu_textline_t*	t,
+  char			ch )
+{
+
+    if (t->len == HU_MAXLINELENGTH)
+	return false;
+    else
+    {
+	t->l[t->len++] = ch;
+	t->l[t->len] = 0;
+	t->needsupdate = 4;
+	return true;
+    }
+
+}
+
+boolean HUlib_delCharFromTextLine(hu_textline_t* t)
+{
+
+    if (!t->len) return false;
+    else
+    {
+	t->l[--t->len] = 0;
+	t->needsupdate = 4;
+	return true;
+    }
+
+}
+
+void
+HUlib_drawTextLine
+( hu_textline_t*	l,
+  boolean		drawcursor )
+{
+
+    int			i;
+    int			w;
+    int			x;
+    unsigned char	c;
+
+    // draw the new stuff
+    x = l->x;
+    for (i=0;i<l->len;i++)
+    {
+	c = toupper(l->l[i]);
+	if (c != ' '
+	    && c >= l->sc
+	    && c <= '_')
+	{
+	    w = SHORT(l->f[c - l->sc]->width);
+	    if (x+w > SCREENWIDTH)
+		break;
+	    V_DrawPatchDirect(x, l->y, FG, l->f[c - l->sc]);
+	    x += w;
+	}
+	else
+	{
+	    x += 4;
+	    if (x >= SCREENWIDTH)
+		break;
+	}
+    }
+
+    // draw the cursor if requested
+    if (drawcursor
+	&& x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH)
+    {
+	V_DrawPatchDirect(x, l->y, FG, l->f['_' - l->sc]);
+    }
+}
+
+
+// sorta called by HU_Erase and just better darn get things straight
+void HUlib_eraseTextLine(hu_textline_t* l)
+{
+    int			lh;
+    int			y;
+    int			yoffset;
+    static boolean	lastautomapactive = true;
+
+    // Only erases when NOT in automap and the screen is reduced,
+    // and the text must either need updating or refreshing
+    // (because of a recent change back from the automap)
+
+    if (!automapactive &&
+	viewwindowx && l->needsupdate)
+    {
+	lh = SHORT(l->f[0]->height) + 1;
+	for (y=l->y,yoffset=y*SCREENWIDTH ; y<l->y+lh ; y++,yoffset+=SCREENWIDTH)
+	{
+	    if (y < viewwindowy || y >= viewwindowy + viewheight)
+		R_VideoErase(yoffset, SCREENWIDTH); // erase entire line
+	    else
+	    {
+		R_VideoErase(yoffset, viewwindowx); // erase left border
+		R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx);
+		// erase right border
+	    }
+	}
+    }
+
+    lastautomapactive = automapactive;
+    if (l->needsupdate) l->needsupdate--;
+
+}
+
+void
+HUlib_initSText
+( hu_stext_t*	s,
+  int		x,
+  int		y,
+  int		h,
+  patch_t**	font,
+  int		startchar,
+  boolean*	on )
+{
+
+    int i;
+
+    s->h = h;
+    s->on = on;
+    s->laston = true;
+    s->cl = 0;
+    for (i=0;i<h;i++)
+	HUlib_initTextLine(&s->l[i],
+			   x, y - i*(SHORT(font[0]->height)+1),
+			   font, startchar);
+
+}
+
+void HUlib_addLineToSText(hu_stext_t* s)
+{
+
+    int i;
+
+    // add a clear line
+    if (++s->cl == s->h)
+	s->cl = 0;
+    HUlib_clearTextLine(&s->l[s->cl]);
+
+    // everything needs updating
+    for (i=0 ; i<s->h ; i++)
+	s->l[i].needsupdate = 4;
+
+}
+
+void
+HUlib_addMessageToSText
+( hu_stext_t*	s,
+  char*		prefix,
+  char*		msg )
+{
+    HUlib_addLineToSText(s);
+    if (prefix)
+	while (*prefix)
+	    HUlib_addCharToTextLine(&s->l[s->cl], *(prefix++));
+
+    while (*msg)
+	HUlib_addCharToTextLine(&s->l[s->cl], *(msg++));
+}
+
+void HUlib_drawSText(hu_stext_t* s)
+{
+    int i, idx;
+    hu_textline_t *l;
+
+    if (!*s->on)
+	return; // if not on, don't draw
+
+    // draw everything
+    for (i=0 ; i<s->h ; i++)
+    {
+	idx = s->cl - i;
+	if (idx < 0)
+	    idx += s->h; // handle queue of lines
+	
+	l = &s->l[idx];
+
+	// need a decision made here on whether to skip the draw
+	HUlib_drawTextLine(l, false); // no cursor, please
+    }
+
+}
+
+void HUlib_eraseSText(hu_stext_t* s)
+{
+
+    int i;
+
+    for (i=0 ; i<s->h ; i++)
+    {
+	if (s->laston && !*s->on)
+	    s->l[i].needsupdate = 4;
+	HUlib_eraseTextLine(&s->l[i]);
+    }
+    s->laston = *s->on;
+
+}
+
+void
+HUlib_initIText
+( hu_itext_t*	it,
+  int		x,
+  int		y,
+  patch_t**	font,
+  int		startchar,
+  boolean*	on )
+{
+    it->lm = 0; // default left margin is start of text
+    it->on = on;
+    it->laston = true;
+    HUlib_initTextLine(&it->l, x, y, font, startchar);
+}
+
+
+// The following deletion routines adhere to the left margin restriction
+void HUlib_delCharFromIText(hu_itext_t* it)
+{
+    if (it->l.len != it->lm)
+	HUlib_delCharFromTextLine(&it->l);
+}
+
+void HUlib_eraseLineFromIText(hu_itext_t* it)
+{
+    while (it->lm != it->l.len)
+	HUlib_delCharFromTextLine(&it->l);
+}
+
+// Resets left margin as well
+void HUlib_resetIText(hu_itext_t* it)
+{
+    it->lm = 0;
+    HUlib_clearTextLine(&it->l);
+}
+
+void
+HUlib_addPrefixToIText
+( hu_itext_t*	it,
+  char*		str )
+{
+    while (*str)
+	HUlib_addCharToTextLine(&it->l, *(str++));
+    it->lm = it->l.len;
+}
+
+// wrapper function for handling general keyed input.
+// returns true if it ate the key
+boolean
+HUlib_keyInIText
+( hu_itext_t*	it,
+  unsigned char ch )
+{
+
+    if (ch >= ' ' && ch < '~') 
+  	HUlib_addCharToTextLine(&it->l, (char) ch);
+    else 
+	if (ch == KEY_BACKSPACE) 
+	    HUlib_delCharFromIText(it);
+	else 
+	    if (ch != KEY_ENTER) 
+		return false; // did not eat key
+
+    return true; // ate the key
+
+}
+
+void HUlib_drawIText(hu_itext_t* it)
+{
+
+    hu_textline_t *l = &it->l;
+
+    if (!*it->on)
+	return;
+    HUlib_drawTextLine(l, true); // draw the line w/ cursor
+
+}
+
+void HUlib_eraseIText(hu_itext_t* it)
+{
+    if (it->laston && !*it->on)
+	it->l.needsupdate = 4;
+    HUlib_eraseTextLine(&it->l);
+    it->laston = *it->on;
+}
+
--- /dev/null
+++ b/sys/src/games/doom/hu_stuff.c
@@ -1,0 +1,638 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:  Heads-up displays
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: hu_stuff.c,v 1.4 1997/02/03 16:47:52 b1 Exp $";
+
+#include <ctype.h>
+
+#include "doomdef.h"
+
+#include "z_zone.h"
+
+#include "m_swap.h"
+
+#include "hu_stuff.h"
+#include "hu_lib.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_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[] =
+{
+    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.
+{
+    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
+};
+
+
+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
+};
+
+
+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, "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 ( gamemode )
+    {
+      case shareware:
+      case registered:
+      case retail:
+	s = HU_TITLE;
+	break;
+
+/* FIXME
+      case pack_plut:
+	s = HU_TITLEP;
+	break;
+      case pack_tnt:
+	s = HU_TITLET;
+	break;
+*/
+	
+      case commercial:
+      default:
+	 s = HU_TITLE2;
+	 break;
+    }
+    
+    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,
+						    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 = 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	altdown = false;
+    int			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];
+
+    switch(ev->type){
+    case ev_keydown:
+    case ev_keyup:
+        if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT)
+        {
+	altdown = ev->type == ev_keydown;
+	return false;
+        }
+        /* no break */
+    default:
+        return false;
+    case ev_char:
+        break;
+    }
+
+    if (!chat_on)
+    {
+	if (ev->data1 == HU_MSGREFRESH)
+	{
+	    message_on = true;
+	    message_counter = HU_MSGTIMEOUT;
+	    eatkey = true;
+	}
+	else if (netgame && ev->data1 == 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->data1 == 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 = HUSTR_TALKTOSELF1;
+			else if (num_nobrainers < 6)
+			    plr->message = HUSTR_TALKTOSELF2;
+			else if (num_nobrainers < 9)
+			    plr->message = HUSTR_TALKTOSELF3;
+			else if (num_nobrainers < 32)
+			    plr->message = HUSTR_TALKTOSELF4;
+			else
+			    plr->message = HUSTR_TALKTOSELF5;
+		    }
+		}
+	    }
+	}
+    }
+    else
+    {
+	c = ev->data1;
+
+	// send a macro
+	if (altdown)
+	{
+	    c = c - '0';
+	    if (c < 0 || c > 9)
+		return false;
+	    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)
+	    {
+		HU_queueChatChar(c);
+	    }
+	    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;
+
+}
--- /dev/null
+++ b/sys/src/games/doom/i_net.c
@@ -1,0 +1,315 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+//
+//-----------------------------------------------------------------------------
+
+#include "doomdef.h"
+#include "doomstat.h"
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include <thread.h>
+#include "m_argv.h"
+#include "i_system.h"
+#include "d_net.h"
+#include "i_net.h"
+#include "w_wad.h"
+
+typedef struct Addr Addr;
+
+enum{
+	HDRSZ = 16+16+16+2+2	/* sizeof Udphdr w/o padding */
+};
+
+static char lsrv[6] = "666";
+
+struct Addr{
+	Udphdr h;
+	char srv[6];	/* convenience */
+	int ready;	/* is connected to udp!*!lsrv */
+	long called;
+};
+static Addr raddr[MAXNETNODES];
+
+static int ucfd;
+static int udfd;
+static int upfd[2];
+static int upid;
+
+
+static void
+conreq(doomdata_t *d)
+{
+	int fd;
+	long t;
+	char ip[64];
+	Addr *p;
+
+	p = &raddr[doomcom->remotenode];
+
+	t = time(nil);
+	if(t - p->called < 1)
+		return;
+
+	snprint(ip, sizeof ip, "%I", p->h.raddr);
+	if((fd = dial(netmkaddr(ip, "udp", p->srv), lsrv, nil, nil)) < 0)
+		sysfatal("dial: %r");
+	if(write(fd, d, doomcom->datalength) != doomcom->datalength)
+		sysfatal("conreq: %r");
+	close(fd);
+	p->called = t;
+}
+
+static void
+dsend(void)
+{
+	int i;
+	uchar buf[HDRSZ+sizeof(doomdata_t)];
+	doomdata_t d;
+
+	hnputl(&d.checksum, netbuffer->checksum);
+	d.player = netbuffer->player;
+	d.retransmitfrom = netbuffer->retransmitfrom;
+	d.starttic = netbuffer->starttic;
+	d.numtics = netbuffer->numtics;
+
+	for(i = 0; i < netbuffer->numtics; i++){
+		d.cmds[i].forwardmove = netbuffer->cmds[i].forwardmove;
+		d.cmds[i].sidemove = netbuffer->cmds[i].sidemove;
+		hnputs(&d.cmds[i].angleturn, netbuffer->cmds[i].angleturn);
+		hnputs(&d.cmds[i].consistancy, netbuffer->cmds[i].consistancy);
+		d.cmds[i].chatchar = netbuffer->cmds[i].chatchar;
+		d.cmds[i].buttons = netbuffer->cmds[i].buttons;
+	}
+
+	if(!raddr[doomcom->remotenode].ready){
+		conreq(&d);
+		return;
+	}
+	memcpy(buf, &raddr[doomcom->remotenode].h, HDRSZ);
+	memcpy(buf+HDRSZ, &d, sizeof d);
+
+	i = doomcom->datalength + HDRSZ;
+	if(write(udfd, buf, i) != i)
+		sysfatal("dsend: %r");
+}
+
+static void
+drecv(void)
+{
+	int n;
+	ushort i;
+	doomdata_t d;
+
+	if(filelength(upfd[1]) < 1){
+		doomcom->remotenode = -1;
+		return;
+	}
+	if((n = read(upfd[1], &d, sizeof d)) <= 0
+	|| read(upfd[1], &i, sizeof i) <= 0)
+		sysfatal("drecv: %r");
+
+	doomcom->remotenode = i;
+	doomcom->datalength = n;
+
+	/* FIXME: proper read/write from/to struct */
+	netbuffer->checksum = nhgetl(&d.checksum);
+	netbuffer->player = d.player;
+	netbuffer->retransmitfrom = d.retransmitfrom;
+	netbuffer->starttic = d.starttic;
+	netbuffer->numtics = d.numtics;
+	for(i = 0; i < netbuffer->numtics; i++){
+		netbuffer->cmds[i].forwardmove = d.cmds[i].forwardmove;
+		netbuffer->cmds[i].sidemove = d.cmds[i].sidemove;
+		netbuffer->cmds[i].angleturn = nhgets(&d.cmds[i].angleturn);
+		netbuffer->cmds[i].consistancy = nhgets(&d.cmds[i].consistancy);
+		netbuffer->cmds[i].chatchar = d.cmds[i].chatchar;
+		netbuffer->cmds[i].buttons = d.cmds[i].buttons;
+	}
+}
+
+static void
+uproc(void*)
+{
+	int n;
+	ushort i;
+	uchar buf[HDRSZ+sizeof(doomdata_t)];
+	Udphdr h;
+
+	upid = getpid();
+	for(;;){
+		if((n = read(udfd, buf, sizeof buf)) <= 0)
+			break;
+		memcpy(&h, buf, HDRSZ);
+
+		for(i = 0; i < doomcom->numnodes; i++)
+			if(equivip6(h.raddr, raddr[i].h.raddr)
+			&& nhgets(h.rport) == nhgets(raddr[i].h.rport))
+				break;
+		if(i == doomcom->numnodes)
+			continue;	/* ignore messages from strangers */
+		if(!raddr[i].ready){	/* FIXME: urgh */
+			raddr[i].ready++;
+			memcpy(&raddr[i].h, &h, sizeof h);
+		}
+
+		if(write(upfd[0], buf+HDRSZ, n - HDRSZ) != n - HDRSZ
+		|| write(upfd[0], &i, sizeof i) != sizeof i)
+			break;
+	}
+}
+
+void
+I_NetCmd(void)
+{
+	if(doomcom->command == CMD_SEND)
+		dsend();
+	else if(doomcom->command == CMD_GET)
+		drecv();
+	else
+		I_Error("invalid netcmd %d", doomcom->command);
+}
+
+void
+I_ShutdownNet(void)
+{
+	postnote(PNPROC, upid, "shutdown");
+	close(upfd[0]);
+	close(upfd[1]);
+	close(udfd);
+	close(ucfd);
+}
+
+static void
+initudp(void)
+{
+	char data[64], adir[40];
+
+	/* FIXME */
+	//if(myipaddr(raddr[0].h.raddr, nil) < 0)
+	//	sysfatal("myipaddr: %r");
+
+	if((ucfd = announce(netmkaddr("*", "udp", lsrv), adir)) < 0)
+		sysfatal("announce: %r");
+	if(fprint(ucfd, "headers") < 0)
+		sysfatal("failed to set headers mode: %r");
+	snprint(data, sizeof data, "%s/data", adir);
+	if((udfd = open(data, ORDWR)) < 0)
+		sysfatal("open: %r");
+
+	if(pipe(upfd) < 0)
+		sysfatal("pipe: %r");
+	if(procrfork(uproc, nil, mainstacksize, RFFDG) < 0)
+		sysfatal("procrfork: %r");
+}
+
+static void
+csip(char *s, Addr *a)	/* raddr!rsrv */
+{
+	int fd, n;
+	char buf[128], *f[3];
+
+	/* FIXME: get netmnt... */
+
+	if((fd = open("/net/cs", ORDWR)) < 0)
+		sysfatal("open: %r");
+
+	snprint(buf, sizeof buf, "udp!%s", s);
+	n = strlen(buf);
+	if(write(fd, buf, n) != n)
+		sysfatal("translating %s: %r", s);
+
+	seek(fd, 0, 0);
+	if((n = read(fd, buf, sizeof(buf)-1)) <= 0)
+		sysfatal("reading cs tables: %r");
+	buf[n] = 0;
+	close(fd);
+
+	if(getfields(buf, f, 3, 1, " !") < 2)
+		sysfatal("bad cs entry %s", buf);
+
+	if(parseip(a->h.raddr, f[1]) < 0)
+		sysfatal("parseip: %r");
+	hnputs(a->h.rport, atoi(f[2]));	/* FIXME */
+	strncpy(a->srv, f[2], sizeof(a->srv)-1);
+}
+
+static int
+netopts(void)
+{
+	int i;
+
+	if((i = M_CheckParm("-dup")) && i < myargc - 1){
+		doomcom->ticdup = myargv[i+1][0] - '0';
+		if(doomcom->ticdup < 1)
+			doomcom->ticdup = 1;
+		if(doomcom->ticdup > 9)
+			doomcom->ticdup = 9;
+	}
+
+	if(M_CheckParm("-extratic"))
+		doomcom->extratics = 1;
+
+	if((i = M_CheckParm("-srv")) && i < myargc - 1)
+		strncpy(lsrv, myargv[i+1], sizeof(lsrv)-1);
+
+	/* [0-3], default 0; player 0 is special */
+	if((i = M_CheckParm("-pn")) && i < myargc - 1)
+		doomcom->consoleplayer = myargv[i+1][0] - '0';
+
+	/* FIXME: d_net.c: don't use remoteaddr=0 as special case (max+1?) */
+	/* remote host address list: -net raddr!rsrv.. */
+	if((i = M_CheckParm("-net")) == 0){
+		/* single player game */
+		doomcom->id = DOOMCOM_ID;
+		doomcom->numplayers = doomcom->numnodes = 1;
+		doomcom->deathmatch = false;
+		netgame = false;
+		return -1;
+	}
+	doomcom->numnodes++;	/* raddr[0] is special cased because ??? */
+	while(++i < myargc && myargv[i][0] != '-'){
+		csip(myargv[i], &raddr[doomcom->numnodes]);
+		doomcom->numnodes++;
+	}
+
+	return 0;
+}
+
+void
+I_InitNetwork(void)
+{
+	doomcom = malloc(sizeof *doomcom);
+	memset(doomcom, 0, sizeof *doomcom);
+
+	doomcom->ticdup = 1;
+	doomcom->extratics = 0;
+	if(netopts() < 0)
+		return;
+	if(doomcom->numnodes < 2)
+		I_Error("netgame with a single node");
+	doomcom->id = DOOMCOM_ID;
+	doomcom->numplayers = doomcom->numnodes;
+
+	fmtinstall('I', eipfmt);
+	initudp();
+
+	netgame = true;
+}
--- /dev/null
+++ b/sys/src/games/doom/i_net.h
@@ -1,0 +1,46 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// DESCRIPTION:
+//	System specific network interface stuff.
+//
+//-----------------------------------------------------------------------------
+
+
+#ifndef __I_NET__
+#define __I_NET__
+
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+
+
+// Called by D_DoomMain.
+
+
+void I_InitNetwork (void);
+void I_NetCmd (void);
+void I_ShutdownNet (void);
+
+
+#endif
+//-----------------------------------------------------------------------------
+//
+// $Log:$
+//
+//-----------------------------------------------------------------------------
--- /dev/null
+++ b/sys/src/games/doom/i_system.c
@@ -1,0 +1,148 @@
+/* i_system.c */
+
+#include "doomdef.h"
+#include "doomtype.h"
+
+#include "i_system.h"
+#include "i_sound.h"
+#include "i_video.h"
+#include "i_net.h"
+
+#include "d_main.h"
+#include "d_net.h"
+#include "g_game.h"
+#include "m_misc.h"
+
+int mb_used = 32;	/* 32MB heap */
+
+void I_Init (void)
+{
+	I_InitSound();
+	I_InitGraphics();
+	I_MouseEnable(1);
+}
+
+byte* I_ZoneBase (int *size)
+{
+	*size = mb_used*1024*1024;
+	return (byte *) malloc(*size);
+}
+
+/* returns time in 1/70th second tics */
+int I_GetTime (void)
+{
+	return (int)((nsec()*TICRATE)/1000000000);
+}
+
+static ticcmd_t emptycmd;
+ticcmd_t* I_BaseTiccmd (void)
+{
+	return &emptycmd;
+}
+
+void I_Quit (void)
+{
+	D_QuitNetGame ();
+	I_ShutdownNet();
+	I_ShutdownSound();
+	I_ShutdownMusic();
+	M_SaveDefaults ();
+	I_ShutdownGraphics();
+	exits(nil);
+}
+
+byte* I_AllocLow (int length)
+{
+	byte *mem;
+        
+	mem = (byte *)malloc (length);
+	memset (mem,0,length);
+	return mem;
+}
+
+void I_Tactile(int on, int off, int total)
+{
+	USED(on, off, total);
+}
+
+//
+// I_Error
+//
+extern boolean demorecording;
+
+void I_Error (char *error, ...)
+{
+    va_list	argptr;
+
+    // Message first.
+    va_start (argptr,error);
+    fprintf (stderr, "Error: ");
+    vfprintf (stderr,error,argptr);
+    fprintf (stderr, "\n");
+    va_end (argptr);
+
+    fflush( stderr );
+
+    // Shutdown. Here might be other errors.
+    if (demorecording)
+	G_CheckDemoStatus();
+
+    D_QuitNetGame ();
+    I_ShutdownNet();
+    I_ShutdownGraphics();
+
+    exits("I_Error");
+}
+
+int I_FileExists (char *filepath)
+{
+	return access(filepath, AEXIST) == 0;
+}
+
+int I_Open (char *filepath)
+{
+	return open(filepath, OREAD);
+}
+
+void I_Close (int handle)
+{
+	close (handle);
+}
+
+int I_Seek (int handle, int n)
+{
+	return seek(handle, n, 0);
+}
+
+int I_Read (int handle, void *buf, int n)
+{
+	return read(handle, buf, n);
+}
+
+char* I_IdentifyWAD(char *wadname)
+{
+	static char path[1024];
+	char *home;
+
+	snprint(path, sizeof path, wadname);
+	if (I_FileExists (path))
+		return path;
+
+	if(home = getenv("home")){
+		snprintf(path, sizeof path, "%s/lib/doom/%s", home, wadname);
+		free(home);
+
+		if (I_FileExists (path))
+			return path;
+	}
+
+	snprintf(path, sizeof path, "/sys/lib/doom/%s", wadname);
+	if (I_FileExists (path))
+		return path;
+
+	snprintf(path, sizeof path, "/sys/games/lib/doom/%s", wadname);
+	if (I_FileExists (path))
+		return path;
+
+	return nil;
+}
--- /dev/null
+++ b/sys/src/games/doom/i_video.c
@@ -1,0 +1,385 @@
+/* i_video.c */
+
+#include "doomdef.h"	// printf
+#include "i_system.h"
+#include "v_video.h"	// screens[]
+#include "d_main.h"	// D_PostEvent
+
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <thread.h>
+
+static int resized;
+static int mouseactive;
+extern int usemouse;
+
+static Rectangle grabout;
+static Point center;
+
+static void kbdproc(void *);
+static void mouseproc(void *);
+
+static u32int cmap[256];
+
+static int kbdpid = -1;
+static int mousepid = -1;
+
+static int
+catch(void *, char *msg)
+{
+	/* in case we crash, disable mouse grab */
+	if(strncmp(msg, "sys:", 4) == 0)
+		mouseactive = 0;
+	return 0;
+}
+
+void I_InitGraphics(void)
+{
+	threadnotify(catch, 1);
+
+	if(initdraw(nil, nil, "doom") < 0)
+		I_Error("I_InitGraphics failed");
+
+	draw(screen, screen->r, display->black, nil, ZP);
+
+	center = addpt(screen->r.min, Pt(Dx(screen->r)/2, Dy(screen->r)/2));
+	grabout = insetrect(screen->r, Dx(screen->r)/4);
+
+	kbdpid = proccreate(kbdproc, nil, 4096);
+	mousepid = proccreate(mouseproc, nil, 4096);
+}
+
+static Channel *conv;
+
+void I_ShutdownGraphics(void)
+{
+	if(conv != nil)
+		chanclose(conv);
+	if(kbdpid != -1){
+		postnote(PNPROC, threadpid(kbdpid), "shutdown");
+		kbdpid = -1;
+	}
+	if(mousepid != -1){
+		postnote(PNPROC, threadpid(mousepid), "shutdown");
+		mousepid = -1;
+	}
+}
+
+void I_SetPalette(byte *palette)
+{
+	uchar *c;
+
+	for(c = (uchar*)cmap; c < (uchar*)cmap+sizeof(cmap); c += 4){
+		c[2] = gammatable[usegamma][*palette++];
+		c[1] = gammatable[usegamma][*palette++];
+		c[0] = gammatable[usegamma][*palette++];
+	}
+}
+
+void I_UpdateNoBlit(void)
+{
+	// DELETEME?
+}
+
+void pal2xrgb(u32int *pal, u8int *s, u32int *d, int n, int scale);
+
+static int screenconvi;
+static uchar screenconv[2][SCREENWIDTH*SCREENHEIGHT];
+
+static void
+convproc(void *p)
+{
+	static u32int buf[SCREENWIDTH*12];
+	int y, scale, oldscale;
+	Image *rowimg;
+	Rectangle r;
+	uchar *s;
+
+	oldscale = 0;
+	rowimg = nil;
+	for(;;){
+		if((s = recvp(p)) == nil)
+			break;
+		scale = Dx(screen->r)/SCREENWIDTH;
+		if(scale <= 0)
+			scale = 1;
+		else if(scale > 12)
+			scale = 12;
+
+		/* where to draw the scaled row */
+		r = rectsubpt(rectaddpt(Rect(0, 0, scale*SCREENWIDTH, scale), center),
+			Pt(scale*SCREENWIDTH/2, scale*SCREENHEIGHT/2));
+
+		/* the row image, y-axis gets scaled with repl flag */
+		if(scale != oldscale){
+			if(rowimg != nil)
+				freeimage(rowimg);
+			rowimg = allocimage(display, Rect(0, 0, scale*SCREENWIDTH, 1), XRGB32, scale > 1, DNofill);
+			if(rowimg == nil)
+				sysfatal("allocimage: %r");
+			oldscale = scale;
+		}
+
+		for(y = 0; y < SCREENHEIGHT; y++){
+			pal2xrgb(cmap, s, buf, SCREENWIDTH, scale);
+			s += SCREENWIDTH;
+			loadimage(rowimg, rowimg->r, (uchar*)buf, 4*scale*SCREENWIDTH);
+			draw(screen, r, rowimg, nil, ZP);
+			r.min.y += scale;
+			r.max.y += scale;
+		}
+		flushimage(display, 1);
+	}
+	if(rowimg != nil)
+		freeimage(rowimg);
+	chanfree(p);
+	threadexits(nil);
+}
+
+void I_FinishUpdate(void)
+{
+	if(resized){
+		if(conv != nil){
+			sendp(conv, nil);
+			conv = nil;
+		}
+		resized = 0;
+		if(getwindow(display, Refnone) < 0)
+			sysfatal("getwindow: %r");
+
+		/* make black background */
+		draw(screen, screen->r, display->black, nil, ZP);
+
+		center = addpt(screen->r.min, Pt(Dx(screen->r)/2, Dy(screen->r)/2));
+		grabout = insetrect(screen->r, Dx(screen->r)/4);
+	}
+
+	if(conv == nil){
+		conv = chancreate(sizeof(uchar*), 0);
+		proccreate(convproc, conv, 4096);
+	}
+	memmove(screenconv[screenconvi], screens[0], sizeof(screenconv[0]));
+	if(sendp(conv, screenconv[screenconvi]) > 0)
+		screenconvi = (screenconvi + 1) % nelem(screenconv);
+}
+
+void I_MouseEnable(int on)
+{
+	static char nocurs[2*4+2*2*16];
+	static int fd = -1;
+
+	if(mouseactive == on || !usemouse)
+		return;
+	if(mouseactive = on){
+		if((fd = open("/dev/cursor", ORDWR|OCEXEC)) < 0)
+			return;
+		write(fd, nocurs, sizeof(nocurs));
+	}else if(fd >= 0) {
+		close(fd);
+		fd = -1;
+	}
+}
+
+void I_ReadScreen(byte *scr)
+{
+	memcpy (scr, screens[0], SCREENWIDTH*SCREENHEIGHT);
+}
+
+void I_BeginRead(void)
+{
+	I_Error("PORTME i_video.c I_BeginRead");
+}
+
+void I_EndRead(void)
+{
+	I_Error("PORTME i_video.c I_EndRead");
+}
+
+void I_StartTic(void)
+{
+}
+
+void I_WaitVBL(int)
+{
+}
+
+
+static int
+runetokey(Rune r)
+{
+	switch(r){
+	case Kleft:
+		return KEY_LEFTARROW;
+	case Kright:
+		return KEY_RIGHTARROW;
+	case Kup:
+		return KEY_UPARROW;
+	case Kdown:
+		return KEY_DOWNARROW;
+
+	case Kshift:
+		return KEY_RSHIFT;
+	case Kctl:
+		return KEY_RCTRL;
+	case Kalt:
+		return KEY_LALT;
+	case Kaltgr:
+		return KEY_RALT;
+
+	case Kbs:
+		return KEY_BACKSPACE;
+	case '\n':
+		return KEY_ENTER;
+	case Kprint:
+		return KEY_PAUSE;
+
+	case KF|1:
+	case KF|2:
+	case KF|3:
+	case KF|4:
+	case KF|5:
+	case KF|6:
+	case KF|7:
+	case KF|8:
+	case KF|9:
+	case KF|10:
+	case KF|11:
+	case KF|12:
+		return KEY_F1+(r-(KF|1));
+
+	default:
+		if(r < 0x80)
+			return r;
+	}
+	return 0;
+}
+
+static void
+kbdproc(void *)
+{
+	char buf[128], buf2[128], *s;
+	int kfd, n;
+	Rune r;
+	event_t e;
+
+	if((kfd = open("/dev/kbd", OREAD)) < 0)
+		sysfatal("can't open kbd: %r");
+
+	buf2[0] = 0;
+	buf2[1] = 0;
+	buf[0] = 0;
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			n = read(kfd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+
+		e.data1 = -1;
+		e.data2 = -1;
+		e.data3 = -1;
+
+		switch(buf[0]){
+		case 'c':
+			chartorune(&r, buf+1);
+			if(r){
+				e.data1 = runetokey(r);
+				e.type = ev_char;
+				D_PostEvent(&e);
+			}
+			/* no break */
+		default:
+			continue;
+		case 'k':
+			s = buf+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf2+1, r) == nil){
+					if(e.data1 = runetokey(r)){
+						e.type = ev_keydown;
+						D_PostEvent(&e);
+					}
+				}
+			}
+			break;
+		case 'K':
+			s = buf2+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf+1, r) == nil){
+					if(e.data1 = runetokey(r)){
+						e.type = ev_keyup;
+						D_PostEvent(&e);
+					}
+				}
+			}
+			break;
+		}
+		strcpy(buf2, buf);
+	}
+	threadexits(nil);
+}
+
+static void
+mouseproc(void *)
+{
+	int fd, n, nerr;
+	Mouse m, om;
+	char buf[1+5*12];
+	event_t e;
+
+	if((fd = open("/dev/mouse", ORDWR)) < 0)
+		sysfatal("can't open mouse: %r");
+
+	memset(&m, 0, sizeof m);
+	memset(&om, 0, sizeof om);
+	nerr = 0;
+	for(;;){
+		n = read(fd, buf, sizeof buf);
+		if(n != 1+4*12){
+			fprint(2, "mouse: bad count %d not 49: %r\n", n);
+			if(n<0 || ++nerr>10)
+				break;
+			continue;
+		}
+		nerr = 0;
+		switch(buf[0]){
+		case 'r':
+			resized = 1;
+			/* fall through */
+		case 'm':
+			if(!mouseactive)
+				break;
+
+			m.xy.x = atoi(buf+1+0*12);
+			m.xy.y = atoi(buf+1+1*12);
+			m.buttons = atoi(buf+1+2*12);
+			m.msec = atoi(buf+1+3*12);
+
+			if(!ptinrect(m.xy, grabout)){
+				fprint(fd, "m%d %d", center.x, center.y);
+
+				m.xy = center;
+				om.xy = center;
+			}
+			
+			e.type = ev_mouse;
+			e.data1 = m.buttons;
+			e.data2 = m.xy.x - om.xy.x;
+			e.data3 = om.xy.y - m.xy.y;
+			D_PostEvent(&e);
+			om = m;
+
+			break;
+		}
+	}
+	threadexits(nil);
+}
+
--- /dev/null
+++ b/sys/src/games/doom/p_local.h
@@ -1,0 +1,292 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// DESCRIPTION:
+//	Play functions, animation, global header.
+//
+//-----------------------------------------------------------------------------
+
+
+#ifndef __P_LOCAL__
+#define __P_LOCAL__
+
+#ifndef __R_LOCAL__
+#include "r_local.h"
+#endif
+
+#define FLOATSPEED		(FRACUNIT*4)
+
+
+#define MAXHEALTH		100
+#define VIEWHEIGHT		(41*FRACUNIT)
+
+// mapblocks are used to check movement
+// against lines and things
+#define MAPBLOCKUNITS	128
+#define MAPBLOCKSIZE	(MAPBLOCKUNITS*FRACUNIT)
+#define MAPBLOCKSHIFT	(FRACBITS+7)
+#define MAPBMASK		(MAPBLOCKSIZE-1)
+#define MAPBTOFRAC		(MAPBLOCKSHIFT-FRACBITS)
+
+
+// player radius for movement checking
+#define PLAYERRADIUS	16*FRACUNIT
+
+// MAXRADIUS is for precalculated sector block boxes
+// the spider demon is larger,
+// but we do not have any moving sectors nearby
+#define MAXRADIUS		32*FRACUNIT
+
+#define GRAVITY		FRACUNIT
+#define MAXMOVE		(30*FRACUNIT)
+
+#define USERANGE		(64*FRACUNIT)
+#define MELEERANGE		(64*FRACUNIT)
+#define MISSILERANGE	(32*64*FRACUNIT)
+
+// follow a player exlusively for 3 seconds
+#define	BASETHRESHOLD	 	100
+
+extern	boolean	noztele;
+extern	boolean	nobounce;
+extern	boolean noskyabs;
+
+
+//
+// P_TICK
+//
+
+// both the head and tail of the thinker list
+extern	thinker_t	thinkercap;	
+
+
+void P_InitThinkers (void);
+void P_AddThinker (thinker_t* thinker);
+void P_RemoveThinker (thinker_t* thinker);
+
+
+//
+// P_PSPR
+//
+void P_SetupPsprites (player_t* curplayer);
+void P_MovePsprites (player_t* curplayer);
+void P_DropWeapon (player_t* player);
+
+
+//
+// P_USER
+//
+void	P_PlayerThink (player_t* player);
+
+
+//
+// P_MOBJ
+//
+#define ONFLOORZ		MININT
+#define ONCEILINGZ		MAXINT
+
+// Time interval for item respawning.
+#define ITEMQUESIZE		128
+
+extern mapthing_t	itemrespawnque[ITEMQUESIZE];
+extern int		itemrespawntime[ITEMQUESIZE];
+extern int		iquehead;
+extern int		iquetail;
+
+
+void P_RespawnSpecials (void);
+
+mobj_t*
+P_SpawnMobj
+( fixed_t	x,
+  fixed_t	y,
+  fixed_t	z,
+  mobjtype_t	type );
+
+void 	P_RemoveMobj (mobj_t* th);
+boolean	P_SetMobjState (mobj_t* mobj, statenum_t state);
+void 	P_MobjThinker (void *mobj, void*);
+
+void	P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z);
+void 	P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage);
+mobj_t* P_SpawnMissile (mobj_t* source, mobj_t* dest, mobjtype_t type);
+void	P_SpawnPlayerMissile (mobj_t* source, mobjtype_t type);
+
+
+//
+// P_ENEMY
+//
+void P_NoiseAlert (mobj_t* target, mobj_t* emmiter);
+
+
+//
+// P_MAPUTL
+//
+typedef struct
+{
+    fixed_t	x;
+    fixed_t	y;
+    fixed_t	dx;
+    fixed_t	dy;
+    
+} divline_t;
+
+typedef struct
+{
+    fixed_t	frac;		// along trace line
+    boolean	isaline;
+    union {
+	mobj_t*	thing;
+	line_t*	line;
+    }			d;
+} intercept_t;
+
+#define MAXINTERCEPTS	128
+
+extern intercept_t	intercepts[MAXINTERCEPTS];
+extern intercept_t*	intercept_p;
+
+typedef boolean (*traverser_t) (intercept_t *in);
+
+fixed_t P_AproxDistance (fixed_t dx, fixed_t dy);
+int 	P_PointOnLineSide (fixed_t x, fixed_t y, line_t* line);
+int 	P_PointOnDivlineSide (fixed_t x, fixed_t y, divline_t* line);
+void 	P_MakeDivline (line_t* li, divline_t* dl);
+fixed_t P_InterceptVector (divline_t* v2, divline_t* v1);
+int 	P_BoxOnLineSide (fixed_t* tmbox, line_t* ld);
+
+extern fixed_t		opentop;
+extern fixed_t 		openbottom;
+extern fixed_t		openrange;
+extern fixed_t		lowfloor;
+
+void 	P_LineOpening (line_t* linedef);
+
+boolean P_BlockLinesIterator (int x, int y, boolean(*func)(line_t*) );
+boolean P_BlockThingsIterator (int x, int y, boolean(*func)(mobj_t*) );
+
+#define PT_ADDLINES		1
+#define PT_ADDTHINGS	2
+#define PT_EARLYOUT		4
+
+extern divline_t	trace;
+
+boolean
+P_PathTraverse
+( fixed_t	x1,
+  fixed_t	y1,
+  fixed_t	x2,
+  fixed_t	y2,
+  int		flags,
+  boolean	(*trav) (intercept_t *));
+
+void P_UnsetThingPosition (mobj_t* thing);
+void P_SetThingPosition (mobj_t* thing);
+
+
+//
+// P_MAP
+//
+
+// If "floatok" true, move would be ok
+// if within "tmfloorz - tmceilingz".
+extern boolean		floatok;
+extern fixed_t		tmfloorz;
+extern fixed_t		tmceilingz;
+
+
+extern	line_t*		ceilingline;
+
+boolean P_CheckPosition (mobj_t *thing, fixed_t x, fixed_t y);
+boolean P_TryMove (mobj_t* thing, fixed_t x, fixed_t y);
+boolean P_TeleportMove (mobj_t* thing, fixed_t x, fixed_t y);
+void	P_SlideMove (mobj_t* mo);
+boolean P_CheckSight (mobj_t* t1, mobj_t* t2);
+void 	P_UseLines (player_t* player);
+
+boolean P_ChangeSector (sector_t* sector, boolean crunch);
+
+extern mobj_t*	linetarget;	// who got hit (or NULL)
+
+fixed_t
+P_AimLineAttack
+( mobj_t*	t1,
+  angle_t	angle,
+  fixed_t	distance );
+
+void
+P_LineAttack
+( mobj_t*	t1,
+  angle_t	angle,
+  fixed_t	distance,
+  fixed_t	slope,
+  int		damage );
+
+void
+P_RadiusAttack
+( mobj_t*	spot,
+  mobj_t*	source,
+  int		damage );
+
+
+
+//
+// P_SETUP
+//
+extern byte*		rejectmatrix;	// for fast sight rejection
+extern short*		blockmaplump;	// offsets in blockmap are from here
+extern short*		blockmap;
+extern int		bmapwidth;
+extern int		bmapheight;	// in mapblocks
+extern fixed_t		bmaporgx;
+extern fixed_t		bmaporgy;	// origin of block map
+extern mobj_t**		blocklinks;	// for thing chains
+
+
+
+//
+// P_INTER
+//
+extern int		maxammo[NUMAMMO];
+extern int		clipammo[NUMAMMO];
+
+void
+P_TouchSpecialThing
+( mobj_t*	special,
+  mobj_t*	toucher );
+
+void
+P_DamageMobj
+( mobj_t*	target,
+  mobj_t*	inflictor,
+  mobj_t*	source,
+  int		damage );
+
+
+//
+// P_SPEC
+//
+#include "p_spec.h"
+
+
+#endif	// __P_LOCAL__
+//-----------------------------------------------------------------------------
+//
+// $Log:$
+//
+//-----------------------------------------------------------------------------
+
+
--- /dev/null
+++ b/sys/src/games/doom/p_map.c
@@ -1,0 +1,1338 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+//	Movement, collision handling.
+//	Shooting and aiming.
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: p_map.c,v 1.5 1997/02/03 22:45:11 b1 Exp $";
+
+#include "m_bbox.h"
+#include "m_random.h"
+#include "i_system.h"
+
+#include "doomdef.h"
+#include "p_local.h"
+
+#include "s_sound.h"
+
+// State.
+#include "doomstat.h"
+#include "r_state.h"
+// Data.
+#include "sounds.h"
+
+
+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
+#define MAXSPECIALCROSS		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
+//
+
+
+//
+// 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++;
+    }
+
+    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;
+
+	    if (thing->type != MT_PLAYER)
+	    {
+		// 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)
+	{
+	    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)
+	{
+	    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 != nil && li->backsector->ceilingpic == skyflatnum
+	    && (!noskyabs || z > li->backsector->ceilingheight))
+		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;
+	
+    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_Random2()<<12;
+	mo->momy = P_Random2()<<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;
+}
+
--- /dev/null
+++ b/sys/src/games/doom/p_mobj.c
@@ -1,0 +1,991 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+//	Moving object handling. Spawn functions.
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: p_mobj.c,v 1.5 1997/02/03 22:45:12 b1 Exp $";
+
+#include "i_system.h"
+#include "z_zone.h"
+#include "m_random.h"
+
+#include "doomdef.h"
+#include "p_local.h"
+#include "sounds.h"
+
+#include "st_stuff.h"
+#include "hu_stuff.h"
+
+#include "s_sound.h"
+
+#include "doomstat.h"
+
+
+void G_PlayerReborn (int player);
+void P_SpawnMapThing (mapthing_t*	mthing);
+
+
+//
+// P_SetMobjState
+// Returns true if the mobj is still present.
+//
+int test;
+
+boolean
+P_SetMobjState
+( mobj_t*	mobj,
+  statenum_t	state )
+{
+    state_t*	st;
+
+    do
+    {
+	if (state == S_NULL)
+	{
+	    mobj->state = (state_t *) S_NULL;
+	    P_RemoveMobj (mobj);
+	    return false;
+	}
+
+	st = &states[state];
+	mobj->state = st;
+	mobj->tics = st->tics;
+	mobj->sprite = st->sprite;
+	mobj->frame = st->frame;
+
+	// Modified handling.
+	// Call action functions when the state is set
+	if (st->action)		
+	    st->action(mobj, NULL);
+	
+	state = st->nextstate;
+    } while (!mobj->tics);
+				
+    return true;
+}
+
+
+//
+// P_ExplodeMissile  
+//
+void P_ExplodeMissile (mobj_t* mo)
+{
+    mo->momx = mo->momy = mo->momz = 0;
+
+    P_SetMobjState (mo, mobjinfo[mo->type].deathstate);
+
+    mo->tics -= P_Random()&3;
+
+    if (mo->tics < 1)
+	mo->tics = 1;
+
+    mo->flags &= ~MF_MISSILE;
+
+    if (mo->info->deathsound)
+	S_StartSound (mo, mo->info->deathsound);
+}
+
+
+//
+// P_XYMovement  
+//
+#define STOPSPEED		0x1000
+#define FRICTION		0xe800
+
+void P_XYMovement (mobj_t* mo) 
+{ 	
+    fixed_t 	ptryx;
+    fixed_t	ptryy;
+    player_t*	player;
+    fixed_t	xmove;
+    fixed_t	ymove;
+			
+    if (!mo->momx && !mo->momy)
+    {
+	if (mo->flags & MF_SKULLFLY)
+	{
+	    // the skull slammed into something
+	    mo->flags &= ~MF_SKULLFLY;
+	    mo->momx = mo->momy = mo->momz = 0;
+
+	    P_SetMobjState (mo, mo->info->spawnstate);
+	}
+	return;
+    }
+	
+    player = mo->player;
+		
+    if (mo->momx > MAXMOVE)
+	mo->momx = MAXMOVE;
+    else if (mo->momx < -MAXMOVE)
+	mo->momx = -MAXMOVE;
+
+    if (mo->momy > MAXMOVE)
+	mo->momy = MAXMOVE;
+    else if (mo->momy < -MAXMOVE)
+	mo->momy = -MAXMOVE;
+		
+    xmove = mo->momx;
+    ymove = mo->momy;
+	
+    do
+    {
+	if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2)
+	{
+	    ptryx = mo->x + xmove/2;
+	    ptryy = mo->y + ymove/2;
+	    xmove >>= 1;
+	    ymove >>= 1;
+	}
+	else
+	{
+	    ptryx = mo->x + xmove;
+	    ptryy = mo->y + ymove;
+	    xmove = ymove = 0;
+	}
+		
+	if (!P_TryMove (mo, ptryx, ptryy))
+	{
+	    // blocked move
+	    if (mo->player)
+	    {	// try to slide along it
+		P_SlideMove (mo);
+	    }
+	    else if (mo->flags & MF_MISSILE)
+	    {
+		// explode a missile
+		if (ceilingline != nil &&
+		    ceilingline->backsector != nil &&
+		    ceilingline->backsector->ceilingpic == skyflatnum &&
+		    (!noskyabs || mo->z > ceilingline->backsector->ceilingheight))
+		{
+		    // Hack to prevent missiles exploding
+		    // against the sky.
+		    // Does not handle sky floors.
+		    P_RemoveMobj (mo);
+		    return;
+		}
+		P_ExplodeMissile (mo);
+	    }
+	    else
+		mo->momx = mo->momy = 0;
+	}
+    } while (xmove || ymove);
+    
+    // slow down
+    if (player && player->cheats & CF_NOMOMENTUM)
+    {
+	// debug option for no sliding at all
+	mo->momx = mo->momy = 0;
+	return;
+    }
+
+    if (mo->flags & (MF_MISSILE | MF_SKULLFLY) )
+	return; 	// no friction for missiles ever
+		
+    if (mo->z > mo->floorz)
+	return;		// no friction when airborne
+
+    if (mo->flags & MF_CORPSE)
+    {
+	// do not stop sliding
+	//  if halfway off a step with some momentum
+	if (mo->momx > FRACUNIT/4
+	    || mo->momx < -FRACUNIT/4
+	    || mo->momy > FRACUNIT/4
+	    || mo->momy < -FRACUNIT/4)
+	{
+	    if (mo->floorz != mo->subsector->sector->floorheight)
+		return;
+	}
+    }
+
+    if (mo->momx > -STOPSPEED
+	&& mo->momx < STOPSPEED
+	&& mo->momy > -STOPSPEED
+	&& mo->momy < STOPSPEED
+	&& (!player
+	    || (player->cmd.forwardmove== 0
+		&& player->cmd.sidemove == 0 ) ) )
+    {
+	// if in a walking frame, stop moving
+	if ( player&&(unsigned)((player->mo->state - states)- S_PLAY_RUN1) < 4)
+	    P_SetMobjState (player->mo, S_PLAY);
+	
+	mo->momx = 0;
+	mo->momy = 0;
+    }
+    else
+    {
+	mo->momx = FixedMul (mo->momx, FRICTION);
+	mo->momy = FixedMul (mo->momy, FRICTION);
+    }
+}
+
+//
+// P_ZMovement
+//
+void P_ZMovement (mobj_t* mo)
+{
+    fixed_t	dist;
+    fixed_t	delta;
+    
+    // check for smooth step up
+    if (mo->player && mo->z < mo->floorz)
+    {
+	mo->player->viewheight -= mo->floorz-mo->z;
+
+	mo->player->deltaviewheight
+	    = (VIEWHEIGHT - mo->player->viewheight)>>3;
+    }
+    
+    // adjust height
+    mo->z += mo->momz;
+	
+    if ( mo->flags & MF_FLOAT
+	 && mo->target)
+    {
+	// float down towards target if too close
+	if ( !(mo->flags & MF_SKULLFLY)
+	     && !(mo->flags & MF_INFLOAT) )
+	{
+	    dist = P_AproxDistance (mo->x - mo->target->x,
+				    mo->y - mo->target->y);
+	    
+	    delta =(mo->target->z + (mo->height>>1)) - mo->z;
+
+	    if (delta<0 && dist < -(delta*3) )
+		mo->z -= FLOATSPEED;
+	    else if (delta>0 && dist < (delta*3) )
+		mo->z += FLOATSPEED;			
+	}
+	
+    }
+    
+    // clip movement
+    if (mo->z <= mo->floorz)
+    {
+	// hit the floor
+
+	if (!nobounce && mo->flags & MF_SKULLFLY)
+	{
+	    // the skull slammed into something
+	    mo->momz = -mo->momz;
+	}
+	
+	if (mo->momz < 0)
+	{
+	    if (mo->player
+		&& mo->momz < -GRAVITY*8)	
+	    {
+		// Squat down.
+		// Decrease viewheight for a moment
+		// after hitting the ground (hard),
+		// and utter appropriate sound.
+		mo->player->deltaviewheight = mo->momz>>3;
+		S_StartSound (mo, sfx_oof);
+	    }
+	    mo->momz = 0;
+	}
+	mo->z = mo->floorz;
+
+	if (nobounce && mo->flags & MF_SKULLFLY)
+	    mo->momz = -mo->momz;
+
+	if ( (mo->flags & MF_MISSILE)
+	     && !(mo->flags & MF_NOCLIP) )
+	{
+	    P_ExplodeMissile (mo);
+	    return;
+	}
+    }
+    else if (! (mo->flags & MF_NOGRAVITY) )
+    {
+	if (mo->momz == 0)
+	    mo->momz = -GRAVITY*2;
+	else
+	    mo->momz -= GRAVITY;
+    }
+	
+    if (mo->z + mo->height > mo->ceilingz)
+    {
+	// hit the ceiling
+	if (mo->momz > 0)
+	    mo->momz = 0;
+	{
+	    mo->z = mo->ceilingz - mo->height;
+	}
+
+	if (mo->flags & MF_SKULLFLY)
+	{	// the skull slammed into something
+	    mo->momz = -mo->momz;
+	}
+	
+	if ( (mo->flags & MF_MISSILE)
+	     && !(mo->flags & MF_NOCLIP) )
+	{
+	    P_ExplodeMissile (mo);
+	    return;
+	}
+    }
+} 
+
+
+
+//
+// P_NightmareRespawn
+//
+void
+P_NightmareRespawn (mobj_t* mobj)
+{
+    fixed_t		x;
+    fixed_t		y;
+    fixed_t		z; 
+    subsector_t*	ss; 
+    mobj_t*		mo;
+    mapthing_t*		mthing;
+		
+    x = mobj->spawnpoint.x << FRACBITS; 
+    y = mobj->spawnpoint.y << FRACBITS; 
+
+    // somthing is occupying it's position?
+    if (!P_CheckPosition (mobj, x, y) ) 
+	return;	// no respwan
+
+    // spawn a teleport fog at old spot
+    // because of removal of the body?
+    mo = P_SpawnMobj (mobj->x,
+		      mobj->y,
+		      mobj->subsector->sector->floorheight , MT_TFOG); 
+    // initiate teleport sound
+    S_StartSound (mo, sfx_telept);
+
+    // spawn a teleport fog at the new spot
+    ss = R_PointInSubsector (x,y); 
+
+    mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_TFOG); 
+
+    S_StartSound (mo, sfx_telept);
+
+    // spawn the new monster
+    mthing = &mobj->spawnpoint;
+	
+    // spawn it
+    if (mobj->info->flags & MF_SPAWNCEILING)
+	z = ONCEILINGZ;
+    else
+	z = ONFLOORZ;
+
+    // inherit attributes from deceased one
+    mo = P_SpawnMobj (x,y,z, mobj->type);
+    memcpy (&mo->spawnpoint, mthing, sizeof(*mthing));
+    mo->angle = ANG45 * (mthing->angle/45);
+
+    if (mthing->options & MTF_AMBUSH)
+	mo->flags |= MF_AMBUSH;
+
+    mo->reactiontime = 18;
+	
+    // remove the old monster,
+    P_RemoveMobj (mobj);
+}
+
+
+//
+// P_MobjThinker
+//
+void P_MobjThinker (void *_mobj, void*)
+{
+    mobj_t *mobj = (mobj_t*)_mobj;
+
+    // momentum movement
+    if (mobj->momx
+	|| mobj->momy
+	|| (mobj->flags&MF_SKULLFLY) )
+    {
+	P_XYMovement (mobj);
+
+	// FIXME: decent NOP/NULL/Nil function pointer please.
+	if (mobj->thinker.function == (actionf_t)(-1))
+	    return;		// mobj was removed
+    }
+    if ( (mobj->z != mobj->floorz)
+	 || mobj->momz )
+    {
+	P_ZMovement (mobj);
+	
+	// FIXME: decent NOP/NULL/Nil function pointer please.
+	if (mobj->thinker.function == (actionf_t)(-1))
+	    return;		// mobj was removed
+    }
+
+    
+    // cycle through states,
+    // calling action functions at transitions
+    if (mobj->tics != -1)
+    {
+	mobj->tics--;
+		
+	// you can cycle through multiple states in a tic
+	if (!mobj->tics)
+	    if (!P_SetMobjState (mobj, mobj->state->nextstate) )
+		return;		// freed itself
+    }
+    else
+    {
+	// check for nightmare respawn
+	if (! (mobj->flags & MF_COUNTKILL) )
+	    return;
+
+	if (!respawnmonsters)
+	    return;
+
+	mobj->movecount++;
+
+	if (mobj->movecount < 12*35)
+	    return;
+
+	if ( leveltime&31 )
+	    return;
+
+	if (P_Random () > 4)
+	    return;
+
+	P_NightmareRespawn (mobj);
+    }
+
+}
+
+
+//
+// P_SpawnMobj
+//
+mobj_t*
+P_SpawnMobj
+( fixed_t	x,
+  fixed_t	y,
+  fixed_t	z,
+  mobjtype_t	type )
+{
+    mobj_t*	mobj;
+    state_t*	st;
+    mobjinfo_t*	info;
+	
+    mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL);
+    memset (mobj, 0, sizeof (*mobj));
+    info = &mobjinfo[type];
+	
+    mobj->type = type;
+    mobj->info = info;
+    mobj->x = x;
+    mobj->y = y;
+    mobj->radius = info->radius;
+    mobj->height = info->height;
+    mobj->flags = info->flags;
+    mobj->health = info->spawnhealth;
+
+    if (gameskill != sk_nightmare)
+	mobj->reactiontime = info->reactiontime;
+    
+    mobj->lastlook = P_Random () % MAXPLAYERS;
+    // do not set the state with P_SetMobjState,
+    // because action routines can not be called yet
+    st = &states[info->spawnstate];
+
+    mobj->state = st;
+    mobj->tics = st->tics;
+    mobj->sprite = st->sprite;
+    mobj->frame = st->frame;
+
+    // set subsector and/or block links
+    P_SetThingPosition (mobj);
+	
+    mobj->floorz = mobj->subsector->sector->floorheight;
+    mobj->ceilingz = mobj->subsector->sector->ceilingheight;
+
+    if (z == ONFLOORZ)
+	mobj->z = mobj->floorz;
+    else if (z == ONCEILINGZ)
+	mobj->z = mobj->ceilingz - mobj->info->height;
+    else 
+	mobj->z = z;
+
+    mobj->thinker.function = P_MobjThinker;
+	
+    P_AddThinker (&mobj->thinker);
+
+    return mobj;
+}
+
+
+//
+// P_RemoveMobj
+//
+mapthing_t	itemrespawnque[ITEMQUESIZE];
+int		itemrespawntime[ITEMQUESIZE];
+int		iquehead;
+int		iquetail;
+
+
+void P_RemoveMobj (mobj_t* mobj)
+{
+    if ((mobj->flags & MF_SPECIAL)
+	&& !(mobj->flags & MF_DROPPED)
+	&& (mobj->type != MT_INV)
+	&& (mobj->type != MT_INS))
+    {
+	memcpy (&itemrespawnque[iquehead], &mobj->spawnpoint, sizeof(mobj->spawnpoint));
+	itemrespawntime[iquehead] = leveltime;
+	iquehead = (iquehead+1)&(ITEMQUESIZE-1);
+
+	// lose one off the end?
+	if (iquehead == iquetail)
+	    iquetail = (iquetail+1)&(ITEMQUESIZE-1);
+    }
+	
+    // unlink from sector and block lists
+    P_UnsetThingPosition (mobj);
+    
+    // stop any playing sound
+    S_StopSound (mobj);
+    
+    // free block
+    P_RemoveThinker ((thinker_t*)mobj);
+}
+
+
+
+
+//
+// P_RespawnSpecials
+//
+void P_RespawnSpecials (void)
+{
+    fixed_t		x;
+    fixed_t		y;
+    fixed_t		z;
+    
+    subsector_t*	ss; 
+    mobj_t*		mo;
+    mapthing_t*		mthing;
+    
+    int			i;
+
+    // only respawn items in deathmatch
+    if (deathmatch != 2)
+	return;	// 
+
+    // nothing left to respawn?
+    if (iquehead == iquetail)
+	return;		
+
+    // wait at least 30 seconds
+    if (leveltime - itemrespawntime[iquetail] < 30*35)
+	return;			
+
+    mthing = &itemrespawnque[iquetail];
+	
+    x = mthing->x << FRACBITS; 
+    y = mthing->y << FRACBITS; 
+	  
+    // spawn a teleport fog at the new spot
+    ss = R_PointInSubsector (x,y); 
+    mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_IFOG); 
+    S_StartSound (mo, sfx_itmbk);
+
+    // find which type to spawn
+    for (i=0 ; i< NUMMOBJTYPES ; i++)
+    {
+	if (mthing->type == mobjinfo[i].doomednum)
+	    break;
+    }
+    
+    // spawn it
+    if (mobjinfo[i].flags & MF_SPAWNCEILING)
+	z = ONCEILINGZ;
+    else
+	z = ONFLOORZ;
+
+    mo = P_SpawnMobj (x,y,z, i);
+    memcpy (&mo->spawnpoint, mthing, sizeof(*mthing));	
+    mo->angle = ANG45 * (mthing->angle/45);
+
+    // pull it from the que
+    iquetail = (iquetail+1)&(ITEMQUESIZE-1);
+}
+
+
+
+
+//
+// P_SpawnPlayer
+// Called when a player is spawned on the level.
+// Most of the player structure stays unchanged
+//  between levels.
+//
+void P_SpawnPlayer (mapthing_t* mthing)
+{
+    player_t*		p;
+    fixed_t		x;
+    fixed_t		y;
+    fixed_t		z;
+
+    mobj_t*		mobj;
+
+    int			i;
+
+    // not playing?
+    if (!playeringame[mthing->type-1])
+	return;					
+		
+    p = &players[mthing->type-1];
+
+    if (p->playerstate == PST_REBORN)
+	G_PlayerReborn (mthing->type-1);
+
+    x 		= mthing->x << FRACBITS;
+    y 		= mthing->y << FRACBITS;
+    z		= ONFLOORZ;
+    mobj	= P_SpawnMobj (x,y,z, MT_PLAYER);
+
+    // set color translations for player sprites
+    if (mthing->type > 1)		
+	mobj->flags |= (mthing->type-1)<<MF_TRANSSHIFT;
+		
+    mobj->angle	= ANG45 * (mthing->angle/45);
+    mobj->player = p;
+    mobj->health = p->health;
+
+    p->mo = mobj;
+    p->playerstate = PST_LIVE;	
+    p->refire = 0;
+    p->message = NULL;
+    p->damagecount = 0;
+    p->bonuscount = 0;
+    p->extralight = 0;
+    p->fixedcolormap = 0;
+    p->viewheight = VIEWHEIGHT;
+
+    // setup gun psprite
+    P_SetupPsprites (p);
+    
+    // give all cards in death match mode
+    if (deathmatch)
+	for (i=0 ; i<NUMCARDS ; i++)
+	    p->cards[i] = true;
+			
+    if (mthing->type-1 == consoleplayer)
+    {
+	// wake up the status bar
+	ST_Start ();
+	// wake up the heads up text
+	HU_Start ();		
+    }
+}
+
+
+//
+// P_SpawnMapThing
+// The fields of the mapthing should
+// already be in host byte order.
+//
+void P_SpawnMapThing (mapthing_t* mthing)
+{
+    int			i;
+    int			bit;
+    mobj_t*		mobj;
+    fixed_t		x;
+    fixed_t		y;
+    fixed_t		z;
+		
+    // count deathmatch start positions
+    if (mthing->type == 11)
+    {
+	if (deathmatch_p < &deathmatchstarts[10])
+	{
+	    memcpy (deathmatch_p, mthing, sizeof(*mthing));
+	    deathmatch_p++;
+	}
+	return;
+    }
+	
+    // check for players specially
+    if (mthing->type <= MAXPLAYERS)
+    {
+	// save spots for respawning in network games
+	memcpy (&playerstarts[mthing->type-1], mthing, sizeof(*mthing));
+	if (!deathmatch)
+	    P_SpawnPlayer (mthing);
+
+	return;
+    }
+
+    // check for apropriate skill level
+    if (!netgame && (mthing->options & 16) )
+	return;
+		
+    if (gameskill == sk_baby)
+	bit = 1;
+    else if (gameskill == sk_nightmare)
+	bit = 4;
+    else
+	bit = 1<<(gameskill-1);
+
+    if (!(mthing->options & bit) )
+	return;
+	
+    // find which type to spawn
+    for (i=0 ; i< NUMMOBJTYPES ; i++)
+	if (mthing->type == mobjinfo[i].doomednum)
+	    break;
+	
+    if (i==NUMMOBJTYPES)
+	I_Error ("P_SpawnMapThing: Unknown type %i at (%i, %i)",
+		 mthing->type,
+		 mthing->x, mthing->y);
+		
+    // don't spawn keycards and players in deathmatch
+    if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH)
+	return;
+		
+    // don't spawn any monsters if -nomonsters
+    if (nomonsters
+	&& ( i == MT_SKULL
+	     || (mobjinfo[i].flags & MF_COUNTKILL)) )
+    {
+	return;
+    }
+    
+    // spawn it
+    x = mthing->x << FRACBITS;
+    y = mthing->y << FRACBITS;
+
+    if (mobjinfo[i].flags & MF_SPAWNCEILING)
+	z = ONCEILINGZ;
+    else
+	z = ONFLOORZ;
+    
+    mobj = P_SpawnMobj (x,y,z, i);
+    memcpy (&mobj->spawnpoint, mthing, sizeof(*mthing));
+
+    if (mobj->tics > 0)
+	mobj->tics = 1 + (P_Random () % mobj->tics);
+    if (mobj->flags & MF_COUNTKILL)
+	totalkills++;
+    if (mobj->flags & MF_COUNTITEM)
+	totalitems++;
+		
+    mobj->angle = ANG45 * (mthing->angle/45);
+    if (mthing->options & MTF_AMBUSH)
+	mobj->flags |= MF_AMBUSH;
+}
+
+
+
+//
+// GAME SPAWN FUNCTIONS
+//
+
+
+//
+// P_SpawnPuff
+//
+extern fixed_t attackrange;
+
+void
+P_SpawnPuff
+( fixed_t	x,
+  fixed_t	y,
+  fixed_t	z )
+{
+    mobj_t*	th;
+	
+    z += P_Random2()<<10;
+
+    th = P_SpawnMobj (x,y,z, MT_PUFF);
+    th->momz = FRACUNIT;
+    th->tics -= P_Random()&3;
+
+    if (th->tics < 1)
+	th->tics = 1;
+	
+    // don't make punches spark on the wall
+    if (attackrange == MELEERANGE)
+	P_SetMobjState (th, S_PUFF3);
+}
+
+
+
+//
+// P_SpawnBlood
+// 
+void
+P_SpawnBlood
+( fixed_t	x,
+  fixed_t	y,
+  fixed_t	z,
+  int		damage )
+{
+    mobj_t*	th;
+	
+    z += P_Random2()<<10;
+    th = P_SpawnMobj (x,y,z, MT_BLOOD);
+    th->momz = FRACUNIT*2;
+    th->tics -= P_Random()&3;
+
+    if (th->tics < 1)
+	th->tics = 1;
+		
+    if (damage <= 12 && damage >= 9)
+	P_SetMobjState (th,S_BLOOD2);
+    else if (damage < 9)
+	P_SetMobjState (th,S_BLOOD3);
+}
+
+
+
+//
+// P_CheckMissileSpawn
+// Moves the missile forward a bit
+//  and possibly explodes it right there.
+//
+void P_CheckMissileSpawn (mobj_t* th)
+{
+    th->tics -= P_Random()&3;
+    if (th->tics < 1)
+	th->tics = 1;
+    
+    // move a little forward so an angle can
+    // be computed if it immediately explodes
+    th->x += (th->momx>>1);
+    th->y += (th->momy>>1);
+    th->z += (th->momz>>1);
+
+    if (!P_TryMove (th, th->x, th->y))
+	P_ExplodeMissile (th);
+}
+
+
+//
+// P_SpawnMissile
+//
+mobj_t*
+P_SpawnMissile
+( mobj_t*	source,
+  mobj_t*	dest,
+  mobjtype_t	type )
+{
+    mobj_t*	th;
+    angle_t	an;
+    int		dist;
+
+    th = P_SpawnMobj (source->x,
+		      source->y,
+		      source->z + 4*8*FRACUNIT, type);
+    
+    if (th->info->seesound)
+	S_StartSound (th, th->info->seesound);
+
+    th->target = source;	// where it came from
+    an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y);	
+
+    // fuzzy player
+    if (dest->flags & MF_SHADOW)
+	an += P_Random2()<<20;	
+
+    th->angle = an;
+    an >>= ANGLETOFINESHIFT;
+    th->momx = FixedMul (th->info->speed, finecosine[an]);
+    th->momy = FixedMul (th->info->speed, finesine[an]);
+	
+    dist = P_AproxDistance (dest->x - source->x, dest->y - source->y);
+    dist = dist / th->info->speed;
+
+    if (dist < 1)
+	dist = 1;
+
+    th->momz = (dest->z - source->z) / dist;
+    P_CheckMissileSpawn (th);
+	
+    return th;
+}
+
+
+//
+// P_SpawnPlayerMissile
+// Tries to aim at a nearby monster
+//
+void
+P_SpawnPlayerMissile
+( mobj_t*	source,
+  mobjtype_t	type )
+{
+    mobj_t*	th;
+    angle_t	an;
+    
+    fixed_t	x;
+    fixed_t	y;
+    fixed_t	z;
+    fixed_t	slope;
+    
+    // see which target is to be aimed at
+    an = source->angle;
+    slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);
+    
+    if (!linetarget)
+    {
+	an += 1<<26;
+	slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);
+
+	if (!linetarget)
+	{
+	    an -= 2<<26;
+	    slope = P_AimLineAttack (source, an, 16*64*FRACUNIT);
+	}
+
+	if (!linetarget)
+	{
+	    an = source->angle;
+	    slope = 0;
+	}
+    }
+		
+    x = source->x;
+    y = source->y;
+    z = source->z + 4*8*FRACUNIT;
+	
+    th = P_SpawnMobj (x,y,z, type);
+
+    if (th->info->seesound)
+	S_StartSound (th, th->info->seesound);
+
+    th->target = source;
+    th->angle = an;
+    th->momx = FixedMul( th->info->speed,
+			 finecosine[an>>ANGLETOFINESHIFT]);
+    th->momy = FixedMul( th->info->speed,
+			 finesine[an>>ANGLETOFINESHIFT]);
+    th->momz = FixedMul( th->info->speed, slope);
+
+    P_CheckMissileSpawn (th);
+}
+
--- /dev/null
+++ b/sys/src/games/doom/s_sound.c
@@ -1,0 +1,803 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:  none
+//
+//-----------------------------------------------------------------------------
+
+
+static const char
+rcsid[] = "$Id: s_sound.c,v 1.6 1997/02/03 22:45:12 b1 Exp $";
+
+#include "i_system.h"
+#include "i_sound.h"
+#include "sounds.h"
+#include "s_sound.h"
+
+#include "z_zone.h"
+#include "m_random.h"
+#include "w_wad.h"
+
+#include "doomdef.h"
+#include "p_local.h"
+
+#include "doomstat.h"
+
+extern player_t *plyr;
+
+// Purpose?
+const char snd_prefixen[]
+= { 'P', 'P', 'A', 'S', 'S', 'S', 'M', 'M', 'M', 'S', 'S', 'S' };
+
+#define S_MAX_VOLUME		127
+
+// when to clip out sounds
+// Does not fit the large outdoor areas.
+#define S_CLIPPING_DIST		(1200*0x10000)
+
+// Distance tp origin when sounds should be maxed out.
+// This should relate to movement clipping resolution
+// (see BLOCKMAP handling).
+// Originally: (200*0x10000).
+#define S_CLOSE_DIST		(160*0x10000)
+
+
+#define S_ATTENUATOR		((S_CLIPPING_DIST-S_CLOSE_DIST)>>FRACBITS)
+
+// Adjustable by menu.
+#define NORM_VOLUME    		snd_MaxVolume
+
+#define NORM_PITCH     		128
+#define NORM_PRIORITY		64
+#define NORM_SEP		128
+
+#define S_PITCH_PERTURB		1
+#define S_STEREO_SWING		(96*0x10000)
+
+// percent attenuation from front to back
+#define S_IFRACVOL		30
+
+#define NA			0
+#define S_NUMCHANNELS		2
+
+
+// Current music/sfx card - index useless
+//  w/o a reference LUT in a sound module.
+extern int snd_MusicDevice;
+extern int snd_SfxDevice;
+// Config file? Same disclaimer as above.
+extern int snd_DesiredMusicDevice;
+extern int snd_DesiredSfxDevice;
+
+
+
+typedef struct
+{
+    // sound information (if null, channel avail.)
+    sfxinfo_t*	sfxinfo;
+
+    // origin of sound
+    void*	origin;
+
+    // handle of the sound being played
+    int		handle;
+    
+} channel_t;
+
+
+// the set of channels available
+static channel_t*	channels;
+
+// These are not used, but should be (menu).
+// Maximum volume of a sound effect.
+// Internal default is max out of 0-15.
+int 		snd_SfxVolume = 15;
+
+// Maximum volume of music. Useless so far.
+int 		snd_MusicVolume = 15; 
+
+
+
+// whether songs are mus_paused
+boolean		mus_paused;	
+
+// music currently being played
+musicinfo_t*	mus_playing=0;
+
+// following is set
+//  by the defaults code in M_misc:
+// number of channels available
+int			numChannels;	
+
+//
+// Internals.
+//
+int
+S_getChannel
+( void*		origin,
+  sfxinfo_t*	sfxinfo );
+
+
+int
+S_AdjustSoundParams
+( mobj_t*	listener,
+  mobj_t*	source,
+  int*		vol,
+  int*		sep,
+  int*		pitch );
+
+void S_StopChannel(int cnum);
+
+
+
+//
+// Initializes sound stuff, including volume
+// Sets channels, SFX and music volume,
+//  allocates channel buffer, sets S_sfx lookup.
+//
+void S_Init
+( int		sfxVolume,
+  int		musicVolume )
+{  
+  int		i;
+
+  printf("S_Init: default sfx volume %d\n", sfxVolume);
+
+  // Whatever these did with DMX, these are rather dummies now.
+  I_SetChannels();
+  
+  S_SetSfxVolume(sfxVolume);
+  // No music with Linux - another dummy.
+  S_SetMusicVolume(musicVolume);
+
+  // Allocating the internal channels for mixing
+  // (the maximum numer of sounds rendered
+  // simultaneously) within zone memory.
+  channels =
+    (channel_t *) Z_Malloc(numChannels*sizeof(channel_t), PU_STATIC, 0);
+  
+  // Free all channels for use
+  for (i=0 ; i<numChannels ; i++)
+    channels[i].sfxinfo = 0;
+  
+  // no sounds are playing, and they are not mus_paused
+  mus_paused = 0;
+}
+
+
+
+
+//
+// Per level startup code.
+// Kills playing sounds at start of level,
+//  determines music if any, changes music.
+//
+void S_Start(void)
+{
+  int cnum;
+  int mnum;
+
+  // kill all playing sounds at start of level
+  //  (trust me - a good idea)
+  for (cnum=0 ; cnum<numChannels ; cnum++)
+    if (channels[cnum].sfxinfo)
+      S_StopChannel(cnum);
+  
+  // start new music for the level
+  mus_paused = 0;
+  
+  if (gamemode == commercial)
+    mnum = mus_runnin + gamemap - 1;
+  else
+  {
+    int spmus[]=
+    {
+      // Song - Who? - Where?
+      
+      mus_e3m4,	// American	e4m1
+      mus_e3m2,	// Romero	e4m2
+      mus_e3m3,	// Shawn	e4m3
+      mus_e1m5,	// American	e4m4
+      mus_e2m7,	// Tim 	e4m5
+      mus_e2m4,	// Romero	e4m6
+      mus_e2m6,	// J.Anderson	e4m7 CHIRON.WAD
+      mus_e2m5,	// Shawn	e4m8
+      mus_e1m9	// Tim		e4m9
+    };
+    
+    if (gameepisode < 4)
+      mnum = mus_e1m1 + (gameepisode-1)*9 + gamemap-1;
+    else
+      mnum = spmus[gamemap-1];
+    }	
+  
+  // HACK FOR COMMERCIAL
+  //  if (commercial && mnum > mus_e3m9)	
+  //      mnum -= mus_e3m9;
+  
+  S_ChangeMusic(mnum, true);
+}	
+
+
+
+
+
+void
+S_StartSoundAtVolume
+( void*		origin_p,
+  int		sfx_id,
+  int		volume )
+{
+
+  int		rc;
+  int		sep;
+  int		pitch;
+  int		priority;
+  sfxinfo_t*	sfx;
+  int		cnum;
+  
+  mobj_t*	origin = (mobj_t *) origin_p;
+  
+  
+  // check for bogus sound #
+  if (sfx_id < 1 || sfx_id > NUMSFX)
+    I_Error("Bad sfx #: %d", sfx_id);
+  
+  sfx = &S_sfx[sfx_id];
+  
+  // Initialize sound parameters
+  if (sfx->link)
+  {
+    pitch = sfx->pitch;
+    priority = sfx->priority;
+    volume += sfx->volume;
+    
+    if (volume < 1)
+      return;
+    
+    if (volume > snd_SfxVolume)
+      volume = snd_SfxVolume;
+  }	
+  else
+  {
+    pitch = NORM_PITCH;
+    priority = NORM_PRIORITY;
+  }
+
+
+  // Check to see if it is audible,
+  //  and if not, modify the params
+  if (origin && origin != plyr->mo)
+  {
+    rc = S_AdjustSoundParams(plyr->mo,
+			     origin,
+			     &volume,
+			     &sep,
+			     &pitch);
+	
+    if ( origin->x == plyr->mo->x
+	 && origin->y == plyr->mo->y)
+    {	
+      sep = NORM_SEP;
+    }
+    
+    if (!rc)
+      return;
+  }	
+  else
+  {
+    sep = NORM_SEP;
+  }
+  
+  // hacks to vary the sfx pitches
+  if (sfx_id >= sfx_sawup
+      && sfx_id <= sfx_sawhit)
+  {	
+    pitch += 8 - (M_Random()&15);
+    
+    if (pitch<0)
+      pitch = 0;
+    else if (pitch>255)
+      pitch = 255;
+  }
+  else if (sfx_id != sfx_itemup
+	   && sfx_id != sfx_tink)
+  {
+    pitch += 16 - (M_Random()&31);
+    
+    if (pitch<0)
+      pitch = 0;
+    else if (pitch>255)
+      pitch = 255;
+  }
+
+  // kill old sound
+  S_StopSound(origin);
+
+  // try to find a channel
+  cnum = S_getChannel(origin, sfx);
+  
+  if (cnum<0)
+    return;
+
+  // Assigns the handle to one of the channels in the
+  //  mix/output buffer.
+  channels[cnum].handle = I_StartSound(sfx_id,
+				       volume,
+				       sep,
+				       pitch,
+				       priority);
+}	
+
+void
+S_StartSound
+( void*		origin,
+  int		sfx_id )
+{
+#ifdef SAWDEBUG
+    // if (sfx_id == sfx_sawful)
+    // sfx_id = sfx_itemup;
+#endif
+  
+    S_StartSoundAtVolume(origin, sfx_id, snd_SfxVolume);
+
+
+    // UNUSED. We had problems, had we not?
+#ifdef SAWDEBUG
+{
+    int i;
+    int n;
+	
+    static mobj_t*      last_saw_origins[10] = {1,1,1,1,1,1,1,1,1,1};
+    static int		first_saw=0;
+    static int		next_saw=0;
+	
+    if (sfx_id == sfx_sawidl
+	|| sfx_id == sfx_sawful
+	|| sfx_id == sfx_sawhit)
+    {
+	for (i=first_saw;i!=next_saw;i=(i+1)%10)
+	    if (last_saw_origins[i] != origin)
+		fprintf(stderr, "old origin 0x%lx != "
+			"origin 0x%lx for sfx %d\n",
+			last_saw_origins[i],
+			origin,
+			sfx_id);
+	    
+	last_saw_origins[next_saw] = origin;
+	next_saw = (next_saw + 1) % 10;
+	if (next_saw == first_saw)
+	    first_saw = (first_saw + 1) % 10;
+	    
+	for (n=i=0; i<numChannels ; i++)
+	{
+	    if (channels[i].sfxinfo == &S_sfx[sfx_sawidl]
+		|| channels[i].sfxinfo == &S_sfx[sfx_sawful]
+		|| channels[i].sfxinfo == &S_sfx[sfx_sawhit]) n++;
+	}
+	    
+	if (n>1)
+	{
+	    for (i=0; i<numChannels ; i++)
+	    {
+		if (channels[i].sfxinfo == &S_sfx[sfx_sawidl]
+		    || channels[i].sfxinfo == &S_sfx[sfx_sawful]
+		    || channels[i].sfxinfo == &S_sfx[sfx_sawhit])
+		{
+		    fprintf(stderr,
+			    "chn: sfxinfo=0x%lx, origin=0x%lx, "
+			    "handle=%d\n",
+			    channels[i].sfxinfo,
+			    channels[i].origin,
+			    channels[i].handle);
+		}
+	    }
+	    fprintf(stderr, "\n");
+	}
+    }
+}
+#endif
+ 
+}
+
+
+
+
+void S_StopSound(void *origin)
+{
+
+    int cnum;
+
+    for (cnum=0 ; cnum<numChannels ; cnum++)
+    {
+	if (channels[cnum].sfxinfo && channels[cnum].origin == origin)
+	{
+	    S_StopChannel(cnum);
+	    break;
+	}
+    }
+}
+
+
+
+
+
+
+
+
+
+//
+// Stop and resume music, during game PAUSE.
+//
+void S_PauseSound(void)
+{
+    if (mus_playing && !mus_paused)
+    {
+	I_PauseSong(mus_playing->handle);
+	mus_paused = true;
+    }
+}
+
+void S_ResumeSound(void)
+{
+    if (mus_playing && mus_paused)
+    {
+	I_ResumeSong(mus_playing->handle);
+	mus_paused = false;
+    }
+}
+
+
+//
+// Updates music & sounds
+//
+void S_UpdateSounds(void* listener_p)
+{
+    int		audible;
+    int		cnum;
+    int		volume;
+    int		sep;
+    int		pitch;
+    sfxinfo_t*	sfx;
+    channel_t*	c;
+    
+    mobj_t*	listener = (mobj_t*)listener_p;
+    
+    for (cnum=0 ; cnum<numChannels ; cnum++)
+    {
+	c = &channels[cnum];
+	sfx = c->sfxinfo;
+
+	if (c->sfxinfo)
+	{
+	    if (I_SoundIsPlaying(c->handle))
+	    {
+		// initialize parameters
+		volume = snd_SfxVolume;
+		pitch = NORM_PITCH;
+		sep = NORM_SEP;
+
+		if (sfx->link)
+		{
+		    pitch = sfx->pitch;
+		    volume += sfx->volume;
+		    if (volume < 1)
+		    {
+			S_StopChannel(cnum);
+			continue;
+		    }
+		    else if (volume > snd_SfxVolume)
+		    {
+			volume = snd_SfxVolume;
+		    }
+		}
+
+		// check non-local sounds for distance clipping
+		//  or modify their params
+		if (c->origin && listener_p != c->origin)
+		{
+		    audible = S_AdjustSoundParams(listener,
+						  c->origin,
+						  &volume,
+						  &sep,
+						  &pitch);
+		    
+		    if (!audible)
+		    {
+			S_StopChannel(cnum);
+		    }
+		    else
+			I_UpdateSoundParams(c->handle, volume, sep, pitch);
+		}
+	    }
+	    else
+	    {
+		// if channel is allocated but sound has stopped,
+		//  free it
+		S_StopChannel(cnum);
+	    }
+	}
+    }
+    // kill music if it is a single-play && finished
+    // if (	mus_playing
+    //      && !I_QrySongPlaying(mus_playing->handle)
+    //      && !mus_paused )
+    // S_StopMusic();
+}
+
+
+void S_SetMusicVolume(int volume)
+{
+    if (volume < 0 || volume > 127)
+    {
+	I_Error("Attempt to set music volume at %d",
+		volume);
+    }    
+
+    I_SetMusicVolume(volume);
+    snd_MusicVolume = volume;
+}
+
+
+
+void S_SetSfxVolume(int volume)
+{
+
+    if (volume < 0 || volume > 127)
+	I_Error("Attempt to set sfx volume at %d", volume);
+
+    snd_SfxVolume = volume;
+
+}
+
+//
+// Starts some music with the music id found in sounds.h.
+//
+void S_StartMusic(int m_id)
+{
+    S_ChangeMusic(m_id, false);
+}
+
+void
+S_ChangeMusic
+( int			musicnum,
+  int			looping )
+{
+    musicinfo_t*	music;
+    char		namebuf[9];
+
+    if ( (musicnum <= mus_None)
+	 || (musicnum >= NUMMUSIC) )
+    {
+        music = nil;
+	I_Error("Bad music number %d", musicnum);
+    }
+    else
+	music = &S_music[musicnum];
+
+    if (mus_playing == music)
+	return;
+
+    // shutdown old music
+    S_StopMusic();
+
+    // get lumpnum if neccessary
+    if (!music->lumpnum)
+    {
+	sprintf(namebuf, "d_%s", music->name);
+	music->lumpnum = W_GetNumForName(namebuf);
+    }
+
+    // load & register it
+    music->data = (void *) W_CacheLumpNum(music->lumpnum, PU_MUSIC);
+    music->handle = 0;
+
+    // play it
+    I_PlaySong(music, looping);
+
+    mus_playing = music;
+}
+
+
+void S_StopMusic(void)
+{
+    if (mus_playing)
+    {
+	if (mus_paused)
+	    I_ResumeSong(mus_playing->handle);
+
+	I_StopSong(mus_playing->handle);
+	Z_ChangeTag(mus_playing->data, PU_CACHE);
+	
+	mus_playing->data = 0;
+	mus_playing = 0;
+    }
+}
+
+
+
+
+void S_StopChannel(int cnum)
+{
+
+    int		i;
+    channel_t*	c = &channels[cnum];
+
+    if (c->sfxinfo)
+    {
+	// stop the sound playing
+	if (I_SoundIsPlaying(c->handle))
+	{
+#ifdef SAWDEBUG
+	    if (c->sfxinfo == &S_sfx[sfx_sawful])
+		fprintf(stderr, "stopped\n");
+#endif
+	    I_StopSound(c->handle);
+	}
+
+	// check to see
+	//  if other channels are playing the sound
+	for (i=0 ; i<numChannels ; i++)
+	{
+	    if (cnum != i
+		&& c->sfxinfo == channels[i].sfxinfo)
+	    {
+		break;
+	    }
+	}
+
+	c->sfxinfo = 0;
+    }
+}
+
+
+
+//
+// Changes volume, stereo-separation, and pitch variables
+//  from the norm of a sound effect to be played.
+// If the sound is not audible, returns a 0.
+// Otherwise, modifies parameters and returns 1.
+//
+int
+S_AdjustSoundParams
+( mobj_t*	listener,
+  mobj_t*	source,
+  int*		vol,
+  int*		sep,
+  int*		/*pitch*/ )
+{
+    fixed_t	approx_dist;
+    fixed_t	adx;
+    fixed_t	ady;
+    angle_t	angle;
+
+    // calculate the distance to sound origin
+    //  and clip it if necessary
+    adx = abs(listener->x - source->x);
+    ady = abs(listener->y - source->y);
+
+    // From _GG1_ p.428. Appox. eucledian distance fast.
+    approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1);
+    
+    if (gamemap != 8
+	&& approx_dist > S_CLIPPING_DIST)
+    {
+	return 0;
+    }
+    
+    // angle of source to listener
+    angle = R_PointToAngle2(listener->x,
+			    listener->y,
+			    source->x,
+			    source->y);
+
+    if (angle > listener->angle)
+	angle = angle - listener->angle;
+    else
+	angle = angle + (0xffffffff - listener->angle);
+
+    angle >>= ANGLETOFINESHIFT;
+
+    // stereo separation
+    *sep = 128 - (FixedMul(S_STEREO_SWING,finesine[angle])>>FRACBITS);
+
+    // volume calculation
+    if (approx_dist < S_CLOSE_DIST)
+    {
+	*vol = snd_SfxVolume;
+    }
+    else if (gamemap == 8)
+    {
+	if (approx_dist > S_CLIPPING_DIST)
+	    approx_dist = S_CLIPPING_DIST;
+
+	*vol = 15+ ((snd_SfxVolume-15)
+		    *((S_CLIPPING_DIST - approx_dist)>>FRACBITS))
+	    / S_ATTENUATOR;
+    }
+    else
+    {
+	// distance effect
+	*vol = (snd_SfxVolume
+		* ((S_CLIPPING_DIST - approx_dist)>>FRACBITS))
+	    / S_ATTENUATOR; 
+    }
+    
+    return (*vol > 0);
+}
+
+
+
+
+//
+// S_getChannel :
+//   If none available, return -1.  Otherwise channel #.
+//
+int
+S_getChannel
+( void*		origin,
+  sfxinfo_t*	sfxinfo )
+{
+    // channel number to use
+    int		cnum;
+    
+    channel_t*	c;
+
+    // Find an open channel
+    for (cnum=0 ; cnum<numChannels ; cnum++)
+    {
+	if (!channels[cnum].sfxinfo)
+	    break;
+	else if (origin &&  channels[cnum].origin ==  origin)
+	{
+	    S_StopChannel(cnum);
+	    break;
+	}
+    }
+
+    // None available
+    if (cnum == numChannels)
+    {
+	// Look for lower priority
+	for (cnum=0 ; cnum<numChannels ; cnum++)
+	    if (channels[cnum].sfxinfo->priority >= sfxinfo->priority) break;
+
+	if (cnum == numChannels)
+	{
+	    // FUCK!  No lower priority.  Sorry, Charlie.    
+	    return -1;
+	}
+	else
+	{
+	    // Otherwise, kick out lower priority.
+	    S_StopChannel(cnum);
+	}
+    }
+
+    c = &channels[cnum];
+
+    // channel is decided to be cnum.
+    c->sfxinfo = sfxinfo;
+    c->origin = origin;
+
+    return cnum;
+}
+
+
+
+
--- /dev/null
+++ b/sys/src/games/doom/st_stuff.c
@@ -1,0 +1,1468 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+//	Status bar code.
+//	Does the face/direction indicator animatin.
+//	Does palette indicators as well (red pain/berserk, bright pickup)
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: st_stuff.c,v 1.6 1997/02/03 22:45:13 b1 Exp $";
+
+#include "i_system.h"
+#include "i_video.h"
+#include "z_zone.h"
+#include "m_random.h"
+#include "w_wad.h"
+
+#include "doomdef.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
+
+	    
+// main player in game
+player_t*	plyr; 
+
+// ST_Start() has just been called
+static boolean		st_firsttime;
+
+// used to execute ST_Init() only once
+static int		veryfirsttime = 1;
+
+// 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;  
+
+
+
+// Massive bunches of cheat shit
+//  to keep it from being easy to figure them out.
+// Yeah, right...
+unsigned char	cheat_mus_seq[] =
+{
+    0xb2, 0x26, 0xb6, 0xae, 0xea, 1, 0, 0, 0xff
+};
+
+unsigned char	cheat_choppers_seq[] =
+{
+    0xb2, 0x26, 0xe2, 0x32, 0xf6, 0x2a, 0x2a, 0xa6, 0x6a, 0xea, 0xff // id...
+};
+
+unsigned char	cheat_god_seq[] =
+{
+    0xb2, 0x26, 0x26, 0xaa, 0x26, 0xff  // iddqd
+};
+
+unsigned char	cheat_ammo_seq[] =
+{
+    0xb2, 0x26, 0xf2, 0x66, 0xa2, 0xff	// idkfa
+};
+
+unsigned char	cheat_ammonokey_seq[] =
+{
+    0xb2, 0x26, 0x66, 0xa2, 0xff	// idfa
+};
+
+
+// Smashing Pumpkins Into Samml Piles Of Putried Debris. 
+unsigned char	cheat_noclip_seq[] =
+{
+    0xb2, 0x26, 0xea, 0x2a, 0xb2,	// idspispopd
+    0xea, 0x2a, 0xf6, 0x2a, 0x26, 0xff
+};
+
+//
+unsigned char	cheat_commercial_noclip_seq[] =
+{
+    0xb2, 0x26, 0xe2, 0x36, 0xb2, 0x2a, 0xff	// idclip
+}; 
+
+
+
+unsigned char	cheat_powerup_seq[7][10] =
+{
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6e, 0xff }, 	// beholdv
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xea, 0xff }, 	// beholds
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xb2, 0xff }, 	// beholdi
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6a, 0xff }, 	// beholdr
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xa2, 0xff }, 	// beholda
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x36, 0xff }, 	// beholdl
+    { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xff }		// behold
+};
+
+
+unsigned char	cheat_clev_seq[] =
+{
+    0xb2, 0x26,  0xe2, 0x36, 0xa6, 0x6e, 1, 0, 0, 0xff	// idclev
+};
+
+
+// my position cheat
+unsigned char	cheat_mypos_seq[] =
+{
+    0xb2, 0x26, 0xb6, 0xba, 0x2a, 0xf6, 0xea, 0xff	// idmypos
+}; 
+
+
+// Now what?
+cheatseq_t	cheat_mus = { cheat_mus_seq, 0 };
+cheatseq_t	cheat_god = { cheat_god_seq, 0 };
+cheatseq_t	cheat_ammo = { cheat_ammo_seq, 0 };
+cheatseq_t	cheat_ammonokey = { cheat_ammonokey_seq, 0 };
+cheatseq_t	cheat_noclip = { cheat_noclip_seq, 0 };
+cheatseq_t	cheat_commercial_noclip = { cheat_commercial_noclip_seq, 0 };
+
+cheatseq_t	cheat_powerup[7] =
+{
+    { cheat_powerup_seq[0], 0 },
+    { cheat_powerup_seq[1], 0 },
+    { cheat_powerup_seq[2], 0 },
+    { cheat_powerup_seq[3], 0 },
+    { cheat_powerup_seq[4], 0 },
+    { cheat_powerup_seq[5], 0 },
+    { cheat_powerup_seq[6], 0 }
+};
+
+cheatseq_t	cheat_choppers = { cheat_choppers_seq, 0 };
+cheatseq_t	cheat_clev = { cheat_clev_seq, 0 };
+cheatseq_t	cheat_mypos = { cheat_mypos_seq, 0 };
+
+
+// 
+extern char*	mapnames[];
+
+
+//
+// STATUS BAR CODE
+//
+void ST_Stop(void);
+
+void ST_refreshBackground(void)
+{
+
+    if (st_statusbaron)
+    {
+	V_DrawPatch(ST_X, 0, BG, sbar);
+
+	if (netgame)
+	    V_DrawPatch(ST_FX, 0, BG, faceback);
+
+	V_CopyRect(ST_X, 0, BG, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y, FG);
+    }
+
+}
+
+
+// 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)
+    {
+      // b. - enabled for more debug fun.
+      // if (gameskill != sk_nightmare) {
+      
+      // 'dqd' cheat for toggleable god mode
+      if (cht_CheckCheat(&cheat_god, ev->data1))
+      {
+	plyr->cheats ^= CF_GODMODE;
+	if (plyr->cheats & CF_GODMODE)
+	{
+	  if (plyr->mo)
+	    plyr->mo->health = 100;
+	  
+	  plyr->health = 100;
+	  plyr->message = STSTR_DQDON;
+	}
+	else 
+	  plyr->message = STSTR_DQDOFF;
+      }
+      // 'fa' cheat for killer fucking arsenal
+      else if (cht_CheckCheat(&cheat_ammonokey, ev->data1))
+      {
+	plyr->armorpoints = 200;
+	plyr->armortype = 2;
+	
+	for (i=0;i<NUMWEAPONS;i++)
+	  plyr->weaponowned[i] = true;
+	
+	for (i=0;i<NUMAMMO;i++)
+	  plyr->ammo[i] = plyr->maxammo[i];
+	
+	plyr->message = STSTR_FAADDED;
+      }
+      // 'kfa' cheat for key full ammo
+      else if (cht_CheckCheat(&cheat_ammo, ev->data1))
+      {
+	plyr->armorpoints = 200;
+	plyr->armortype = 2;
+	
+	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 = STSTR_KFAADDED;
+      }
+      // 'mus' cheat for changing music
+      else if (cht_CheckCheat(&cheat_mus, ev->data1))
+      {
+	
+	char	buf[3];
+	int		musnum;
+	
+	plyr->message = STSTR_MUS;
+	cht_GetParam(&cheat_mus, buf);
+	
+	if (gamemode == commercial)
+	{
+	  musnum = (buf[0]-'0')*10 + buf[1]-'0' - 1;
+	  
+	  if (musnum < 0 || musnum > 34)
+	    plyr->message = STSTR_NOMUS;
+	  else
+	    S_ChangeMusic(mus_runnin + musnum, 1);
+	}
+	else
+	{
+	  musnum = (buf[0]-'1')*9 + buf[1]-'1';
+	  
+	  if (musnum < 0 || musnum > 31)
+	    plyr->message = STSTR_NOMUS;
+	  else
+	    S_ChangeMusic(mus_e1m1 + musnum, 1);
+	}
+      }
+      // Simplified, accepting both "noclip" and "idspispopd".
+      // no clipping mode cheat
+      else if ( cht_CheckCheat(&cheat_noclip, ev->data1) 
+		|| cht_CheckCheat(&cheat_commercial_noclip,ev->data1) )
+      {	
+	plyr->cheats ^= CF_NOCLIP;
+	
+	if (plyr->cheats & CF_NOCLIP)
+	  plyr->message = STSTR_NCON;
+	else
+	  plyr->message = STSTR_NCOFF;
+      }
+      // 'behold?' power-up cheats
+      for (i=0;i<6;i++)
+      {
+	if (cht_CheckCheat(&cheat_powerup[i], ev->data1))
+	{
+	  if (!plyr->powers[i])
+	    P_GivePower( plyr, i);
+	  else if (i!=pw_strength)
+	    plyr->powers[i] = 1;
+	  else
+	    plyr->powers[i] = 0;
+	  
+	  plyr->message = STSTR_BEHOLDX;
+	}
+      }
+      
+      // 'behold' power-up menu
+      if (cht_CheckCheat(&cheat_powerup[6], ev->data1))
+      {
+	plyr->message = STSTR_BEHOLD;
+      }
+      // 'choppers' invulnerability & chainsaw
+      else if (cht_CheckCheat(&cheat_choppers, ev->data1))
+      {
+	plyr->weaponowned[wp_chainsaw] = true;
+	plyr->powers[pw_invulnerability] = true;
+	plyr->message = STSTR_CHOPPERS;
+      }
+      // 'mypos' for player position
+      else if (cht_CheckCheat(&cheat_mypos, ev->data1))
+      {
+	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 (cht_CheckCheat(&cheat_clev, ev->data1))
+    {
+      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';
+      }
+
+      // 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 = 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 (st_oldhealth - plyr->health > 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 (st_oldhealth - plyr->health > 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;
+
+    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();
+
+}
+
+void ST_loadGraphics(void)
+{
+
+    int		i;
+    int		j;
+    int		facenum;
+    
+    char	namebuf[9];
+
+    // Load the numbers, tall and short
+    for (i=0;i<10;i++)
+    {
+	sprintf(namebuf, "STTNUM%d", i);
+	tallnum[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC);
+
+	sprintf(namebuf, "STYSNUM%d", i);
+	shortnum[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC);
+    }
+
+    // Load percent key.
+    //Note: why not load STMINUS here, too?
+    tallpercent = (patch_t *) W_CacheLumpName("STTPRCNT", PU_STATIC);
+
+    // key cards
+    for (i=0;i<NUMCARDS;i++)
+    {
+	sprintf(namebuf, "STKEYS%d", i);
+	keys[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC);
+    }
+
+    // arms background
+    armsbg = (patch_t *) W_CacheLumpName("STARMS", PU_STATIC);
+
+    // arms ownership widgets
+    for (i=0;i<6;i++)
+    {
+	sprintf(namebuf, "STGNUM%d", i+2);
+
+	// gray #
+	arms[i][0] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC);
+
+	// yellow #
+	arms[i][1] = shortnum[i+2]; 
+    }
+
+    // face backgrounds for different color players
+    sprintf(namebuf, "STFB%d", consoleplayer);
+    faceback = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC);
+
+    // status bar background bits
+    sbar = (patch_t *) W_CacheLumpName("STBAR", PU_STATIC);
+
+    // face states
+    facenum = 0;
+    for (i=0;i<ST_NUMPAINFACES;i++)
+    {
+	for (j=0;j<ST_NUMSTRAIGHTFACES;j++)
+	{
+	    sprintf(namebuf, "STFST%d%d", i, j);
+	    faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+	}
+	sprintf(namebuf, "STFTR%d0", i);	// turn right
+	faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+	sprintf(namebuf, "STFTL%d0", i);	// turn left
+	faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+	sprintf(namebuf, "STFOUCH%d", i);	// ouch!
+	faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+	sprintf(namebuf, "STFEVL%d", i);	// evil grin ;)
+	faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+	sprintf(namebuf, "STFKILL%d", i);	// pissed off
+	faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
+    }
+    faces[facenum++] = W_CacheLumpName("STFGOD0", PU_STATIC);
+    faces[facenum  ] = W_CacheLumpName("STFDEAD0", PU_STATIC);
+
+}
+
+void ST_loadData(void)
+{
+    lu_palette = W_GetNumForName ("PLAYPAL");
+    ST_loadGraphics();
+}
+
+void ST_unloadGraphics(void)
+{
+
+    int i;
+
+    // unload the numbers, tall and short
+    for (i=0;i<10;i++)
+    {
+	Z_ChangeTag(tallnum[i], PU_CACHE);
+	Z_ChangeTag(shortnum[i], PU_CACHE);
+    }
+    // unload tall percent
+    Z_ChangeTag(tallpercent, PU_CACHE); 
+
+    // unload arms background
+    Z_ChangeTag(armsbg, PU_CACHE); 
+
+    // unload gray #'s
+    for (i=0;i<6;i++)
+	Z_ChangeTag(arms[i][0], PU_CACHE);
+    
+    // unload the key cards
+    for (i=0;i<NUMCARDS;i++)
+	Z_ChangeTag(keys[i], PU_CACHE);
+
+    Z_ChangeTag(sbar, PU_CACHE);
+    Z_ChangeTag(faceback, PU_CACHE);
+
+    for (i=0;i<ST_NUMFACES;i++)
+	Z_ChangeTag(faces[i], PU_CACHE);
+
+    // Note: nobody ain't seen no unloading
+    //   of stminus yet. Dude.
+    
+
+}
+
+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)
+{
+    veryfirsttime = 0;
+    ST_loadData();
+    screens[4] = (byte *) Z_Malloc(ST_WIDTH*ST_HEIGHT, PU_STATIC, 0);
+}
--- /dev/null
+++ b/sys/src/games/doom/w_wad.h
@@ -1,0 +1,77 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This source is available for distribution and/or modification
+// only under the terms of the DOOM Source Code License as
+// published by id Software. All rights reserved.
+//
+// The source is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
+// for more details.
+//
+// DESCRIPTION:
+//	WAD I/O functions.
+//
+//-----------------------------------------------------------------------------
+
+
+#ifndef __W_WAD__
+#define __W_WAD__
+
+
+/* TYPES */
+typedef struct
+{
+	/* Should be "IWAD" or "PWAD" */
+	char	identification[4];		
+	int	numlumps;
+	int	infotableofs;
+} wadinfo_t;
+
+
+typedef struct
+{
+	int	filepos;
+	int	size;
+	char	name[8];
+} filelump_t;
+
+/* WADFILE I/O related stuff */
+typedef struct
+{
+	char	name[8];
+	int	handle;
+	int	position;
+	int	size;
+} lumpinfo_t;
+
+
+extern	void**		lumpcache;
+extern	lumpinfo_t*	lumpinfo;
+extern	int		numlumps;
+
+void    W_InitMultipleFiles (char** filenames);
+void    W_Reload (void);
+
+int	W_CheckNumForName (char* name);
+int	W_GetNumForName (char* name);
+
+int	W_LumpLength (int lump);
+void    W_ReadLump (int lump, void *dest);
+
+void*	W_CacheLumpNum (int lump, int tag);
+void*	W_CacheLumpName (char* name, int tag);
+
+vlong	filelength(int);
+
+#endif
+//-----------------------------------------------------------------------------
+//
+// $Log:$
+//
+//-----------------------------------------------------------------------------