//-----------------------------------------------------------------------------
//
// 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.
//
//-----------------------------------------------------------------------------
/* Pure DOOM usage

   Do this:
      #define DOOM_IMPLEMENTATION
   before you include this file in *one* C or C++ file to create the implementation.

   // i.e. it should look like this:
   #include ...
   #include ...
   #include ...
   #define DOOM_IMPLEMENTATION
   #include "PureDOOM.h"

*/
//-----------------------------------------------------------------------------


#ifndef __DOOM_H__
#define __DOOM_H__


// Sample rate of sound samples from doom
#define DOOM_SAMPLERATE 11025

// MIDI tick needs to be called 140 times per seconds
#define DOOM_MIDI_RATE 140

// Hide menu options. If for say your platform doesn't support mouse or
// MIDI playback, you can hide these settings from the menu.
#define DOOM_FLAG_HIDE_MOUSE_OPTIONS 1 // Remove mouse options from menu
#define DOOM_FLAG_HIDE_SOUND_OPTIONS 2 // Remove sound options from menu
#define DOOM_FLAG_HIDE_MUSIC_OPTIONS 4 // Remove music options from menu

// Darken background when menu is open, making it more readable. This
// uses a bit more CPU and redraws the HUD every frame
#define DOOM_FLAG_MENU_DARKEN_BG 8


#if __cplusplus
extern "C" {
#endif

typedef enum
{
    DOOM_SEEK_CUR = 1,
    DOOM_SEEK_END = 2,
    DOOM_SEEK_SET = 0
} doom_seek_t;


typedef void(*doom_print_fn)(const char* str);
typedef void*(*doom_malloc_fn)(int size);
typedef void(*doom_free_fn)(void* ptr);
typedef void*(*doom_open_fn)(const char* filename, const char* mode);
typedef void(*doom_close_fn)(void* handle);
typedef int(*doom_read_fn)(void* handle, void *buf, int count);
typedef int(*doom_write_fn)(void* handle, const void *buf, int count);
typedef int(*doom_seek_fn)(void* handle, int offset, doom_seek_t origin);
typedef int(*doom_tell_fn)(void* handle);
typedef int(*doom_eof_fn)(void* handle);
typedef void(*doom_gettime_fn)(int* sec, int* usec);
typedef void(*doom_exit_fn)(int code);
typedef char*(*doom_getenv_fn)(const char* var);


// Doom key mapping
typedef enum
{
    DOOM_KEY_UNKNOWN = -1,
    DOOM_KEY_TAB = 9,
    DOOM_KEY_ENTER = 13,
    DOOM_KEY_ESCAPE = 27,
    DOOM_KEY_SPACE = 32,
    DOOM_KEY_APOSTROPHE = '\'',
    DOOM_KEY_MULTIPLY = '*',
    DOOM_KEY_COMMA = ',',
    DOOM_KEY_MINUS = 0x2d,
    DOOM_KEY_PERIOD = '.',
    DOOM_KEY_SLASH = '/',
    DOOM_KEY_0 = '0',
    DOOM_KEY_1 = '1',
    DOOM_KEY_2 = '2',
    DOOM_KEY_3 = '3',
    DOOM_KEY_4 = '4',
    DOOM_KEY_5 = '5',
    DOOM_KEY_6 = '6',
    DOOM_KEY_7 = '7',
    DOOM_KEY_8 = '8',
    DOOM_KEY_9 = '9',
    DOOM_KEY_SEMICOLON = ';',
    DOOM_KEY_EQUALS = 0x3d,
    DOOM_KEY_LEFT_BRACKET = '[',
    DOOM_KEY_RIGHT_BRACKET = ']',
    DOOM_KEY_A = 'a',
    DOOM_KEY_B = 'b',
    DOOM_KEY_C = 'c',
    DOOM_KEY_D = 'd',
    DOOM_KEY_E = 'e',
    DOOM_KEY_F = 'f',
    DOOM_KEY_G = 'g',
    DOOM_KEY_H = 'h',
    DOOM_KEY_I = 'i',
    DOOM_KEY_J = 'j',
    DOOM_KEY_K = 'k',
    DOOM_KEY_L = 'l',
    DOOM_KEY_M = 'm',
    DOOM_KEY_N = 'n',
    DOOM_KEY_O = 'o',
    DOOM_KEY_P = 'p',
    DOOM_KEY_Q = 'q',
    DOOM_KEY_R = 'r',
    DOOM_KEY_S = 's',
    DOOM_KEY_T = 't',
    DOOM_KEY_U = 'u',
    DOOM_KEY_V = 'v',
    DOOM_KEY_W = 'w',
    DOOM_KEY_X = 'x',
    DOOM_KEY_Y = 'y',
    DOOM_KEY_Z = 'z',
    DOOM_KEY_BACKSPACE = 127,
    DOOM_KEY_CTRL = (0x80 + 0x1d), // Both left and right
    DOOM_KEY_LEFT_ARROW = 0xac,
    DOOM_KEY_UP_ARROW = 0xad,
    DOOM_KEY_RIGHT_ARROW = 0xae,
    DOOM_KEY_DOWN_ARROW = 0xaf,
    DOOM_KEY_SHIFT = (0x80 + 0x36), // Both left and right
    DOOM_KEY_ALT = (0x80 + 0x38), // Both left and right
    DOOM_KEY_F1 = (0x80 + 0x3b),
    DOOM_KEY_F2 = (0x80 + 0x3c),
    DOOM_KEY_F3 = (0x80 + 0x3d),
    DOOM_KEY_F4 = (0x80 + 0x3e),
    DOOM_KEY_F5 = (0x80 + 0x3f),
    DOOM_KEY_F6 = (0x80 + 0x40),
    DOOM_KEY_F7 = (0x80 + 0x41),
    DOOM_KEY_F8 = (0x80 + 0x42),
    DOOM_KEY_F9 = (0x80 + 0x43),
    DOOM_KEY_F10 = (0x80 + 0x44),
    DOOM_KEY_F11 = (0x80 + 0x57),
    DOOM_KEY_F12 = (0x80 + 0x58),
    DOOM_KEY_PAUSE = 0xff
} doom_key_t;


// Mouse button mapping
typedef enum
{
    DOOM_LEFT_BUTTON = 0,
    DOOM_RIGHT_BUTTON = 1,
    DOOM_MIDDLE_BUTTON = 2
} doom_button_t;


// For the software renderer. Default is 320x200
void doom_set_resolution(int width, int height);

// Set default configurations. Lets say, changing arrows to WASD as default controls
void doom_set_default_int(const char* name, int value);
void doom_set_default_string(const char* name, const char* value);

// set callbacks
void doom_set_print(doom_print_fn print_fn);
void doom_set_malloc(doom_malloc_fn malloc_fn, doom_free_fn free_fn);
void doom_set_file_io(doom_open_fn open_fn,
                      doom_close_fn close_fn,
                      doom_read_fn read_fn,
                      doom_write_fn write_fn,
                      doom_seek_fn seek_fn,
                      doom_tell_fn tell_fn,
                      doom_eof_fn eof_fn);
void doom_set_gettime(doom_gettime_fn gettime_fn);
void doom_set_exit(doom_exit_fn exit_fn);
void doom_set_getenv(doom_getenv_fn getenv_fn);

// Initializes DOOM and start things up. Call only call one
void doom_init(int argc, char** argv, int flags);

// Call this every frame
void doom_update(); // This will update at 35 FPS
void doom_force_update(); // This will run a frame everytime it's called, regardless of FPS.

// Channels: 1 = indexed, 3 = RGB, 4 = RGBA
const unsigned char* doom_get_framebuffer(int channels);

// It is always 2048 bytes in size
short* doom_get_sound_buffer();

// Call this 140 times per second. Or about every 7ms. 
// Returns midi message. Keep calling it until it returns 0.
unsigned long doom_tick_midi();

// Events
void doom_key_down(doom_key_t key);
void doom_key_up(doom_key_t key);
void doom_button_down(doom_button_t button);
void doom_button_up(doom_button_t button);
void doom_mouse_move(int delta_x, int delta_y);


#ifdef __cplusplus
} // extern "C"
#endif

#endif
#ifndef __D_ENGLSH__
#define __D_ENGLSH__


//
// Printed strings for translation
//

//
// D_Main.C
//
#define D_DEVSTR "Development mode ON.\n"
#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n"

//
//        M_Menu.C
//
#define PRESSKEY "press a key."
#define PRESSYN "press y or n."
#define QUITMSG "are you sure you want to\nquit this great game?"
#define LOADNET "you can't do load while in a net game!\n\n" PRESSKEY
#define QLOADNET "you can't quickload during a netgame!\n\n" PRESSKEY
#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n" PRESSKEY
#define SAVEDEAD "you can't save if you aren't playing!\n\n" PRESSKEY
#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n" PRESSYN
#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n" PRESSYN

#define QSPROMPT_1 "quicksave over your game named\n\n'"
#define QSPROMPT_2 "'?\n\n" PRESSYN
#define QLPROMPT_1 "do you want to quickload the game named\n\n'%s"
#define QLPROMPT_2 "'?\n\n" PRESSYN

#define NEWGAME \
"you can't start a new game\n"\
"while in a network game.\n\n" PRESSKEY

#define NIGHTMARE \
"are you sure? this skill level\n"\
"isn't even remotely fair.\n\n" PRESSYN

#define SWSTRING \
"this is the shareware version of doom.\n\n"\
"you need to order the entire trilogy.\n\n" PRESSKEY

#define MSGOFF "Messages OFF"
#define MSGON "Messages ON"
#define CROSSOFF "Crosshair OFF"
#define CROSSON "Crosshair ON"
#define ALWAYSRUNOFF "Always run OFF"
#define ALWAYSRUNON "Always run ON"
#define NETEND "you can't end a netgame!\n\n" PRESSKEY
#define ENDGAME "are you sure you want to end the game?\n\n" PRESSYN

#define DOSY "(press y to quit)"

#define DETAILHI "High detail"
#define DETAILLO "Low detail"
#define GAMMALVL0 "Gamma correction OFF"
#define GAMMALVL1 "Gamma correction level 1"
#define GAMMALVL2 "Gamma correction level 2"
#define GAMMALVL3 "Gamma correction level 3"
#define GAMMALVL4 "Gamma correction level 4"
#define EMPTYSTRING "empty slot"

//
// P_inter.C
//
#define GOTARMOR    "Picked up the armor."
#define GOTMEGA     "Picked up the MegaArmor!"
#define GOTHTHBONUS "Picked up a health bonus."
#define GOTARMBONUS "Picked up an armor bonus."
#define GOTSTIM     "Picked up a stimpack."
#define GOTMEDINEED "Picked up a medikit that you REALLY need!"
#define GOTMEDIKIT  "Picked up a medikit."
#define GOTSUPER    "Supercharge!"

#define GOTBLUECARD "Picked up a blue keycard."
#define GOTYELWCARD "Picked up a yellow keycard."
#define GOTREDCARD  "Picked up a red keycard."
#define GOTBLUESKUL "Picked up a blue skull key."
#define GOTYELWSKUL "Picked up a yellow skull key."
#define GOTREDSKULL "Picked up a red skull key."

#define GOTINVUL    "Invulnerability!"
#define GOTBERSERK  "Berserk!"
#define GOTINVIS    "Partial Invisibility"
#define GOTSUIT     "Radiation Shielding Suit"
#define GOTMAP      "Computer Area Map"
#define GOTVISOR    "Light Amplification Visor"
#define GOTMSPHERE  "MegaSphere!"

#define GOTCLIP     "Picked up a clip."
#define GOTCLIPBOX  "Picked up a box of bullets."
#define GOTROCKET   "Picked up a rocket."
#define GOTROCKBOX  "Picked up a box of rockets."
#define GOTCELL     "Picked up an energy cell."
#define GOTCELLBOX  "Picked up an energy cell pack."
#define GOTSHELLS   "Picked up 4 shotgun shells."
#define GOTSHELLBOX "Picked up a box of shotgun shells."
#define GOTBACKPACK "Picked up a backpack full of ammo!"

#define GOTBFG9000  "You got the BFG9000!  Oh, yes."
#define GOTCHAINGUN "You got the chaingun!"
#define GOTCHAINSAW "A chainsaw!  Find some meat!"
#define GOTLAUNCHER "You got the rocket launcher!"
#define GOTPLASMA   "You got the plasma gun!"
#define GOTSHOTGUN  "You got the shotgun!"
#define GOTSHOTGUN2 "You got the super shotgun!"

//
// P_Doors.C
//
#define PD_BLUEO    "You need a blue key to activate this object"
#define PD_REDO     "You need a red key to activate this object"
#define PD_YELLOWO  "You need a yellow key to activate this object"
#define PD_BLUEK    "You need a blue key to open this door"
#define PD_REDK     "You need a red key to open this door"
#define PD_YELLOWK  "You need a yellow key to open this door"

//
// G_game.C
//
#define GGSAVED     "game saved."

//
// HU_stuff.C
//
#define HUSTR_MSGU "[Message unsent]"

#define HUSTR_E1M1 "E1M1: Hangar"
#define HUSTR_E1M2 "E1M2: Nuclear Plant"
#define HUSTR_E1M3 "E1M3: Toxin Refinery"
#define HUSTR_E1M4 "E1M4: Command Control"
#define HUSTR_E1M5 "E1M5: Phobos Lab"
#define HUSTR_E1M6 "E1M6: Central Processing"
#define HUSTR_E1M7 "E1M7: Computer Station"
#define HUSTR_E1M8 "E1M8: Phobos Anomaly"
#define HUSTR_E1M9 "E1M9: Military Base"

#define HUSTR_E2M1 "E2M1: Deimos Anomaly"
#define HUSTR_E2M2 "E2M2: Containment Area"
#define HUSTR_E2M3 "E2M3: Refinery"
#define HUSTR_E2M4 "E2M4: Deimos Lab"
#define HUSTR_E2M5 "E2M5: Command Center"
#define HUSTR_E2M6 "E2M6: Halls of the Damned"
#define HUSTR_E2M7 "E2M7: Spawning Vats"
#define HUSTR_E2M8 "E2M8: Tower of Babel"
#define HUSTR_E2M9 "E2M9: Fortress of Mystery"

#define HUSTR_E3M1 "E3M1: Hell Keep"
#define HUSTR_E3M2 "E3M2: Slough of Despair"
#define HUSTR_E3M3 "E3M3: Pandemonium"
#define HUSTR_E3M4 "E3M4: House of Pain"
#define HUSTR_E3M5 "E3M5: Unholy Cathedral"
#define HUSTR_E3M6 "E3M6: Mt. Erebus"
#define HUSTR_E3M7 "E3M7: Limbo"
#define HUSTR_E3M8 "E3M8: Dis"
#define HUSTR_E3M9 "E3M9: Warrens"

#define HUSTR_E4M1 "E4M1: Hell Beneath"
#define HUSTR_E4M2 "E4M2: Perfect Hatred"
#define HUSTR_E4M3 "E4M3: Sever The Wicked"
#define HUSTR_E4M4 "E4M4: Unruly Evil"
#define HUSTR_E4M5 "E4M5: They Will Repent"
#define HUSTR_E4M6 "E4M6: Against Thee Wickedly"
#define HUSTR_E4M7 "E4M7: And Hell Followed"
#define HUSTR_E4M8 "E4M8: Unto The Cruel"
#define HUSTR_E4M9 "E4M9: Fear"

#define HUSTR_1 "level 1: entryway"
#define HUSTR_2 "level 2: underhalls"
#define HUSTR_3 "level 3: the gantlet"
#define HUSTR_4 "level 4: the focus"
#define HUSTR_5 "level 5: the waste tunnels"
#define HUSTR_6 "level 6: the crusher"
#define HUSTR_7 "level 7: dead simple"
#define HUSTR_8 "level 8: tricks and traps"
#define HUSTR_9 "level 9: the pit"
#define HUSTR_10 "level 10: refueling base"
#define HUSTR_11 "level 11: 'o' of destruction!"

#define HUSTR_12 "level 12: the factory"
#define HUSTR_13 "level 13: downtown"
#define HUSTR_14 "level 14: the inmost dens"
#define HUSTR_15 "level 15: industrial zone"
#define HUSTR_16 "level 16: suburbs"
#define HUSTR_17 "level 17: tenements"
#define HUSTR_18 "level 18: the courtyard"
#define HUSTR_19 "level 19: the citadel"
#define HUSTR_20 "level 20: gotcha!"

#define HUSTR_21 "level 21: nirvana"
#define HUSTR_22 "level 22: the catacombs"
#define HUSTR_23 "level 23: barrels o' fun"
#define HUSTR_24 "level 24: the chasm"
#define HUSTR_25 "level 25: bloodfalls"
#define HUSTR_26 "level 26: the abandoned mines"
#define HUSTR_27 "level 27: monster condo"
#define HUSTR_28 "level 28: the spirit world"
#define HUSTR_29 "level 29: the living end"
#define HUSTR_30 "level 30: icon of sin"

#define HUSTR_31 "level 31: wolfenstein"
#define HUSTR_32 "level 32: grosse"

#define PHUSTR_1 "level 1: congo"
#define PHUSTR_2 "level 2: well of souls"
#define PHUSTR_3 "level 3: aztec"
#define PHUSTR_4 "level 4: caged"
#define PHUSTR_5 "level 5: ghost town"
#define PHUSTR_6 "level 6: baron's lair"
#define PHUSTR_7 "level 7: caughtyard"
#define PHUSTR_8 "level 8: realm"
#define PHUSTR_9 "level 9: abattoire"
#define PHUSTR_10 "level 10: onslaught"
#define PHUSTR_11 "level 11: hunted"

#define PHUSTR_12 "level 12: speed"
#define PHUSTR_13 "level 13: the crypt"
#define PHUSTR_14 "level 14: genesis"
#define PHUSTR_15 "level 15: the twilight"
#define PHUSTR_16 "level 16: the omen"
#define PHUSTR_17 "level 17: compound"
#define PHUSTR_18 "level 18: neurosphere"
#define PHUSTR_19 "level 19: nme"
#define PHUSTR_20 "level 20: the death domain"

#define PHUSTR_21 "level 21: slayer"
#define PHUSTR_22 "level 22: impossible mission"
#define PHUSTR_23 "level 23: tombstone"
#define PHUSTR_24 "level 24: the final frontier"
#define PHUSTR_25 "level 25: the temple of darkness"
#define PHUSTR_26 "level 26: bunker"
#define PHUSTR_27 "level 27: anti-christ"
#define PHUSTR_28 "level 28: the sewers"
#define PHUSTR_29 "level 29: odyssey of noises"
#define PHUSTR_30 "level 30: the gateway of hell"

#define PHUSTR_31 "level 31: cyberden"
#define PHUSTR_32 "level 32: go 2 it"

#define THUSTR_1 "level 1: system control"
#define THUSTR_2 "level 2: human bbq"
#define THUSTR_3 "level 3: power control"
#define THUSTR_4 "level 4: wormhole"
#define THUSTR_5 "level 5: hanger"
#define THUSTR_6 "level 6: open season"
#define THUSTR_7 "level 7: prison"
#define THUSTR_8 "level 8: metal"
#define THUSTR_9 "level 9: stronghold"
#define THUSTR_10 "level 10: redemption"
#define THUSTR_11 "level 11: storage facility"

#define THUSTR_12 "level 12: crater"
#define THUSTR_13 "level 13: nukage processing"
#define THUSTR_14 "level 14: steel works"
#define THUSTR_15 "level 15: dead zone"
#define THUSTR_16 "level 16: deepest reaches"
#define THUSTR_17 "level 17: processing area"
#define THUSTR_18 "level 18: mill"
#define THUSTR_19 "level 19: shipping/respawning"
#define THUSTR_20 "level 20: central processing"

#define THUSTR_21 "level 21: administration center"
#define THUSTR_22 "level 22: habitat"
#define THUSTR_23 "level 23: lunar mining project"
#define THUSTR_24 "level 24: quarry"
#define THUSTR_25 "level 25: baron's den"
#define THUSTR_26 "level 26: ballistyx"
#define THUSTR_27 "level 27: mount pain"
#define THUSTR_28 "level 28: heck"
#define THUSTR_29 "level 29: river styx"
#define THUSTR_30 "level 30: last call"

#define THUSTR_31 "level 31: pharaoh"
#define THUSTR_32 "level 32: caribbean"

#define HUSTR_CHATMACRO1 "I'm ready to kick butt!"
#define HUSTR_CHATMACRO2 "I'm OK."
#define HUSTR_CHATMACRO3 "I'm not looking too good!"
#define HUSTR_CHATMACRO4 "Help!"
#define HUSTR_CHATMACRO5 "You suck!"
#define HUSTR_CHATMACRO6 "Next time, scumbag..."
#define HUSTR_CHATMACRO7 "Come here!"
#define HUSTR_CHATMACRO8 "I'll take care of it."
#define HUSTR_CHATMACRO9 "Yes"
#define HUSTR_CHATMACRO0 "No"

#define HUSTR_TALKTOSELF1 "You mumble to yourself"
#define HUSTR_TALKTOSELF2 "Who's there?"
#define HUSTR_TALKTOSELF3 "You scare yourself"
#define HUSTR_TALKTOSELF4 "You start to rave"
#define HUSTR_TALKTOSELF5 "You've lost it..."

#define HUSTR_MESSAGESENT "[Message Sent]"

// The following should NOT be changed unless it seems
// just AWFULLY necessary

#define HUSTR_PLRGREEN  "Green: "
#define HUSTR_PLRINDIGO "Indigo: "
#define HUSTR_PLRBROWN  "Brown: "
#define HUSTR_PLRRED    "Red: "

#define HUSTR_KEYGREEN  'g'
#define HUSTR_KEYINDIGO 'i'
#define HUSTR_KEYBROWN  'b'
#define HUSTR_KEYRED    'r'

//
// AM_map.C
//

#define AMSTR_FOLLOWON  "Follow Mode ON"
#define AMSTR_FOLLOWOFF "Follow Mode OFF"

#define AMSTR_GRIDON    "Grid ON"
#define AMSTR_GRIDOFF   "Grid OFF"

#define AMSTR_MARKEDSPOT    "Marked Spot"
#define AMSTR_MARKSCLEARED  "All Marks Cleared"

//
// ST_stuff.C
//

#define STSTR_MUS       "Music Change"
#define STSTR_NOMUS     "IMPOSSIBLE SELECTION"
#define STSTR_DQDON     "Degreelessness Mode On"
#define STSTR_DQDOFF    "Degreelessness Mode Off"

#define STSTR_KFAADDED  "Very Happy Ammo Added"
#define STSTR_FAADDED   "Ammo (no keys) Added"

#define STSTR_NCON      "No Clipping Mode ON"
#define STSTR_NCOFF     "No Clipping Mode OFF"

#define STSTR_BEHOLD    "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp"
#define STSTR_BEHOLDX   "Power-up Toggled"

#define STSTR_CHOPPERS  "... doesn't suck - GM"
#define STSTR_CLEV      "Changing Level..."

//
// F_Finale.C
//
#define E1TEXT \
"Once you beat the big badasses and\n"\
"clean out the moon base you're supposed\n"\
"to win, aren't you? Aren't you? Where's\n"\
"your fat reward and ticket home? What\n"\
"the hell is this? It's not supposed to\n"\
"end this way!\n"\
"\n" \
"It stinks like rotten meat, but looks\n"\
"like the lost Deimos base.  Looks like\n"\
"you're stuck on The Shores of Hell.\n"\
"The only way out is through.\n"\
"\n"\
"To continue the DOOM experience, play\n"\
"The Shores of Hell and its amazing\n"\
"sequel, Inferno!\n"


#define E2TEXT \
"You've done it! The hideous cyber-\n"\
"demon lord that ruled the lost Deimos\n"\
"moon base has been slain and you\n"\
"are triumphant! But ... where are\n"\
"you? You clamber to the edge of the\n"\
"moon and look down to see the awful\n"\
"truth.\n" \
"\n"\
"Deimos floats above Hell itself!\n"\
"You've never heard of anyone escaping\n"\
"from Hell, but you'll make the bastards\n"\
"sorry they ever heard of you! Quickly,\n"\
"you rappel down to  the surface of\n"\
"Hell.\n"\
"\n" \
"Now, it's on to the final chapter of\n"\
"DOOM! -- Inferno."


#define E3TEXT \
"The loathsome spiderdemon that\n"\
"masterminded the invasion of the moon\n"\
"bases and caused so much death has had\n"\
"its ass kicked for all time.\n"\
"\n"\
"A hidden doorway opens and you enter.\n"\
"You've proven too tough for Hell to\n"\
"contain, and now Hell at last plays\n"\
"fair -- for you emerge from the door\n"\
"to see the green fields of Earth!\n"\
"Home at last.\n" \
"\n"\
"You wonder what's been happening on\n"\
"Earth while you were battling evil\n"\
"unleashed. It's good that no Hell-\n"\
"spawn could have come through that\n"\
"door with you ..."


#define E4TEXT \
"the spider mastermind must have sent forth\n"\
"its legions of hellspawn before your\n"\
"final confrontation with that terrible\n"\
"beast from hell.  but you stepped forward\n"\
"and brought forth eternal damnation and\n"\
"suffering upon the horde as a true hero\n"\
"would in the face of something so evil.\n"\
"\n"\
"besides, someone was gonna pay for what\n"\
"happened to daisy, your pet rabbit.\n"\
"\n"\
"but now, you see spread before you more\n"\
"potential pain and gibbitude as a nation\n"\
"of demons run amok among our cities.\n"\
"\n"\
"next stop, hell on earth!"


// after level 6, put this:
#define C1TEXT \
"YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \
"STARPORT. BUT SOMETHING IS WRONG. THE\n" \
"MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \
"WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \
"IS BEING SUBVERTED BY THEIR PRESENCE.\n" \
"\n"\
"AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \
"FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \
"YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \
"OF THE STARBASE AND FIND THE CONTROLLING\n" \
"SWITCH WHICH HOLDS EARTH'S POPULATION\n" \
"HOSTAGE."

// After level 11, put this:
#define C2TEXT \
"YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \
"HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\
"THE NIGHTMARE.  NOW YOU ARE THE ONLY\n"\
"HUMAN LEFT ON THE FACE OF THE PLANET.\n"\
"CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\
"AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\
"YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\
"THAT YOU HAVE SAVED YOUR SPECIES.\n"\
"\n"\
"BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\
"MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\
"THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\
"GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\
"ENTRY.  THE ALIEN BASE IS IN THE HEART OF\n"\
"YOUR OWN HOME CITY, NOT FAR FROM THE\n"\
"STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\
"UP AND RETURN TO THE FRAY."


// After level 20, put this:
#define C3TEXT \
"YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\
"SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\
"YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\
"ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\
"TEETH AND PLUNGE THROUGH IT.\n"\
"\n"\
"THERE MUST BE A WAY TO CLOSE IT ON THE\n"\
"OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\
"GOT TO GO THROUGH HELL TO GET TO IT?"


// After level 29, put this:
#define C4TEXT \
"THE HORRENDOUS VISAGE OF THE BIGGEST\n"\
"DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\
"YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\
"HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\
"UP AND DIES, ITS THRASHING LIMBS\n"\
"DEVASTATING UNTOLD MILES OF HELL'S\n"\
"SURFACE.\n"\
"\n"\
"YOU'VE DONE IT. THE INVASION IS OVER.\n"\
"EARTH IS SAVED. HELL IS A WRECK. YOU\n"\
"WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\
"DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\
"FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\
"HOME. REBUILDING EARTH OUGHT TO BE A\n"\
"LOT MORE FUN THAN RUINING IT WAS.\n"



// Before level 31, put this:
#define C5TEXT \
"CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\
"LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\
"HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\
"WHO THE INMATES OF THIS CORNER OF HELL\n"\
"WILL BE."


// Before level 32, put this:
#define C6TEXT \
"CONGRATULATIONS, YOU'VE FOUND THE\n"\
"SUPER SECRET LEVEL!  YOU'D BETTER\n"\
"BLAZE THROUGH THIS ONE!\n"


// after map 06        
#define P1TEXT  \
"You gloat over the steaming carcass of the\n"\
"Guardian.  With its death, you've wrested\n"\
"the Accelerator from the stinking claws\n"\
"of Hell.  You relax and glance around the\n"\
"room.  Damn!  There was supposed to be at\n"\
"least one working prototype, but you can't\n"\
"see it. The demons must have taken it.\n"\
"\n"\
"You must find the prototype, or all your\n"\
"struggles will have been wasted. Keep\n"\
"moving, keep fighting, keep killing.\n"\
"Oh yes, keep living, too."


// after map 11
#define P2TEXT \
"Even the deadly Arch-Vile labyrinth could\n"\
"not stop you, and you've gotten to the\n"\
"prototype Accelerator which is soon\n"\
"efficiently and permanently deactivated.\n"\
"\n"\
"You're good at that kind of thing."


// after map 20
#define P3TEXT \
"You've bashed and battered your way into\n"\
"the heart of the devil-hive.  Time for a\n"\
"Search-and-Destroy mission, aimed at the\n"\
"Gatekeeper, whose foul offspring is\n"\
"cascading to Earth.  Yeah, he's bad. But\n"\
"you know who's worse!\n"\
"\n"\
"Grinning evilly, you check your gear, and\n"\
"get ready to give the bastard a little Hell\n"\
"of your own making!"

// after map 30
#define P4TEXT \
"The Gatekeeper's evil face is splattered\n"\
"all over the place.  As its tattered corpse\n"\
"collapses, an inverted Gate forms and\n"\
"sucks down the shards of the last\n"\
"prototype Accelerator, not to mention the\n"\
"few remaining demons.  You're done. Hell\n"\
"has gone back to pounding bad dead folks \n"\
"instead of good live ones.  Remember to\n"\
"tell your grandkids to put a rocket\n"\
"launcher in your coffin. If you go to Hell\n"\
"when you die, you'll need it for some\n"\
"final cleaning-up ..."

// before map 31
#define P5TEXT \
"You've found the second-hardest level we\n"\
"got. Hope you have a saved game a level or\n"\
"two previous.  If not, be prepared to die\n"\
"aplenty. For master marines only."

// before map 32
#define P6TEXT \
"Betcha wondered just what WAS the hardest\n"\
"level we had ready for ya?  Now you know.\n"\
"No one gets out alive."


#define T1TEXT \
"You've fought your way out of the infested\n"\
"experimental labs.   It seems that UAC has\n"\
"once again gulped it down.  With their\n"\
"high turnover, it must be hard for poor\n"\
"old UAC to buy corporate health insurance\n"\
"nowadays..\n"\
"\n"\
"Ahead lies the military complex, now\n"\
"swarming with diseased horrors hot to get\n"\
"their teeth into you. With luck, the\n"\
"complex still has some warlike ordnance\n"\
"laying around."


#define T2TEXT \
"You hear the grinding of heavy machinery\n"\
"ahead.  You sure hope they're not stamping\n"\
"out new hellspawn, but you're ready to\n"\
"ream out a whole herd if you have to.\n"\
"They might be planning a blood feast, but\n"\
"you feel about as mean as two thousand\n"\
"maniacs packed into one mad killer.\n"\
"\n"\
"You don't plan to go down easy."


#define T3TEXT \
"The vista opening ahead looks real damn\n"\
"familiar. Smells familiar, too -- like\n"\
"fried excrement. You didn't like this\n"\
"place before, and you sure as hell ain't\n"\
"planning to like it now. The more you\n"\
"brood on it, the madder you get.\n"\
"Hefting your gun, an evil grin trickles\n"\
"onto your face. Time to take some names."

#define T4TEXT \
"Suddenly, all is silent, from one horizon\n"\
"to the other. The agonizing echo of Hell\n"\
"fades away, the nightmare sky turns to\n"\
"blue, the heaps of monster corpses start \n"\
"to evaporate along with the evil stench \n"\
"that filled the air. Jeeze, maybe you've\n"\
"done it. Have you really won?\n"\
"\n"\
"Something rumbles in the distance.\n"\
"A blue light begins to glow inside the\n"\
"ruined skull of the demon-spitter."


#define T5TEXT \
"What now? Looks totally different. Kind\n"\
"of like King Tut's condo. Well,\n"\
"whatever's here can't be any worse\n"\
"than usual. Can it?  Or maybe it's best\n"\
"to let sleeping gods lie.."


#define T6TEXT \
"Time for a vacation. You've burst the\n"\
"bowels of hell and by golly you're ready\n"\
"for a break. You mutter to yourself,\n"\
"Maybe someone else can kick Hell's ass\n"\
"next time around. Ahead lies a quiet town,\n"\
"with peaceful flowing water, quaint\n"\
"buildings, and presumably no Hellspawn.\n"\
"\n"\
"As you step off the transport, you hear\n"\
"the stomp of a cyberdemon's iron shoe."


//
// Character cast strings F_FINALE.C
//
#define CC_ZOMBIE   "ZOMBIEMAN"
#define CC_SHOTGUN  "SHOTGUN GUY"
#define CC_HEAVY    "HEAVY WEAPON DUDE"
#define CC_IMP      "IMP"
#define CC_DEMON    "DEMON"
#define CC_LOST     "LOST SOUL"
#define CC_CACO     "CACODEMON"
#define CC_HELL     "HELL KNIGHT"
#define CC_BARON    "BARON OF HELL"
#define CC_ARACH    "ARACHNOTRON"
#define CC_PAIN     "PAIN ELEMENTAL"
#define CC_REVEN    "REVENANT"
#define CC_MANCU    "MANCUBUS"
#define CC_ARCH     "ARCH-VILE"
#define CC_SPIDER   "THE SPIDER MASTERMIND"
#define CC_CYBER    "THE CYBERDEMON"
#define CC_HERO     "OUR HERO"


#endif


#ifndef __D_THINK__
#define __D_THINK__


//
// Experimental stuff.
// To compile this as "ANSI C with classes"
//  we will need to handle the various
//  action functions cleanly.
//
typedef void (*actionf_v)();
typedef void (*actionf_p1)(void*);
typedef void (*actionf_p2)(void*, void*);

typedef union
{
    actionf_p1 acp1;
    actionf_v  acv;
    actionf_p2 acp2;
} actionf_t;


// Historically, "think_t" is yet another
//  function pointer to a routine to handle
//  an actor.
typedef actionf_t think_t;


// Doubly linked list of actors.
typedef struct thinker_s
{
    struct thinker_s* prev;
    struct thinker_s* next;
    think_t function;
} thinker_t;


#endif


#ifndef __DOOM_CONFIG_H__
#define __DOOM_CONFIG_H__


#if defined(WIN32)
#define DOOM_WIN32
#elif defined(__APPLE__)
#define DOOM_APPLE
#else
#define DOOM_LINUX
#endif


//#include "DOOM.h"


#define doom_abs(x) ((x) < 0 ? -(x) : (x))


extern char error_buf[260];
extern int doom_flags;
extern doom_print_fn doom_print;
extern doom_malloc_fn doom_malloc;
extern doom_free_fn doom_free;
extern doom_open_fn doom_open;
extern doom_close_fn doom_close;
extern doom_read_fn doom_read;
extern doom_write_fn doom_write;
extern doom_seek_fn doom_seek;
extern doom_tell_fn doom_tell;
extern doom_eof_fn doom_eof;
extern doom_gettime_fn doom_gettime;
extern doom_exit_fn doom_exit;
extern doom_getenv_fn doom_getenv;


const char* doom_itoa(int i, int radix);
const char* doom_ctoa(char c);
const char* doom_ptoa(void* p);
void doom_memset(void* ptr, int value, int num);
void* doom_memcpy(void* destination, const void* source, int num);
int doom_fprint(void* handle, const char* str);
int doom_strlen(const char* str);
char* doom_concat(char* dst, const char* src);
char* doom_strcpy(char* destination, const char* source);
char* doom_strncpy(char* destination, const char* source, int num);
int doom_strcmp(const char* str1, const char* str2);
int doom_strncmp(const char* str1, const char* str2, int n);
int doom_strcasecmp(const char* str1, const char* str2);
int doom_strncasecmp(const char* str1, const char* str2, int n);
int doom_atoi(const char* str);
int doom_atox(const char* str);
int doom_toupper(int c);


#endif
#ifndef __DOOMDEF__
#define __DOOMDEF__






//
// Global parameters/defines.
//
// DOOM version
enum
{
    VERSION = 110
};


// Game mode handling - identify IWAD version
//  to handle IWAD dependend animations etc.
typedef enum
{
    shareware,      // DOOM 1 shareware, E1, M9
    registered,     // DOOM 1 registered, E3, M27
    commercial,     // DOOM 2 retail, E1 M34
    // DOOM 2 german edition not handled
    retail,         // DOOM 1 retail, E4, M36
    indetermined    // Well, no IWAD found.
} GameMode_t;


// Mission packs - might be useful for TC stuff?
typedef enum
{
    doom,       // DOOM 1
    doom2,      // DOOM 2
    pack_tnt,   // TNT mission pack
    pack_plut,  // Plutonia pack
    none
} GameMission_t;


// Identify language to use, software localization.
typedef enum
{
    english,
    french,
    german,
    unknown
} Language_t;


// If rangecheck is undefined,
// most parameter validation debugging code will not be compiled
#define RANGECHECK

//
// For resize of screen, at start of game.
// It will not work dynamically, see visplanes.
//
#define BASE_WIDTH 320

// It is educational but futile to change this
//  scaling e.g. to 2. Drawing of status bar,
//  menues etc. is tied to the scale implied
//  by the graphics.
#define SCREEN_MUL 1
#define INV_ASPECT_RATIO 0.625 // 0.75, ideally

// Defines suck. C sucks.
// C++ might sucks for OOP, but it sure is a better C.
// So there.
// extern int SCREENWIDTH;
#define SCREENWIDTH 320
#define SCREENHEIGHT 200

// The maximum number of players, multiplayer/networking.
#define MAXPLAYERS 4

// State updates, number of tics / second.
#if defined(DOOM_FAST_TICK)
#define TICKMUL 2
#else
#define TICKMUL 1
#endif
#define TICRATE (35 * TICKMUL)


// The current state of the game: whether we are
// playing, gazing at the intermission screen,
// the game final animation, or a demo. 
typedef enum
{
    GS_LEVEL,
    GS_INTERMISSION,
    GS_FINALE,
    GS_DEMOSCREEN
} gamestate_t;


//
// Difficulty/skill settings/filters.
//

// Skill flags.
#define MTF_EASY    1
#define MTF_NORMAL  2
#define MTF_HARD    4

// Deaf monsters/do not react to sound.
#define MTF_AMBUSH  8


typedef enum
{
    sk_baby,
    sk_easy,
    sk_medium,
    sk_hard,
    sk_nightmare
} skill_t;


//
// Key cards.
//
typedef enum
{
    it_bluecard,
    it_yellowcard,
    it_redcard,
    it_blueskull,
    it_yellowskull,
    it_redskull,
    NUMCARDS
} card_t;


// The defined weapons,
// including a marker indicating
// user has not changed weapon.
typedef enum
{
    wp_fist,
    wp_pistol,
    wp_shotgun,
    wp_chaingun,
    wp_missile,
    wp_plasma,
    wp_bfg,
    wp_chainsaw,
    wp_supershotgun,
    NUMWEAPONS,
    // No pending weapon change.
    wp_nochange
} weapontype_t;


// Ammunition types defined.
typedef enum
{
    am_clip,    // Pistol / chaingun ammo.
    am_shell,   // Shotgun / double barreled shotgun.
    am_cell,    // Plasma rifle, BFG.
    am_misl,    // Missile launcher.
    NUMAMMO,
    am_noammo   // Unlimited for chainsaw / fist.        
} ammotype_t;


// Power up artifacts.
typedef enum
{
    pw_invulnerability,
    pw_strength,
    pw_invisibility,
    pw_ironfeet,
    pw_allmap,
    pw_infrared,
    NUMPOWERS
} powertype_t;


//
// Power up durations,
//  how many seconds till expiration,
//  assuming TICRATE is 35 ticks/second.
//
typedef enum
{
    INVULNTICS = (30 * TICRATE),
    INVISTICS = (60 * TICRATE),
    INFRATICS = (120 * TICRATE),
    IRONTICS = (60 * TICRATE)
} powerduration_t;


//
// DOOM keyboard definition.
// This is the stuff configured by Setup.Exe.
// Most key data are simple ascii (uppercased).
//
#define KEY_RIGHTARROW  0xae
#define KEY_LEFTARROW   0xac
#define KEY_UPARROW     0xad
#define KEY_DOWNARROW   0xaf
#define KEY_ESCAPE      27
#define KEY_ENTER       13
#define KEY_TAB         9
#define KEY_F1          (0x80+0x3b)
#define KEY_F2          (0x80+0x3c)
#define KEY_F3          (0x80+0x3d)
#define KEY_F4          (0x80+0x3e)
#define KEY_F5          (0x80+0x3f)
#define KEY_F6          (0x80+0x40)
#define KEY_F7          (0x80+0x41)
#define KEY_F8          (0x80+0x42)
#define KEY_F9          (0x80+0x43)
#define KEY_F10         (0x80+0x44)
#define KEY_F11         (0x80+0x57)
#define KEY_F12         (0x80+0x58)

#define KEY_BACKSPACE   127
#define KEY_PAUSE       0xff

#define KEY_EQUALS      0x3d
#define KEY_MINUS       0x2d

#define KEY_RSHIFT      (0x80+0x36)
#define KEY_RCTRL       (0x80+0x1d)
#define KEY_RALT        (0x80+0x38)

#define KEY_LALT        KEY_RALT


#endif          // __DOOMDEF__


#ifndef __D_ITEMS__
#define __D_ITEMS__


//#include "doomdef.h"


// Weapon info: sprite frames, ammunition use.
typedef struct
{
    ammotype_t ammo;
    int upstate;
    int downstate;
    int readystate;
    int atkstate;
    int flashstate;
} weaponinfo_t;


extern weaponinfo_t weaponinfo[NUMWEAPONS];


#endif


#ifndef __DOOMTYPE__
#define __DOOMTYPE__


// Fixed to use builtin bool type with C++.
#ifdef __cplusplus
typedef bool doom_boolean;
#else
#if !defined(false) && !defined(true)
typedef enum
{
    false, true
} doom_boolean;
#else
typedef int doom_boolean;
#endif
#endif


typedef unsigned char byte;


#define DOOM_MAXCHAR    ((char)0x7f)
#define DOOM_MAXSHORT   ((short)0x7fff)

// Max pos 32-bit int.
#define DOOM_MAXINT     ((int)0x7fffffff)        
#define DOOM_MAXLONG    ((long)0x7fffffff)
#define DOOM_MINCHAR    ((char)0x80)
#define DOOM_MINSHORT   ((short)0x8000)

// Max negative 32-bit integer.
#define DOOM_MININT     ((int)0x80000000)        
#define DOOM_MINLONG    ((long)0x80000000)


#endif


#ifndef __D_EVENT__
#define __D_EVENT__


//#include "doomtype.h"


//
// Event handling.
//

// Input event types.
typedef enum
{
    ev_keydown,
    ev_keyup,
    ev_mouse,
    ev_joystick
} evtype_t;


// Event structure.
typedef struct
{
    evtype_t type;
    int data1;  // keys / mouse/joystick buttons
    int data2;  // mouse/joystick x move
    int data3;  // mouse/joystick y move
} event_t;


typedef enum
{
    ga_nothing,
    ga_loadlevel,
    ga_newgame,
    ga_loadgame,
    ga_savegame,
    ga_playdemo,
    ga_completed,
    ga_victory,
    ga_worlddone,
    ga_screenshot
} gameaction_t;


//
// Button/action code definitions.
//
typedef enum
{
    // Press "Fire".
    BT_ATTACK = 1,
    // Use button, to open doors, activate switches.
    BT_USE = 2,

    // Flag: game events, not really buttons.
    BT_SPECIAL = 128,
    BT_SPECIALMASK = 3,

    // Flag, weapon change pending.
    // If true, the next 3 bits hold weapon num.
    BT_CHANGE = 4,
    // The 3bit weapon mask and shift, convenience.
    BT_WEAPONMASK = (8 + 16 + 32),
    BT_WEAPONSHIFT = 3,

    // Pause the game.
    BTS_PAUSE = 1,
    // Save the game at each console.
    BTS_SAVEGAME = 2,

    // Savegame slot numbers
    //  occupy the second byte of buttons.    
    BTS_SAVEMASK = (4 + 8 + 16),
    BTS_SAVESHIFT = 2,
} buttoncode_t;


//
// GLOBAL VARIABLES
//
#define MAXEVENTS (64 * 64) // [pd] Crank up the number because we pump them faster

extern event_t events[MAXEVENTS];
extern int eventhead;
extern int eventtail;

extern gameaction_t gameaction;


#endif


#ifndef __AMMAP_H__
#define __AMMAP_H__


//#include "d_event.h"


// Used by ST StatusBar stuff.
#define AM_MSGHEADER (('a'<<24)+('m'<<16))
#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8))
#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8))


// Called by main loop.
doom_boolean AM_Responder(event_t* ev);

// Called by main loop.
void AM_Ticker(void);

// Called by main loop,
// called instead of view drawer if automap active.
void AM_Drawer(void);

// Called to force the automap to quit
// if the level is completed while it is up.
void AM_Stop(void);


#endif


#ifndef __D_MAIN__
#define __D_MAIN__


//#include "d_event.h"


#define MAXWADFILES 20


extern char* wadfiles[MAXWADFILES];


void D_AddFile(char* file);

//
// D_DoomMain()
// Not a globally visible function, just included for source reference,
// calls all startup code, parses command line options.
// If not overrided by user input, calls N_AdvanceDemo.
//
void D_DoomMain(void);

// Called by IO functions when input is detected.
void D_PostEvent(event_t* ev);

//
// BASE LEVEL
//
void D_PageTicker(void);
void D_PageDrawer(void);
void D_AdvanceDemo(void);
void D_StartTitle(void);


#endif
#ifndef __D_TEXTUR__
#define __D_TEXTUR__


//#include "doomtype.h"


//
// Flats?
//
// a pic is an unmasked block of pixels
typedef struct
{
    byte width;
    byte height;
    byte data;
} pic_t;


#endif


#ifndef __D_TICCMD__
#define __D_TICCMD__


//#include "doomtype.h"


// The data sampled per tick (single player)
// and transmitted to other peers (multiplayer).
// Mainly movements/button commands per game tick,
// plus a checksum for internal state consistency.
typedef struct
{
    char forwardmove;   // *2048 for move
    char sidemove;      // *2048 for move
    short angleturn;    // <<16 for angle delta
    short consistancy;  // checks for net game
    byte chatchar;
    byte buttons;
} ticcmd_t;


#endif


#ifndef __DOOMDATA__
#define __DOOMDATA__


// The most basic types we use, portability.
//#include "doomtype.h"

// Some global defines, that configure the game.
//#include "doomdef.h"


//
// Map level types.
// The following data structures define the persistent format
// used in the lumps of the WAD files.
//

// Lump order in a map WAD: each map needs a couple of lumps
// to provide a complete scene geometry description.
enum
{
    ML_LABEL,       // A separator, name, ExMx or MAPxx
    ML_THINGS,      // Monsters, items..
    ML_LINEDEFS,    // LineDefs, from editing
    ML_SIDEDEFS,    // SideDefs, from editing
    ML_VERTEXES,    // Vertices, edited and BSP splits generated
    ML_SEGS,        // LineSegs, from LineDefs split by BSP
    ML_SSECTORS,    // SubSectors, list of LineSegs
    ML_NODES,       // BSP nodes
    ML_SECTORS,     // Sectors, from editing
    ML_REJECT,      // LUT, sector-sector visibility        
    ML_BLOCKMAP     // LUT, motion clipping, walls/grid element
};


// A single Vertex.
typedef struct
{
    short x;
    short y;
} mapvertex_t;


// A SideDef, defining the visual appearance of a wall,
// by setting textures and offsets.
typedef struct
{
    short textureoffset;
    short rowoffset;
    char toptexture[8];
    char bottomtexture[8];
    char midtexture[8];
    // Front sector, towards viewer.
    short sector;
} mapsidedef_t;


// A LineDef, as used for editing, and as input
// to the BSP builder.
typedef struct
{
    short v1;
    short v2;
    short flags;
    short special;
    short tag;
    // sidenum[1] will be -1 if one sided
    short sidenum[2];
} maplinedef_t;


//
// LineDef attributes.
//

// Solid, is an obstacle.
#define ML_BLOCKING 1

// Blocks monsters only.
#define ML_BLOCKMONSTERS 2

// Backside will not be present at all
//  if not two sided.
#define ML_TWOSIDED 4

// If a texture is pegged, the texture will have
// the end exposed to air held constant at the
// top or bottom of the texture (stairs or pulled
// down things) and will move with a height change
// of one of the neighbor sectors.
// Unpegged textures allways have the first row of
// the texture at the top pixel of the line for both
// top and bottom textures (use next to windows).

// upper texture unpegged
#define ML_DONTPEGTOP 8

// lower texture unpegged
#define ML_DONTPEGBOTTOM 16        

// In AutoMap: don't map as two sided: IT'S A SECRET!
#define ML_SECRET 32

// Sound rendering: don't let sound cross two of these.
#define ML_SOUNDBLOCK 64

// Don't draw on the automap at all.
#define ML_DONTDRAW 128

// Set if already seen, thus drawn in automap.
#define ML_MAPPED 256


// Sector definition, from editing.
typedef struct
{
    short floorheight;
    short ceilingheight;
    char floorpic[8];
    char ceilingpic[8];
    short lightlevel;
    short special;
    short tag;
} mapsector_t;


// SubSector, as generated by BSP.
typedef struct
{
    short numsegs;
    // Index of first one, segs are stored sequentially.
    short firstseg;
} mapsubsector_t;


// LineSeg, generated by splitting LineDefs
// using partition lines selected by BSP builder.
typedef struct
{
    short v1;
    short v2;
    short angle;
    short linedef;
    short side;
    short offset;
} mapseg_t;


//
// BSP node structure.
//

// Indicate a leaf.
#define NF_SUBSECTOR 0x8000

typedef struct
{
    // Partition line from (x,y) to x+dx,y+dy)
    short x;
    short y;
    short dx;
    short dy;

    // Bounding box for each child,
    // clip against view frustum.
    short bbox[2][4];

    // If NF_SUBSECTOR its a subsector,
    // else it's a node of another subtree.
    unsigned short children[2];
} mapnode_t;


// Thing definition, position, orientation and type,
// plus skill/visibility flags and attributes.
typedef struct
{
    short x;
    short y;
    short angle;
    short type;
    short options;
} mapthing_t;


#endif // __DOOMDATA__


#ifndef __DSTRINGS__
#define __DSTRINGS__


// All important printed strings.
// Language selection (message strings).
// Use -DFRENCH etc.

#ifdef FRENCH
//#include  "d_french.h" // Leave the extra space there, to throw off regex in PureDOOM.h creation
#else
//#include "d_englsh.h"
#endif


// Misc. other strings.
#define SAVEGAMENAME "doomsav"

//
// File locations,
//  relative to current position.
// Path names are OS-sensitive.
//
#define DEVMAPS "devmaps"
#define DEVDATA "devdata"

// Not done in french?

// QuitDOOM messages
#define NUM_QUITMESSAGES   22


extern char* endmsg[];


#endif


#ifndef __F_FINALE__
#define __F_FINALE__


//#include "doomtype.h"
//#include "d_event.h"


//
// FINALE
//

// Called by main loop.
doom_boolean F_Responder(event_t* ev);

// Called by main loop.
void F_Ticker(void);

// Called by main loop.
void F_Drawer(void);

void F_StartFinale(void);


#endif


#ifndef __F_WIPE_H__
#define __F_WIPE_H__


//
// SCREEN WIPE PACKAGE
//

enum
{
    // simple gradual pixel change for 8-bit only
    wipe_ColorXForm,

    // weird screen melt
    wipe_Melt,

    wipe_NUMWIPES
};


int wipe_StartScreen(int x, int y, int width, int height);
int wipe_EndScreen(int x, int y, int width, int height);
int wipe_ScreenWipe(int wipeno, int x, int y, int width, int height, int ticks);


#endif


#ifndef __G_GAME__
#define __G_GAME__


//#include "doomdef.h"
//#include "d_event.h"


//
// GAME
//
void G_DeathMatchSpawnPlayer(int playernum);

void G_InitNew(skill_t skill, int episode, int map);

// Can be called by the startup code or M_Responder.
// A normal game starts at map 1,
// but a warp test can start elsewhere
void G_DeferedInitNew(skill_t skill, int episode, int map);

void G_DeferedPlayDemo(char* demo);

// Can be called by the startup code or M_Responder,
// calls P_SetupLevel or W_EnterWorld.
void G_LoadGame(char* name);

void G_DoLoadGame(void);

// Called by M_Responder.
void G_SaveGame(int slot, char* description);

// Only called by startup code.
void G_RecordDemo(char* name);

void G_BeginRecording(void);

void G_TimeDemo(char* name);
doom_boolean G_CheckDemoStatus(void);

void G_ExitLevel(void);
void G_SecretExitLevel(void);

void G_WorldDone(void);

void G_Ticker(void);
doom_boolean G_Responder(event_t* ev);

void G_ScreenShot(void);


#endif


#ifndef __HU_STUFF_H__
#define __HU_STUFF_H__


//#include "d_event.h"


//
// Globally visible constants.
//
#define HU_FONTSTART '!'    // the first font characters
#define HU_FONTEND '_'      // the last font characters

// Calculate # of glyphs in font.
#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1)        

#define HU_BROADCAST 5

#define HU_MSGREFRESH KEY_ENTER
#define HU_MSGX 0
#define HU_MSGY 0
#define HU_MSGWIDTH 64  // in characters
#define HU_MSGHEIGHT 1  // in lines

#define HU_MSGTIMEOUT (4*TICRATE)


//
// HEADS UP TEXT
//

void HU_Init(void);
void HU_Start(void);
doom_boolean HU_Responder(event_t* ev);
void HU_Ticker(void);
void HU_Drawer(void);
char HU_dequeueChatChar(void);
void HU_Erase(void);


#endif


#ifndef __I_NET__
#define __I_NET__


// Called by D_DoomMain.

void I_InitNetwork (void);
void I_NetCmd (void);


#endif


#ifndef __I_SYSTEM__
#define __I_SYSTEM__


//#include "d_ticcmd.h"
//#include "d_event.h"


// Called by DoomMain.
void I_Init(void);

// Called by startup code
// to get the ammount of memory to malloc
// for the zone management.
byte* I_ZoneBase(int* size);

// Called by D_DoomLoop,
// returns current time in tics.
int I_GetTime(void);

// Called by D_DoomLoop,
// called before processing any tics in a frame
// (just after displaying a frame).
// Time consuming syncronous operations
// are performed here (joystick reading).
// Can call D_PostEvent.
void I_StartFrame(void);

// Called by D_DoomLoop,
// called before processing each tic in a frame.
// Quick syncronous operations are performed here.
// Can call D_PostEvent.
void I_StartTic(void);

// Asynchronous interrupt functions should maintain private queues
// that are read by the synchronous functions
// to be converted into events.

// Either returns a null ticcmd,
// or calls a loadable driver to build it.
// This ticcmd will then be modified by the gameloop
// for normal input.
ticcmd_t* I_BaseTiccmd(void);

// Called by M_Responder when quit is selected.
// Clean exit, displays sell blurb.
void I_Quit(void);

// Allocates from low memory under dos,
// just mallocs under unix
byte* I_AllocLow(int length);

void I_Tactile(int on, int off, int total);

void I_Error(char* error);


#endif


#ifndef __I_VIDEO__
#define __I_VIDEO__


//#include "doomtype.h"


// Called by D_DoomMain,
// determines the hardware configuration
// and sets up the video mode
void I_InitGraphics(void);

void I_ShutdownGraphics(void);

// Takes full 8 bit values.
void I_SetPalette(byte* palette);

void I_UpdateNoBlit(void);
void I_FinishUpdate(void);

// Wait for vertical retrace or pause a bit.
void I_WaitVBL(int count);

void I_ReadScreen(byte* scr);

void I_BeginRead(void);
void I_EndRead(void);


#endif


#ifndef __INFO__
#define __INFO__


// Needed for action function pointer handling.
//#include "d_think.h"


typedef enum
{
    SPR_TROO,
    SPR_SHTG,
    SPR_PUNG,
    SPR_PISG,
    SPR_PISF,
    SPR_SHTF,
    SPR_SHT2,
    SPR_CHGG,
    SPR_CHGF,
    SPR_MISG,
    SPR_MISF,
    SPR_SAWG,
    SPR_PLSG,
    SPR_PLSF,
    SPR_BFGG,
    SPR_BFGF,
    SPR_BLUD,
    SPR_PUFF,
    SPR_BAL1,
    SPR_BAL2,
    SPR_PLSS,
    SPR_PLSE,
    SPR_MISL,
    SPR_BFS1,
    SPR_BFE1,
    SPR_BFE2,
    SPR_TFOG,
    SPR_IFOG,
    SPR_PLAY,
    SPR_POSS,
    SPR_SPOS,
    SPR_VILE,
    SPR_FIRE,
    SPR_FATB,
    SPR_FBXP,
    SPR_SKEL,
    SPR_MANF,
    SPR_FATT,
    SPR_CPOS,
    SPR_SARG,
    SPR_HEAD,
    SPR_BAL7,
    SPR_BOSS,
    SPR_BOS2,
    SPR_SKUL,
    SPR_SPID,
    SPR_BSPI,
    SPR_APLS,
    SPR_APBX,
    SPR_CYBR,
    SPR_PAIN,
    SPR_SSWV,
    SPR_KEEN,
    SPR_BBRN,
    SPR_BOSF,
    SPR_ARM1,
    SPR_ARM2,
    SPR_BAR1,
    SPR_BEXP,
    SPR_FCAN,
    SPR_BON1,
    SPR_BON2,
    SPR_BKEY,
    SPR_RKEY,
    SPR_YKEY,
    SPR_BSKU,
    SPR_RSKU,
    SPR_YSKU,
    SPR_STIM,
    SPR_MEDI,
    SPR_SOUL,
    SPR_PINV,
    SPR_PSTR,
    SPR_PINS,
    SPR_MEGA,
    SPR_SUIT,
    SPR_PMAP,
    SPR_PVIS,
    SPR_CLIP,
    SPR_AMMO,
    SPR_ROCK,
    SPR_BROK,
    SPR_CELL,
    SPR_CELP,
    SPR_SHEL,
    SPR_SBOX,
    SPR_BPAK,
    SPR_BFUG,
    SPR_MGUN,
    SPR_CSAW,
    SPR_LAUN,
    SPR_PLAS,
    SPR_SHOT,
    SPR_SGN2,
    SPR_COLU,
    SPR_SMT2,
    SPR_GOR1,
    SPR_POL2,
    SPR_POL5,
    SPR_POL4,
    SPR_POL3,
    SPR_POL1,
    SPR_POL6,
    SPR_GOR2,
    SPR_GOR3,
    SPR_GOR4,
    SPR_GOR5,
    SPR_SMIT,
    SPR_COL1,
    SPR_COL2,
    SPR_COL3,
    SPR_COL4,
    SPR_CAND,
    SPR_CBRA,
    SPR_COL6,
    SPR_TRE1,
    SPR_TRE2,
    SPR_ELEC,
    SPR_CEYE,
    SPR_FSKU,
    SPR_COL5,
    SPR_TBLU,
    SPR_TGRN,
    SPR_TRED,
    SPR_SMBT,
    SPR_SMGT,
    SPR_SMRT,
    SPR_HDB1,
    SPR_HDB2,
    SPR_HDB3,
    SPR_HDB4,
    SPR_HDB5,
    SPR_HDB6,
    SPR_POB1,
    SPR_POB2,
    SPR_BRS1,
    SPR_TLMP,
    SPR_TLP2,
    NUMSPRITES
} spritenum_t;


typedef enum
{
    S_NULL,
    S_LIGHTDONE,
    S_PUNCH,
    S_PUNCHDOWN,
    S_PUNCHUP,
    S_PUNCH1,
    S_PUNCH2,
    S_PUNCH3,
    S_PUNCH4,
    S_PUNCH5,
    S_PISTOL,
    S_PISTOLDOWN,
    S_PISTOLUP,
    S_PISTOL1,
    S_PISTOL2,
    S_PISTOL3,
    S_PISTOL4,
    S_PISTOLFLASH,
    S_SGUN,
    S_SGUNDOWN,
    S_SGUNUP,
    S_SGUN1,
    S_SGUN2,
    S_SGUN3,
    S_SGUN4,
    S_SGUN5,
    S_SGUN6,
    S_SGUN7,
    S_SGUN8,
    S_SGUN9,
    S_SGUNFLASH1,
    S_SGUNFLASH2,
    S_DSGUN,
    S_DSGUNDOWN,
    S_DSGUNUP,
    S_DSGUN1,
    S_DSGUN2,
    S_DSGUN3,
    S_DSGUN4,
    S_DSGUN5,
    S_DSGUN6,
    S_DSGUN7,
    S_DSGUN8,
    S_DSGUN9,
    S_DSGUN10,
    S_DSNR1,
    S_DSNR2,
    S_DSGUNFLASH1,
    S_DSGUNFLASH2,
    S_CHAIN,
    S_CHAINDOWN,
    S_CHAINUP,
    S_CHAIN1,
    S_CHAIN2,
    S_CHAIN3,
    S_CHAINFLASH1,
    S_CHAINFLASH2,
    S_MISSILE,
    S_MISSILEDOWN,
    S_MISSILEUP,
    S_MISSILE1,
    S_MISSILE2,
    S_MISSILE3,
    S_MISSILEFLASH1,
    S_MISSILEFLASH2,
    S_MISSILEFLASH3,
    S_MISSILEFLASH4,
    S_SAW,
    S_SAWB,
    S_SAWDOWN,
    S_SAWUP,
    S_SAW1,
    S_SAW2,
    S_SAW3,
    S_PLASMA,
    S_PLASMADOWN,
    S_PLASMAUP,
    S_PLASMA1,
    S_PLASMA2,
    S_PLASMAFLASH1,
    S_PLASMAFLASH2,
    S_BFG,
    S_BFGDOWN,
    S_BFGUP,
    S_BFG1,
    S_BFG2,
    S_BFG3,
    S_BFG4,
    S_BFGFLASH1,
    S_BFGFLASH2,
    S_BLOOD1,
    S_BLOOD2,
    S_BLOOD3,
    S_PUFF1,
    S_PUFF2,
    S_PUFF3,
    S_PUFF4,
    S_TBALL1,
    S_TBALL2,
    S_TBALLX1,
    S_TBALLX2,
    S_TBALLX3,
    S_RBALL1,
    S_RBALL2,
    S_RBALLX1,
    S_RBALLX2,
    S_RBALLX3,
    S_PLASBALL,
    S_PLASBALL2,
    S_PLASEXP,
    S_PLASEXP2,
    S_PLASEXP3,
    S_PLASEXP4,
    S_PLASEXP5,
    S_ROCKET,
    S_BFGSHOT,
    S_BFGSHOT2,
    S_BFGLAND,
    S_BFGLAND2,
    S_BFGLAND3,
    S_BFGLAND4,
    S_BFGLAND5,
    S_BFGLAND6,
    S_BFGEXP,
    S_BFGEXP2,
    S_BFGEXP3,
    S_BFGEXP4,
    S_EXPLODE1,
    S_EXPLODE2,
    S_EXPLODE3,
    S_TFOG,
    S_TFOG01,
    S_TFOG02,
    S_TFOG2,
    S_TFOG3,
    S_TFOG4,
    S_TFOG5,
    S_TFOG6,
    S_TFOG7,
    S_TFOG8,
    S_TFOG9,
    S_TFOG10,
    S_IFOG,
    S_IFOG01,
    S_IFOG02,
    S_IFOG2,
    S_IFOG3,
    S_IFOG4,
    S_IFOG5,
    S_PLAY,
    S_PLAY_RUN1,
    S_PLAY_RUN2,
    S_PLAY_RUN3,
    S_PLAY_RUN4,
    S_PLAY_ATK1,
    S_PLAY_ATK2,
    S_PLAY_PAIN,
    S_PLAY_PAIN2,
    S_PLAY_DIE1,
    S_PLAY_DIE2,
    S_PLAY_DIE3,
    S_PLAY_DIE4,
    S_PLAY_DIE5,
    S_PLAY_DIE6,
    S_PLAY_DIE7,
    S_PLAY_XDIE1,
    S_PLAY_XDIE2,
    S_PLAY_XDIE3,
    S_PLAY_XDIE4,
    S_PLAY_XDIE5,
    S_PLAY_XDIE6,
    S_PLAY_XDIE7,
    S_PLAY_XDIE8,
    S_PLAY_XDIE9,
    S_POSS_STND,
    S_POSS_STND2,
    S_POSS_RUN1,
    S_POSS_RUN2,
    S_POSS_RUN3,
    S_POSS_RUN4,
    S_POSS_RUN5,
    S_POSS_RUN6,
    S_POSS_RUN7,
    S_POSS_RUN8,
    S_POSS_ATK1,
    S_POSS_ATK2,
    S_POSS_ATK3,
    S_POSS_PAIN,
    S_POSS_PAIN2,
    S_POSS_DIE1,
    S_POSS_DIE2,
    S_POSS_DIE3,
    S_POSS_DIE4,
    S_POSS_DIE5,
    S_POSS_XDIE1,
    S_POSS_XDIE2,
    S_POSS_XDIE3,
    S_POSS_XDIE4,
    S_POSS_XDIE5,
    S_POSS_XDIE6,
    S_POSS_XDIE7,
    S_POSS_XDIE8,
    S_POSS_XDIE9,
    S_POSS_RAISE1,
    S_POSS_RAISE2,
    S_POSS_RAISE3,
    S_POSS_RAISE4,
    S_SPOS_STND,
    S_SPOS_STND2,
    S_SPOS_RUN1,
    S_SPOS_RUN2,
    S_SPOS_RUN3,
    S_SPOS_RUN4,
    S_SPOS_RUN5,
    S_SPOS_RUN6,
    S_SPOS_RUN7,
    S_SPOS_RUN8,
    S_SPOS_ATK1,
    S_SPOS_ATK2,
    S_SPOS_ATK3,
    S_SPOS_PAIN,
    S_SPOS_PAIN2,
    S_SPOS_DIE1,
    S_SPOS_DIE2,
    S_SPOS_DIE3,
    S_SPOS_DIE4,
    S_SPOS_DIE5,
    S_SPOS_XDIE1,
    S_SPOS_XDIE2,
    S_SPOS_XDIE3,
    S_SPOS_XDIE4,
    S_SPOS_XDIE5,
    S_SPOS_XDIE6,
    S_SPOS_XDIE7,
    S_SPOS_XDIE8,
    S_SPOS_XDIE9,
    S_SPOS_RAISE1,
    S_SPOS_RAISE2,
    S_SPOS_RAISE3,
    S_SPOS_RAISE4,
    S_SPOS_RAISE5,
    S_VILE_STND,
    S_VILE_STND2,
    S_VILE_RUN1,
    S_VILE_RUN2,
    S_VILE_RUN3,
    S_VILE_RUN4,
    S_VILE_RUN5,
    S_VILE_RUN6,
    S_VILE_RUN7,
    S_VILE_RUN8,
    S_VILE_RUN9,
    S_VILE_RUN10,
    S_VILE_RUN11,
    S_VILE_RUN12,
    S_VILE_ATK1,
    S_VILE_ATK2,
    S_VILE_ATK3,
    S_VILE_ATK4,
    S_VILE_ATK5,
    S_VILE_ATK6,
    S_VILE_ATK7,
    S_VILE_ATK8,
    S_VILE_ATK9,
    S_VILE_ATK10,
    S_VILE_ATK11,
    S_VILE_HEAL1,
    S_VILE_HEAL2,
    S_VILE_HEAL3,
    S_VILE_PAIN,
    S_VILE_PAIN2,
    S_VILE_DIE1,
    S_VILE_DIE2,
    S_VILE_DIE3,
    S_VILE_DIE4,
    S_VILE_DIE5,
    S_VILE_DIE6,
    S_VILE_DIE7,
    S_VILE_DIE8,
    S_VILE_DIE9,
    S_VILE_DIE10,
    S_FIRE1,
    S_FIRE2,
    S_FIRE3,
    S_FIRE4,
    S_FIRE5,
    S_FIRE6,
    S_FIRE7,
    S_FIRE8,
    S_FIRE9,
    S_FIRE10,
    S_FIRE11,
    S_FIRE12,
    S_FIRE13,
    S_FIRE14,
    S_FIRE15,
    S_FIRE16,
    S_FIRE17,
    S_FIRE18,
    S_FIRE19,
    S_FIRE20,
    S_FIRE21,
    S_FIRE22,
    S_FIRE23,
    S_FIRE24,
    S_FIRE25,
    S_FIRE26,
    S_FIRE27,
    S_FIRE28,
    S_FIRE29,
    S_FIRE30,
    S_SMOKE1,
    S_SMOKE2,
    S_SMOKE3,
    S_SMOKE4,
    S_SMOKE5,
    S_TRACER,
    S_TRACER2,
    S_TRACEEXP1,
    S_TRACEEXP2,
    S_TRACEEXP3,
    S_SKEL_STND,
    S_SKEL_STND2,
    S_SKEL_RUN1,
    S_SKEL_RUN2,
    S_SKEL_RUN3,
    S_SKEL_RUN4,
    S_SKEL_RUN5,
    S_SKEL_RUN6,
    S_SKEL_RUN7,
    S_SKEL_RUN8,
    S_SKEL_RUN9,
    S_SKEL_RUN10,
    S_SKEL_RUN11,
    S_SKEL_RUN12,
    S_SKEL_FIST1,
    S_SKEL_FIST2,
    S_SKEL_FIST3,
    S_SKEL_FIST4,
    S_SKEL_MISS1,
    S_SKEL_MISS2,
    S_SKEL_MISS3,
    S_SKEL_MISS4,
    S_SKEL_PAIN,
    S_SKEL_PAIN2,
    S_SKEL_DIE1,
    S_SKEL_DIE2,
    S_SKEL_DIE3,
    S_SKEL_DIE4,
    S_SKEL_DIE5,
    S_SKEL_DIE6,
    S_SKEL_RAISE1,
    S_SKEL_RAISE2,
    S_SKEL_RAISE3,
    S_SKEL_RAISE4,
    S_SKEL_RAISE5,
    S_SKEL_RAISE6,
    S_FATSHOT1,
    S_FATSHOT2,
    S_FATSHOTX1,
    S_FATSHOTX2,
    S_FATSHOTX3,
    S_FATT_STND,
    S_FATT_STND2,
    S_FATT_RUN1,
    S_FATT_RUN2,
    S_FATT_RUN3,
    S_FATT_RUN4,
    S_FATT_RUN5,
    S_FATT_RUN6,
    S_FATT_RUN7,
    S_FATT_RUN8,
    S_FATT_RUN9,
    S_FATT_RUN10,
    S_FATT_RUN11,
    S_FATT_RUN12,
    S_FATT_ATK1,
    S_FATT_ATK2,
    S_FATT_ATK3,
    S_FATT_ATK4,
    S_FATT_ATK5,
    S_FATT_ATK6,
    S_FATT_ATK7,
    S_FATT_ATK8,
    S_FATT_ATK9,
    S_FATT_ATK10,
    S_FATT_PAIN,
    S_FATT_PAIN2,
    S_FATT_DIE1,
    S_FATT_DIE2,
    S_FATT_DIE3,
    S_FATT_DIE4,
    S_FATT_DIE5,
    S_FATT_DIE6,
    S_FATT_DIE7,
    S_FATT_DIE8,
    S_FATT_DIE9,
    S_FATT_DIE10,
    S_FATT_RAISE1,
    S_FATT_RAISE2,
    S_FATT_RAISE3,
    S_FATT_RAISE4,
    S_FATT_RAISE5,
    S_FATT_RAISE6,
    S_FATT_RAISE7,
    S_FATT_RAISE8,
    S_CPOS_STND,
    S_CPOS_STND2,
    S_CPOS_RUN1,
    S_CPOS_RUN2,
    S_CPOS_RUN3,
    S_CPOS_RUN4,
    S_CPOS_RUN5,
    S_CPOS_RUN6,
    S_CPOS_RUN7,
    S_CPOS_RUN8,
    S_CPOS_ATK1,
    S_CPOS_ATK2,
    S_CPOS_ATK3,
    S_CPOS_ATK4,
    S_CPOS_PAIN,
    S_CPOS_PAIN2,
    S_CPOS_DIE1,
    S_CPOS_DIE2,
    S_CPOS_DIE3,
    S_CPOS_DIE4,
    S_CPOS_DIE5,
    S_CPOS_DIE6,
    S_CPOS_DIE7,
    S_CPOS_XDIE1,
    S_CPOS_XDIE2,
    S_CPOS_XDIE3,
    S_CPOS_XDIE4,
    S_CPOS_XDIE5,
    S_CPOS_XDIE6,
    S_CPOS_RAISE1,
    S_CPOS_RAISE2,
    S_CPOS_RAISE3,
    S_CPOS_RAISE4,
    S_CPOS_RAISE5,
    S_CPOS_RAISE6,
    S_CPOS_RAISE7,
    S_TROO_STND,
    S_TROO_STND2,
    S_TROO_RUN1,
    S_TROO_RUN2,
    S_TROO_RUN3,
    S_TROO_RUN4,
    S_TROO_RUN5,
    S_TROO_RUN6,
    S_TROO_RUN7,
    S_TROO_RUN8,
    S_TROO_ATK1,
    S_TROO_ATK2,
    S_TROO_ATK3,
    S_TROO_PAIN,
    S_TROO_PAIN2,
    S_TROO_DIE1,
    S_TROO_DIE2,
    S_TROO_DIE3,
    S_TROO_DIE4,
    S_TROO_DIE5,
    S_TROO_XDIE1,
    S_TROO_XDIE2,
    S_TROO_XDIE3,
    S_TROO_XDIE4,
    S_TROO_XDIE5,
    S_TROO_XDIE6,
    S_TROO_XDIE7,
    S_TROO_XDIE8,
    S_TROO_RAISE1,
    S_TROO_RAISE2,
    S_TROO_RAISE3,
    S_TROO_RAISE4,
    S_TROO_RAISE5,
    S_SARG_STND,
    S_SARG_STND2,
    S_SARG_RUN1,
    S_SARG_RUN2,
    S_SARG_RUN3,
    S_SARG_RUN4,
    S_SARG_RUN5,
    S_SARG_RUN6,
    S_SARG_RUN7,
    S_SARG_RUN8,
    S_SARG_ATK1,
    S_SARG_ATK2,
    S_SARG_ATK3,
    S_SARG_PAIN,
    S_SARG_PAIN2,
    S_SARG_DIE1,
    S_SARG_DIE2,
    S_SARG_DIE3,
    S_SARG_DIE4,
    S_SARG_DIE5,
    S_SARG_DIE6,
    S_SARG_RAISE1,
    S_SARG_RAISE2,
    S_SARG_RAISE3,
    S_SARG_RAISE4,
    S_SARG_RAISE5,
    S_SARG_RAISE6,
    S_HEAD_STND,
    S_HEAD_RUN1,
    S_HEAD_ATK1,
    S_HEAD_ATK2,
    S_HEAD_ATK3,
    S_HEAD_PAIN,
    S_HEAD_PAIN2,
    S_HEAD_PAIN3,
    S_HEAD_DIE1,
    S_HEAD_DIE2,
    S_HEAD_DIE3,
    S_HEAD_DIE4,
    S_HEAD_DIE5,
    S_HEAD_DIE6,
    S_HEAD_RAISE1,
    S_HEAD_RAISE2,
    S_HEAD_RAISE3,
    S_HEAD_RAISE4,
    S_HEAD_RAISE5,
    S_HEAD_RAISE6,
    S_BRBALL1,
    S_BRBALL2,
    S_BRBALLX1,
    S_BRBALLX2,
    S_BRBALLX3,
    S_BOSS_STND,
    S_BOSS_STND2,
    S_BOSS_RUN1,
    S_BOSS_RUN2,
    S_BOSS_RUN3,
    S_BOSS_RUN4,
    S_BOSS_RUN5,
    S_BOSS_RUN6,
    S_BOSS_RUN7,
    S_BOSS_RUN8,
    S_BOSS_ATK1,
    S_BOSS_ATK2,
    S_BOSS_ATK3,
    S_BOSS_PAIN,
    S_BOSS_PAIN2,
    S_BOSS_DIE1,
    S_BOSS_DIE2,
    S_BOSS_DIE3,
    S_BOSS_DIE4,
    S_BOSS_DIE5,
    S_BOSS_DIE6,
    S_BOSS_DIE7,
    S_BOSS_RAISE1,
    S_BOSS_RAISE2,
    S_BOSS_RAISE3,
    S_BOSS_RAISE4,
    S_BOSS_RAISE5,
    S_BOSS_RAISE6,
    S_BOSS_RAISE7,
    S_BOS2_STND,
    S_BOS2_STND2,
    S_BOS2_RUN1,
    S_BOS2_RUN2,
    S_BOS2_RUN3,
    S_BOS2_RUN4,
    S_BOS2_RUN5,
    S_BOS2_RUN6,
    S_BOS2_RUN7,
    S_BOS2_RUN8,
    S_BOS2_ATK1,
    S_BOS2_ATK2,
    S_BOS2_ATK3,
    S_BOS2_PAIN,
    S_BOS2_PAIN2,
    S_BOS2_DIE1,
    S_BOS2_DIE2,
    S_BOS2_DIE3,
    S_BOS2_DIE4,
    S_BOS2_DIE5,
    S_BOS2_DIE6,
    S_BOS2_DIE7,
    S_BOS2_RAISE1,
    S_BOS2_RAISE2,
    S_BOS2_RAISE3,
    S_BOS2_RAISE4,
    S_BOS2_RAISE5,
    S_BOS2_RAISE6,
    S_BOS2_RAISE7,
    S_SKULL_STND,
    S_SKULL_STND2,
    S_SKULL_RUN1,
    S_SKULL_RUN2,
    S_SKULL_ATK1,
    S_SKULL_ATK2,
    S_SKULL_ATK3,
    S_SKULL_ATK4,
    S_SKULL_PAIN,
    S_SKULL_PAIN2,
    S_SKULL_DIE1,
    S_SKULL_DIE2,
    S_SKULL_DIE3,
    S_SKULL_DIE4,
    S_SKULL_DIE5,
    S_SKULL_DIE6,
    S_SPID_STND,
    S_SPID_STND2,
    S_SPID_RUN1,
    S_SPID_RUN2,
    S_SPID_RUN3,
    S_SPID_RUN4,
    S_SPID_RUN5,
    S_SPID_RUN6,
    S_SPID_RUN7,
    S_SPID_RUN8,
    S_SPID_RUN9,
    S_SPID_RUN10,
    S_SPID_RUN11,
    S_SPID_RUN12,
    S_SPID_ATK1,
    S_SPID_ATK2,
    S_SPID_ATK3,
    S_SPID_ATK4,
    S_SPID_PAIN,
    S_SPID_PAIN2,
    S_SPID_DIE1,
    S_SPID_DIE2,
    S_SPID_DIE3,
    S_SPID_DIE4,
    S_SPID_DIE5,
    S_SPID_DIE6,
    S_SPID_DIE7,
    S_SPID_DIE8,
    S_SPID_DIE9,
    S_SPID_DIE10,
    S_SPID_DIE11,
    S_BSPI_STND,
    S_BSPI_STND2,
    S_BSPI_SIGHT,
    S_BSPI_RUN1,
    S_BSPI_RUN2,
    S_BSPI_RUN3,
    S_BSPI_RUN4,
    S_BSPI_RUN5,
    S_BSPI_RUN6,
    S_BSPI_RUN7,
    S_BSPI_RUN8,
    S_BSPI_RUN9,
    S_BSPI_RUN10,
    S_BSPI_RUN11,
    S_BSPI_RUN12,
    S_BSPI_ATK1,
    S_BSPI_ATK2,
    S_BSPI_ATK3,
    S_BSPI_ATK4,
    S_BSPI_PAIN,
    S_BSPI_PAIN2,
    S_BSPI_DIE1,
    S_BSPI_DIE2,
    S_BSPI_DIE3,
    S_BSPI_DIE4,
    S_BSPI_DIE5,
    S_BSPI_DIE6,
    S_BSPI_DIE7,
    S_BSPI_RAISE1,
    S_BSPI_RAISE2,
    S_BSPI_RAISE3,
    S_BSPI_RAISE4,
    S_BSPI_RAISE5,
    S_BSPI_RAISE6,
    S_BSPI_RAISE7,
    S_ARACH_PLAZ,
    S_ARACH_PLAZ2,
    S_ARACH_PLEX,
    S_ARACH_PLEX2,
    S_ARACH_PLEX3,
    S_ARACH_PLEX4,
    S_ARACH_PLEX5,
    S_CYBER_STND,
    S_CYBER_STND2,
    S_CYBER_RUN1,
    S_CYBER_RUN2,
    S_CYBER_RUN3,
    S_CYBER_RUN4,
    S_CYBER_RUN5,
    S_CYBER_RUN6,
    S_CYBER_RUN7,
    S_CYBER_RUN8,
    S_CYBER_ATK1,
    S_CYBER_ATK2,
    S_CYBER_ATK3,
    S_CYBER_ATK4,
    S_CYBER_ATK5,
    S_CYBER_ATK6,
    S_CYBER_PAIN,
    S_CYBER_DIE1,
    S_CYBER_DIE2,
    S_CYBER_DIE3,
    S_CYBER_DIE4,
    S_CYBER_DIE5,
    S_CYBER_DIE6,
    S_CYBER_DIE7,
    S_CYBER_DIE8,
    S_CYBER_DIE9,
    S_CYBER_DIE10,
    S_PAIN_STND,
    S_PAIN_RUN1,
    S_PAIN_RUN2,
    S_PAIN_RUN3,
    S_PAIN_RUN4,
    S_PAIN_RUN5,
    S_PAIN_RUN6,
    S_PAIN_ATK1,
    S_PAIN_ATK2,
    S_PAIN_ATK3,
    S_PAIN_ATK4,
    S_PAIN_PAIN,
    S_PAIN_PAIN2,
    S_PAIN_DIE1,
    S_PAIN_DIE2,
    S_PAIN_DIE3,
    S_PAIN_DIE4,
    S_PAIN_DIE5,
    S_PAIN_DIE6,
    S_PAIN_RAISE1,
    S_PAIN_RAISE2,
    S_PAIN_RAISE3,
    S_PAIN_RAISE4,
    S_PAIN_RAISE5,
    S_PAIN_RAISE6,
    S_SSWV_STND,
    S_SSWV_STND2,
    S_SSWV_RUN1,
    S_SSWV_RUN2,
    S_SSWV_RUN3,
    S_SSWV_RUN4,
    S_SSWV_RUN5,
    S_SSWV_RUN6,
    S_SSWV_RUN7,
    S_SSWV_RUN8,
    S_SSWV_ATK1,
    S_SSWV_ATK2,
    S_SSWV_ATK3,
    S_SSWV_ATK4,
    S_SSWV_ATK5,
    S_SSWV_ATK6,
    S_SSWV_PAIN,
    S_SSWV_PAIN2,
    S_SSWV_DIE1,
    S_SSWV_DIE2,
    S_SSWV_DIE3,
    S_SSWV_DIE4,
    S_SSWV_DIE5,
    S_SSWV_XDIE1,
    S_SSWV_XDIE2,
    S_SSWV_XDIE3,
    S_SSWV_XDIE4,
    S_SSWV_XDIE5,
    S_SSWV_XDIE6,
    S_SSWV_XDIE7,
    S_SSWV_XDIE8,
    S_SSWV_XDIE9,
    S_SSWV_RAISE1,
    S_SSWV_RAISE2,
    S_SSWV_RAISE3,
    S_SSWV_RAISE4,
    S_SSWV_RAISE5,
    S_KEENSTND,
    S_COMMKEEN,
    S_COMMKEEN2,
    S_COMMKEEN3,
    S_COMMKEEN4,
    S_COMMKEEN5,
    S_COMMKEEN6,
    S_COMMKEEN7,
    S_COMMKEEN8,
    S_COMMKEEN9,
    S_COMMKEEN10,
    S_COMMKEEN11,
    S_COMMKEEN12,
    S_KEENPAIN,
    S_KEENPAIN2,
    S_BRAIN,
    S_BRAIN_PAIN,
    S_BRAIN_DIE1,
    S_BRAIN_DIE2,
    S_BRAIN_DIE3,
    S_BRAIN_DIE4,
    S_BRAINEYE,
    S_BRAINEYESEE,
    S_BRAINEYE1,
    S_SPAWN1,
    S_SPAWN2,
    S_SPAWN3,
    S_SPAWN4,
    S_SPAWNFIRE1,
    S_SPAWNFIRE2,
    S_SPAWNFIRE3,
    S_SPAWNFIRE4,
    S_SPAWNFIRE5,
    S_SPAWNFIRE6,
    S_SPAWNFIRE7,
    S_SPAWNFIRE8,
    S_BRAINEXPLODE1,
    S_BRAINEXPLODE2,
    S_BRAINEXPLODE3,
    S_ARM1,
    S_ARM1A,
    S_ARM2,
    S_ARM2A,
    S_BAR1,
    S_BAR2,
    S_BEXP,
    S_BEXP2,
    S_BEXP3,
    S_BEXP4,
    S_BEXP5,
    S_BBAR1,
    S_BBAR2,
    S_BBAR3,
    S_BON1,
    S_BON1A,
    S_BON1B,
    S_BON1C,
    S_BON1D,
    S_BON1E,
    S_BON2,
    S_BON2A,
    S_BON2B,
    S_BON2C,
    S_BON2D,
    S_BON2E,
    S_BKEY,
    S_BKEY2,
    S_RKEY,
    S_RKEY2,
    S_YKEY,
    S_YKEY2,
    S_BSKULL,
    S_BSKULL2,
    S_RSKULL,
    S_RSKULL2,
    S_YSKULL,
    S_YSKULL2,
    S_STIM,
    S_MEDI,
    S_SOUL,
    S_SOUL2,
    S_SOUL3,
    S_SOUL4,
    S_SOUL5,
    S_SOUL6,
    S_PINV,
    S_PINV2,
    S_PINV3,
    S_PINV4,
    S_PSTR,
    S_PINS,
    S_PINS2,
    S_PINS3,
    S_PINS4,
    S_MEGA,
    S_MEGA2,
    S_MEGA3,
    S_MEGA4,
    S_SUIT,
    S_PMAP,
    S_PMAP2,
    S_PMAP3,
    S_PMAP4,
    S_PMAP5,
    S_PMAP6,
    S_PVIS,
    S_PVIS2,
    S_CLIP,
    S_AMMO,
    S_ROCK,
    S_BROK,
    S_CELL,
    S_CELP,
    S_SHEL,
    S_SBOX,
    S_BPAK,
    S_BFUG,
    S_MGUN,
    S_CSAW,
    S_LAUN,
    S_PLAS,
    S_SHOT,
    S_SHOT2,
    S_COLU,
    S_STALAG,
    S_BLOODYTWITCH,
    S_BLOODYTWITCH2,
    S_BLOODYTWITCH3,
    S_BLOODYTWITCH4,
    S_DEADTORSO,
    S_DEADBOTTOM,
    S_HEADSONSTICK,
    S_GIBS,
    S_HEADONASTICK,
    S_HEADCANDLES,
    S_HEADCANDLES2,
    S_DEADSTICK,
    S_LIVESTICK,
    S_LIVESTICK2,
    S_MEAT2,
    S_MEAT3,
    S_MEAT4,
    S_MEAT5,
    S_STALAGTITE,
    S_TALLGRNCOL,
    S_SHRTGRNCOL,
    S_TALLREDCOL,
    S_SHRTREDCOL,
    S_CANDLESTIK,
    S_CANDELABRA,
    S_SKULLCOL,
    S_TORCHTREE,
    S_BIGTREE,
    S_TECHPILLAR,
    S_EVILEYE,
    S_EVILEYE2,
    S_EVILEYE3,
    S_EVILEYE4,
    S_FLOATSKULL,
    S_FLOATSKULL2,
    S_FLOATSKULL3,
    S_HEARTCOL,
    S_HEARTCOL2,
    S_BLUETORCH,
    S_BLUETORCH2,
    S_BLUETORCH3,
    S_BLUETORCH4,
    S_GREENTORCH,
    S_GREENTORCH2,
    S_GREENTORCH3,
    S_GREENTORCH4,
    S_REDTORCH,
    S_REDTORCH2,
    S_REDTORCH3,
    S_REDTORCH4,
    S_BTORCHSHRT,
    S_BTORCHSHRT2,
    S_BTORCHSHRT3,
    S_BTORCHSHRT4,
    S_GTORCHSHRT,
    S_GTORCHSHRT2,
    S_GTORCHSHRT3,
    S_GTORCHSHRT4,
    S_RTORCHSHRT,
    S_RTORCHSHRT2,
    S_RTORCHSHRT3,
    S_RTORCHSHRT4,
    S_HANGNOGUTS,
    S_HANGBNOBRAIN,
    S_HANGTLOOKDN,
    S_HANGTSKULL,
    S_HANGTLOOKUP,
    S_HANGTNOBRAIN,
    S_COLONGIBS,
    S_SMALLPOOL,
    S_BRAINSTEM,
    S_TECHLAMP,
    S_TECHLAMP2,
    S_TECHLAMP3,
    S_TECHLAMP4,
    S_TECH2LAMP,
    S_TECH2LAMP2,
    S_TECH2LAMP3,
    S_TECH2LAMP4,
    NUMSTATES
} statenum_t;


typedef struct
{
    spritenum_t sprite;
    long frame;
    long tics;
    actionf_t action;
    statenum_t nextstate;
    long misc1, misc2;
} state_t;


extern state_t states[NUMSTATES];
extern char* sprnames[NUMSPRITES + 1];


typedef enum
{
    MT_PLAYER,
    MT_POSSESSED,
    MT_SHOTGUY,
    MT_VILE,
    MT_FIRE,
    MT_UNDEAD,
    MT_TRACER,
    MT_SMOKE,
    MT_FATSO,
    MT_FATSHOT,
    MT_CHAINGUY,
    MT_TROOP,
    MT_SERGEANT,
    MT_SHADOWS,
    MT_HEAD,
    MT_BRUISER,
    MT_BRUISERSHOT,
    MT_KNIGHT,
    MT_SKULL,
    MT_SPIDER,
    MT_BABY,
    MT_CYBORG,
    MT_PAIN,
    MT_WOLFSS,
    MT_KEEN,
    MT_BOSSBRAIN,
    MT_BOSSSPIT,
    MT_BOSSTARGET,
    MT_SPAWNSHOT,
    MT_SPAWNFIRE,
    MT_BARREL,
    MT_TROOPSHOT,
    MT_HEADSHOT,
    MT_ROCKET,
    MT_PLASMA,
    MT_BFG,
    MT_ARACHPLAZ,
    MT_PUFF,
    MT_BLOOD,
    MT_TFOG,
    MT_IFOG,
    MT_TELEPORTMAN,
    MT_EXTRABFG,
    MT_MISC0,
    MT_MISC1,
    MT_MISC2,
    MT_MISC3,
    MT_MISC4,
    MT_MISC5,
    MT_MISC6,
    MT_MISC7,
    MT_MISC8,
    MT_MISC9,
    MT_MISC10,
    MT_MISC11,
    MT_MISC12,
    MT_INV,
    MT_MISC13,
    MT_INS,
    MT_MISC14,
    MT_MISC15,
    MT_MISC16,
    MT_MEGA,
    MT_CLIP,
    MT_MISC17,
    MT_MISC18,
    MT_MISC19,
    MT_MISC20,
    MT_MISC21,
    MT_MISC22,
    MT_MISC23,
    MT_MISC24,
    MT_MISC25,
    MT_CHAINGUN,
    MT_MISC26,
    MT_MISC27,
    MT_MISC28,
    MT_SHOTGUN,
    MT_SUPERSHOTGUN,
    MT_MISC29,
    MT_MISC30,
    MT_MISC31,
    MT_MISC32,
    MT_MISC33,
    MT_MISC34,
    MT_MISC35,
    MT_MISC36,
    MT_MISC37,
    MT_MISC38,
    MT_MISC39,
    MT_MISC40,
    MT_MISC41,
    MT_MISC42,
    MT_MISC43,
    MT_MISC44,
    MT_MISC45,
    MT_MISC46,
    MT_MISC47,
    MT_MISC48,
    MT_MISC49,
    MT_MISC50,
    MT_MISC51,
    MT_MISC52,
    MT_MISC53,
    MT_MISC54,
    MT_MISC55,
    MT_MISC56,
    MT_MISC57,
    MT_MISC58,
    MT_MISC59,
    MT_MISC60,
    MT_MISC61,
    MT_MISC62,
    MT_MISC63,
    MT_MISC64,
    MT_MISC65,
    MT_MISC66,
    MT_MISC67,
    MT_MISC68,
    MT_MISC69,
    MT_MISC70,
    MT_MISC71,
    MT_MISC72,
    MT_MISC73,
    MT_MISC74,
    MT_MISC75,
    MT_MISC76,
    MT_MISC77,
    MT_MISC78,
    MT_MISC79,
    MT_MISC80,
    MT_MISC81,
    MT_MISC82,
    MT_MISC83,
    MT_MISC84,
    MT_MISC85,
    MT_MISC86,
    NUMMOBJTYPES
} mobjtype_t;


typedef struct
{
    int        doomednum;
    int        spawnstate;
    int        spawnhealth;
    int        seestate;
    int        seesound;
    int        reactiontime;
    int        attacksound;
    int        painstate;
    int        painchance;
    int        painsound;
    int        meleestate;
    int        missilestate;
    int        deathstate;
    int        xdeathstate;
    int        deathsound;
    int        speed;
    int        radius;
    int        height;
    int        mass;
    int        damage;
    int        activesound;
    int        flags;
    int        raisestate;
} mobjinfo_t;


extern mobjinfo_t mobjinfo[NUMMOBJTYPES];


#endif


#ifndef __M_ARGV__
#define __M_ARGV__


//
// MISC
//
extern int myargc;
extern char** myargv;

// Returns the position of the given parameter
// in the arg list (0 if not found).
int M_CheckParm(char* check);


#endif


#ifndef __M_CHEAT__
#define __M_CHEAT__


//
// CHEAT SEQUENCE PACKAGE
//

#define SCRAMBLE(a) \
((((a)&1)<<7) + (((a)&2)<<5) + ((a)&4) + (((a)&8)<<1) \
 + (((a)&16)>>1) + ((a)&32) + (((a)&64)>>5) + (((a)&128)>>7))


typedef struct
{
    unsigned char* sequence;
    unsigned char* p;
} cheatseq_t;


int cht_CheckCheat(cheatseq_t* cht, char key);
void cht_GetParam(cheatseq_t* cht, char* buffer);


#endif


#ifndef __M_FIXED__
#define __M_FIXED__


//
// Fixed point, 32bit as 16.16.
//
#define FRACBITS 16
#define FRACUNIT (1<<FRACBITS)


typedef int fixed_t;


fixed_t FixedMul(fixed_t a, fixed_t b);
fixed_t FixedDiv(fixed_t a, fixed_t b);
fixed_t FixedDiv2(fixed_t a, fixed_t b);


#endif


#ifndef __M_BBOX__
#define __M_BBOX__


//#include "doomtype.h"
//#include "m_fixed.h"


// Bounding box coordinate storage.
enum
{
    BOXTOP,
    BOXBOTTOM,
    BOXLEFT,
    BOXRIGHT
}; // bbox coordinates


// Bounding box functions.
void M_ClearBox(fixed_t* box);
void M_AddToBox(fixed_t* box, fixed_t x, fixed_t y);


#endif


#ifndef __M_MENU__
#define __M_MENU__


//#include "d_event.h"


//
// MENUS
//
 
// Called by main loop,
// saves config file and calls I_Quit when user exits.
// Even when the menu is not displayed,
// this can resize the view and change game parameters.
// Does all the real work of the menu interaction.
doom_boolean M_Responder(event_t* ev);

// Called by main loop,
// only used for menu (skull cursor) animation.
void M_Ticker(void);

// Called by main loop,
// draws the menus directly into the screen buffer.
void M_Drawer(void);

// Called by D_DoomMain,
// loads the config file.
void M_Init(void);

// Called by intro code to force menu up upon a keypress,
// does nothing if menu is already up.
void M_StartControlPanel(void);


#endif


#ifndef __M_MISC__
#define __M_MISC__


//#include "doomtype.h"


//
// MISC
//
typedef struct
{
    char* name;
    int* location;
    int defaultvalue;
    int scantranslate; // PC scan code hack
    int untranslated; // lousy hack
    char** text_location; // [pd] int* location was used to store text pointer. Can't change to intptr_t unless we change all settings type
    char* default_text_value; // [pd] So we don't change defaultvalue behavior for int to intptr_t
} default_t;


doom_boolean M_WriteFile(char const* name, void* source, int length);
int M_ReadFile(char const* name, byte** buffer);
void M_ScreenShot(void);
void M_LoadDefaults(void);
void M_SaveDefaults(void);
int M_DrawText(int x, int y, doom_boolean direct, char* string);


#endif


#ifndef __M_RANDOM__
#define __M_RANDOM__


//#include "doomtype.h"


// Returns a number from 0 to 255,
// from a lookup table.
int M_Random(void);

// As M_Random, but used only by the play simulation.
int P_Random(void);

// Fix randoms for demos.
void M_ClearRandom(void);


#endif


#ifndef __M_SWAP__
#define __M_SWAP__


// Endianess handling.
// WAD files are stored little endian.
#ifdef __BIG_ENDIAN__
unsigned short SwapSHORT(unsigned short);
unsigned long SwapLONG(unsigned long);
#define SHORT(x) ((short)SwapSHORT((unsigned short) (x)))
#define LONG(x) ((long)SwapLONG((unsigned long) (x)))
#else
#define SHORT(x) (x)
#define LONG(x) (x)
#endif


#endif


#ifndef __P_SAVEG__
#define __P_SAVEG__


//#include "doomtype.h"


// Persistent storage/archiving.
// These are the load / save game routines.
void P_ArchivePlayers(void);
void P_UnArchivePlayers(void);
void P_ArchiveWorld(void);
void P_UnArchiveWorld(void);
void P_ArchiveThinkers(void);
void P_UnArchiveThinkers(void);
void P_ArchiveSpecials(void);
void P_UnArchiveSpecials(void);

extern byte* save_p;


#endif


#ifndef __P_SETUP__
#define __P_SETUP__


// NOT called by W_Ticker. Fixme.
void P_SetupLevel(int episode, int map, int playermask, skill_t skill);

// Called by startup code.
void P_Init(void);


#endif


#ifndef __P_TICK__
#define __P_TICK__


// Called by C_Ticker,
// can call G_PlayerExited.
// Carries out all thinking of monsters and players.
void P_Ticker(void);


#endif


#ifndef __R_SKY__
#define __R_SKY__

// SKY, store the number for name.
#define SKYFLATNAME "F_SKY1"

// The sky map is 256*128*4 maps.
#define ANGLETOSKYSHIFT 22

extern int skytexture;
extern int skytexturemid;

// Called whenever the view size changes.
void R_InitSkyMap(void);

#endif


#ifndef __S_SOUND__
#define __S_SOUND__


//
// 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);

//
// Per level startup code.
// Kills playing sounds at start of level,
//  determines music if any, changes music.
//
void S_Start(void);

//
// Start sound for thing at <origin>
//  using <sound_id> from sounds.h
//
void S_StartSound(void* origin, int sound_id);

// Will start a sound at a given volume.
void S_StartSoundAtVolume(void* origin, int sound_id, int volume);

// Stop sound for thing at <origin>
void S_StopSound(void* origin);

// Start music using <music_id> from sounds.h
void S_StartMusic(int music_id);

// Start music using <music_id> from sounds.h,
// and set whether looping
void S_ChangeMusic(int music_id, int looping);

// Stops the music fer sure.
void S_StopMusic(void);

// Stop and resume music, during game PAUSE.
void S_PauseSound(void);
void S_ResumeSound(void);

//
// Updates music & sounds
//
void S_UpdateSounds(void* listener);

void S_SetMusicVolume(int volume);
void S_SetSfxVolume(int volume);


#endif


#ifndef __SOUNDS__
#define __SOUNDS__


//
// SoundFX struct.
//
typedef struct sfxinfo_struct sfxinfo_t;

struct sfxinfo_struct
{
    // up to 6-character name
    char* name;

    // Sfx singularity (only one at a time)
    int singularity;

    // Sfx priority
    int priority;

    // referenced sound if a link
    sfxinfo_t* link;

    // pitch if a link
    int pitch;

    // volume if a link
    int volume;

    // sound data
    void* data;

    // this is checked every second to see if sound
    // can be thrown out (if 0, then decrement, if -1,
    // then throw out, if > 0, then it is in use)
    int usefulness;

    // lump number of sfx
    int lumpnum;
};


//
// MusicInfo struct.
//
typedef struct
{
    // up to 6-character name
    char* name;

    // lump number of music
    int lumpnum;

    // music data
    void* data;

    // music handle once registered
    int handle;
} musicinfo_t;


// the complete set of sound effects
extern sfxinfo_t S_sfx[];

// the complete set of music
extern musicinfo_t S_music[];

//
// Identifiers for all music in game.
//
typedef enum
{
    mus_None,
    mus_e1m1,
    mus_e1m2,
    mus_e1m3,
    mus_e1m4,
    mus_e1m5,
    mus_e1m6,
    mus_e1m7,
    mus_e1m8,
    mus_e1m9,
    mus_e2m1,
    mus_e2m2,
    mus_e2m3,
    mus_e2m4,
    mus_e2m5,
    mus_e2m6,
    mus_e2m7,
    mus_e2m8,
    mus_e2m9,
    mus_e3m1,
    mus_e3m2,
    mus_e3m3,
    mus_e3m4,
    mus_e3m5,
    mus_e3m6,
    mus_e3m7,
    mus_e3m8,
    mus_e3m9,
    mus_inter,
    mus_intro,
    mus_bunny,
    mus_victor,
    mus_introa,
    mus_runnin,
    mus_stalks,
    mus_countd,
    mus_betwee,
    mus_doom,
    mus_the_da,
    mus_shawn,
    mus_ddtblu,
    mus_in_cit,
    mus_dead,
    mus_stlks2,
    mus_theda2,
    mus_doom2,
    mus_ddtbl2,
    mus_runni2,
    mus_dead2,
    mus_stlks3,
    mus_romero,
    mus_shawn2,
    mus_messag,
    mus_count2,
    mus_ddtbl3,
    mus_ampie,
    mus_theda3,
    mus_adrian,
    mus_messg2,
    mus_romer2,
    mus_tense,
    mus_shawn3,
    mus_openin,
    mus_evil,
    mus_ultima,
    mus_read_m,
    mus_dm2ttl,
    mus_dm2int,
    NUMMUSIC
} musicenum_t;


//
// Identifiers for all sfx in game.
//
typedef enum
{
    sfx_None,
    sfx_pistol,
    sfx_shotgn,
    sfx_sgcock,
    sfx_dshtgn,
    sfx_dbopn,
    sfx_dbcls,
    sfx_dbload,
    sfx_plasma,
    sfx_bfg,
    sfx_sawup,
    sfx_sawidl,
    sfx_sawful,
    sfx_sawhit,
    sfx_rlaunc,
    sfx_rxplod,
    sfx_firsht,
    sfx_firxpl,
    sfx_pstart,
    sfx_pstop,
    sfx_doropn,
    sfx_dorcls,
    sfx_stnmov,
    sfx_swtchn,
    sfx_swtchx,
    sfx_plpain,
    sfx_dmpain,
    sfx_popain,
    sfx_vipain,
    sfx_mnpain,
    sfx_pepain,
    sfx_slop,
    sfx_itemup,
    sfx_wpnup,
    sfx_oof,
    sfx_telept,
    sfx_posit1,
    sfx_posit2,
    sfx_posit3,
    sfx_bgsit1,
    sfx_bgsit2,
    sfx_sgtsit,
    sfx_cacsit,
    sfx_brssit,
    sfx_cybsit,
    sfx_spisit,
    sfx_bspsit,
    sfx_kntsit,
    sfx_vilsit,
    sfx_mansit,
    sfx_pesit,
    sfx_sklatk,
    sfx_sgtatk,
    sfx_skepch,
    sfx_vilatk,
    sfx_claw,
    sfx_skeswg,
    sfx_pldeth,
    sfx_pdiehi,
    sfx_podth1,
    sfx_podth2,
    sfx_podth3,
    sfx_bgdth1,
    sfx_bgdth2,
    sfx_sgtdth,
    sfx_cacdth,
    sfx_skldth,
    sfx_brsdth,
    sfx_cybdth,
    sfx_spidth,
    sfx_bspdth,
    sfx_vildth,
    sfx_kntdth,
    sfx_pedth,
    sfx_skedth,
    sfx_posact,
    sfx_bgact,
    sfx_dmact,
    sfx_bspact,
    sfx_bspwlk,
    sfx_vilact,
    sfx_noway,
    sfx_barexp,
    sfx_punch,
    sfx_hoof,
    sfx_metal,
    sfx_chgun,
    sfx_tink,
    sfx_bdopn,
    sfx_bdcls,
    sfx_itmbk,
    sfx_flame,
    sfx_flamst,
    sfx_getpow,
    sfx_bospit,
    sfx_boscub,
    sfx_bossit,
    sfx_bospn,
    sfx_bosdth,
    sfx_manatk,
    sfx_mandth,
    sfx_sssit,
    sfx_ssdth,
    sfx_keenpn,
    sfx_keendt,
    sfx_skeact,
    sfx_skesit,
    sfx_skeatk,
    sfx_radio,
    NUMSFX
} sfxenum_t;

#endif


#ifndef __STSTUFF_H__
#define __STSTUFF_H__

//#include "doomtype.h"
//#include "d_event.h"

// Size of statusbar.
// Now sensitive for scaling.
#define ST_HEIGHT (32 * SCREEN_MUL)
#define ST_WIDTH SCREENWIDTH
#define ST_Y (SCREENHEIGHT - ST_HEIGHT)


//
// STATUS BAR
//

// Called by main loop.
doom_boolean ST_Responder(event_t* ev);

// Called by main loop.
void ST_Ticker(void);

// Called by main loop.
void ST_Drawer(doom_boolean fullscreen, doom_boolean refresh);

// Called when the console player is spawned on each level.
void ST_Start(void);

// Called by startup code.
void ST_Init(void);


// States for status bar code.
typedef enum
{
    AutomapState,
    FirstPersonState
} st_stateenum_t;


// States for the chat code.
typedef enum
{
    StartChatState,
    WaitDestState,
    GetChatState
} st_chatstateenum_t;


doom_boolean ST_Responder(event_t* ev);

#endif


#ifndef __TABLES__
#define __TABLES__


#ifdef LINUX

#else
#define PI 3.141592657
#endif

//#include "m_fixed.h"

#define FINEANGLES 8192
#define FINEMASK (FINEANGLES-1)

// 0x100000000 to 0x2000
#define ANGLETOFINESHIFT 19                

// Effective size is 10240.
extern fixed_t finesine[5 * FINEANGLES / 4];

// Re-use data, is just PI/2 pahse shift.
extern fixed_t* finecosine;


// Effective size is 4096.
extern fixed_t finetangent[FINEANGLES / 2];

// Binary Angle Measument, BAM.
#define ANG45 0x20000000
#define ANG90 0x40000000
#define ANG180 0x80000000
#define ANG270 0xc0000000

#define SLOPERANGE 2048
#define SLOPEBITS 11
#define DBITS (FRACBITS - SLOPEBITS)

typedef unsigned angle_t;

// Effective size is 2049;
// The +1 size is to handle the case when x==y
//  without additional checking.
extern angle_t                tantoangle[SLOPERANGE + 1];


// Utility function,
//  called by R_PointToAngle.
int SlopeDiv(unsigned num, unsigned den);


#endif


#ifndef __P_MOBJ__
#define __P_MOBJ__


// Basics.
//#include "tables.h"
//#include "m_fixed.h"

// We need the thinker_t stuff.
//#include "d_think.h"

// We need the WAD data structure for Map things,
// from the THINGS lump.
//#include "doomdata.h"

// States are tied to finite states are
//  tied to animation frames.
// Needs precompiled tables/data structures.
//#include "info.h"


//
// NOTES: mobj_t
//
// mobj_ts are used to tell the refresh where to draw an image,
// tell the world simulation when objects are contacted,
// and tell the sound driver how to position a sound.
//
// The refresh uses the next and prev links to follow
// lists of things in sectors as they are being drawn.
// The sprite, frame, and angle elements determine which patch_t
// is used to draw the sprite if it is visible.
// The sprite and frame values are allmost allways set
// from state_t structures.
// The statescr.exe utility generates the states.h and states.c
// files that contain the sprite/frame numbers from the
// statescr.txt source file.
// The xyz origin point represents a point at the bottom middle
// of the sprite (between the feet of a biped).
// This is the default origin position for patch_ts grabbed
// with lumpy.exe.
// A walking creature will have its z equal to the floor
// it is standing on.
//
// The sound code uses the x,y, and subsector fields
// to do stereo positioning of any sound effited by the mobj_t.
//
// The play simulation uses the blocklinks, x,y,z, radius, height
// to determine when mobj_ts are touching each other,
// touching lines in the map, or hit by trace lines (gunshots,
// lines of sight, etc).
// The mobj_t->flags element has various bit flags
// used by the simulation.
//
// Every mobj_t is linked into a single sector
// based on its origin coordinates.
// The subsector_t is found with R_PointInSubsector(x,y),
// and the sector_t can be found with subsector->sector.
// The sector links are only used by the rendering code,
// the play simulation does not care about them at all.
//
// Any mobj_t that needs to be acted upon by something else
// in the play world (block movement, be shot, etc) will also
// need to be linked into the blockmap.
// If the thing has the MF_NOBLOCK flag set, it will not use
// the block links. It can still interact with other things,
// but only as the instigator (missiles will run into other
// things, but nothing can run into a missile).
// Each block in the grid is 128*128 units, and knows about
// every line_t that it contains a piece of, and every
// interactable mobj_t that has its origin contained.  
//
// A valid mobj_t is a mobj_t that has the proper subsector_t
// filled in for its xy coordinates and is linked into the
// sector from which the subsector was made, or has the
// MF_NOSECTOR flag set (the subsector_t needs to be valid
// even if MF_NOSECTOR is set), and is linked into a blockmap
// block or has the MF_NOBLOCKMAP flag set.
// Links should only be modified by the P_[Un]SetThingPosition()
// functions.
// Do not change the MF_NO? flags while a thing is valid.
//
// Any questions?
//

//
// Misc. mobj flags
//
typedef enum
{
    // Call P_SpecialThing when touched.
    MF_SPECIAL = 1,
    // Blocks.
    MF_SOLID = 2,
    // Can be hit.
    MF_SHOOTABLE = 4,
    // Don't use the sector links (invisible but touchable).
    MF_NOSECTOR = 8,
    // Don't use the blocklinks (inert but displayable)
    MF_NOBLOCKMAP = 16,

    // Not to be activated by sound, deaf monster.
    MF_AMBUSH = 32,
    // Will try to attack right back.
    MF_JUSTHIT = 64,
    // Will take at least one step before attacking.
    MF_JUSTATTACKED = 128,
    // On level spawning (initial position),
    //  hang from ceiling instead of stand on floor.
    MF_SPAWNCEILING = 256,
    // Don't apply gravity (every tic),
    //  that is, object will float, keeping current height
    //  or changing it actively.
    MF_NOGRAVITY = 512,

    // Movement flags.
    // This allows jumps from high places.
    MF_DROPOFF = 0x400,
    // For players, will pick up items.
    MF_PICKUP = 0x800,
    // Player cheat. ???
    MF_NOCLIP = 0x1000,
    // Player: keep info about sliding along walls.
    MF_SLIDE = 0x2000,
    // Allow moves to any height, no gravity.
    // For active floaters, e.g. cacodemons, pain elementals.
    MF_FLOAT = 0x4000,
    // Don't cross lines
    //   ??? or look at heights on teleport.
    MF_TELEPORT = 0x8000,
    // Don't hit same species, explode on block.
    // Player missiles as well as fireballs of various kinds.
    MF_MISSILE = 0x10000,
    // Dropped by a demon, not level spawned.
    // E.g. ammo clips dropped by dying former humans.
    MF_DROPPED = 0x20000,
    // Use fuzzy draw (shadow demons or spectres),
    //  temporary player invisibility powerup.
    MF_SHADOW = 0x40000,
    // Flag: don't bleed when shot (use puff),
    //  barrels and shootable furniture shall not bleed.
    MF_NOBLOOD = 0x80000,
    // Don't stop moving halfway off a step,
    //  that is, have dead bodies slide down all the way.
    MF_CORPSE = 0x100000,
    // Floating to a height for a move, ???
    //  don't auto float to target's height.
    MF_INFLOAT = 0x200000,

    // On kill, count this enemy object
    //  towards intermission kill total.
    // Happy gathering.
    MF_COUNTKILL = 0x400000,

    // On picking up, count this item object
    //  towards intermission item total.
    MF_COUNTITEM = 0x800000,

    // Special handling: skull in flight.
    // Neither a cacodemon nor a missile.
    MF_SKULLFLY = 0x1000000,

    // Don't spawn this object
    //  in death match mode (e.g. key cards).
    MF_NOTDMATCH = 0x2000000,

    // Player sprites in multiplayer modes are modified
    //  using an internal color lookup table for re-indexing.
    // If 0x4 0x8 or 0xc,
    //  use a translation table for player colormaps
    MF_TRANSLATION = 0xc000000,
    // Hmm ???.
    MF_TRANSSHIFT = 26
} mobjflag_t;


// Map Object definition.
typedef struct mobj_s
{
    // List: thinker links.
    thinker_t thinker;

    // Info for drawing: position.
    fixed_t x;
    fixed_t y;
    fixed_t z;

    // More list: links in sector (if needed)
    struct mobj_s* snext;
    struct mobj_s* sprev;

    //More drawing info: to determine current sprite.
    angle_t angle;        // orientation
    spritenum_t sprite;        // used to find patch_t and flip value
    int frame;        // might be ORed with FF_FULLBRIGHT

    // Interaction info, by BLOCKMAP.
    // Links in blocks (if needed).
    struct mobj_s* bnext;
    struct mobj_s* bprev;

    struct subsector_s* subsector;

    // The closest interval over all contacted Sectors.
    fixed_t floorz;
    fixed_t ceilingz;

    // For movement checking.
    fixed_t radius;
    fixed_t height;

    // Momentums, used to update position.
    fixed_t momx;
    fixed_t momy;
    fixed_t momz;

    // If == validcount, already checked.
    int validcount;

    mobjtype_t type;
    mobjinfo_t* info; // &mobjinfo[mobj->type]

    int tics; // state tic counter
    state_t* state;
    int flags;
    int health;

    // Movement direction, movement generation (zig-zagging).
    int movedir; // 0-7
    int movecount; // when 0, select a new dir

    // Thing being chased/attacked (or 0),
    // also the originator for missiles.
    struct mobj_s* target;

    // Reaction time: if non 0, don't attack yet.
    // Used by player to freeze a bit after teleporting.
    int reactiontime;

    // If >0, the target will be chased
    // no matter what (even if shot)
    int threshold;

    // Additional info record for player avatars only.
    // Only valid if type == MT_PLAYER
    struct player_s* player;

    // Player number last looked for.
    int lastlook;

    // For nightmare respawn.
    mapthing_t spawnpoint;

    // Thing being chased/attacked for tracers.
    struct mobj_s* tracer;
} mobj_t;


#endif


#ifndef __P_PSPR__
#define __P_PSPR__

// Basic data types.
// Needs fixed point, and BAM angles.
//#include "m_fixed.h"
//#include "tables.h"

// Needs to include the precompiled
// sprite animation tables.
// Header generated by multigen utility.
// This includes all the data for thing animation,
// i.e. the Thing Atrributes table
// and the Frame Sequence table.
//#include "info.h"

//
// Frame flags:
// handles maximum brightness (torches, muzzle flare, light sources)
//
#define FF_FULLBRIGHT 0x8000 // flag in thing->frame
#define FF_FRAMEMASK 0x7fff


//
// Overlay psprites are scaled shapes
// drawn directly on the view screen,
// coordinates are given for a 320*200 view screen.
//
typedef enum
{
    ps_weapon,
    ps_flash,
    NUMPSPRITES
} psprnum_t;


typedef struct
{
    state_t* state;        // a 0 state means not active
    int tics;
    fixed_t sx;
    fixed_t sy;
} pspdef_t;


#endif


#ifndef __D_PLAYER__
#define __D_PLAYER__


// The player data structure depends on a number
// of other structs: items (internal inventory),
// animation states (closely tied to the sprites
// used to represent them, unfortunately).
//#include "d_items.h"
//#include "p_pspr.h"

// In addition, the player is just a special
// case of the generic moving object/actor.
//#include "p_mobj.h"

// Finally, for odd reasons, the player input
// is buffered within the player data struct,
// as commands per game tick.
//#include "d_ticcmd.h"


//
// Player states.
//
typedef enum
{
    // Playing or camping.
    PST_LIVE,
    // Dead on the ground, view follows killer.
    PST_DEAD,
    // Ready to restart/respawn???
    PST_REBORN
} playerstate_t;


//
// Player internal flags, for cheats and debug.
//
typedef enum
{
    // No clipping, walk through barriers.
    CF_NOCLIP = 1,
    // No damage, no health loss.
    CF_GODMODE = 2,
    // Not really a cheat, just a debug aid.
    CF_NOMOMENTUM = 4
} cheat_t;


//
// Extended player object info: player_t
//
typedef struct player_s
{
    mobj_t* mo;
    playerstate_t playerstate;
    ticcmd_t cmd;

    // Determine POV,
    //  including viewpoint bobbing during movement.
    // Focal origin above r.z
    fixed_t viewz;
    // Base height above floor for viewz.
    fixed_t viewheight;
    // Bob/squat speed.
    fixed_t deltaviewheight;
    // bounded/scaled total momentum.
    fixed_t bob;

    // This is only used between levels,
    // mo->health is used during levels.
    int health;
    int armorpoints;
    // Armor type is 0-2.
    int armortype;

    // Power ups. invinc and invis are tic counters.
    int powers[NUMPOWERS];
    doom_boolean cards[NUMCARDS];
    doom_boolean backpack;

    // Frags, kills of other players.
    int frags[MAXPLAYERS];
    weapontype_t readyweapon;

    // Is wp_nochange if not changing.
    weapontype_t pendingweapon;

    doom_boolean weaponowned[NUMWEAPONS];
    int ammo[NUMAMMO];
    int maxammo[NUMAMMO];

    // True if button down last tic.
    int attackdown;
    int usedown;

    // Bit flags, for cheats and debug.
    // See cheat_t, above.
    int cheats;

    // Refired shots are less accurate.
    int refire;

    // For intermission stats.
    int killcount;
    int itemcount;
    int secretcount;

    // Hint messages.
    char* message;

    // For screen flashing (red or bright).
    int damagecount;
    int bonuscount;

    // Who did damage (0 for floors/ceilings).
    mobj_t* attacker;

    // So gun flashes light up areas.
    int extralight;

    // Current PLAYPAL, ???
    //  can be set to REDCOLORMAP for pain, etc.
    int fixedcolormap;

    // Player skin colorshift,
    //  0-3 for which color to draw player.
    int colormap;

    // Overlay view sprites (gun, etc).
    pspdef_t psprites[NUMPSPRITES];

    // True if secret level has been done.
    doom_boolean didsecret;
} player_t;


//
// INTERMISSION
// Structure passed e.g. to WI_Start(wb)
//
typedef struct
{
    doom_boolean in;        // whether the player is in game

    // Player stats, kills, collected items etc.
    int skills;
    int sitems;
    int ssecret;
    int stime;
    int frags[4];
    int score;        // current score on entry, modified on return
} wbplayerstruct_t;


typedef struct
{
    int epsd;        // episode # (0-2)

    // if true, splash the secret level
    doom_boolean didsecret;

    // previous and next levels, origin 0
    int last;
    int next;

    int maxkills;
    int maxitems;
    int maxsecret;
    int maxfrags;

    // the par time
    int partime;

    // index of this player in game
    int pnum;

    wbplayerstruct_t plyr[MAXPLAYERS];
} wbstartstruct_t;


#endif


#ifndef __D_NET__
#define __D_NET__


//#include "d_player.h"


//
// Network play related stuff.
// There is a data struct that stores network
// communication related stuff, and another
// one that defines the actual packets to
// be transmitted.
//

#define DOOMCOM_ID                0x12345678l

// Max computers/players in a game.
#define MAXNETNODES                8

// Networking and tick handling related.
#define BACKUPTICS                12


typedef enum
{
    CMD_SEND = 1,
    CMD_GET = 2
} command_t;


//
// Network packet data.
//
typedef struct
{
    // High bit is retransmit request.
    unsigned checksum;
    // Only valid if NCMD_RETRANSMIT.
    byte retransmitfrom;

    byte starttic;
    byte player;
    byte numtics;
    ticcmd_t cmds[BACKUPTICS];
} doomdata_t;


typedef struct
{
    // Supposed to be DOOMCOM_ID?
    long id;

    // DOOM executes an int to execute commands.
    short intnum;
    // Communication between DOOM and the driver.
    // Is CMD_SEND or CMD_GET.
    short command;
    // Is dest for send, set by get (-1 = no packet).
    short remotenode;

    // Number of bytes in doomdata to be sent
    short datalength;

    // Info common to all nodes.
    // Console is allways node 0.
    short numnodes;
    // Flag: 1 = no duplication, 2-5 = dup for slow nets.
    short ticdup;
    // Flag: 1 = send a backup tic in every packet.
    short extratics;
    // Flag: 1 = deathmatch.
    short deathmatch;
    // Flag: -1 = new game, 0-5 = load savegame
    short savegame;
    short episode;  // 1-3
    short map;      // 1-9
    short skill;    // 1-5

    // Info specific to this node.
    short consoleplayer;
    short numplayers;

    // These are related to the 3-display mode,
    //  in which two drones looking left and right
    //  were used to render two additional views
    //  on two additional computers.
    // Probably not operational anymore.
    // 1 = left, 0 = center, -1 = right
    short angleoffset;
    // 1 = drone
    short drone;

    // The packet data to be sent.
    doomdata_t data;
} doomcom_t;


// Create any new ticcmds and broadcast to other players.
void NetUpdate(void);

// Broadcasts special packets to other players
//  to notify of game exit
void D_QuitNetGame(void);

//? how many ticks to run?
void TryRunTics(void);


#endif


#ifndef __D_STATE__
#define __D_STATE__


// We need globally shared data structures,
//  for defining the global state variables.
//#include "doomdata.h"
//#include "d_net.h"

// We need the playr data structure as well.
//#include "d_player.h"


// ------------------------
// Command line parameters.
//
extern doom_boolean nomonsters;     // checkparm of -nomonsters
extern doom_boolean respawnparm;    // checkparm of -respawn
extern doom_boolean fastparm;       // checkparm of -fast
extern doom_boolean devparm;        // DEBUG: launched with -devparm


// -----------------------------------------------------
// Game Mode - identify IWAD as shareware, retail etc.
//
extern GameMode_t gamemode;
extern GameMission_t gamemission;

// Set if homebrew PWAD stuff has been added.
extern doom_boolean modifiedgame;


// -------------------------------------------
// Language.
extern Language_t language;


// -------------------------------------------
// Selected skill type, map etc.
//

// Defaults for menu, methinks.
extern skill_t startskill;
extern int startepisode;
extern int startmap;

extern doom_boolean autostart;

// Selected by user. 
extern skill_t gameskill;
extern int gameepisode;
extern int gamemap;

// Nightmare mode flag, single player.
extern doom_boolean respawnmonsters;

// Netgame? Only true if >1 player.
extern doom_boolean netgame;

// Flag: true only if started as net deathmatch.
// An enum might handle altdeath/cooperative better.
extern doom_boolean deathmatch;

// -------------------------
// Internal parameters for sound rendering.
// These have been taken from the DOS version,
// but are not (yet) supported with Linux
// (e.g. no sound volume adjustment with menu.

// These are not used, but should be (menu).
// From m_menu.c:
// Sound FX volume has default, 0 - 15
// Music volume has default, 0 - 15
// These are multiplied by 8.
extern int snd_SfxVolume;      // maximum volume for sound
extern int snd_MusicVolume;    // maximum volume for music

// Current music/sfx card - index useless
//  w/o a reference LUT in a sound module.
// Ideally, this would use indices found
//  in: /usr/include/linux/soundcard.h
extern int snd_MusicDevice;
extern int snd_SfxDevice;

// Config file? Same disclaimer as above.
extern int snd_DesiredMusicDevice;
extern int snd_DesiredSfxDevice;


// -------------------------
// Status flags for refresh.
//

// Depending on view size - no status bar?
// Note that there is no way to disable the
//  status bar explicitely.
extern doom_boolean statusbaractive;

extern doom_boolean automapactive;  // In AutoMap mode?
extern doom_boolean menuactive;     // Menu overlayed?
extern doom_boolean paused;         // Game Pause?

extern doom_boolean viewactive;

extern doom_boolean nodrawers;
extern doom_boolean noblit;

extern int viewwindowx;
extern int viewwindowy;
extern int viewheight;
extern int viewwidth;
extern int scaledviewwidth;


// This one is related to the 3-screen display mode.
// ANG90 = left side, ANG270 = right
extern int viewangleoffset;

// Player taking events, and displaying.
extern int consoleplayer;
extern int displayplayer;


// -------------------------------------
// Scores, rating.
// Statistics on a given map, for intermission.
//
extern int totalkills;
extern int totalitems;
extern int totalsecret;

// Timer, for scores.
extern int levelstarttic;   // gametic at level start
extern int leveltime;       // tics in game play for par


// --------------------------------------
// DEMO playback/recording related stuff.
// No demo, there is a human player in charge?
// Disable save/end game?
extern doom_boolean usergame;

//?
extern doom_boolean demoplayback;
extern doom_boolean demorecording;

// Quit after playing a demo from cmdline.
extern doom_boolean singledemo;

//?
extern gamestate_t gamestate;


//-----------------------------
// Internal parameters, fixed.
// These are set by the engine, and not changed
// according to user inputs. Partly load from
// WAD, partly set at startup time.

extern int gametic;


// Bookkeeping on players - state.
extern player_t players[MAXPLAYERS];

// Alive? Disconnected?
extern doom_boolean playeringame[MAXPLAYERS];


// Player spawn spots for deathmatch.
#define MAX_DM_STARTS 10
extern mapthing_t deathmatchstarts[MAX_DM_STARTS];
extern mapthing_t* deathmatch_p;

// Player spawn spots.
extern mapthing_t playerstarts[MAXPLAYERS];

// Intermission stats.
// Parameters for world map / intermission.
extern wbstartstruct_t wminfo;


// LUT of ammunition limits for each kind.
// This doubles with BackPack powerup item.
extern int maxammo[NUMAMMO];


//-----------------------------------------
// Internal parameters, used for engine.
//

// File handling stuff.
extern char basedefault[1024];
extern void* debugfile;

// if true, load all graphics at level load
extern doom_boolean precache;

// wipegamestate can be set to -1
// to force a wipe on the next draw
extern gamestate_t wipegamestate;

extern int mouseSensitivity;
//?
// debug flag to cancel adaptiveness
extern doom_boolean singletics;

extern int bodyqueslot;


// Needed to store the number of the dummy sky flat.
// Used for rendering,
//  as well as tracking projectiles etc.
extern int skyflatnum;


// Netgame stuff (buffers and pointers, i.e. indices).

// This is ???
extern doomcom_t* doomcom;

// This points inside doomcom.
extern doomdata_t* netbuffer;


extern ticcmd_t localcmds[BACKUPTICS];
extern int rndindex;

extern int maketic;
extern int nettics[MAXNETNODES];

extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS];
extern int ticdup;


#endif


#ifndef __I_SOUND__
#define __I_SOUND__


//#include "doomdef.h"



//#include "doomstat.h"
//#include "sounds.h"


// Init at program start...
void I_InitSound();

// ... update sound buffer and audio device at runtime...
void I_UpdateSound(void);
void I_SubmitSound(void);

// ... shut down and relase at program termination.
void I_ShutdownSound(void);


//
//  SFX I/O
//

// Initialize channels?
void I_SetChannels();

// Get raw data lump index for sound descriptor.
int I_GetSfxLumpNum(sfxinfo_t* sfxinfo);

// Starts a sound in a particular sound channel.
int I_StartSound(int id, int vol, int sep, int pitch, int priority);

// Stops a sound channel.
void I_StopSound(int handle);

// Called by S_*() functions
//  to see if a channel is still playing.
// Returns 0 if no longer playing, 1 if playing.
int I_SoundIsPlaying(int handle);

// Updates the volume, separation,
//  and pitch of a sound channel.
void I_UpdateSoundParams(int handle, int vol, int sep, int pitch);


//
//  MUSIC I/O
//
void I_InitMusic(void);
void I_ShutdownMusic(void);

// Volume.
void I_SetMusicVolume(int volume);

// PAUSE game handling.
void I_PauseSong(int handle);
void I_ResumeSong(int handle);

// Registers a song handle to song data.
int I_RegisterSong(void* data);

// Called by anything that wishes to start music.
//  plays a song, and when the song is done,
//  starts playing it again in an endless loop.
// Horrible thing to do, considering.
void I_PlaySong(int handle, int looping);

// Stops a song over 3 seconds.
void I_StopSong(int handle);

// See above (register), then think backwards
void I_UnRegisterSong(int handle);

// Get next MIDI message
unsigned long I_TickSong();


#endif


#ifndef __P_INTER__
#define __P_INTER__


//#include "d_player.h"


doom_boolean P_GivePower(player_t*, int);


#endif


#ifndef __R_DEFS__
#define __R_DEFS__


// Screenwidth.
//#include "doomdef.h"

// Some more or less basic data types
// we depend on.
//#include "m_fixed.h"

// We rely on the thinker data struct
// to handle sound origins in sectors.
//#include "d_think.h"
// SECTORS do store MObjs anyway.
//#include "p_mobj.h"


// Silhouette, needed for clipping Segs (mainly)
// and sprites representing things.
#define SIL_NONE 0
#define SIL_BOTTOM 1
#define SIL_TOP 2
#define SIL_BOTH 3

#define MAXDRAWSEGS 256


//
// INTERNAL MAP TYPES
// used by play and refresh
//

//
// Your plain vanilla vertex.
// Note: transformed values not buffered locally,
// like some DOOM-alikes ("wt", "WebView") did.
//
typedef struct
{
    fixed_t        x;
    fixed_t        y;
} vertex_t;


// Forward of LineDefs, for Sectors.
struct line_s;

// Each sector has a degenmobj_t in its center
// for sound origin purposes.
// I suppose this does not handle sound from
// moving objects (doppler), because
// position is prolly just buffered, not
// updated.
typedef struct
{
    thinker_t                thinker;        // not used for anything
    fixed_t                x;
    fixed_t                y;
    fixed_t                z;
} degenmobj_t;

//
// The SECTORS record, at runtime.
// Stores things/mobjs.
//
typedef struct
{
    fixed_t floorheight;
    fixed_t ceilingheight;
    short floorpic;
    short ceilingpic;
    short lightlevel;
    short special;
    short tag;

    // 0 = untraversed, 1,2 = sndlines -1
    int soundtraversed;

    // thing that made a sound (or null)
    mobj_t* soundtarget;

    // mapblock bounding box for height changes
    int blockbox[4];

    // origin for any sounds played by the sector
    degenmobj_t soundorg;

    // if == validcount, already checked
    int validcount;

    // list of mobjs in sector
    mobj_t* thinglist;

    // thinker_t for reversable actions
    void* specialdata;

    int linecount;
    struct line_s** lines;        // [linecount] size
} sector_t;


//
// The SideDef.
//
typedef struct
{
    // add this to the calculated texture column
    fixed_t textureoffset;

    // add this to the calculated texture top
    fixed_t rowoffset;

    // Texture indices.
    // We do not maintain names here. 
    short toptexture;
    short bottomtexture;
    short midtexture;

    // Sector the SideDef is facing.
    sector_t* sector;
} side_t;


//
// Move clipping aid for LineDefs.
//
typedef enum
{
    ST_HORIZONTAL,
    ST_VERTICAL,
    ST_POSITIVE,
    ST_NEGATIVE
} slopetype_t;


typedef struct line_s
{
    // Vertices, from v1 to v2.
    vertex_t* v1;
    vertex_t* v2;

    // Precalculated v2 - v1 for side checking.
    fixed_t dx;
    fixed_t dy;

    // Animation related.
    short flags;
    short special;
    short tag;

    // Visual appearance: SideDefs.
    // sidenum[1] will be -1 if one sided
    short sidenum[2];

    // Neat. Another bounding box, for the extent
    // of the LineDef.
    fixed_t bbox[4];

    // To aid move clipping.
    slopetype_t slopetype;

    // Front and back sector.
    // Note: redundant? Can be retrieved from SideDefs.
    sector_t* frontsector;
    sector_t* backsector;

    // if == validcount, already checked
    int validcount;

    // thinker_t for reversable actions
    void* specialdata;
} line_t;


//
// A SubSector.
// References a Sector.
// Basically, this is a list of LineSegs,
// indicating the visible walls that define
// (all or some) sides of a convex BSP leaf.
//
typedef struct subsector_s
{
    sector_t* sector;
    short numlines;
    short firstline;
} subsector_t;


//
// The LineSeg.
//
typedef struct
{
    vertex_t* v1;
    vertex_t* v2;

    fixed_t offset;

    angle_t angle;

    side_t* sidedef;
    line_t* linedef;

    // Sector references.
    // Could be retrieved from linedef, too.
    // backsector is 0 for one sided lines
    sector_t* frontsector;
    sector_t* backsector;
} seg_t;


//
// BSP node.
//
typedef struct
{
    // Partition line.
    fixed_t x;
    fixed_t y;
    fixed_t dx;
    fixed_t dy;

    // Bounding box for each child.
    fixed_t bbox[2][4];

    // If NF_SUBSECTOR its a subsector.
    unsigned short children[2];
} node_t;


// posts are runs of non masked source pixels
typedef struct
{
    byte topdelta;        // -1 is the last post in a column
    byte length;         // length data bytes follows
} post_t;

// column_t is a list of 0 or more post_t, (byte)-1 terminated
typedef post_t column_t;


//
// OTHER TYPES
//

// This could be wider for >8 bit display.
// Indeed, true color support is posibble
//  precalculating 24bpp lightmap/colormap LUT.
//  from darkening PLAYPAL to all black.
// Could even us emore than 32 levels.
typedef byte lighttable_t;


//
// ?
//
typedef struct drawseg_s
{
    seg_t* curline;
    int x1;
    int x2;

    fixed_t scale1;
    fixed_t scale2;
    fixed_t scalestep;

    // 0=none, 1=bottom, 2=top, 3=both
    int silhouette;

    // do not clip sprites above this
    fixed_t bsilheight;

    // do not clip sprites below this
    fixed_t tsilheight;

    // Pointers to lists for sprite clipping,
    //  all three adjusted so [x1] is first value.
    short* sprtopclip;
    short* sprbottomclip;
    short* maskedtexturecol;
} drawseg_t;


// Patches.
// A patch holds one or more columns.
// Patches are used for sprites and all masked pictures,
// and we compose textures from the TEXTURE1/2 lists
// of patches.
typedef struct
{
    short width;                // bounding box size 
    short height;
    short leftoffset;        // pixels to the left of origin 
    short topoffset;        // pixels below the origin 
    int columnofs[8];        // only [width] used
    // the [0] is &columnofs[width] 
} patch_t;


// A vissprite_t is a thing
//  that will be drawn during a refresh.
// I.e. a sprite object that is partly visible.
typedef struct vissprite_s
{
    // Doubly linked list.
    struct vissprite_s* prev;
    struct vissprite_s* next;

    int x1;
    int x2;

    // for line side calculation
    fixed_t gx;
    fixed_t gy;

    // global bottom / top for silhouette clipping
    fixed_t gz;
    fixed_t gzt;

    // horizontal position of x1
    fixed_t startfrac;

    fixed_t scale;

    // negative if flipped
    fixed_t xiscale;

    fixed_t texturemid;
    int patch;

    // for color translation and shadow draw,
    //  maxbright frames as well
    lighttable_t* colormap;

    int mobjflags;
} vissprite_t;


//        
// Sprites are patches with a special naming convention
//  so they can be recognized by R_InitSprites.
// The base name is NNNNFx or NNNNFxFx, with
//  x indicating the rotation, x = 0, 1-7.
// The sprite and frame specified by a thing_t
//  is range checked at run time.
// A sprite is a patch_t that is assumed to represent
//  a three dimensional object and may have multiple
//  rotations pre drawn.
// Horizontal flipping is used to save space,
//  thus NNNNF2F5 defines a mirrored patch.
// Some sprites will only have one picture used
// for all views: NNNNF0
//
typedef struct
{
    // If false use 0 for any position.
    // Note: as eight entries are available,
    //  we might as well insert the same name eight times.
    doom_boolean rotate;

    // Lump to use for view angles 0-7.
    short lump[8];

    // Flip bit (1 = flip) to use for view angles 0-7.
    byte flip[8];
} spriteframe_t;


//
// A sprite definition:
//  a number of animation frames.
//
typedef struct
{
    int numframes;
    spriteframe_t* spriteframes;
} spritedef_t;


//
// Now what is a visplane, anyway?
// 
typedef struct
{
    fixed_t height;
    int picnum;
    int lightlevel;
    int minx;
    int maxx;

    // leave pads for [minx-1]/[maxx+1]

    byte pad1;
    // Here lies the rub for all
    //  dynamic resize/change of resolution.
    byte top[SCREENWIDTH];
    byte pad2;
    byte pad3;
    // See above.
    byte bottom[SCREENWIDTH];
    byte pad4;
} visplane_t;


#endif


#ifndef __HULIB__
#define __HULIB__


// We are referring to patches.
//#include "r_defs.h"


// background and foreground screen numbers
// different from other modules.
#define BG 1
#define FG 0

// font stuff
#define HU_CHARERASE KEY_BACKSPACE

#define HU_MAXLINES 4
#define HU_MAXLINELENGTH 80


//
// Typedefs of widgets
//

// Text Line widget
//  (parent of Scrolling Text and Input Text widgets)
typedef struct
{
    // left-justified position of scrolling text window
    int x;
    int y;

    patch_t** f;                        // font
    int sc;                        // start character
    char l[HU_MAXLINELENGTH + 1];        // line of text
    int len;                              // current line length

    // whether this line needs to be udpated
    int needsupdate;
} hu_textline_t;


// Scrolling Text window widget
//  (child of Text Line widget)
typedef struct
{
    hu_textline_t l[HU_MAXLINES];        // text lines to draw
    int h;                // height in lines
    int cl;                // current line number

    // pointer to doom_boolean stating whether to update window
    doom_boolean* on;
    doom_boolean laston;                // last value of *->on.
} hu_stext_t;


// Input Text Line widget
//  (child of Text Line widget)
typedef struct
{
    hu_textline_t l;                // text line to input on

     // left margin past which I am not to delete characters
    int lm;

    // pointer to doom_boolean stating whether to update window
    doom_boolean* on;
    doom_boolean laston; // last value of *->on;
} hu_itext_t;


//
// Widget creation, access, and update routines
//

// initializes heads-up widget library
void HUlib_init(void);

//
// textline code
//

// clear a line of text
void HUlib_clearTextLine(hu_textline_t* t);

void HUlib_initTextLine(hu_textline_t* t, int x, int y, patch_t** f, int sc);

// returns success
doom_boolean HUlib_addCharToTextLine(hu_textline_t* t, char ch);

// returns success
doom_boolean HUlib_delCharFromTextLine(hu_textline_t* t);

// draws tline
void HUlib_drawTextLine(hu_textline_t* l, doom_boolean drawcursor);

// erases text line
void HUlib_eraseTextLine(hu_textline_t* l);


//
// Scrolling Text window widget routines
//

// ?
void HUlib_initSText(hu_stext_t* s,
                     int x,
                     int y,
                     int h,
                     patch_t** font,
                     int startchar,
                     doom_boolean* on);

// add a new line
void HUlib_addLineToSText(hu_stext_t* s);

// ?
void HUlib_addMessageToSText(hu_stext_t* s, char* prefix, char* msg);

// draws stext
void HUlib_drawSText(hu_stext_t* s);

// erases all stext lines
void HUlib_eraseSText(hu_stext_t* s);

// Input Text Line widget routines
void HUlib_initIText(hu_itext_t* it,
                     int x,
                     int y,
                     patch_t** font,
                     int startchar,
                     doom_boolean* on);

// enforces left margin
void HUlib_delCharFromIText(hu_itext_t* it);

// enforces left margin
void HUlib_eraseLineFromIText(hu_itext_t* it);

// resets line and left margin
void HUlib_resetIText(hu_itext_t* it);

// left of left-margin
void HUlib_addPrefixToIText(hu_itext_t* it, char* str);

// whether eaten
doom_boolean HUlib_keyInIText(hu_itext_t* it, unsigned char ch);

void HUlib_drawIText(hu_itext_t* it);

// erases all itext lines
void HUlib_eraseIText(hu_itext_t* it);


#endif


#ifndef __P_SPEC__
#define __P_SPEC__


//#include "p_mobj.h"
//#include "r_defs.h"


//
// End-level timer (-TIMER option)
//
extern doom_boolean levelTimer;
extern int levelTimeCount;


// Define values for map objects
#define MO_TELEPORTMAN          14


// at game start
void P_InitPicAnims(void);

// at map load
void P_SpawnSpecials(void);

// every tic
void P_UpdateSpecials(void);

// when needed
doom_boolean P_UseSpecialLine(mobj_t* thing, line_t* line, int side);

void P_ShootSpecialLine(mobj_t* thing, line_t* line);
void P_CrossSpecialLine(int linenum, int side, mobj_t* thing);
void P_PlayerInSpecialSector(player_t* player);
int twoSided(int sector, int line);
sector_t* getSector(int currentSector, int line, int side);
side_t* getSide(int currentSector, int line, int side);
fixed_t P_FindLowestFloorSurrounding(sector_t* sec);
fixed_t P_FindHighestFloorSurrounding(sector_t* sec);
fixed_t P_FindNextHighestFloor(sector_t* sec, int currentheight);
fixed_t P_FindLowestCeilingSurrounding(sector_t* sec);
fixed_t P_FindHighestCeilingSurrounding(sector_t* sec);
int P_FindSectorFromLineTag(line_t* line, int start);
int P_FindMinSurroundingLight(sector_t* sector, int max);
sector_t* getNextSector(line_t* line, sector_t* sec);

//
// SPECIAL
//
int EV_DoDonut(line_t* line);


//
// P_LIGHTS
//
typedef struct
{
    thinker_t thinker;
    sector_t* sector;
    int count;
    int maxlight;
    int minlight;
} fireflicker_t;


typedef struct
{
    thinker_t thinker;
    sector_t* sector;
    int count;
    int maxlight;
    int minlight;
    int maxtime;
    int mintime;
} lightflash_t;


typedef struct
{
    thinker_t thinker;
    sector_t* sector;
    int count;
    int minlight;
    int maxlight;
    int darktime;
    int brighttime;
} strobe_t;


typedef struct
{
    thinker_t thinker;
    sector_t* sector;
    int minlight;
    int maxlight;
    int direction;
} glow_t;


#define GLOWSPEED 8
#define STROBEBRIGHT 5
#define FASTDARK 15
#define SLOWDARK 35

void P_SpawnFireFlicker(sector_t* sector);
void T_LightFlash(lightflash_t* flash);
void P_SpawnLightFlash(sector_t* sector);
void T_StrobeFlash(strobe_t* flash);

void P_SpawnStrobeFlash(sector_t* sector, int fastOrSlow, int inSync);
void EV_StartLightStrobing(line_t* line);
void EV_TurnTagLightsOff(line_t* line);

void EV_LightTurnOn(line_t* line, int bright);

void T_Glow(glow_t* g);
void P_SpawnGlowingLight(sector_t* sector);


//
// P_SWITCH
//
typedef struct
{
    char name1[9];
    char name2[9];
    short episode;
} switchlist_t;


typedef enum
{
    top,
    middle,
    bottom
} bwhere_e;


typedef struct
{
    line_t* line;
    bwhere_e where;
    int btexture;
    int btimer;
    mobj_t* soundorg;
} button_t;


// max # of wall switches in a level
#define MAXSWITCHES 50

// 4 players, 4 buttons each at once, max.
#define MAXBUTTONS 16

// 1 second, in ticks. 
#define BUTTONTIME 35             


extern button_t buttonlist[MAXBUTTONS];


void P_ChangeSwitchTexture(line_t* line, int useAgain);
void P_InitSwitchList(void);


//
// P_PLATS
//
typedef enum
{
    up,
    down,
    waiting,
    in_stasis
} plat_e;


typedef enum
{
    perpetualRaise,
    downWaitUpStay,
    raiseAndChange,
    raiseToNearestAndChange,
    blazeDWUS
} plattype_e;


typedef struct
{
    thinker_t thinker;
    sector_t* sector;
    fixed_t speed;
    fixed_t low;
    fixed_t high;
    int wait;
    int count;
    plat_e status;
    plat_e oldstatus;
    doom_boolean crush;
    int tag;
    plattype_e type;
} plat_t;


#define PLATWAIT 3
#define PLATSPEED FRACUNIT
#define MAXPLATS 30


extern plat_t* activeplats[MAXPLATS];


void T_PlatRaise(plat_t* plat);
int EV_DoPlat(line_t* line, plattype_e type, int amount);
void P_AddActivePlat(plat_t* plat);
void P_RemoveActivePlat(plat_t* plat);
void EV_StopPlat(line_t* line);
void P_ActivateInStasis(int tag);


//
// P_DOORS
//
typedef enum
{
    door_normal,
    close30ThenOpen,
    door_close,
    door_open,
    raiseIn5Mins,
    blazeRaise,
    blazeOpen,
    blazeClose
} vldoor_e;


typedef struct
{
    thinker_t thinker;
    vldoor_e type;
    sector_t* sector;
    fixed_t topheight;
    fixed_t speed;

    // 1 = up, 0 = waiting at top, -1 = down
    int direction;

    // tics to wait at the top
    int topwait;

    // (keep in case a door going down is reset)
    // when it reaches 0, start going down
    int topcountdown;
} vldoor_t;


#define VDOORSPEED                FRACUNIT*2
#define VDOORWAIT                150


void EV_VerticalDoor(line_t* line, mobj_t* thing);
int EV_DoDoor(line_t* line, vldoor_e type);
int EV_DoLockedDoor(line_t* line, vldoor_e type, mobj_t* thing);
void T_VerticalDoor(vldoor_t* door);
void P_SpawnDoorCloseIn30(sector_t* sec);
void P_SpawnDoorRaiseIn5Mins(sector_t* sec, int secnum);


//
// P_CEILNG
//
typedef enum
{
    lowerToFloor,
    raiseToHighest,
    lowerAndCrush,
    crushAndRaise,
    fastCrushAndRaise,
    silentCrushAndRaise
} ceiling_e;


typedef struct
{
    thinker_t thinker;
    ceiling_e type;
    sector_t* sector;
    fixed_t bottomheight;
    fixed_t topheight;
    fixed_t speed;
    doom_boolean crush;

    // 1 = up, 0 = waiting, -1 = down
    int direction;

    // ID
    int tag;
    int olddirection;
} ceiling_t;


#define CEILSPEED FRACUNIT
#define CEILWAIT 150
#define MAXCEILINGS 30


extern ceiling_t* activeceilings[MAXCEILINGS];


int EV_DoCeiling(line_t* line, ceiling_e type);
void T_MoveCeiling(ceiling_t* ceiling);
void P_AddActiveCeiling(ceiling_t* c);
void P_RemoveActiveCeiling(ceiling_t* c);
int EV_CeilingCrushStop(line_t* line);
void P_ActivateInStasisCeiling(line_t* line);


//
// P_FLOOR
//
typedef enum
{
    // lower floor to highest surrounding floor
    lowerFloor,

    // lower floor to lowest surrounding floor
    lowerFloorToLowest,

    // lower floor to highest surrounding floor VERY FAST
    turboLower,

    // raise floor to lowest surrounding CEILING
    raiseFloor,

    // raise floor to next highest surrounding floor
    raiseFloorToNearest,

    // raise floor to shortest height texture around it
    raiseToTexture,

    // lower floor to lowest surrounding floor
    //  and change floorpic
    lowerAndChange,

    raiseFloor24,
    raiseFloor24AndChange,
    raiseFloorCrush,

    // raise to next highest floor, turbo-speed
    raiseFloorTurbo,
    donutRaise,
    raiseFloor512
} floor_e;


typedef enum
{
    build8,        // slowly build by 8
    turbo16        // quickly build by 16
} stair_e;


typedef struct
{
    thinker_t thinker;
    floor_e type;
    doom_boolean crush;
    sector_t* sector;
    int direction;
    int newspecial;
    short texture;
    fixed_t floordestheight;
    fixed_t speed;
} floormove_t;


#define FLOORSPEED FRACUNIT


typedef enum
{
    ok,
    crushed,
    pastdest
} result_e;


result_e T_MovePlane(sector_t* sector, fixed_t speed, fixed_t dest, doom_boolean crush, int floorOrCeiling, int direction);
int EV_BuildStairs(line_t* line, stair_e type);
int EV_DoFloor(line_t* line, floor_e floortype);
void T_MoveFloor(floormove_t* floor);

//
// P_TELEPT
//
int EV_Teleport(line_t* line, int side, mobj_t* thing);


#endif


#ifndef __R_BSP__
#define __R_BSP__


//#include "r_defs.h"


extern seg_t* curline;
extern side_t* sidedef;
extern line_t* linedef;
extern sector_t* frontsector;
extern sector_t* backsector;

extern int rw_x;
extern int rw_stopx;

extern doom_boolean segtextured;

// false if the back side is the same plane
extern doom_boolean markfloor;
extern doom_boolean markceiling;

extern doom_boolean skymap;

extern drawseg_t drawsegs[MAXDRAWSEGS];
extern drawseg_t* ds_p;

extern lighttable_t** hscalelight;
extern lighttable_t** vscalelight;
extern lighttable_t** dscalelight;


typedef void (*drawfunc_t) (int start, int stop);


// BSP?
void R_ClearClipSegs(void);
void R_ClearDrawSegs(void);
void R_RenderBSPNode(int bspnum);


#endif


#ifndef __R_DRAW__
#define __R_DRAW__


//#include "r_defs.h"


extern lighttable_t* dc_colormap;
extern int dc_x;
extern int dc_yl;
extern int dc_yh;
extern fixed_t dc_iscale;
extern fixed_t dc_texturemid;

// first pixel in a column
extern byte* dc_source;


// The span blitting interface.
// Hook in assembler or system specific BLT
//  here.
void R_DrawColumn(void);
void R_DrawColumnLow(void);

// The Spectre/Invisibility effect.
void R_DrawFuzzColumn(void);
void R_DrawFuzzColumnLow(void);

// Draw with color translation tables,
//  for player sprite rendering,
//  Green/Red/Blue/Indigo shirts.
void R_DrawTranslatedColumn(void);
void R_DrawTranslatedColumnLow(void);

void R_VideoErase(unsigned ofs, int count);

extern int ds_y;
extern int ds_x1;
extern int ds_x2;

extern lighttable_t* ds_colormap;

extern fixed_t ds_xfrac;
extern fixed_t ds_yfrac;
extern fixed_t ds_xstep;
extern fixed_t ds_ystep;

// start of a 64*64 tile image
extern byte* ds_source;

extern byte* translationtables;
extern byte* dc_translation;


// Span blitting for rows, floor/ceiling.
// No Sepctre effect needed.
void R_DrawSpan(void);

// Low resolution mode, 160x200?
void R_DrawSpanLow(void);

void R_InitBuffer(int width, int height);


// Initialize color translation tables,
//  for player rendering etc.
void R_InitTranslationTables(void);

// Rendering function.
void R_FillBackScreen(void);

// If the view size is not full screen, draws a border around it.
void R_DrawViewBorder(void);


#endif


#ifndef __R_SEGS__
#define __R_SEGS__


//#include "r_defs.h"


void R_RenderMaskedSegRange(drawseg_t* ds, int x1, int x2);


#endif


#ifndef __R_STATE__
#define __R_STATE__


//#include "d_player.h" // Need data structure definitions.
//#include "r_defs.h"


//
// Refresh internal data structures,
// for rendering.
//

// needed for texture pegging
extern fixed_t* textureheight;

// needed for pre rendering (fracs)
extern fixed_t* spritewidth;

extern fixed_t* spriteoffset;
extern fixed_t* spritetopoffset;

extern lighttable_t* colormaps;

extern int viewwidth;
extern int scaledviewwidth;
extern int viewheight;

extern int firstflat;

// for global animation
extern int* flattranslation;
extern int* texturetranslation;

// Sprite....
extern int firstspritelump;
extern int lastspritelump;
extern int numspritelumps;

//
// Lookup tables for map data.
//
extern int numsprites;
extern spritedef_t* sprites;

extern int numvertexes;
extern vertex_t* vertexes;

extern int numsegs;
extern seg_t* segs;

extern int numsectors;
extern sector_t* sectors;

extern int numsubsectors;
extern subsector_t* subsectors;

extern int numnodes;
extern node_t* nodes;

extern int numlines;
extern line_t* lines;

extern int numsides;
extern side_t* sides;

//
// POV data.
//
extern fixed_t viewx;
extern fixed_t viewy;
extern fixed_t viewz;

extern angle_t viewangle;
extern player_t* viewplayer;

// ?
extern angle_t clipangle;

extern int viewangletox[FINEANGLES / 2];
extern angle_t xtoviewangle[SCREENWIDTH + 1];

extern fixed_t rw_distance;
extern angle_t rw_normalangle;

// angle to line origin
extern int rw_angle1;

// Segs count?
extern int sscount;

extern visplane_t* floorplane;
extern visplane_t* ceilingplane;


#endif


#ifndef __R_DATA__
#define __R_DATA__


//#include "r_defs.h"
//#include "r_state.h"


// Retrieve column data for span blitting.
byte* R_GetColumn(int tex, int col);

// I/O, setting up the stuff.
void R_InitData(void);
void R_PrecacheLevel(void);

// Retrieval.
// Floor/ceiling opaque texture tiles,
// lookup by name. For animation?
int R_FlatNumForName(char* name);

// Called by P_Ticker for switches and animations,
// returns the texture number for the texture name.
int R_TextureNumForName(char* name);
int R_CheckTextureNumForName(char* name);

#endif


#ifndef __R_MAIN__
#define __R_MAIN__

//#include "d_player.h"
//#include "r_data.h"


//
// POV related.
//
extern fixed_t viewcos;
extern fixed_t viewsin;

extern int viewwidth;
extern int viewheight;
extern int viewwindowx;
extern int viewwindowy;

extern int centerx;
extern int centery;

extern fixed_t centerxfrac;
extern fixed_t centeryfrac;
extern fixed_t projection;

extern int validcount;

extern int linecount;
extern int loopcount;


//
// Lighting LUT.
// Used for z-depth cuing per column/row,
// and other lighting effects (sector ambient, flash).
//

// Lighting constants.
// Now why not 32 levels here?
#define LIGHTLEVELS 16
#define LIGHTSEGSHIFT 4

#define MAXLIGHTSCALE 48
#define LIGHTSCALESHIFT 12
#define MAXLIGHTZ 128
#define LIGHTZSHIFT 20

extern lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE];
extern lighttable_t* scalelightfixed[MAXLIGHTSCALE];
extern lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ];

extern int extralight;
extern lighttable_t* fixedcolormap;


// Number of diminishing brightness levels.
// There a 0-31, i.e. 32 LUT in the COLORMAP lump.
#define NUMCOLORMAPS 32


// Blocky/low detail mode.
//B remove this?
//  0 = high, 1 = low
extern int detailshift;


//
// Function pointers to switch refresh/drawing functions.
// Used to select shadow mode etc.
//
extern void (*colfunc) (void);
extern void (*basecolfunc) (void);
extern void (*fuzzcolfunc) (void);
// No shadow effects on floors.
extern void (*spanfunc) (void);


//
// Utility functions.
int R_PointOnSide(fixed_t x, fixed_t y, node_t* node);
int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t* line);
angle_t R_PointToAngle(fixed_t x, fixed_t y);
angle_t R_PointToAngle2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2);
fixed_t R_PointToDist(fixed_t x, fixed_t y);
fixed_t R_ScaleFromGlobalAngle(angle_t visangle);
subsector_t* R_PointInSubsector(fixed_t x, fixed_t y);
void R_AddPointToBox(int x, int y, fixed_t* box);


//
// REFRESH - the actual rendering functions.
//

// Called by G_Drawer.
void R_RenderPlayerView(player_t* player);

// Called by startup code.
void R_Init(void);

// Called by M_Responder.
void R_SetViewSize(int blocks, int detail);

#endif


#ifndef __R_PLANE__
#define __R_PLANE__


//#include "r_data.h"


// Visplane related.
extern short* lastopening;

typedef void (*planefunction_t) (int top, int bottom);

extern planefunction_t floorfunc;
extern planefunction_t ceilingfunc_t;

extern short floorclip[SCREENWIDTH];
extern short ceilingclip[SCREENWIDTH];

extern fixed_t yslope[SCREENHEIGHT];
extern fixed_t distscale[SCREENWIDTH];

void R_InitPlanes(void);
void R_ClearPlanes(void);
void R_MapPlane(int y, int x1, int x2);
void R_MakeSpans(int x, int t1, int b1, int t2, int b2);
void R_DrawPlanes(void);
visplane_t* R_FindPlane(fixed_t height, int picnum, int lightlevel);
visplane_t* R_CheckPlane(visplane_t* pl, int start, int stop);


#endif


#ifndef __R_THINGS__
#define __R_THINGS__


//#include "r_defs.h"


#define MAXVISSPRITES 128


extern vissprite_t vissprites[MAXVISSPRITES];
extern vissprite_t* vissprite_p;
extern vissprite_t vsprsortedhead;

// Constant arrays used for psprite clipping
// and initializing clipping.
extern short negonearray[SCREENWIDTH];
extern short screenheightarray[SCREENWIDTH];

// vars for R_DrawMaskedColumn
extern short* mfloorclip;
extern short* mceilingclip;
extern fixed_t spryscale;
extern fixed_t sprtopscreen;

extern fixed_t pspritescale;
extern fixed_t pspriteiscale;


void R_DrawMaskedColumn(column_t* column);
void R_SortVisSprites(void);
void R_AddSprites(sector_t* sec);
void R_InitSprites(char** namelist);
void R_ClearSprites(void);
void R_DrawMasked(void);


#endif


#ifndef __R_LOCAL__
#define __R_LOCAL__

// Binary Angles, sine/cosine/atan lookups.
//#include "tables.h"

// Screen size related parameters.
//#include "doomdef.h"

// Include the refresh/render data structs.
//#include "r_data.h"

//
// Separate header file for each module.
//
//#include "r_main.h"
//#include "r_bsp.h"
//#include "r_segs.h"
//#include "r_plane.h"
//#include "r_data.h"
//#include "r_things.h"
//#include "r_draw.h"

#endif // __R_LOCAL__


#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


//
// 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 DOOM_MININT
#define ONCEILINGZ DOOM_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);
doom_boolean P_SetMobjState(mobj_t* mobj, statenum_t state);
void P_MobjThinker(mobj_t* mobj);
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
    doom_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 doom_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);


doom_boolean P_BlockLinesIterator(int x, int y, doom_boolean(*func)(line_t*));
doom_boolean P_BlockThingsIterator(int x, int y, doom_boolean(*func)(mobj_t*));


#define PT_ADDLINES     1
#define PT_ADDTHINGS    2
#define PT_EARLYOUT     4


extern divline_t trace;


doom_boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, int flags, doom_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 doom_boolean floatok;
extern fixed_t tmfloorz;
extern fixed_t tmceilingz;

extern line_t* ceilingline;


doom_boolean P_CheckPosition(mobj_t* thing, fixed_t x, fixed_t y);
doom_boolean P_TryMove(mobj_t* thing, fixed_t x, fixed_t y);
doom_boolean P_TeleportMove(mobj_t* thing, fixed_t x, fixed_t y);
void P_SlideMove(mobj_t* mo);
doom_boolean P_CheckSight(mobj_t* t1, mobj_t* t2);
void P_UseLines(player_t* player);
doom_boolean P_ChangeSector(sector_t* sector, doom_boolean crunch);


extern mobj_t* linetarget;        // who got hit (or 0)


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__


#ifndef __STLIB__
#define __STLIB__


// We are referring to patches.
//#include "r_defs.h"


//
// Background and foreground screen numbers
//
#define STLIB_BG 4
#define STLIB_FG 0


//
// Typedefs of widgets
//

// Number widget
typedef struct
{
    // upper right-hand corner
    //  of the number (right-justified)
    int x;
    int y;

    // max # of digits in number
    int width;

    // last number value
    int oldnum;

    // pointer to current value
    int* num;

    // pointer to doom_boolean stating
    //  whether to update number
    doom_boolean* on;

    // list of patches for 0-9
    patch_t** p;

    // user data
    int data;
} st_number_t;


// Percent widget ("child" of number widget,
//  or, more precisely, contains a number widget.)
typedef struct
{
    // number information
    st_number_t n;

    // percent sign graphic
    patch_t* p;
} st_percent_t;


// Multiple Icon widget
typedef struct
{
    // center-justified location of icons
    int x;
    int y;

    // last icon number
    int oldinum;

    // pointer to current icon
    int* inum;

    // pointer to doom_boolean stating
    //  whether to update icon
    doom_boolean* on;

    // list of icons
    patch_t** p;

    // user data
    int data;
} st_multicon_t;


// Binary Icon widget
typedef struct
{
    // center-justified location of icon
    int x;
    int y;

    // last icon value
    int oldval;

    // pointer to current icon status
    doom_boolean* val;

    // pointer to doom_boolean
    //  stating whether to update icon
    doom_boolean* on;


    patch_t* p; // icon
    int data;   // user data

} st_binicon_t;


//
// Widget creation, access, and update routines
//

// Initializes widget library.
// More precisely, initialize STMINUS,
// everything else is done somewhere else.
//
void STlib_init(void);

// Number widget routines
void STlib_initNum(st_number_t* n,
                   int x,
                   int y,
                   patch_t** pl,
                   int* num,
                   doom_boolean* on,
                   int width);

void STlib_updateNum(st_number_t* n, doom_boolean refresh);

// Percent widget routines
void STlib_initPercent(st_percent_t* p,
                       int x,
                       int y,
                       patch_t** pl,
                       int* num,
                       doom_boolean* on,
                       patch_t* percent);

void STlib_updatePercent(st_percent_t* per, int refresh);

// Multiple Icon widget routines
void STlib_initMultIcon(st_multicon_t* mi,
                        int x,
                        int y,
                        patch_t** il,
                        int* inum,
                        doom_boolean* on);

void STlib_updateMultIcon(st_multicon_t* mi, doom_boolean refresh);

// Binary Icon widget routines
void STlib_initBinIcon(st_binicon_t* b,
                       int x,
                       int y,
                       patch_t* i,
                       doom_boolean* val,
                       doom_boolean* on);

void STlib_updateBinIcon(st_binicon_t* bi, doom_boolean refresh);

#endif


#ifndef __V_VIDEO__
#define __V_VIDEO__

//#include "doomtype.h"
//#include "doomdef.h"
//#include "r_data.h" // Needed because we are refering to patches.

//
// VIDEO
//

#define CENTERY (SCREENHEIGHT / 2)


// Screen 0 is the screen updated by I_Update screen.
// Screen 1 is an extra buffer.
extern byte* screens[5];
extern int dirtybox[4];
extern byte gammatable[5][256];
extern int usegamma;


// Allocates buffer screens, call before R_Init.
void V_Init(void);

void V_CopyRect(int srcx,
                int srcy,
                int srcscrn,
                int width,
                int height,
                int destx,
                int desty,
                int destscrn);

void V_DrawPatch(int x,
                 int y,
                 int scrn,
                 patch_t* patch);

void V_DrawPatchDirect(int x, 
                       int y, 
                       int scrn, 
                       patch_t* patch);

void V_DrawPatchRectDirect(int x, int y, int scrn, patch_t* patch, int src_x, int src_w);

// Draw a linear block of pixels into the view buffer.
void V_DrawBlock(int x,
                 int y,
                 int scrn,
                 int width,
                 int height,
                 byte* src);

// Reads a linear block of pixels into the view buffer.
void V_GetBlock(int x,
                int y,
                int scrn,
                int width,
                int height,
                byte* dest);

void V_MarkRect(int x,
                int y,
                int width,
                int height);

#endif


#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];
    void* 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);


#endif


#ifndef __WI_STUFF__
#define __WI_STUFF__

//#include "doomdef.h"

// States for the intermission
typedef enum
{
    NoState = -1,
    StatCount,
    ShowNextLoc
} stateenum_t;

// Called by main loop, animate the intermission.
void WI_Ticker(void);

// Called by main loop,
// draws the intermission directly into the screen buffer.
void WI_Drawer(void);

// Setup for an intermission screen.
void WI_Start(wbstartstruct_t* wbstartstruct);

#endif


#ifndef __Z_ZONE__
#define __Z_ZONE__




//
// ZONE MEMORY
// PU - purge tags.
// Tags < 100 are not overwritten until freed.
#define PU_STATIC       1        // static entire execution time
#define PU_SOUND        2        // static while playing
#define PU_MUSIC        3        // static while playing
#define PU_DAVE         4        // anything else Dave wants static
#define PU_LEVEL        50       // static until level exited
#define PU_LEVSPEC      51       // a special thinker in a level
// Tags >= 100 are purgable whenever needed.
#define PU_PURGELEVEL   100
#define PU_CACHE        101


void  Z_Init(void);
void* Z_Malloc(int size, int tag, void* ptr);
void  Z_Free(void* ptr);
void  Z_FreeTags(int lowtag, int hightag);
void  Z_DumpHeap(int lowtag, int hightag);
void  Z_FileDumpHeap(void* f);
void  Z_CheckHeap(void);
void  Z_ChangeTag2(void* ptr, int tag);
int   Z_FreeMemory(void);


typedef struct memblock_s
{
    int size;       // including the header and possibly tiny fragments
    void** user;    // 0 if a free block
    int tag;        // purgelevel
    int id;         // should be ZONEID
    struct memblock_s* next;
    struct memblock_s* prev;
} memblock_t;

//
// This is used to get the local FILE:LINE info from CPP
// prior to really call the function in question.
//
#define Z_ChangeTag(p,t) \
{ \
    if (( (memblock_t *)( (byte *)(p) - sizeof(memblock_t)))->id!=0x1d4a11) \
    { \
        /*I_Error("Error: Z_CT at "__FILE__":%i",__LINE__);*/ \
        char buf[260]; \
        doom_strcpy(buf, "Error: Z_CT at " __FILE__ ":"); \
        doom_concat(buf, doom_itoa(__LINE__, 10)); \
        I_Error(buf); \
    } \
    Z_ChangeTag2(p,t); \
};


#endif



#if defined(DOOM_IMPLEMENTATION)
#if defined(WIN32)
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_NONSTDC_NO_DEPRECATE
#endif

//#include "DOOM.h"

//#include "d_main.h"
//#include "doomdef.h"
//#include "doomtype.h"
//#include "i_system.h"
//#include "m_argv.h"
//#include "m_misc.h"


extern byte* screens[5];
extern unsigned char screen_palette[256 * 3];
extern doom_boolean is_wiping_screen;
extern default_t defaults[];
extern int numdefaults;
extern signed short mixbuffer[2048];


static unsigned char* screen_buffer = 0;
static unsigned char* final_screen_buffer = 0;
static int last_update_time = 0;
static int button_states[3] = { 0 };
static char itoa_buf[20];


char error_buf[260];
int doom_flags = 0;
doom_print_fn doom_print = 0;
doom_malloc_fn doom_malloc = 0;
doom_free_fn doom_free = 0;
doom_open_fn doom_open = 0;
doom_close_fn doom_close = 0;
doom_read_fn doom_read = 0;
doom_write_fn doom_write = 0;
doom_seek_fn doom_seek = 0;
doom_tell_fn doom_tell = 0;
doom_eof_fn doom_eof = 0;
doom_gettime_fn doom_gettime = 0;
doom_exit_fn doom_exit = 0;
doom_getenv_fn doom_getenv = 0;


void D_DoomLoop(void);
void D_UpdateWipe(void);
void I_UpdateSound();
unsigned long I_TickSong();


#if defined(DOOM_IMPLEMENT_PRINT)
#include <stdio.h>
static void doom_print_impl(const char* str)
{
    printf("%s", str);
}
#else
static void doom_print_impl(const char* str) {}
#endif

#if defined(DOOM_IMPLEMENT_MALLOC)
#include <stdlib.h>
static void* doom_malloc_impl(int size)
{
    return malloc((size_t)size);
}
static void doom_free_impl(void* ptr)
{
    free(ptr);
}
#else
static void* doom_malloc_impl(int size) { return 0; }
static void doom_free_impl(void* ptr) {}
#endif


#if defined(DOOM_IMPLEMENT_FILE_IO)
#include <stdio.h>
void* doom_open_impl(const char* filename, const char* mode)
{
    return fopen(filename, mode);
}
void doom_close_impl(void* handle)
{
    fclose(handle);
}
int doom_read_impl(void* handle, void *buf, int count)
{
    return (int)fread(buf, 1, count, handle);
}
int doom_write_impl(void* handle, const void *buf, int count)
{
    return (int)fwrite(buf, 1, count, handle);
}
int doom_seek_impl(void* handle, int offset, doom_seek_t origin)
{
    return fseek(handle, offset, origin);
}
int doom_tell_impl(void* handle)
{
    return (int)ftell(handle);
}
int doom_eof_impl(void* handle)
{
    return feof(handle);
}
#else
void* doom_open_impl(const char* filename, const char* mode)
{
    return 0;
}
void doom_close_impl(void* handle) {}
int doom_read_impl(void* handle, void *buf, int count)
{
    return -1;
}
int doom_write_impl(void* handle, const void *buf, int count)
{
    return -1;
}
int doom_seek_impl(void* handle, int offset, doom_seek_t origin)
{
    return -1;
}
int doom_tell_impl(void* handle)
{
    return -1;
}
int doom_eof_impl(void* handle)
{
    return 1;
}
#endif


#if defined(DOOM_IMPLEMENT_GETTIME)
#if defined(WIN32)
#include <winsock.h>
#else
#include <sys/time.h>
#endif
void doom_gettime_impl(int* sec, int* usec)
{
#if defined(WIN32)
    static const unsigned long long EPOCH = ((unsigned long long)116444736000000000ULL);
    SYSTEMTIME system_time;
    FILETIME file_time;
    unsigned long long time;
    GetSystemTime(&system_time);
    SystemTimeToFileTime(&system_time, &file_time);
    time = ((unsigned long long)file_time.dwLowDateTime);
    time += ((unsigned long long)file_time.dwHighDateTime) << 32;
    *sec = (int)((time - EPOCH) / 10000000L);
    *usec = (int)(system_time.wMilliseconds * 1000);
#else
    struct timeval tp;

#ifdef __linux__
    gettimeofday(&tp, NULL);
#else
    struct timezone tzp;
    gettimeofday(&tp, &tzp);
#endif

    *sec = tp.tv_sec;
    *usec = tp.tv_usec;
#endif
}
#else
void doom_gettime_impl(int* sec, int* usec)
{
    *sec = 0;
    *usec = 0;
}
#endif


#if defined(DOOM_IMPLEMENT_EXIT)
#include <stdlib.h>
void doom_exit_impl(int code)
{
    exit(code);
}
#else
void doom_exit_impl(int code) {}
#endif


#if defined(DOOM_IMPLEMENT_GETENV)
#include <stdlib.h>
char* doom_getenv_impl(const char* var)
{
    return getenv(var);
}
#else
char* doom_getenv_impl(const char* var) { return 0; }
#endif


void doom_memset(void* ptr, int value, int num)
{
    unsigned char* p = ptr;
    for (int i = 0; i < num; ++i, ++p)
    {
        *p = (unsigned char)value;
    }
}


void* doom_memcpy(void* destination, const void* source, int num)
{
    unsigned char* dst = destination;
    const unsigned char* src = source;

    for (int i = 0; i < num; ++i, ++dst, ++src)
    {
        *dst = *src;
    }

    return destination;
}


int doom_strlen(const char* str)
{
    int len = 0;
    while (*str++) ++len;
    return len;
}


char* doom_concat(char* dst, const char* src)
{
    char* ret = dst;
    dst += doom_strlen(dst);

    while (*src) *dst++ = *src++;
    *dst = *src; // \0

    return ret;
}


char* doom_strcpy(char* dst, const char* src)
{
    char* ret = dst;

    while (*src) *dst++ = *src++;
    *dst = *src; // \0

    return ret;
}


char* doom_strncpy(char* dst, const char* src, int num)
{
    int i = 0;

    for (; i < num; ++i)
    {
        if (!src[i]) break;
        dst[i] = src[i];
    }

    while (i < num) dst[i++] = '\0';

    return dst;
}


int doom_strcmp(const char* str1, const char* str2)
{
    int ret = 0;

    while (!(ret = *(unsigned char*)str1 - *(unsigned char*) str2) && *str1)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_strncmp(const char* str1, const char* str2, int n)
{
    int ret = 0;
    int count = 1;

    while (!(ret = *(unsigned char*)str1 - *(unsigned char*) str2) && *str1 && count++ < n)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_toupper(int c)
{
    if (c >= 'a' && c <= 'z') return c - 'a' + 'A';
    return c;
}


int doom_strcasecmp(const char* str1, const char* str2)
{
    int ret = 0;

    while (!(ret = doom_toupper(*(unsigned char*)str1) - doom_toupper(*(unsigned char*)str2)) && *str1)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_strncasecmp(const char* str1, const char* str2, int n)
{
    int ret = 0;
    int count = 1;

    while (!(ret = doom_toupper(*(unsigned char*)str1) - doom_toupper(*(unsigned char*)str2)) && *str1 && count++ < n)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_atoi(const char* str)
{
    int i = 0;
    int c;

    while ((c = *str++) != 0)
    {
        i *= 10;
        i += c - '0';
    }

    return i;
}


int doom_atox(const char* str)
{
    int i = 0;
    int c;

    while ((c = *str++) != 0)
    {
        i *= 16;
        if (c >= '0' && c <= '9')
            i += c - '0';
        else
            i += c - 'A' + 10;
    }

    return i;
}


const char* doom_itoa(int k, int radix)
{
    int i = k < 0 ? -k : k;
    if (i == 0)
    {
        itoa_buf[0] = '0';
        itoa_buf[1] = '\0';
        return itoa_buf;
    }

    int idx = k < 0 ? 1 : 0;
    int j = i;
    while (j)
    {
        j /= radix;
        idx++;
    }
    itoa_buf[idx] = '\0';

    if (radix == 10)
    {
        while (i)
        {
            itoa_buf[--idx] = '0' + (i % 10);
            i /= 10;
        }
    }
    else
    {
        while (i)
        {
            int k = (i & 0xF);
            if (k >= 10)
                itoa_buf[--idx] = 'A' + ((i & 0xF) - 10);
            else
                itoa_buf[--idx] = '0' + (i & 0xF);
            i >>= 4;
        }
    }

    if (k < 0) itoa_buf[0] = '-';

    return itoa_buf;
}


const char* doom_ctoa(char c)
{
    itoa_buf[0] = c;
    itoa_buf[1] = '\0';
    return itoa_buf;
}


const char* doom_ptoa(void* p)
{
    int idx = 0;
    unsigned long long i = (unsigned long long)p;

    itoa_buf[idx++] = '0';
    itoa_buf[idx++] = 'x';

    while (i)
    {
        int k = (i & 0xF);
        if (k >= 10)
            itoa_buf[idx++] = 'A' + ((i & 0xF) - 10);
        else
            itoa_buf[idx++] = '0' + (i & 0xF);
        i >>= 4;
    }

    itoa_buf[idx] = '\0';
    return itoa_buf;
}


int doom_fprint(void* handle, const char* str)
{
    return doom_write(handle, str, doom_strlen(str));
}


static default_t* get_default(const char* name)
{
    for (int i = 0; i < numdefaults; ++i)
    {
        if (doom_strcmp(defaults[i].name, name) == 0) return &defaults[i];
    }
    return 0;
}


void doom_set_resolution(int width, int height)
{
    if (width <= 0 || height <= 0) return;
    // SCREENWIDTH = width;
    // SCREENHEIGHT = height;
}


void doom_set_default_int(const char* name, int value)
{
    default_t* def = get_default(name);
    if (!def) return;
    def->defaultvalue = value;
}


void doom_set_default_string(const char* name, const char* value)
{
    default_t* def = get_default(name);
    if (!def) return;
    def->default_text_value = (char*)value;
}


void doom_set_print(doom_print_fn print_fn)
{
    doom_print = print_fn;
}


void doom_set_malloc(doom_malloc_fn malloc_fn, doom_free_fn free_fn)
{
    doom_malloc = malloc_fn;
    doom_free = free_fn;
}


void doom_set_file_io(doom_open_fn open_fn,
                      doom_close_fn close_fn,
                      doom_read_fn read_fn,
                      doom_write_fn write_fn,
                      doom_seek_fn seek_fn,
                      doom_tell_fn tell_fn,
                      doom_eof_fn eof_fn)
{
    doom_open = open_fn;
    doom_close = close_fn;
    doom_read = read_fn;
    doom_write = write_fn;
    doom_seek = seek_fn;
    doom_tell = tell_fn;
    doom_eof = eof_fn;
}


void doom_set_gettime(doom_gettime_fn gettime_fn)
{
    doom_gettime = gettime_fn;
}


void doom_set_exit(doom_exit_fn exit_fn)
{
    doom_exit = exit_fn;
}


void doom_set_getenv(doom_getenv_fn getenv_fn)
{
    doom_getenv = getenv_fn;
}


void doom_init(int argc, char** argv, int flags)
{
    if (!doom_print) doom_print = doom_print_impl;
    if (!doom_malloc) doom_malloc = doom_malloc_impl;
    if (!doom_free) doom_free = doom_free_impl;
    if (!doom_open) doom_open = doom_open_impl;
    if (!doom_close) doom_close = doom_close_impl;
    if (!doom_read) doom_read = doom_read_impl;
    if (!doom_write) doom_write = doom_write_impl;
    if (!doom_seek) doom_seek = doom_seek_impl;
    if (!doom_tell) doom_tell = doom_tell_impl;
    if (!doom_eof) doom_eof = doom_eof_impl;
    if (!doom_gettime) doom_gettime = doom_gettime_impl;
    if (!doom_exit) doom_exit = doom_exit_impl;
    if (!doom_getenv) doom_getenv = doom_getenv_impl;

    screen_buffer = doom_malloc(SCREENWIDTH * SCREENHEIGHT);
    final_screen_buffer = doom_malloc(SCREENWIDTH * SCREENHEIGHT * 4);
    last_update_time = I_GetTime();

    myargc = argc;
    myargv = argv;
    doom_flags = flags;

    D_DoomMain();
}


void doom_update()
{
    int now = I_GetTime();
    int delta_time = now - last_update_time;

    while (delta_time-- > 0)
    {
        if (is_wiping_screen)
            D_UpdateWipe();
        else
            D_DoomLoop();
    }

    last_update_time = now;
}


void doom_force_update()
{
    if (is_wiping_screen)
        D_UpdateWipe();
    else
        D_DoomLoop();
}


const unsigned char* doom_get_framebuffer(int channels)
{
    int i, len;

    doom_memcpy(screen_buffer, screens[0], SCREENWIDTH * SCREENHEIGHT);

    extern doom_boolean menuactive;
    extern gamestate_t gamestate; 
    extern doom_boolean automapactive;
    extern int crosshair;

    // Draw crosshair
    if (crosshair && 
        !menuactive &&
        gamestate == GS_LEVEL &&
        !automapactive)
    {
        int y;
        extern int setblocks;
        if (setblocks == 11) y = SCREENHEIGHT / 2 + 8;
        else y = SCREENHEIGHT / 2 - 8;
        for (i = 0; i < 2; ++i)
        {
            screen_buffer[SCREENWIDTH / 2 - 2 - i + y * SCREENWIDTH] = 4;
            screen_buffer[SCREENWIDTH / 2 + 2 + i + y * SCREENWIDTH] = 4;
        }
        for (i = 0; i < 2; ++i)
        {
            screen_buffer[SCREENWIDTH / 2 + (y - 2 - i) * SCREENWIDTH] = 4;
            screen_buffer[SCREENWIDTH / 2 + (y + 2 + i) * SCREENWIDTH] = 4;
        }
    }

    if (channels == 1)
    {
        return screen_buffer;
    }
    else if (channels == 3)
    {
        for (i = 0, len = SCREENWIDTH * SCREENHEIGHT; i < len; ++i)
        {
            int k = i * 3;
            int kpal = screen_buffer[i] * 3;
            final_screen_buffer[k + 0] = screen_palette[kpal + 0];
            final_screen_buffer[k + 1] = screen_palette[kpal + 1];
            final_screen_buffer[k + 2] = screen_palette[kpal + 2];
        }
        return final_screen_buffer;
    }
    else if (channels == 4)
    {
        for (i = 0, len = SCREENWIDTH * SCREENHEIGHT; i < len; ++i)
        {
            int k = i * 4;
            int kpal = screen_buffer[i] * 3;
            final_screen_buffer[k + 0] = screen_palette[kpal + 0];
            final_screen_buffer[k + 1] = screen_palette[kpal + 1];
            final_screen_buffer[k + 2] = screen_palette[kpal + 2];
            final_screen_buffer[k + 3] = 255;
        }
        return final_screen_buffer;
    }
    else
    {
        return 0;
    }
}


unsigned long doom_tick_midi()
{
    return I_TickSong();
}


short* doom_get_sound_buffer()
{
    I_UpdateSound();
    return mixbuffer;
}


void doom_key_down(doom_key_t key)
{
    event_t event;
    event.type = ev_keydown;
    event.data1 = (int)key;
    D_PostEvent(&event);
}


void doom_key_up(doom_key_t key)
{
    event_t event;
    event.type = ev_keyup;
    event.data1 = (int)key;
    D_PostEvent(&event);
}


void doom_button_down(doom_button_t button)
{
    button_states[button] = 1;

    event_t event;
    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);
    event.data2 = event.data3 = 0;
    D_PostEvent(&event);
}


void doom_button_up(doom_button_t button)
{
    button_states[button] = 0;

    event_t event;
    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);

    event.data1 =
        event.data1
        ^ (button_states[0] ? 1 : 0)
        ^ (button_states[1] ? 2 : 0)
        ^ (button_states[2] ? 4 : 0);

    event.data2 = event.data3 = 0;
    D_PostEvent(&event);
}


void doom_mouse_move(int delta_x, int delta_y)
{
    event_t event;

    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);
    event.data2 = delta_x;
    event.data3 = -delta_y;

    if (event.data2 || event.data3)
    {
        D_PostEvent(&event);
    }
}
#define REDS        (256-5*16)
#define REDRANGE    16
#define BLUES       (256-4*16+8)
#define BLUERANGE   8
#define GREENS      (7*16)
#define GREENRANGE  16
#define GRAYS       (6*16)
#define GRAYSRANGE  16
#define BROWNS      (4*16)
#define BROWNRANGE  16
#define YELLOWS     (256-32+7)
#define YELLOWRANGE 1
#define BLACK       0
#define WHITE       (256-47)

// Automap colors
#define BACKGROUND          BLACK
#define YOURCOLORS          WHITE
#define YOURRANGE           0
#define WALLCOLORS          REDS
#define WALLRANGE           REDRANGE
#define TSWALLCOLORS        GRAYS
#define TSWALLRANGE         GRAYSRANGE
#define FDWALLCOLORS        BROWNS
#define FDWALLRANGE         BROWNRANGE
#define CDWALLCOLORS        YELLOWS
#define CDWALLRANGE         YELLOWRANGE
#define THINGCOLORS         GREENS
#define THINGRANGE          GREENRANGE
#define SECRETWALLCOLORS    WALLCOLORS
#define SECRETWALLRANGE     WALLRANGE
#define GRIDCOLORS          (GRAYS + GRAYSRANGE/2)
#define GRIDRANGE           0
#define XHAIRCOLORS         GRAYS

// drawing stuff
#define FB 0

#define AM_PANDOWNKEY   KEY_DOWNARROW
#define AM_PANUPKEY     KEY_UPARROW
#define AM_PANRIGHTKEY  KEY_RIGHTARROW
#define AM_PANLEFTKEY   KEY_LEFTARROW
#define AM_ZOOMINKEY    '='
#define AM_ZOOMOUTKEY   '-'
#define AM_STARTKEY     KEY_TAB
#define AM_ENDKEY       KEY_TAB
#define AM_GOBIGKEY     '0'
#define AM_FOLLOWKEY    'f'
#define AM_GRIDKEY      'g'
#define AM_MARKKEY      'm'
#define AM_CLEARMARKKEY 'c'

#define AM_NUMMARKPOINTS 10

// scale on entry
#define INITSCALEMTOF (.2*FRACUNIT)
// how much the automap moves window per tic in frame-buffer coordinates
// moves 140 pixels in 1 second
#define F_PANINC        4
// how much zoom-in per tic
// goes to 2x in 1 second
#define M_ZOOMIN        ((int) (1.02*FRACUNIT))
// how much zoom-out per tic
// pulls out to 0.5x in 1 second
#define M_ZOOMOUT       ((int) (FRACUNIT/1.02))

// translates between frame-buffer and map distances
#define FTOM(x) FixedMul(((x)<<16),scale_ftom)
#define MTOF(x) (FixedMul((x),scale_mtof)>>16)
// translates between frame-buffer and map coordinates
#define CXMTOF(x)  (f_x + MTOF((x)-m_x))
#define CYMTOF(y)  (f_y + (f_h - MTOF((y)-m_y)))

// the following is crap
#define LINE_NEVERSEE ML_DONTDRAW


typedef struct
{
    int x, y;
} fpoint_t;


typedef struct
{
    fpoint_t a, b;
} fline_t;


typedef struct
{
    fixed_t                x, y;
} mpoint_t;


typedef struct
{
    mpoint_t a, b;
} mline_t;


typedef struct
{
    fixed_t slp, islp;
} islope_t;


//
// The vector graphics for the automap.
// A line drawing of the player pointing right,
// starting from the middle.
//
#define R ((8*PLAYERRADIUS)/7)
mline_t player_arrow[] = {
    { { -R + R / 8, 0 }, { R, 0 } }, // -----
    { { R, 0 }, { R - R / 2, R / 4 } },  // ----->
    { { R, 0 }, { R - R / 2, -R / 4 } },
    { { -R + R / 8, 0 }, { -R - R / 8, R / 4 } }, // >---->
    { { -R + R / 8, 0 }, { -R - R / 8, -R / 4 } },
    { { -R + 3 * R / 8, 0 }, { -R + R / 8, R / 4 } }, // >>--->
    { { -R + 3 * R / 8, 0 }, { -R + R / 8, -R / 4 } }
};
#undef R
#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t))

#define R ((8*PLAYERRADIUS)/7)
mline_t cheat_player_arrow[] = {
    { { -R + R / 8, 0 }, { R, 0 } }, // -----
    { { R, 0 }, { R - R / 2, R / 6 } },  // ----->
    { { R, 0 }, { R - R / 2, -R / 6 } },
    { { -R + R / 8, 0 }, { -R - R / 8, R / 6 } }, // >----->
    { { -R + R / 8, 0 }, { -R - R / 8, -R / 6 } },
    { { -R + 3 * R / 8, 0 }, { -R + R / 8, R / 6 } }, // >>----->
    { { -R + 3 * R / 8, 0 }, { -R + R / 8, -R / 6 } },
    { { -R / 2, 0 }, { -R / 2, -R / 6 } }, // >>-d--->
    { { -R / 2, -R / 6 }, { -R / 2 + R / 6, -R / 6 } },
    { { -R / 2 + R / 6, -R / 6 }, { -R / 2 + R / 6, R / 4 } },
    { { -R / 6, 0 }, { -R / 6, -R / 6 } }, // >>-dd-->
    { { -R / 6, -R / 6 }, { 0, -R / 6 } },
    { { 0, -R / 6 }, { 0, R / 4 } },
    { { R / 6, R / 4 }, { R / 6, -R / 7 } }, // >>-ddt->
    { { R / 6, -R / 7 }, { R / 6 + R / 32, -R / 7 - R / 32 } },
    { { R / 6 + R / 32, -R / 7 - R / 32 }, { R / 6 + R / 10, -R / 7 } }
};
#undef R
#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t))

#define R (FRACUNIT)
mline_t triangle_guy[] = {
    { { (fixed_t)(-.867 * R), (fixed_t)(-.5 * R) }, { (fixed_t)(.867 * R), (fixed_t)(-.5 * R) } },
    { { (fixed_t)(.867 * R), (fixed_t)(-.5 * R) } , { 0, R } },
    { { 0, R }, { (fixed_t)(-.867 * R), (fixed_t)(-.5 * R) } }
};
#undef R
#define NUMTRIANGLEGUYLINES (sizeof(triangle_guy)/sizeof(mline_t))

#define R (FRACUNIT)
mline_t thintriangle_guy[] = {
    { { (fixed_t)(-.5 * R), (fixed_t)(-.7 * R) }, { R, 0 } },
    { { R, 0 }, { (fixed_t)(-.5 * R), (fixed_t)(.7 * R) } },
    { { (fixed_t)(-.5 * R), (fixed_t)(.7 * R) }, { (fixed_t)(-.5 * R), (fixed_t)(-.7 * R) } }
};
#undef R
#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t))


static int cheating = 0;
static int grid = 0;

static int leveljuststarted = 1; // kluge until AM_LevelInit() is called

static int finit_width = SCREENWIDTH;
static int finit_height = SCREENHEIGHT - 32;

// location of window on screen
static int f_x;
static int f_y;

// size of window on screen
static int f_w;
static int f_h;

static int lightlev; // used for funky strobing effect
static byte* fb; // pseudo-frame buffer
static int amclock;

static mpoint_t m_paninc; // how far the window pans each tic (map coords)
static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords)
static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords)

static fixed_t m_x, m_y;   // LL x,y where the window is on the map (map coords)
static fixed_t m_x2, m_y2; // UR x,y where the window is on the map (map coords)

//
// width/height of window on map (map coords)
//
static fixed_t m_w;
static fixed_t m_h;

// based on level size
static fixed_t min_x;
static fixed_t min_y;
static fixed_t max_x;
static fixed_t max_y;

static fixed_t max_w; // max_x-min_x,
static fixed_t max_h; // max_y-min_y

// based on player size
static fixed_t min_w;
static fixed_t min_h;

static fixed_t min_scale_mtof; // used to tell when to stop zooming out
static fixed_t max_scale_mtof; // used to tell when to stop zooming in

// old stuff for recovery later
static fixed_t old_m_w, old_m_h;
static fixed_t old_m_x, old_m_y;

// old location used by the Follower routine
static mpoint_t f_oldloc;

// used by MTOF to scale from map-to-frame-buffer coords
static fixed_t scale_mtof = (fixed_t)INITSCALEMTOF;
// used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof)
static fixed_t scale_ftom;

static player_t* plr; // the player represented by an arrow

static patch_t* marknums[10]; // numbers used for marking by the automap
static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
static int markpointnum = 0; // next point to be assigned

static int followplayer = 1; // specifies whether to follow the player around

static unsigned char cheat_amap_seq[] = { 0xb2, 0x26, 0x26, 0x2e, 0xff };
static cheatseq_t cheat_amap = { cheat_amap_seq, 0 };

static doom_boolean stopped = true;


doom_boolean automapactive = false;


extern doom_boolean viewactive;


void V_MarkRect(int x, int y, int width, int height);


// Calculates the slope and slope according to the x-axis of a line
// segment in map coordinates (with the upright y-axis n' all) so
// that it can be used with the brain-dead drawing stuff.
void AM_getIslope(mline_t* ml, islope_t* is)
{
    int dx, dy;

    dy = ml->a.y - ml->b.y;
    dx = ml->b.x - ml->a.x;
    if (!dy) is->islp = (dx < 0 ? -DOOM_MAXINT : DOOM_MAXINT);
    else is->islp = FixedDiv(dx, dy);
    if (!dx) is->slp = (dy < 0 ? -DOOM_MAXINT : DOOM_MAXINT);
    else is->slp = FixedDiv(dy, dx);
}


//
//
//
void AM_activateNewScale(void)
{
    m_x += m_w / 2;
    m_y += m_h / 2;
    m_w = FTOM(f_w);
    m_h = FTOM(f_h);
    m_x -= m_w / 2;
    m_y -= m_h / 2;
    m_x2 = m_x + m_w;
    m_y2 = m_y + m_h;
}


//
//
//
void AM_saveScaleAndLoc(void)
{
    old_m_x = m_x;
    old_m_y = m_y;
    old_m_w = m_w;
    old_m_h = m_h;
}


//
//
//
void AM_restoreScaleAndLoc(void)
{
    m_w = old_m_w;
    m_h = old_m_h;
    if (!followplayer)
    {
        m_x = old_m_x;
        m_y = old_m_y;
    }
    else
    {
        m_x = plr->mo->x - m_w / 2;
        m_y = plr->mo->y - m_h / 2;
    }
    m_x2 = m_x + m_w;
    m_y2 = m_y + m_h;

    // Change the scaling multipliers
    scale_mtof = FixedDiv(f_w << FRACBITS, m_w);
    scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
}


//
// adds a marker at the current location
//
void AM_addMark(void)
{
    markpoints[markpointnum].x = m_x + m_w / 2;
    markpoints[markpointnum].y = m_y + m_h / 2;
    markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
}


//
// Determines bounding box of all vertices,
// sets global variables controlling zoom range.
//
void AM_findMinMaxBoundaries(void)
{
    int i;
    fixed_t a;
    fixed_t b;

    min_x = min_y = DOOM_MAXINT;
    max_x = max_y = -DOOM_MAXINT;

    for (i = 0; i < numvertexes; i++)
    {
        if (vertexes[i].x < min_x)
            min_x = vertexes[i].x;
        else if (vertexes[i].x > max_x)
            max_x = vertexes[i].x;

        if (vertexes[i].y < min_y)
            min_y = vertexes[i].y;
        else if (vertexes[i].y > max_y)
            max_y = vertexes[i].y;
    }

    max_w = max_x - min_x;
    max_h = max_y - min_y;

    min_w = 2 * PLAYERRADIUS; // const? never changed?
    min_h = 2 * PLAYERRADIUS;

    a = FixedDiv(f_w << FRACBITS, max_w);
    b = FixedDiv(f_h << FRACBITS, max_h);

    min_scale_mtof = a < b ? a : b;
    max_scale_mtof = FixedDiv(f_h << FRACBITS, 2 * PLAYERRADIUS);
}


//
//
//
void AM_changeWindowLoc(void)
{
    if (m_paninc.x || m_paninc.y)
    {
        followplayer = 0;
        f_oldloc.x = DOOM_MAXINT;
    }

    m_x += m_paninc.x;
    m_y += m_paninc.y;

    if (m_x + m_w / 2 > max_x)
        m_x = max_x - m_w / 2;
    else if (m_x + m_w / 2 < min_x)
        m_x = min_x - m_w / 2;

    if (m_y + m_h / 2 > max_y)
        m_y = max_y - m_h / 2;
    else if (m_y + m_h / 2 < min_y)
        m_y = min_y - m_h / 2;

    m_x2 = m_x + m_w;
    m_y2 = m_y + m_h;
}


//
//
//
void AM_initVariables(void)
{
    int pnum;
    static event_t st_notify = { ev_keyup, AM_MSGENTERED };

    automapactive = true;
    fb = screens[0];

    f_oldloc.x = DOOM_MAXINT;
    amclock = 0;
    lightlev = 0;

    m_paninc.x = m_paninc.y = 0;
    ftom_zoommul = FRACUNIT;
    mtof_zoommul = FRACUNIT;

    m_w = FTOM(f_w);
    m_h = FTOM(f_h);

    // find player to center on initially
    if (!playeringame[pnum = consoleplayer])
        for (pnum = 0; pnum < MAXPLAYERS; pnum++)
            if (playeringame[pnum])
                break;

    plr = &players[pnum];
    m_x = plr->mo->x - m_w / 2;
    m_y = plr->mo->y - m_h / 2;
    AM_changeWindowLoc();

    // for saving & restoring
    old_m_x = m_x;
    old_m_y = m_y;
    old_m_w = m_w;
    old_m_h = m_h;

    // inform the status bar of the change
    ST_Responder(&st_notify);
}


//
// 
//
void AM_loadPics(void)
{
    int i;
    char namebuf[9];

    for (i = 0; i < 10; i++)
    {
        doom_concat(doom_strcpy(namebuf, "AMMNUM"), doom_itoa(i, 10));
        marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
    }
}


void AM_unloadPics(void)
{
    int i;

    for (i = 0; i < 10; i++)
        Z_ChangeTag(marknums[i], PU_CACHE);
}


void AM_clearMarks(void)
{
    int i;

    for (i = 0; i < AM_NUMMARKPOINTS; i++)
        markpoints[i].x = -1; // means empty
    markpointnum = 0;
}


//
// should be called at the start of every level
// right now, i figure it out myself
//
void AM_LevelInit(void)
{
    leveljuststarted = 0;

    f_x = f_y = 0;
    f_w = finit_width;
    f_h = finit_height;

    AM_clearMarks();

    AM_findMinMaxBoundaries();
    scale_mtof = FixedDiv(min_scale_mtof, (int)(0.7 * FRACUNIT));
    if (scale_mtof > max_scale_mtof)
        scale_mtof = min_scale_mtof;
    scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
}


//
//
//
void AM_Stop(void)
{
    static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED };

    AM_unloadPics();
    automapactive = false;
    ST_Responder(&st_notify);
    stopped = true;
}


//
//
//
void AM_Start(void)
{
    static int lastlevel = -1, lastepisode = -1;

    if (!stopped) AM_Stop();
    stopped = false;
    if (lastlevel != gamemap || lastepisode != gameepisode)
    {
        AM_LevelInit();
        lastlevel = gamemap;
        lastepisode = gameepisode;
    }
    AM_initVariables();
    AM_loadPics();
}


//
// set the window scale to the maximum size
//
void AM_minOutWindowScale(void)
{
    scale_mtof = min_scale_mtof;
    scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
    AM_activateNewScale();
}


//
// set the window scale to the minimum size
//
void AM_maxOutWindowScale(void)
{
    scale_mtof = max_scale_mtof;
    scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
    AM_activateNewScale();
}


//
// Handle events (user inputs) in automap mode
//
doom_boolean AM_Responder(event_t* ev)
{
    int rc;
    static int cheatstate = 0;
    static int bigstate = 0;
    static char buffer[20];

    rc = false;

    if (!automapactive)
    {
        if (ev->type == ev_keydown && ev->data1 == AM_STARTKEY)
        {
            AM_Start();
            viewactive = false;
            rc = true;
        }
    }

    else if (ev->type == ev_keydown)
    {

        rc = true;
        switch (ev->data1)
        {
            case AM_PANRIGHTKEY: // pan right
                if (!followplayer) m_paninc.x = FTOM(F_PANINC);
                else rc = false;
                break;
            case AM_PANLEFTKEY: // pan left
                if (!followplayer) m_paninc.x = -FTOM(F_PANINC);
                else rc = false;
                break;
            case AM_PANUPKEY: // pan up
                if (!followplayer) m_paninc.y = FTOM(F_PANINC);
                else rc = false;
                break;
            case AM_PANDOWNKEY: // pan down
                if (!followplayer) m_paninc.y = -FTOM(F_PANINC);
                else rc = false;
                break;
            case AM_ZOOMOUTKEY: // zoom out
                mtof_zoommul = M_ZOOMOUT;
                ftom_zoommul = M_ZOOMIN;
                break;
            case AM_ZOOMINKEY: // zoom in
                mtof_zoommul = M_ZOOMIN;
                ftom_zoommul = M_ZOOMOUT;
                break;
            case AM_ENDKEY:
                bigstate = 0;
                viewactive = true;
                AM_Stop();
                break;
            case AM_GOBIGKEY:
                bigstate = !bigstate;
                if (bigstate)
                {
                    AM_saveScaleAndLoc();
                    AM_minOutWindowScale();
                }
                else AM_restoreScaleAndLoc();
                break;
            case AM_FOLLOWKEY:
                followplayer = !followplayer;
                f_oldloc.x = DOOM_MAXINT;
                plr->message = followplayer ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF;
                break;
            case AM_GRIDKEY:
                grid = !grid;
                plr->message = grid ? AMSTR_GRIDON : AMSTR_GRIDOFF;
                break;
            case AM_MARKKEY:
                doom_strcpy(buffer, AMSTR_MARKEDSPOT);
                doom_concat(buffer, " ");
                doom_concat(buffer, doom_itoa(markpointnum, 10));
                //doom_sprintf(buffer, "%s %d", AMSTR_MARKEDSPOT, markpointnum);
                plr->message = buffer;
                AM_addMark();
                break;
            case AM_CLEARMARKKEY:
                AM_clearMarks();
                plr->message = AMSTR_MARKSCLEARED;
                break;
            default:
                cheatstate = 0;
                rc = false;
        }
        if (!deathmatch && cht_CheckCheat(&cheat_amap, ev->data1))
        {
            rc = false;
            cheating = (cheating + 1) % 3;
        }
    }

    else if (ev->type == ev_keyup)
    {
        rc = false;
        switch (ev->data1)
        {
            case AM_PANRIGHTKEY:
                if (!followplayer) m_paninc.x = 0;
                break;
            case AM_PANLEFTKEY:
                if (!followplayer) m_paninc.x = 0;
                break;
            case AM_PANUPKEY:
                if (!followplayer) m_paninc.y = 0;
                break;
            case AM_PANDOWNKEY:
                if (!followplayer) m_paninc.y = 0;
                break;
            case AM_ZOOMOUTKEY:
            case AM_ZOOMINKEY:
                mtof_zoommul = FRACUNIT;
                ftom_zoommul = FRACUNIT;
                break;
        }
    }

    return rc;
}


//
// Zooming
//
void AM_changeWindowScale(void)
{

    // Change the scaling multipliers
    scale_mtof = FixedMul(scale_mtof, mtof_zoommul);
    scale_ftom = FixedDiv(FRACUNIT, scale_mtof);

    if (scale_mtof < min_scale_mtof)
        AM_minOutWindowScale();
    else if (scale_mtof > max_scale_mtof)
        AM_maxOutWindowScale();
    else
        AM_activateNewScale();
}


//
//
//
void AM_doFollowPlayer(void)
{
    if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y)
    {
        m_x = FTOM(MTOF(plr->mo->x)) - m_w / 2;
        m_y = FTOM(MTOF(plr->mo->y)) - m_h / 2;
        m_x2 = m_x + m_w;
        m_y2 = m_y + m_h;
        f_oldloc.x = plr->mo->x;
        f_oldloc.y = plr->mo->y;
    }
}


//
//
//
void AM_updateLightLev(void)
{
    static int nexttic = 0;
    //static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 };
    static int litelevels[] = { 0, 4, 7, 10, 12, 14, 15, 15 };
    static int litelevelscnt = 0;

    // Change light level
    if (amclock > nexttic)
    {
        lightlev = litelevels[litelevelscnt++];
        if (litelevelscnt == sizeof(litelevels) / sizeof(int)) litelevelscnt = 0;
        nexttic = amclock + 6 - (amclock % 6);
    }
}


//
// Updates on Game Tick
//
void AM_Ticker(void)
{
    if (!automapactive)
        return;

    amclock++;

    if (followplayer)
        AM_doFollowPlayer();

    // Change the zoom if necessary
    if (ftom_zoommul != FRACUNIT)
        AM_changeWindowScale();

    // Change x,y location
    if (m_paninc.x || m_paninc.y)
        AM_changeWindowLoc();

    // Update light level
    // AM_updateLightLev();
}


//
// Clear automap frame buffer.
//
void AM_clearFB(int color)
{
    doom_memset(fb, color, f_w * f_h);
}


//
// Automap clipping of lines.
//
// Based on Cohen-Sutherland clipping algorithm but with a slightly
// faster reject and precalculated slopes.  If the speed is needed,
// use a hash algorithm to handle  the common cases.
//
doom_boolean AM_clipMline(mline_t* ml, fline_t* fl)
{
    enum
    {
        LEFT = 1,
        RIGHT = 2,
        BOTTOM = 4,
        TOP = 8
    };

    register int outcode1 = 0;
    register int outcode2 = 0;
    register int outside;

    fpoint_t tmp;
    int dx;
    int dy;


#define DOOUTCODE(oc, mx, my) \
    (oc) = 0; \
    if ((my) < 0) (oc) |= TOP; \
    else if ((my) >= f_h) (oc) |= BOTTOM; \
    if ((mx) < 0) (oc) |= LEFT; \
    else if ((mx) >= f_w) (oc) |= RIGHT;


    // do trivial rejects and outcodes
    if (ml->a.y > m_y2)
        outcode1 = TOP;
    else if (ml->a.y < m_y)
        outcode1 = BOTTOM;

    if (ml->b.y > m_y2)
        outcode2 = TOP;
    else if (ml->b.y < m_y)
        outcode2 = BOTTOM;

    if (outcode1 & outcode2)
        return false; // trivially outside

    if (ml->a.x < m_x)
        outcode1 |= LEFT;
    else if (ml->a.x > m_x2)
        outcode1 |= RIGHT;

    if (ml->b.x < m_x)
        outcode2 |= LEFT;
    else if (ml->b.x > m_x2)
        outcode2 |= RIGHT;

    if (outcode1 & outcode2)
        return false; // trivially outside

    // transform to frame-buffer coordinates.
    fl->a.x = CXMTOF(ml->a.x);
    fl->a.y = CYMTOF(ml->a.y);
    fl->b.x = CXMTOF(ml->b.x);
    fl->b.y = CYMTOF(ml->b.y);

    DOOUTCODE(outcode1, fl->a.x, fl->a.y);
    DOOUTCODE(outcode2, fl->b.x, fl->b.y);

    if (outcode1 & outcode2)
        return false;

    while (outcode1 | outcode2)
    {
        // may be partially inside box
        // find an outside point
        if (outcode1)
            outside = outcode1;
        else
            outside = outcode2;

        // clip to each side
        if (outside & TOP)
        {
            dy = fl->a.y - fl->b.y;
            dx = fl->b.x - fl->a.x;
            tmp.x = fl->a.x + (dx * (fl->a.y)) / dy;
            tmp.y = 0;
        }
        else if (outside & BOTTOM)
        {
            dy = fl->a.y - fl->b.y;
            dx = fl->b.x - fl->a.x;
            tmp.x = fl->a.x + (dx * (fl->a.y - f_h)) / dy;
            tmp.y = f_h - 1;
        }
        else if (outside & RIGHT)
        {
            dy = fl->b.y - fl->a.y;
            dx = fl->b.x - fl->a.x;
            tmp.y = fl->a.y + (dy * (f_w - 1 - fl->a.x)) / dx;
            tmp.x = f_w - 1;
        }
        else if (outside & LEFT)
        {
            dy = fl->b.y - fl->a.y;
            dx = fl->b.x - fl->a.x;
            tmp.y = fl->a.y + (dy * (-fl->a.x)) / dx;
            tmp.x = 0;
        }

        if (outside == outcode1)
        {
            fl->a = tmp;
            DOOUTCODE(outcode1, fl->a.x, fl->a.y);
        }
        else
        {
            fl->b = tmp;
            DOOUTCODE(outcode2, fl->b.x, fl->b.y);
        }

        if (outcode1 & outcode2)
            return false; // trivially outside
    }

    return true;
}
#undef DOOUTCODE


//
// Classic Bresenham w/ whatever optimizations needed for speed
//
void AM_drawFline(fline_t* fl, int color)
{
    register int x;
    register int y;
    register int dx;
    register int dy;
    register int sx;
    register int sy;
    register int ax;
    register int ay;
    register int d;

    static int fuck = 0;

    // For debugging only
#if 0 // [pd] Don't waste CPU cycles testing this then
    if (fl->a.x < 0 || fl->a.x >= f_w
        || fl->a.y < 0 || fl->a.y >= f_h
        || fl->b.x < 0 || fl->b.x >= f_w
        || fl->b.y < 0 || fl->b.y >= f_h)
    {
        doom_print("fuck ");
        doom_print(doom_itoa(fuck++, 10));
        doom_print("\r");
        return;
    }
#endif

#define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc)

    dx = fl->b.x - fl->a.x;
    ax = 2 * (dx < 0 ? -dx : dx);
    sx = dx < 0 ? -1 : 1;

    dy = fl->b.y - fl->a.y;
    ay = 2 * (dy < 0 ? -dy : dy);
    sy = dy < 0 ? -1 : 1;

    x = fl->a.x;
    y = fl->a.y;

    if (ax > ay)
    {
        d = ay - ax / 2;
        while (1)
        {
            PUTDOT(x, y, color);
            if (x == fl->b.x) return;
            if (d >= 0)
            {
                y += sy;
                d -= ax;
            }
            x += sx;
            d += ay;
        }
    }
    else
    {
        d = ax - ay / 2;
        while (1)
        {
            PUTDOT(x, y, color);
            if (y == fl->b.y) return;
            if (d >= 0)
            {
                x += sx;
                d -= ay;
            }
            y += sy;
            d += ax;
        }
    }
}


//
// Clip lines, draw visible part sof lines.
//
void AM_drawMline(mline_t* ml, int color)
{
    static fline_t fl;

    if (AM_clipMline(ml, &fl))
        AM_drawFline(&fl, color); // draws it on frame buffer using fb coords
}


//
// Draws flat (floor/ceiling tile) aligned grid lines.
//
void AM_drawGrid(int color)
{
    fixed_t x, y;
    fixed_t start, end;
    mline_t ml;

    // Figure out start of vertical gridlines
    start = m_x;
    if ((start - bmaporgx) % (MAPBLOCKUNITS << FRACBITS))
        start += (MAPBLOCKUNITS << FRACBITS)
        - ((start - bmaporgx) % (MAPBLOCKUNITS << FRACBITS));
    end = m_x + m_w;

    // draw vertical gridlines
    ml.a.y = m_y;
    ml.b.y = m_y + m_h;
    for (x = start; x < end; x += (MAPBLOCKUNITS << FRACBITS))
    {
        ml.a.x = x;
        ml.b.x = x;
        AM_drawMline(&ml, color);
    }

    // Figure out start of horizontal gridlines
    start = m_y;
    if ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS))
        start += (MAPBLOCKUNITS << FRACBITS)
        - ((start - bmaporgy) % (MAPBLOCKUNITS << FRACBITS));
    end = m_y + m_h;

    // draw horizontal gridlines
    ml.a.x = m_x;
    ml.b.x = m_x + m_w;
    for (y = start; y < end; y += (MAPBLOCKUNITS << FRACBITS))
    {
        ml.a.y = y;
        ml.b.y = y;
        AM_drawMline(&ml, color);
    }
}


//
// Determines visible lines, draws them.
// This is LineDef based, not LineSeg based.
//
void AM_drawWalls(void)
{
    int i;
    static mline_t l;

    for (i = 0; i < numlines; i++)
    {
        l.a.x = lines[i].v1->x;
        l.a.y = lines[i].v1->y;
        l.b.x = lines[i].v2->x;
        l.b.y = lines[i].v2->y;
        if (cheating || (lines[i].flags & ML_MAPPED))
        {
            if ((lines[i].flags & LINE_NEVERSEE) && !cheating)
                continue;
            if (!lines[i].backsector)
            {
                AM_drawMline(&l, WALLCOLORS + lightlev);
            }
            else
            {
                if (lines[i].special == 39)
                { // teleporters
                    AM_drawMline(&l, WALLCOLORS + WALLRANGE / 2);
                }
                else if (lines[i].flags & ML_SECRET) // secret door
                {
                    if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev);
                    else AM_drawMline(&l, WALLCOLORS + lightlev);
                }
                else if (lines[i].backsector->floorheight
                         != lines[i].frontsector->floorheight)
                {
                    AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change
                }
                else if (lines[i].backsector->ceilingheight
                         != lines[i].frontsector->ceilingheight)
                {
                    AM_drawMline(&l, CDWALLCOLORS + lightlev); // ceiling level change
                }
                else if (cheating)
                {
                    AM_drawMline(&l, TSWALLCOLORS + lightlev);
                }
            }
        }
        else if (plr->powers[pw_allmap])
        {
            if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS + 3);
        }
    }
}


//
// Rotation in 2D.
// Used to rotate player arrow line character.
//
void AM_rotate(fixed_t* x, fixed_t* y, angle_t a)
{
    fixed_t tmpx;

    tmpx =
        FixedMul(*x, finecosine[a >> ANGLETOFINESHIFT])
        - FixedMul(*y, finesine[a >> ANGLETOFINESHIFT]);

    *y =
        FixedMul(*x, finesine[a >> ANGLETOFINESHIFT])
        + FixedMul(*y, finecosine[a >> ANGLETOFINESHIFT]);

    *x = tmpx;
}


void AM_drawLineCharacter(mline_t* lineguy,
                          int lineguylines,
                          fixed_t scale,
                          angle_t angle,
                          int color,
                          fixed_t x,
                          fixed_t y)
{
    int i;
    mline_t l;

    for (i = 0; i < lineguylines; i++)
    {
        l.a.x = lineguy[i].a.x;
        l.a.y = lineguy[i].a.y;

        if (scale)
        {
            l.a.x = FixedMul(scale, l.a.x);
            l.a.y = FixedMul(scale, l.a.y);
        }

        if (angle)
            AM_rotate(&l.a.x, &l.a.y, angle);

        l.a.x += x;
        l.a.y += y;

        l.b.x = lineguy[i].b.x;
        l.b.y = lineguy[i].b.y;

        if (scale)
        {
            l.b.x = FixedMul(scale, l.b.x);
            l.b.y = FixedMul(scale, l.b.y);
        }

        if (angle)
            AM_rotate(&l.b.x, &l.b.y, angle);

        l.b.x += x;
        l.b.y += y;

        AM_drawMline(&l, color);
    }
}


void AM_drawPlayers(void)
{
    int                i;
    player_t*          p;
    static int         their_colors[] = { GREENS, GRAYS, BROWNS, REDS };
    int                their_color = -1;
    int                color;

    if (!netgame)
    {
        if (cheating)
            AM_drawLineCharacter
            (cheat_player_arrow, NUMCHEATPLYRLINES, 0,
             plr->mo->angle, WHITE, plr->mo->x, plr->mo->y);
        else
            AM_drawLineCharacter
            (player_arrow, NUMPLYRLINES, 0, plr->mo->angle,
             WHITE, plr->mo->x, plr->mo->y);
        return;
    }

    for (i = 0; i < MAXPLAYERS; i++)
    {
        their_color++;
        p = &players[i];

        if ((deathmatch && !singledemo) && p != plr)
            continue;

        if (!playeringame[i])
            continue;

        if (p->powers[pw_invisibility])
            color = 246; // *close* to black
        else
            color = their_colors[their_color];

        AM_drawLineCharacter
        (player_arrow, NUMPLYRLINES, 0, p->mo->angle,
         color, p->mo->x, p->mo->y);
    }
}


void AM_drawThings(int colors, int colorrange)
{
    int i;
    mobj_t* t;

    for (i = 0; i < numsectors; i++)
    {
        t = sectors[i].thinglist;
        while (t)
        {
            AM_drawLineCharacter
            (thintriangle_guy, NUMTHINTRIANGLEGUYLINES,
             16 << FRACBITS, t->angle, colors + lightlev, t->x, t->y);
            t = t->snext;
        }
    }
}


void AM_drawMarks(void)
{
    int i, fx, fy, w, h;

    for (i = 0; i < AM_NUMMARKPOINTS; i++)
    {
        if (markpoints[i].x != -1)
        {
            //      w = SHORT(marknums[i]->width);
            //      h = SHORT(marknums[i]->height);
            w = 5; // because something's wrong with the wad, i guess
            h = 6; // because something's wrong with the wad, i guess
            fx = CXMTOF(markpoints[i].x);
            fy = CYMTOF(markpoints[i].y);
            if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
                V_DrawPatch(fx, fy, FB, marknums[i]);
        }
    }
}


void AM_drawCrosshair(int color)
{
    fb[(f_w * (f_h + 1)) / 2] = color; // single point for now
}


void AM_Drawer(void)
{
    if (!automapactive) return;

    AM_clearFB(BACKGROUND);
    if (grid)
        AM_drawGrid(GRIDCOLORS);
    AM_drawWalls();
    AM_drawPlayers();
    if (cheating == 2)
        AM_drawThings(THINGCOLORS, THINGRANGE);
    AM_drawCrosshair(XHAIRCOLORS);

    AM_drawMarks();

    V_MarkRect(f_x, f_y, f_w, f_h);
}
weaponinfo_t weaponinfo[NUMWEAPONS] =
{
    {
        // fist
        am_noammo,
        S_PUNCHUP,
        S_PUNCHDOWN,
        S_PUNCH,
        S_PUNCH1,
        S_NULL
    },
    {
        // pistol
        am_clip,
        S_PISTOLUP,
        S_PISTOLDOWN,
        S_PISTOL,
        S_PISTOL1,
        S_PISTOLFLASH
    },
    {
        // shotgun
        am_shell,
        S_SGUNUP,
        S_SGUNDOWN,
        S_SGUN,
        S_SGUN1,
        S_SGUNFLASH1
    },
    {
        // chaingun
        am_clip,
        S_CHAINUP,
        S_CHAINDOWN,
        S_CHAIN,
        S_CHAIN1,
        S_CHAINFLASH1
    },
    {
        // missile launcher
        am_misl,
        S_MISSILEUP,
        S_MISSILEDOWN,
        S_MISSILE,
        S_MISSILE1,
        S_MISSILEFLASH1
    },
    {
        // plasma rifle
        am_cell,
        S_PLASMAUP,
        S_PLASMADOWN,
        S_PLASMA,
        S_PLASMA1,
        S_PLASMAFLASH1
    },
    {
        // bfg 9000
        am_cell,
        S_BFGUP,
        S_BFGDOWN,
        S_BFG,
        S_BFG1,
        S_BFGFLASH1
    },
    {
        // chainsaw
        am_noammo,
        S_SAWUP,
        S_SAWDOWN,
        S_SAW,
        S_SAW1,
        S_NULL
    },
    {
        // super shotgun
        am_shell,
        S_DSGUNUP,
        S_DSGUNDOWN,
        S_DSGUN,
        S_DSGUN1,
        S_DSGUNFLASH1
    },
};
#if defined(DOOM_WIN32)


#define X_OK 0
#define W_OK 2
#define R_OK 4
#define RW_OK 6
#elif defined(DOOM_APPLE)


#endif

//#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"


#define MAXARGVS 100


char* wadfiles[MAXWADFILES];

doom_boolean devparm; // started game with -devparm
doom_boolean nomonsters; // checkparm of -nomonsters
doom_boolean respawnparm; // checkparm of -respawn
doom_boolean fastparm; // checkparm of -fast

doom_boolean drone;

doom_boolean singletics = true; // debug flag to cancel adaptiveness

doom_boolean is_wiping_screen = false;

skill_t startskill;
int startepisode;
int startmap;
doom_boolean autostart;

void* debugfile = 0;

doom_boolean advancedemo;

char wadfile[1024]; // primary wad file
char mapdir[1024]; // directory of development maps
char basedefault[1024]; // default file

//
// 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;

// wipegamestate can be set to -1 to force a wipe on the next draw
gamestate_t wipegamestate = GS_DEMOSCREEN;
void R_ExecuteSetViewSize(void);

// print title for every printed line
char title[128];


extern  doom_boolean inhelpscreens;
extern doom_boolean setsizeneeded;
extern int showMessages;
extern doom_boolean demorecording;


void D_DoomLoop(void);
void D_CheckNetGame(void);
void D_ProcessEvents(void);
void G_BuildTiccmd(ticcmd_t* cmd);
void D_DoAdvanceDemo(void);


//
// D_PostEvent
// Called by the I/O functions when input is detected
//
void D_PostEvent(event_t* ev)
{
    events[eventhead] = *ev;
    eventhead++;
    eventhead = (eventhead) & (MAXEVENTS - 1);
}


//
// 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; )
    {
        ev = &events[eventtail];
        if (!M_Responder(ev))
            G_Responder(ev);
        // else menu ate the event
        eventtail++;
        eventtail = (eventtail) & (MAXEVENTS - 1);
    }
}


//
// D_Display
//  draw current display, possibly wiping it from the previous
//
void D_Display(void)
{
    static doom_boolean viewactivestate = false;
    static doom_boolean menuactivestate = false;
    static doom_boolean inhelpscreensstate = false;
    static doom_boolean fullscreen = false;
    static gamestate_t oldgamestate = -1;
    static int borderdrawcount;
    int wipestart;
    int y;
    doom_boolean wipe;
    doom_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
    is_wiping_screen = wipe;
    if (!wipe)
    {
        I_FinishUpdate(); // page flip or blit buffer
        return;
    }

    // wipe update
    wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);

    wipestart = I_GetTime() - 1;

#if 0 // [pd] Moved to D_UpdateWipe
    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
    } while (!done);
#endif
}


//
//  D_DoomLoop
//
void D_UpdateWipe(void)
{
    if (wipe_ScreenWipe(wipe_Melt, 0, 0, SCREENWIDTH, SCREENHEIGHT, 1))
        is_wiping_screen = false;
}


void D_DoomLoop(void)
{
#if 0 // [pd] Moved to D_DoomMain()
    if (demorecording)
        G_BeginRecording();

    if (M_CheckParm("-debugfile"))
    {
        char    filename[20];
        //doom_sprintf(filename, "debug%i.txt", consoleplayer);
        doom_print("debug output to: %s\n", filename);
        debugfile = doom_open(filename, "w");
    }

    I_InitGraphics();
#endif

    // while (1)
    {
        // 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();

#if 0 // [pd] Sound is queried by the application's audio thread.
        // Sound mixing for the buffer is snychronous.
        I_UpdateSound();

        // Synchronous sound output is explicitly called.
        // Update sound output.
        I_SubmitSound();
#endif
    }
}


//
//  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();
}


//
// D_AddFile
//
void D_AddFile(char* file)
{
    int numwadfiles;
    char* newfile;

    for (numwadfiles = 0; wadfiles[numwadfiles]; numwadfiles++)
        ;

    newfile = doom_malloc(doom_strlen(file) + 1);
    doom_strcpy(newfile, file);

    wadfiles[numwadfiles] = newfile;
}


//
// IdentifyVersion
// Checks availability of IWAD files by name,
// to determine whether registered/commercial features
// should be executed (notably loading PWAD's).
//
void IdentifyVersion(void)
{
    char* doom1wad;
    char* doomwad;
    char* doomuwad;
    char* doom2wad;

    char* doom2fwad;
    char* plutoniawad;
    char* tntwad;

    char* home;
    char* doomwaddir;
    doomwaddir = doom_getenv("DOOMWADDIR");
    if (!doomwaddir)
        doomwaddir = ".";

    // Commercial.
    doom2wad = doom_malloc(doom_strlen(doomwaddir) + 1 + 9 + 1);
    //doom_sprintf(doom2wad, "%s/doom2.wad", doomwaddir);
    doom_strcpy(doom2wad, doomwaddir);
    doom_concat(doom2wad, "/doom2.wad");

    // Retail.
    doomuwad = doom_malloc(doom_strlen(doomwaddir) + 1 + 8 + 1);
    //doom_sprintf(doomuwad, "%s/doomu.wad", doomwaddir);
    doom_strcpy(doomuwad, doomwaddir);
    doom_concat(doomuwad, "/doomu.wad");

    // Registered.
    doomwad = doom_malloc(doom_strlen(doomwaddir) + 1 + 8 + 1);
    //doom_sprintf(doomwad, "%s/doom.wad", doomwaddir);
    doom_strcpy(doomwad, doomwaddir);
    doom_concat(doomwad, "/doom.wad");

    // Shareware.
    doom1wad = doom_malloc(doom_strlen(doomwaddir) + 1 + 9 + 1);
    //doom_sprintf(doom1wad, "%s/doom1.wad", doomwaddir);
    doom_strcpy(doom1wad, doomwaddir);
    doom_concat(doom1wad, "/doom1.wad");

    // Bug, dear Shawn.
   // Insufficient malloc, caused spurious realloc errors.
    plutoniawad = doom_malloc(doom_strlen(doomwaddir) + 1 +/*9*/12 + 1);
    //doom_sprintf(plutoniawad, "%s/plutonia.wad", doomwaddir);
    doom_strcpy(plutoniawad, doomwaddir);
    doom_concat(plutoniawad, "/plutonia.wad");

    tntwad = doom_malloc(doom_strlen(doomwaddir) + 1 + 9 + 1);
    //doom_sprintf(tntwad, "%s/tnt.wad", doomwaddir);
    doom_strcpy(tntwad, doomwaddir);
    doom_concat(tntwad, "/tnt.wad");

    // French stuff.
    doom2fwad = doom_malloc(doom_strlen(doomwaddir) + 1 + 10 + 1);
    //doom_sprintf(doom2fwad, "%s/doom2f.wad", doomwaddir);
    doom_strcpy(doom2fwad, doomwaddir);
    doom_concat(doom2fwad, "/doom2f.wad");

#if !defined(DOOM_WIN32)
    home = doom_getenv("HOME");
    if (!home)
        I_Error("Error: Please set $HOME to your home directory");
#else
    home = ".";
#endif
    //doom_sprintf(basedefault, "%s/.doomrc", home);
    doom_strcpy(basedefault, home);
    doom_concat(basedefault, "/.doomrc");

    if (M_CheckParm("-shdev"))
    {
        gamemode = shareware;
        devparm = true;
        D_AddFile(DEVDATA"doom1.wad");
        D_AddFile(DEVMAPS"data_se/texture1.lmp");
        D_AddFile(DEVMAPS"data_se/pnames.lmp");
        doom_strcpy(basedefault, DEVDATA"default.cfg");
        return;
    }

    if (M_CheckParm("-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");
        doom_strcpy(basedefault, DEVDATA"default.cfg");
        return;
    }

    if (M_CheckParm("-comdev"))
    {
        gamemode = commercial;
        devparm = true;
        /* I don't bother
        if(plutonia)
            D_AddFile (DEVDATA"plutonia.wad");
        else if(tnt)
            D_AddFile (DEVDATA"tnt.wad");
        else*/
        D_AddFile(DEVDATA"doom2.wad");

        D_AddFile(DEVMAPS"cdata/texture1.lmp");
        D_AddFile(DEVMAPS"cdata/pnames.lmp");
        doom_strcpy(basedefault, DEVDATA"default.cfg");
        return;
    }

    void* f;
    if (f = doom_open(doom2fwad, "rb"))
    {
        doom_close(f);
        gamemode = commercial;
        // C'est ridicule!
        // Let's handle languages in config files, okay?
        language = french;
        doom_print("French version\n");
        D_AddFile(doom2fwad);
        return;
    }

    if (f = doom_open(doom2wad, "rb"))
    {
        doom_close(f);
        gamemode = commercial;
        D_AddFile(doom2wad);
        return;
    }

    if (f = doom_open(plutoniawad, "rb"))
    {
        doom_close(f);
        gamemode = commercial;
        D_AddFile(plutoniawad);
        return;
    }

    if (f = doom_open(tntwad, "rb"))
    {
        doom_close(f);
        gamemode = commercial;
        D_AddFile(tntwad);
        return;
    }

    if (f = doom_open(doomuwad, "rb"))
    {
        doom_close(f);
        gamemode = retail;
        D_AddFile(doomuwad);
        return;
    }

    if (f = doom_open(doomwad, "rb"))
    {
        doom_close(f);
        gamemode = registered;
        D_AddFile(doomwad);
        return;
    }

    if (f = doom_open(doom1wad, "rb"))
    {
        doom_close(f);
        gamemode = shareware;
        D_AddFile(doom1wad);
        return;
    }

    doom_print("Game mode indeterminate.\n");
    gamemode = indetermined;
}


//
// Find a Response File
//
void FindResponseFile(void)
{
    int i;

    for (i = 1; i < myargc; i++)
        if (myargv[i][0] == '@')
        {
            void* handle = 0;
            int size;
            int k;
            int index;
            int indexinfile;
            char* infile;
            char* file;
            char* moreargs[20];
            char* firstargv;

            // READ THE RESPONSE FILE INTO MEMORY
            handle = doom_open(&myargv[i][1], "rb");
            if (!handle)
            {
                doom_print("\nNo such response file!");
                doom_exit(1);
            }
            doom_print("Found response file %s!\n");
            doom_print(&myargv[i][1]);
            doom_print("!\n");
            doom_seek(handle, 0, DOOM_SEEK_END);
            size = doom_tell(handle);
            doom_seek(handle, 0, DOOM_SEEK_SET);
            file = doom_malloc(size);
            doom_read(handle, file, size * 1);
            doom_close(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 = doom_malloc(sizeof(char*) * MAXARGVS);
            doom_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
            doom_print(doom_itoa(myargc, 10));
            doom_print(" command-line args:\n");
            for (k = 1; k < myargc; k++)
            {
                //doom_print("%s\n", myargv[k]);
                doom_print(myargv[k]);
                doom_print("\n");
            }

            break;
        }
}


//
// D_DoomMain
//
void D_DoomMain(void)
{
    int p;
    char file[256];

    FindResponseFile();

    IdentifyVersion();

    modifiedgame = false;

    nomonsters = M_CheckParm("-nomonsters");
    respawnparm = M_CheckParm("-respawn");
    fastparm = M_CheckParm("-fast");
    devparm = M_CheckParm("-devparm");
    if (M_CheckParm("-altdeath"))
        deathmatch = 2;
    else if (M_CheckParm("-deathmatch"))
        deathmatch = 1;

    switch (gamemode)
    {
        case retail:
            //doom_sprintf(title,
            //        "                         "
            //        "The Ultimate DOOM Startup v%i.%i"
            //        "                           ",
            //        VERSION / 100, VERSION % 100);
            doom_strcpy(title, "                         " "The Ultimate DOOM Startup v");
            doom_concat(title, doom_itoa(VERSION / 100, 10));
            doom_concat(title, ".");
            doom_concat(title, doom_itoa(VERSION % 100, 10));
            doom_concat(title, "                           ");
            break;
        case shareware:
            //doom_sprintf(title,
            //        "                            "
            //        "DOOM Shareware Startup v%i.%i"
            //        "                           ",
            //        VERSION / 100, VERSION % 100);
            doom_strcpy(title, "                            " "DOOM Shareware Startup v");
            doom_concat(title, doom_itoa(VERSION / 100, 10));
            doom_concat(title, ".");
            doom_concat(title, doom_itoa(VERSION % 100, 10));
            doom_concat(title, "                           ");
            break;
        case registered:
            //doom_sprintf(title,
            //        "                            "
            //        "DOOM Registered Startup v%i.%i"
            //        "                           ",
            //        VERSION / 100, VERSION % 100);
            doom_strcpy(title, "                            " "DOOM Registered Startup v");
            doom_concat(title, doom_itoa(VERSION / 100, 10));
            doom_concat(title, ".");
            doom_concat(title, doom_itoa(VERSION % 100, 10));
            doom_concat(title, "                           ");
            break;
        case commercial:
            //doom_sprintf(title,
            //        "                         "
            //        "DOOM 2: Hell on Earth v%i.%i"
            //        "                           ",
            //        VERSION / 100, VERSION % 100);
            doom_strcpy(title, "                         " "DOOM 2: Hell on Earth v");
            doom_concat(title, doom_itoa(VERSION / 100, 10));
            doom_concat(title, ".");
            doom_concat(title, doom_itoa(VERSION % 100, 10));
            doom_concat(title, "                           ");
            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:
            //doom_sprintf(title,
            //        "                     "
            //        "Public DOOM - v%i.%i"
            //        "                           ",
            //        VERSION / 100, VERSION % 100);
            doom_strcpy(title, "                     " "Public DOOM - v");
            doom_concat(title, doom_itoa(VERSION / 100, 10));
            doom_concat(title, ".");
            doom_concat(title, doom_itoa(VERSION % 100, 10));
            doom_concat(title, "                           ");
            break;
    }

    //doom_print("%s\n", title);
    doom_print(title);
    doom_print("\n");

    if (devparm)
        doom_print(D_DEVSTR);

#if 0 // [pd] Ignore cdrom
    if (M_CheckParm("-cdrom"))
    {
        doom_print(D_CDROM);
        mkdir("c:\\doomdata", 0);
        doom_strcpy(basedefault, "c:/doomdata/default.cfg");
    }
#endif

    // turbo option
    if ((p = M_CheckParm("-turbo")))
    {
        int     scale = 200;
        extern int forwardmove[2];
        extern int sidemove[2];

        if (p < myargc - 1)
            scale = doom_atoi(myargv[p + 1]);
        if (scale < 10)
            scale = 10;
        if (scale > 400)
            scale = 400;
        //doom_print("turbo scale: %i%%\n", scale);
        doom_print("turbo scale: ");
        doom_print(doom_itoa(scale, 10));
        doom_print("%%\n");
        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:
                //doom_sprintf(file, "~"DEVMAPS"E%cM%c.wad", myargv[p + 1][0], myargv[p + 2][0]);
                doom_strcpy(file, "~"DEVMAPS"E");
                doom_concat(file, doom_ctoa(myargv[p + 1][0]));
                doom_concat(file, "M");
                doom_concat(file, doom_ctoa(myargv[p + 2][0]));
                doom_concat(file, ".wad");

                //doom_print("Warping to Episode %s, Map %s.\n", myargv[p + 1], myargv[p + 2]);
                doom_print("Warping to Episode ");
                doom_print(myargv[p + 1]);
                doom_print(", Map ");
                doom_print(myargv[p + 2]);
                doom_print(".\n");
                break;

            case commercial:
            default:
                p = doom_atoi(myargv[p + 1]);
                if (p < 10)
                {
                    //doom_sprintf(file, "~"DEVMAPS"cdata/map0%i.wad", p);
                    doom_strcpy(file, "~"DEVMAPS"cdata/map0");
                    doom_concat(file, doom_itoa(p, 10));
                    doom_concat(file, ".wad");
                }
                else
                {
                    //doom_sprintf(file, "~"DEVMAPS"cdata/map%i.wad", p);
                    doom_strcpy(file, "~"DEVMAPS"cdata/map");
                    doom_concat(file, doom_itoa(p, 10));
                    doom_concat(file, ".wad");
                }
                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)
    {
        //doom_sprintf(file, "%s.lmp", myargv[p + 1]);
        doom_strcpy(file, myargv[p + 1]);
        doom_concat(file, ".lmp");
        D_AddFile(file);
        //doom_print("Playing demo %s.lmp.\n", myargv[p + 1]);
        doom_print("Playing demo ");
        doom_print(myargv[p + 1]);
        doom_print(".lmp.\n");
    }

    // 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 = doom_atoi(myargv[p + 1]);
        //doom_print("Levels will end after %d minute", time);
        doom_print("Levels will end after ");
        doom_print(doom_itoa(time, 10));
        doom_print(" minute");
        if (time > 1)
            doom_print("s");
        doom_print(".\n");
    }

    p = M_CheckParm("-avg");
    if (p && p < myargc - 1 && deathmatch)
        doom_print("Austin Virtual Gaming: Levels will end after 20 minutes\n");

    p = M_CheckParm("-warp");
    if (p && p < myargc - 1)
    {
        if (gamemode == commercial)
            startmap = doom_atoi(myargv[p + 1]);
        else
        {
            startepisode = myargv[p + 1][0] - '0';
            startmap = myargv[p + 2][0] - '0';
        }
        autostart = true;
    }

    // init subsystems
    doom_print("V_Init: allocate screens.\n");
    V_Init();

    doom_print("M_LoadDefaults: Load system defaults.\n");
    M_LoadDefaults();              // load before initing other systems

    doom_print("Z_Init: Init zone memory allocation daemon. \n");
    Z_Init();

    doom_print("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("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("Error: \nThis is not the registered version.");
    }

    // Iff additonal PWAD files are used, print modified banner
    if (modifiedgame)
    {
        /*m*/doom_print(
            "===========================================================================\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"
        );
        //getchar();
    }


    // Check and print which version is executed.
    switch (gamemode)
    {
        case shareware:
        case indetermined:
            doom_print(
                "===========================================================================\n"
                "                                Shareware!\n"
                "===========================================================================\n"
            );
            break;
        case registered:
        case retail:
        case commercial:
            doom_print(
                "===========================================================================\n"
                "                 Commercial product - do not distribute!\n"
                "         Please report software piracy to the SPA: 1-800-388-PIR8\n"
                "===========================================================================\n"
            );
            break;

        default:
            // Ouch.
            break;
    }

    doom_print("M_Init: Init miscellaneous info.\n");
    M_Init();

    doom_print("R_Init: Init DOOM refresh daemon - ");
    R_Init();

    doom_print("\nP_Init: Init Playloop state.\n");
    P_Init();

    doom_print("I_Init: Setting up machine state.\n");
    I_Init();

    doom_print("D_CheckNetGame: Checking network game status.\n");
    D_CheckNetGame();

    doom_print("S_Init: Setting up sound.\n");
    S_Init(snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/);

    doom_print("HU_Init: Setting up heads up display.\n");
    HU_Init();

    doom_print("ST_Init: Init status bar.\n");
    ST_Init();

    // check for a driver that wants intermission stats
#if 0 // [pd] Unsure how to test this
    p = M_CheckParm("-statcopy");
    if (p && p < myargc - 1)
    {
        // for statistics driver
        extern  void* statcopy;

        statcopy = (void*)atoll(myargv[p + 1]);
        doom_print("External statistics registered.\n");
    }
#endif

    // start the apropriate game based on parms
    p = M_CheckParm("-record");

    if (p && p < myargc - 1)
    {
        G_RecordDemo(myargv[p + 1]);
        autostart = true;
    }

    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)
    {
#if 0 // [pd] We don't support the cdrom flag
        if (M_CheckParm("-cdrom"))
        {
            //doom_sprintf(file, "c:\\doomdata\\"SAVEGAMENAME"%c.dsg", myargv[p + 1][0]);
        }
        else
#endif
        {
            //doom_sprintf(file, SAVEGAMENAME"%c.dsg", myargv[p + 1][0]);
            doom_strcpy(file, SAVEGAMENAME);
            doom_concat(file, doom_ctoa(myargv[p + 1][0]));
            doom_concat(file, ".dsg");
        }
        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 [ddps] Called by app

    if (demorecording)
        G_BeginRecording();

    if (M_CheckParm("-debugfile"))
    {
        char    filename[20];
        //doom_sprintf(filename, "debug%i.txt", consoleplayer);
        doom_strcpy(filename, "debug");
        doom_concat(filename, doom_itoa(consoleplayer, 10));
        doom_concat(filename, ".txt");
        //doom_print("debug output to: %s\n", filename);
        doom_print("debug output to: ");
        doom_print(filename);
        doom_print("\n");
        debugfile = doom_open(filename, "w");
    }

    I_InitGraphics();
}
#define NCMD_EXIT       0x80000000
#define NCMD_RETRANSMIT 0x40000000
#define NCMD_SETUP      0x20000000
#define NCMD_KILL       0x10000000 // kill game
#define NCMD_CHECKSUM   0x0fffffff

#define RESENDCOUNT     10
#define PL_DRONE        0x80 // bit flag in doomdata->player


doomcom_t* doomcom;
doomdata_t* netbuffer; // points inside doomcom

ticcmd_t localcmds[BACKUPTICS];

ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS];
int nettics[MAXNETNODES];
doom_boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
doom_boolean remoteresend[MAXNETNODES]; // set when local needs tics
int resendto[MAXNETNODES]; // set when remote needs tics
int resendcount[MAXNETNODES];

int nodeforplayer[MAXPLAYERS];

int maketic;
int lastnettic;
int skiptics;
int ticdup;
int maxsend; // BACKUPTICS/(2*ticdup)-1

doom_boolean reboundpacket;
doomdata_t reboundstore;

char exitmsg[80];
int gametime;
int frametics[4];
int frameon;
int frameskip[4];
int oldnettics;


extern int viewangleoffset;
extern doom_boolean advancedemo;


void D_ProcessEvents(void);
void G_BuildTiccmd(ticcmd_t* cmd);
void D_DoAdvanceDemo(void);


//
//
//
int NetbufferSize(void)
{
    return (int)(long long)&(((doomdata_t*)0)->cmds[netbuffer->numtics]);
}


//
// Checksum 
//
unsigned NetbufferChecksum(void)
{
    unsigned c;
    int i, l;

    c = 0x1234567;

    // FIXME -endianess?
// #ifdef NORMALUNIX
    return 0;                        // byte order problems
// #endif

    l = (NetbufferSize() - (int)(long long)&(((doomdata_t*)0)->retransmitfrom)) / 4;
    for (i = 0; i < l; i++)
        c += ((unsigned*)&netbuffer->retransmitfrom)[i] * (i + 1);

    return c & NCMD_CHECKSUM;
}


//
//
//
int ExpandTics(int low)
{
    int delta;

    delta = low - (maketic & 0xff);

    if (delta >= -64 && delta <= 64)
        return (maketic & ~0xff) + low;
    if (delta > 64)
        return (maketic & ~0xff) - 256 + low;
    if (delta < -64)
        return (maketic & ~0xff) + 256 + low;

    //I_Error("Error: ExpandTics: strange value %i at maketic %i", low, maketic);
    doom_strcpy(error_buf, "Error: ExpandTics: strange value ");
    doom_concat(error_buf, doom_itoa(low, 10));
    doom_concat(error_buf, " at maketic ");
    doom_concat(error_buf, doom_itoa(maketic, 10));
    I_Error(error_buf);
    return 0;
}


//
// HSendPacket
//
void HSendPacket(int node, int flags)
{
    netbuffer->checksum = NetbufferChecksum() | flags;

    if (!node)
    {
        reboundstore = *netbuffer;
        reboundpacket = true;
        return;
    }

    if (demoplayback)
        return;

    if (!netgame)
        I_Error("Error: Tried to transmit to another node");

    doomcom->command = CMD_SEND;
    doomcom->remotenode = node;
    doomcom->datalength = NetbufferSize();

    if (debugfile)
    {
        int                i;
        int                realretrans;
        if (netbuffer->checksum & NCMD_RETRANSMIT)
            realretrans = ExpandTics(netbuffer->retransmitfrom);
        else
            realretrans = -1;

        {
            //fprintf(debugfile, "send (%i + %i, R %i) [%i] ",
            //        ExpandTics(netbuffer->starttic),
            //        netbuffer->numtics, realretrans, doomcom->datalength);
            doom_fprint(debugfile, "send (");
            doom_fprint(debugfile, doom_itoa(ExpandTics(netbuffer->starttic), 10));
            doom_fprint(debugfile, " + ");
            doom_fprint(debugfile, doom_itoa(netbuffer->numtics, 10));
            doom_fprint(debugfile, ", R ");
            doom_fprint(debugfile, doom_itoa(realretrans, 10));
            doom_fprint(debugfile, ") [");
            doom_fprint(debugfile, doom_itoa(doomcom->datalength, 10));
            doom_fprint(debugfile, "] ");
        }

        for (i = 0; i < doomcom->datalength; i++)
        {
            //fprintf(debugfile, "%i ", ((byte*)netbuffer)[i]);
            doom_fprint(debugfile, doom_itoa(((byte*)netbuffer)[i], 10));
            doom_fprint(debugfile, " ");
        }

        doom_fprint(debugfile, "\n");
    }

    I_NetCmd();
}


//
// HGetPacket
// Returns false if no packet is waiting
//
doom_boolean HGetPacket(void)
{
    if (reboundpacket)
    {
        *netbuffer = reboundstore;
        doomcom->remotenode = 0;
        reboundpacket = false;
        return true;
    }

    if (!netgame)
        return false;

    if (demoplayback)
        return false;

    doomcom->command = CMD_GET;
    I_NetCmd();

    if (doomcom->remotenode == -1)
        return false;

    if (doomcom->datalength != NetbufferSize())
    {
        if (debugfile)
        {
            //fprintf(debugfile, "bad packet length %i\n", doomcom->datalength);
            doom_fprint(debugfile, "bad packet length ");
            doom_fprint(debugfile, doom_itoa(doomcom->datalength, 10));
            doom_fprint(debugfile, "\n");
        }
        return false;
    }

    if (NetbufferChecksum() != (netbuffer->checksum & NCMD_CHECKSUM))
    {
        if (debugfile)
        {
            doom_fprint(debugfile, "bad packet checksum\n");
        }
        return false;
    }

    if (debugfile)
    {
        int                realretrans;
        int        i;

        if (netbuffer->checksum & NCMD_SETUP)
        {
            doom_fprint(debugfile, "setup packet\n");
        }
        else
        {
            if (netbuffer->checksum & NCMD_RETRANSMIT)
                realretrans = ExpandTics(netbuffer->retransmitfrom);
            else
                realretrans = -1;

            {
                //fprintf(debugfile, "get %i = (%i + %i, R %i)[%i] ",
                //        doomcom->remotenode,
                //        ExpandTics(netbuffer->starttic),
                //        netbuffer->numtics, realretrans, doomcom->datalength);
                doom_fprint(debugfile, "get ");
                doom_fprint(debugfile, doom_itoa(doomcom->remotenode, 10));
                doom_fprint(debugfile, " = (");
                doom_fprint(debugfile, doom_itoa(ExpandTics(netbuffer->starttic), 10));
                doom_fprint(debugfile, " + ");
                doom_fprint(debugfile, doom_itoa(netbuffer->numtics, 10));
                doom_fprint(debugfile, ", R ");
                doom_fprint(debugfile, doom_itoa(realretrans, 10));
                doom_fprint(debugfile, ")[");
                doom_fprint(debugfile, doom_itoa(doomcom->datalength, 10));
                doom_fprint(debugfile, "] ");
            }

            for (i = 0; i < doomcom->datalength; i++)
            {
                //fprintf(debugfile, "%i ", ((byte*)netbuffer)[i]);
                doom_fprint(debugfile, doom_itoa(((byte*)netbuffer)[i], 10));
                doom_fprint(debugfile, " ");
            }
            doom_fprint(debugfile, "\n");
        }
    }
    return true;
}


//
// GetPackets
//
void GetPackets(void)
{
    int netconsole;
    int netnode;
    ticcmd_t* src, *dest;
    int realend;
    int realstart;

    while (HGetPacket())
    {
        if (netbuffer->checksum & NCMD_SETUP)
            continue;                // extra setup packet

        netconsole = netbuffer->player & ~PL_DRONE;
        netnode = doomcom->remotenode;

        // to save bytes, only the low byte of tic numbers are sent
        // Figure out what the rest of the bytes are
        realstart = ExpandTics(netbuffer->starttic);
        realend = (realstart + netbuffer->numtics);

        // check for exiting the game
        if (netbuffer->checksum & NCMD_EXIT)
        {
            if (!nodeingame[netnode])
                continue;
            nodeingame[netnode] = false;
            playeringame[netconsole] = false;
            doom_strcpy(exitmsg, "Player 1 left the game");
            exitmsg[7] += netconsole;
            players[consoleplayer].message = exitmsg;
            if (demorecording)
                G_CheckDemoStatus();
            continue;
        }

        // check for a remote game kill
        if (netbuffer->checksum & NCMD_KILL)
            I_Error("Error: Killed by network driver");

        nodeforplayer[netconsole] = netnode;

        // check for retransmit request
        if (resendcount[netnode] <= 0
            && (netbuffer->checksum & NCMD_RETRANSMIT))
        {
            resendto[netnode] = ExpandTics(netbuffer->retransmitfrom);
            if (debugfile)
            {
                //fprintf(debugfile, "retransmit from %i\n", resendto[netnode]);
                doom_fprint(debugfile, "retransmit from ");
                doom_fprint(debugfile, doom_itoa(resendto[netnode], 10));
                doom_fprint(debugfile, "\n");
            }
            resendcount[netnode] = RESENDCOUNT;
        }
        else
            resendcount[netnode]--;

        // check for out of order / duplicated packet                
        if (realend == nettics[netnode])
            continue;

        if (realend < nettics[netnode])
        {
            if (debugfile)
            {
                //fprintf(debugfile,
                //        "out of order packet (%i + %i)\n",
                //        realstart, netbuffer->numtics);
                doom_fprint(debugfile, "out of order packet (");
                doom_fprint(debugfile, doom_itoa(realstart, 10));
                doom_fprint(debugfile, " + ");
                doom_fprint(debugfile, doom_itoa(netbuffer->numtics, 10));
                doom_fprint(debugfile, ")\n");
            }
            continue;
        }

        // check for a missed packet
        if (realstart > nettics[netnode])
        {
            // stop processing until the other system resends the missed tics
            if (debugfile)
            {
                //fprintf(debugfile,
                //        "missed tics from %i (%i - %i)\n",
                //        netnode, realstart, nettics[netnode]);
                doom_fprint(debugfile, "missed tics from ");
                doom_fprint(debugfile, doom_itoa(netnode, 10));
                doom_fprint(debugfile, " (");
                doom_fprint(debugfile, doom_itoa(realstart, 10));
                doom_fprint(debugfile, " - ");
                doom_fprint(debugfile, doom_itoa(nettics[netnode], 10));
                doom_fprint(debugfile, ")\n");
            }
            remoteresend[netnode] = true;
            continue;
        }

        // update command store from the packet
        {
            int                start;

            remoteresend[netnode] = false;

            start = nettics[netnode] - realstart;
            src = &netbuffer->cmds[start];

            while (nettics[netnode] < realend)
            {
                dest = &netcmds[netconsole][nettics[netnode] % BACKUPTICS];
                nettics[netnode]++;
                *dest = *src;
                src++;
            }
        }
    }
}


//
// NetUpdate
// Builds ticcmds for console player,
// sends out a packet
//
void NetUpdate(void)
{
    int nowtime;
    int newtics;
    int i, j;
    int realstart;
    int gameticdiv;

    // check time
    nowtime = I_GetTime() / ticdup;
    newtics = nowtime - gametime;
    gametime = nowtime;

    if (newtics <= 0) // nothing new to update
        goto listen;

    if (skiptics <= newtics)
    {
        newtics -= skiptics;
        skiptics = 0;
    }
    else
    {
        skiptics -= newtics;
        newtics = 0;
    }

    netbuffer->player = consoleplayer;

    // build new ticcmds for console player
    gameticdiv = gametic / ticdup;
    for (i = 0; i < newtics; i++)
    {
        I_StartTic();
        D_ProcessEvents();
        if (maketic - gameticdiv >= BACKUPTICS / 2 - 1)
            break;          // can't hold any more

        //doom_print ("mk:%i ",maketic);
        G_BuildTiccmd(&localcmds[maketic % BACKUPTICS]);
        maketic++;
    }


    if (singletics)
        return;         // singletic update is syncronous

    // send the packet to the other nodes
    for (i = 0; i < doomcom->numnodes; i++)
        if (nodeingame[i])
        {
            netbuffer->starttic = realstart = resendto[i];
            netbuffer->numtics = maketic - realstart;
            if (netbuffer->numtics > BACKUPTICS)
                I_Error("Error: NetUpdate: netbuffer->numtics > BACKUPTICS");

            resendto[i] = maketic - doomcom->extratics;

            for (j = 0; j < netbuffer->numtics; j++)
                netbuffer->cmds[j] =
                localcmds[(realstart + j) % BACKUPTICS];

            if (remoteresend[i])
            {
                netbuffer->retransmitfrom = nettics[i];
                HSendPacket(i, NCMD_RETRANSMIT);
            }
            else
            {
                netbuffer->retransmitfrom = 0;
                HSendPacket(i, 0);
            }
        }

    // listen for other packets
listen:
    GetPackets();
}


//
// CheckAbort
//
void CheckAbort(void)
{
    event_t* ev;
    int stoptic;

    stoptic = I_GetTime() + 2;
    while (I_GetTime() < stoptic)
        I_StartTic();

    I_StartTic();
    for (; eventtail != eventhead
         ; )
    {
        ev = &events[eventtail];
        if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE)
            I_Error("Error: Network game synchronization aborted.");
    }

    eventtail++;
    eventtail = (eventtail) & (MAXEVENTS - 1);
}


//
// D_ArbitrateNetStart
//
void D_ArbitrateNetStart(void)
{
    int i;
    doom_boolean gotinfo[MAXNETNODES];

    autostart = true;
    doom_memset(gotinfo, 0, sizeof(gotinfo));

    if (doomcom->consoleplayer)
    {
        // listen for setup info from key player
        doom_print("listening for network start info...\n");
        while (1)
        {
            CheckAbort();
            if (!HGetPacket())
                continue;
            if (netbuffer->checksum & NCMD_SETUP)
            {
                if (netbuffer->player != VERSION)
                    I_Error("Error: Different DOOM versions cannot play a net game!");
                startskill = netbuffer->retransmitfrom & 15;
                deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6;
                nomonsters = (netbuffer->retransmitfrom & 0x20) > 0;
                respawnparm = (netbuffer->retransmitfrom & 0x10) > 0;
                startmap = netbuffer->starttic & 0x3f;
                startepisode = netbuffer->starttic >> 6;
                return;
            }
        }
    }
    else
    {
        // key player, send the setup info
        doom_print("sending network start info...\n");
        do
        {
            CheckAbort();
            for (i = 0; i < doomcom->numnodes; i++)
            {
                netbuffer->retransmitfrom = startskill;
                if (deathmatch)
                    netbuffer->retransmitfrom |= (deathmatch << 6);
                if (nomonsters)
                    netbuffer->retransmitfrom |= 0x20;
                if (respawnparm)
                    netbuffer->retransmitfrom |= 0x10;
                netbuffer->starttic = startepisode * 64 + startmap;
                netbuffer->player = VERSION;
                netbuffer->numtics = 0;
                HSendPacket(i, NCMD_SETUP);
            }

#if 1
            for (i = 10; i && HGetPacket(); --i)
            {
                if ((netbuffer->player & 0x7f) < MAXNETNODES)
                    gotinfo[netbuffer->player & 0x7f] = true;
            }
#else
            while (HGetPacket())
            {
                gotinfo[netbuffer->player & 0x7f] = true;
            }
#endif

            for (i = 1; i < doomcom->numnodes; i++)
                if (!gotinfo[i])
                    break;
        } while (i < doomcom->numnodes);
    }
}


//
// D_CheckNetGame
// Works out player numbers among the net participants
//
void D_CheckNetGame(void)
{
    int i;

    for (i = 0; i < MAXNETNODES; i++)
    {
        nodeingame[i] = false;
        nettics[i] = 0;
        remoteresend[i] = false;        // set when local needs tics
        resendto[i] = 0;                // which tic to start sending
    }

    // I_InitNetwork sets doomcom and netgame
    I_InitNetwork();
    if (doomcom->id != DOOMCOM_ID)
        I_Error("Error: Doomcom buffer invalid!");

    netbuffer = &doomcom->data;
    consoleplayer = displayplayer = doomcom->consoleplayer;
    if (netgame)
        D_ArbitrateNetStart();

    //doom_print("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n",
    //       startskill, deathmatch, startmap, startepisode);
    doom_print("startskill ");
    doom_print(doom_itoa(startskill, 10));
    doom_print("  deathmatch: ");
    doom_print(doom_itoa(deathmatch, 10));
    doom_print("  startmap: ");
    doom_print(doom_itoa(startmap, 10));
    doom_print("  startepisode: ");
    doom_print(doom_itoa(startepisode, 10));
    doom_print("\n");

    // read values out of doomcom
    ticdup = doomcom->ticdup;
    maxsend = BACKUPTICS / (2 * ticdup) - 1;
    if (maxsend < 1)
        maxsend = 1;

    for (i = 0; i < doomcom->numplayers; i++)
        playeringame[i] = true;
    for (i = 0; i < doomcom->numnodes; i++)
        nodeingame[i] = true;

    //doom_print("player %i of %i (%i nodes)\n",
    //       consoleplayer + 1, doomcom->numplayers, doomcom->numnodes);
    doom_print("player ");
    doom_print(doom_itoa(consoleplayer + 1, 10));
    doom_print(" of ");
    doom_print(doom_itoa(doomcom->numplayers, 10));
    doom_print(" (");
    doom_print(doom_itoa(doomcom->numnodes, 10));
    doom_print(" nodes)\n");
}


//
// D_QuitNetGame
// Called before quitting to leave a net game
// without hanging the other players
//
void D_QuitNetGame(void)
{
    int i, j;

    if (debugfile)
        doom_close(debugfile);

    if (!netgame || !usergame || consoleplayer == -1 || demoplayback)
        return;

    // send a bunch of packets for security
    netbuffer->player = consoleplayer;
    netbuffer->numtics = 0;
    for (i = 0; i < 4; i++)
    {
        for (j = 1; j < doomcom->numnodes; j++)
            if (nodeingame[j])
                HSendPacket(j, NCMD_EXIT);
        I_WaitVBL(1);
    }
}


//
// TryRunTics
//
void TryRunTics(void)
{
    int                i;
    int                lowtic;
    int                entertic;
    static int         oldentertics;
    int                realtics;
    int                availabletics;
    int                counts;
    int                numplaying;

    // get real tics                
    entertic = I_GetTime() / ticdup;
    realtics = entertic - oldentertics;
    oldentertics = entertic;

    // get available tics
    NetUpdate();

    lowtic = DOOM_MAXINT;
    numplaying = 0;
    for (i = 0; i < doomcom->numnodes; i++)
    {
        if (nodeingame[i])
        {
            numplaying++;
            if (nettics[i] < lowtic)
                lowtic = nettics[i];
        }
    }
    availabletics = lowtic - gametic / ticdup;

    // decide how many tics to run
    if (realtics < availabletics - 1)
        counts = realtics + 1;
    else if (realtics < availabletics)
        counts = realtics;
    else
        counts = availabletics;

    if (counts < 1)
        counts = 1;

    frameon++;

    if (debugfile)
    {
        //fprintf(debugfile,
        //        "=======real: %i  avail: %i  game: %i\n",
        //        realtics, availabletics, counts);
        doom_fprint(debugfile, "=======real: ");
        doom_fprint(debugfile, doom_itoa(realtics, 10));
        doom_fprint(debugfile, "  avail: ");
        doom_fprint(debugfile, doom_itoa(availabletics, 10));
        doom_fprint(debugfile, "  game: ");
        doom_fprint(debugfile, doom_itoa(counts, 10));
        doom_fprint(debugfile, "\n");
    }

    if (!demoplayback)
    {
        // ideally nettics[0] should be 1 - 3 tics above lowtic
        // if we are consistantly slower, speed up time
        for (i = 0; i < MAXPLAYERS; i++)
            if (playeringame[i])
                break;
        if (consoleplayer == i)
        {
            // the key player does not adapt
        }
        else
        {
            if (nettics[0] <= nettics[nodeforplayer[i]])
            {
                gametime--;
                // doom_print ("-");
            }
            frameskip[frameon & 3] = (oldnettics > nettics[nodeforplayer[i]]);
            oldnettics = nettics[0];
            if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
            {
                skiptics = 1;
                // doom_print ("+");
            }
        }
    }// demoplayback

    // wait for new tics if needed
    while (lowtic < gametic / ticdup + counts)
    {
        NetUpdate();
        lowtic = DOOM_MAXINT;

        for (i = 0; i < doomcom->numnodes; i++)
            if (nodeingame[i] && nettics[i] < lowtic)
                lowtic = nettics[i];

        if (lowtic < gametic / ticdup)
            I_Error("Error: TryRunTics: lowtic < gametic");

        // don't stay in here forever -- give the menu a chance to work
        if (I_GetTime() / ticdup - entertic >= 20)
        {
            M_Ticker();
            return;
        }
    }

    // run the count * ticdup dics
    while (counts--)
    {
        for (i = 0; i < ticdup; i++)
        {
            if (gametic / ticdup > lowtic)
                I_Error("Error: gametic>lowtic");
            if (advancedemo)
                D_DoAdvanceDemo();
            M_Ticker();
            G_Ticker();
            gametic++;

            // modify command for duplicated tics
            if (i != ticdup - 1)
            {
                ticcmd_t* cmd;
                int                        buf;
                int                        j;

                buf = (gametic / ticdup) % BACKUPTICS;
                for (j = 0; j < MAXPLAYERS; j++)
                {
                    cmd = &netcmds[j][buf];
                    cmd->chatchar = 0;
                    if (cmd->buttons & BT_SPECIAL)
                        cmd->buttons = 0;
                }
            }
        }
        NetUpdate(); // check for new console commands
    }
}
GameMode_t gamemode = indetermined;
GameMission_t gamemission = doom;

// Language.
Language_t language = english;

// Set if homebrew PWAD stuff has been added.
doom_boolean modifiedgame;
char* endmsg[NUM_QUITMESSAGES + 1] =
{
    // DOOM1
    QUITMSG,
    "please don't leave, there's more\ndemons to toast!",
    "let's beat it -- this is turning\ninto a bloodbath!",
    "i wouldn't leave if i were you.\ndos is much worse.",
    "you're trying to say you like dos\nbetter than me, right?",
    "don't leave yet -- there's a\ndemon around that corner!",
    "ya know, next time you come in here\ni'm gonna toast ya.",
    "go ahead and leave. see if i care.",

    // QuitDOOM II messages
    "you want to quit?\nthen, thou hast lost an eighth!",
    "don't go now, there's a \ndimensional shambler waiting\nat the dos prompt!",
    "get outta here and go back\nto your boring programs.",
    "if i were your boss, i'd \n deathmatch ya in a minute!",
    "look, bud. you leave now\nand you forfeit your body count!",
    "just leave. when you come\nback, i'll be waiting with a bat.",
    "you're lucky i don't smack\nyou for thinking about leaving.",

    // FinalDOOM?
    "fuck you, pussy!\nget the fuck out!",
    "you quit and i'll jizz\nin your cystholes!",
    "if you leave, i'll make\nthe lord drink my jizz.",
    "hey, ron! can we say\n'fuck' in the game?",
    "i'd leave: this is just\nmore monsters and levels.\nwhat a load.",
    "suck it down, asshole!\nyou're a fucking wimp!",
    "don't quit now! we're \nstill spending your money!",

    // Internal debug. Different style, too.
    "THIS IS NO MESSAGE!\nPage intentionally left blank."
};
#define TEXTSPEED 3
#define TEXTWAIT 250


typedef struct
{
    char* name;
    mobjtype_t type;
} castinfo_t;


// Stage of animation:
//  0 = text, 1 = art screen, 2 = character cast
int finalestage;

int finalecount;

char* e1text = E1TEXT;
char* e2text = E2TEXT;
char* e3text = E3TEXT;
char* e4text = E4TEXT;

char* c1text = C1TEXT;
char* c2text = C2TEXT;
char* c3text = C3TEXT;
char* c4text = C4TEXT;
char* c5text = C5TEXT;
char* c6text = C6TEXT;

char* p1text = P1TEXT;
char* p2text = P2TEXT;
char* p3text = P3TEXT;
char* p4text = P4TEXT;
char* p5text = P5TEXT;
char* p6text = P6TEXT;

char* t1text = T1TEXT;
char* t2text = T2TEXT;
char* t3text = T3TEXT;
char* t4text = T4TEXT;
char* t5text = T5TEXT;
char* t6text = T6TEXT;

char* finaletext;
char* finaleflat;

castinfo_t castorder[] = {
    {CC_ZOMBIE, MT_POSSESSED},
    {CC_SHOTGUN, MT_SHOTGUY},
    {CC_HEAVY, MT_CHAINGUY},
    {CC_IMP, MT_TROOP},
    {CC_DEMON, MT_SERGEANT},
    {CC_LOST, MT_SKULL},
    {CC_CACO, MT_HEAD},
    {CC_HELL, MT_KNIGHT},
    {CC_BARON, MT_BRUISER},
    {CC_ARACH, MT_BABY},
    {CC_PAIN, MT_PAIN},
    {CC_REVEN, MT_UNDEAD},
    {CC_MANCU, MT_FATSO},
    {CC_ARCH, MT_VILE},
    {CC_SPIDER, MT_SPIDER},
    {CC_CYBER, MT_CYBORG},
    {CC_HERO, MT_PLAYER},

    {0,0}
};

int castnum;
int casttics;
state_t* caststate;
doom_boolean castdeath;
int castframes;
int castonmelee;
doom_boolean castattacking;


//
// F_StartCast
//
extern gamestate_t wipegamestate;
extern patch_t* hu_font[HU_FONTSIZE];


void         F_StartCast(void);
void         F_CastTicker(void);
doom_boolean F_CastResponder(event_t* ev);
void         F_CastDrawer(void);
void         V_DrawPatchFlipped(int x, int y, int scrn, patch_t* patch);


//
// F_StartFinale
//
void F_StartFinale(void)
{
    gameaction = ga_nothing;
    gamestate = GS_FINALE;
    viewactive = false;
    automapactive = false;

    // Okay - IWAD dependend stuff.
    // This has been changed severly, and
    //  some stuff might have changed in the process.
    switch (gamemode)
    {

        // DOOM 1 - E1, E3 or E4, but each nine missions
        case shareware:
        case registered:
        case retail:
        {
            S_ChangeMusic(mus_victor, true);

            switch (gameepisode)
            {
                case 1:
                    finaleflat = "FLOOR4_8";
                    finaletext = e1text;
                    break;
                case 2:
                    finaleflat = "SFLR6_1";
                    finaletext = e2text;
                    break;
                case 3:
                    finaleflat = "MFLR8_4";
                    finaletext = e3text;
                    break;
                case 4:
                    finaleflat = "MFLR8_3";
                    finaletext = e4text;
                    break;
                default:
                    // Ouch.
                    break;
            }
            break;
        }

        // DOOM II and missions packs with E1, M34
        case commercial:
        {
            S_ChangeMusic(mus_read_m, true);

            switch (gamemap)
            {
                case 6:
                    finaleflat = "SLIME16";
                    finaletext = c1text;
                    break;
                case 11:
                    finaleflat = "RROCK14";
                    finaletext = c2text;
                    break;
                case 20:
                    finaleflat = "RROCK07";
                    finaletext = c3text;
                    break;
                case 30:
                    finaleflat = "RROCK17";
                    finaletext = c4text;
                    break;
                case 15:
                    finaleflat = "RROCK13";
                    finaletext = c5text;
                    break;
                case 31:
                    finaleflat = "RROCK19";
                    finaletext = c6text;
                    break;
                default:
                    // Ouch.
                    break;
            }
            break;
        }


        // Indeterminate.
        default:
            S_ChangeMusic(mus_read_m, true);
            finaleflat = "F_SKY1"; // Not used anywhere else.
            finaletext = c1text;  // FIXME - other text, music?
            break;
    }

    finalestage = 0;
    finalecount = 0;
}


doom_boolean F_Responder(event_t* event)
{
    if (finalestage == 2)
        return F_CastResponder(event);

    return false;
}


//
// F_Ticker
//
void F_Ticker(void)
{
    int i;

    // check for skipping
    if ((gamemode == commercial)
        && (finalecount > 50))
    {
        // go on to the next level
        for (i = 0; i < MAXPLAYERS; i++)
            if (players[i].cmd.buttons)
                break;

        if (i < MAXPLAYERS)
        {
            if (gamemap == 30)
                F_StartCast();
            else
                gameaction = ga_worlddone;
        }
    }

    // advance animation
    finalecount++;

    if (finalestage == 2)
    {
        F_CastTicker();
        return;
    }

    if (gamemode == commercial)
        return;

    if (!finalestage && finalecount > doom_strlen(finaletext) * TEXTSPEED + TEXTWAIT)
    {
        finalecount = 0;
        finalestage = 1;
        wipegamestate = -1;                // force a wipe
        if (gameepisode == 3)
            S_StartMusic(mus_bunny);
    }
}


//
// F_TextWrite
//
void F_TextWrite(void)
{
    byte* src;
    byte* dest;

    int x, y, w;
    int count;
    char* ch;
    int c;
    int cx;
    int cy;

    // erase the entire screen to a tiled background
    src = W_CacheLumpName(finaleflat, PU_CACHE);
    dest = screens[0];

    for (y = 0; y < SCREENHEIGHT; y++)
    {
        for (x = 0; x < SCREENWIDTH / 64; x++)
        {
            doom_memcpy(dest, src + ((y & 63) << 6), 64);
            dest += 64;
        }
        if (SCREENWIDTH & 63)
        {
            doom_memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63);
            dest += (SCREENWIDTH & 63);
        }
    }

    V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    // draw some of the text onto the screen
    cx = 10;
    cy = 10;
    ch = finaletext;

    count = (finalecount - 10) / TEXTSPEED;
    if (count < 0)
        count = 0;
    for (; count; count--)
    {
        c = *ch++;
        if (!c)
            break;
        if (c == '\n')
        {
            cx = 10;
            cy += 11;
            continue;
        }

        c = doom_toupper(c) - HU_FONTSTART;
        if (c < 0 || c> HU_FONTSIZE)
        {
            cx += 4;
            continue;
        }

        w = SHORT(hu_font[c]->width);
        if (cx + w > SCREENWIDTH)
            break;
        V_DrawPatch(cx, cy, 0, hu_font[c]);
        cx += w;
    }
}


//
// Final DOOM 2 animation
// Casting by id Software.
//   in order of appearance
//
void F_StartCast(void)
{
    wipegamestate = -1; // force a screen wipe
    castnum = 0;
    caststate = &states[mobjinfo[castorder[castnum].type].seestate];
    casttics = caststate->tics;
    castdeath = false;
    finalestage = 2;
    castframes = 0;
    castonmelee = 0;
    castattacking = false;
    S_ChangeMusic(mus_evil, true);
}


//
// F_CastTicker
//
void F_CastTicker(void)
{
    int st;
    int sfx;

    if (--casttics > 0)
        return; // not time to change state yet

    if (caststate->tics == -1 || caststate->nextstate == S_NULL)
    {
        // switch from deathstate to next monster
        castnum++;
        castdeath = false;
        if (castorder[castnum].name == 0)
            castnum = 0;
        if (mobjinfo[castorder[castnum].type].seesound)
            S_StartSound(0, mobjinfo[castorder[castnum].type].seesound);
        caststate = &states[mobjinfo[castorder[castnum].type].seestate];
        castframes = 0;
    }
    else
    {
        // just advance to next state in animation
        if (caststate == &states[S_PLAY_ATK1])
            goto stopattack;        // Oh, gross hack!
        st = caststate->nextstate;
        caststate = &states[st];
        castframes++;

        // sound hacks....
        switch (st)
        {
            case S_PLAY_ATK1:        sfx = sfx_dshtgn; break;
            case S_POSS_ATK2:        sfx = sfx_pistol; break;
            case S_SPOS_ATK2:        sfx = sfx_shotgn; break;
            case S_VILE_ATK2:        sfx = sfx_vilatk; break;
            case S_SKEL_FIST2:        sfx = sfx_skeswg; break;
            case S_SKEL_FIST4:        sfx = sfx_skepch; break;
            case S_SKEL_MISS2:        sfx = sfx_skeatk; break;
            case S_FATT_ATK8:
            case S_FATT_ATK5:
            case S_FATT_ATK2:        sfx = sfx_firsht; break;
            case S_CPOS_ATK2:
            case S_CPOS_ATK3:
            case S_CPOS_ATK4:        sfx = sfx_shotgn; break;
            case S_TROO_ATK3:        sfx = sfx_claw; break;
            case S_SARG_ATK2:        sfx = sfx_sgtatk; break;
            case S_BOSS_ATK2:
            case S_BOS2_ATK2:
            case S_HEAD_ATK2:        sfx = sfx_firsht; break;
            case S_SKULL_ATK2:        sfx = sfx_sklatk; break;
            case S_SPID_ATK2:
            case S_SPID_ATK3:        sfx = sfx_shotgn; break;
            case S_BSPI_ATK2:        sfx = sfx_plasma; break;
            case S_CYBER_ATK2:
            case S_CYBER_ATK4:
            case S_CYBER_ATK6:        sfx = sfx_rlaunc; break;
            case S_PAIN_ATK3:        sfx = sfx_sklatk; break;
            default: sfx = 0; break;
        }

        if (sfx)
            S_StartSound(0, sfx);
    }

    if (castframes == 12)
    {
        // go into attack frame
        castattacking = true;
        if (castonmelee)
            caststate = &states[mobjinfo[castorder[castnum].type].meleestate];
        else
            caststate = &states[mobjinfo[castorder[castnum].type].missilestate];
        castonmelee ^= 1;
        if (caststate == &states[S_NULL])
        {
            if (castonmelee)
                caststate =
                &states[mobjinfo[castorder[castnum].type].meleestate];
            else
                caststate =
                &states[mobjinfo[castorder[castnum].type].missilestate];
        }
    }

    if (castattacking)
    {
        if (castframes == 24
            || caststate == &states[mobjinfo[castorder[castnum].type].seestate])
        {
stopattack:
            castattacking = false;
            castframes = 0;
            caststate = &states[mobjinfo[castorder[castnum].type].seestate];
        }
    }

    casttics = caststate->tics;
    if (casttics == -1)
        casttics = 15;
}


//
// F_CastResponder
//
doom_boolean F_CastResponder(event_t* ev)
{
    if (ev->type != ev_keydown)
        return false;

    if (castdeath)
        return true; // already in dying frames

    // go into death frame
    castdeath = true;
    caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
    casttics = caststate->tics;
    castframes = 0;
    castattacking = false;
    if (mobjinfo[castorder[castnum].type].deathsound)
        S_StartSound(0, mobjinfo[castorder[castnum].type].deathsound);

    return true;
}


void F_CastPrint(char* text)
{
    char* ch;
    int c;
    int cx;
    int w;
    int width;

    // find width
    ch = text;
    width = 0;

    while (ch)
    {
        c = *ch++;
        if (!c)
            break;
        c = doom_toupper(c) - HU_FONTSTART;
        if (c < 0 || c> HU_FONTSIZE)
        {
            width += 4;
            continue;
        }

        w = SHORT(hu_font[c]->width);
        width += w;
    }

    // draw it
    cx = 160 - width / 2;
    ch = text;
    while (ch)
    {
        c = *ch++;
        if (!c)
            break;
        c = doom_toupper(c) - HU_FONTSTART;
        if (c < 0 || c> HU_FONTSIZE)
        {
            cx += 4;
            continue;
        }

        w = SHORT(hu_font[c]->width);
        V_DrawPatch(cx, 180, 0, hu_font[c]);
        cx += w;
    }
}


//
// F_CastDrawer
//
void F_CastDrawer(void)
{
    spritedef_t* sprdef;
    spriteframe_t* sprframe;
    int lump;
    doom_boolean flip;
    patch_t* patch;

    // erase the entire screen to a background
    V_DrawPatch(0, 0, 0, W_CacheLumpName("BOSSBACK", PU_CACHE));

    F_CastPrint(castorder[castnum].name);

    // draw the current frame in the middle of the screen
    sprdef = &sprites[caststate->sprite];
    sprframe = &sprdef->spriteframes[caststate->frame & FF_FRAMEMASK];
    lump = sprframe->lump[0];
    flip = (doom_boolean)sprframe->flip[0];

    patch = W_CacheLumpNum(lump + firstspritelump, PU_CACHE);
    if (flip)
        V_DrawPatchFlipped(160, 170, 0, patch);
    else
        V_DrawPatch(160, 170, 0, patch);
}


//
// F_DrawPatchCol
//
void F_DrawPatchCol(int x, patch_t* patch, int col)
{
    column_t* column;
    byte* source;
    byte* dest;
    byte* desttop;
    int count;

    column = (column_t*)((byte*)patch + LONG(patch->columnofs[col]));
    desttop = screens[0] + x;

    // step through the posts in a column
    while (column->topdelta != 0xff)
    {
        source = (byte*)column + 3;
        dest = desttop + column->topdelta * SCREENWIDTH;
        count = column->length;

        while (count--)
        {
            *dest = *source++;
            dest += SCREENWIDTH;
        }
        column = (column_t*)((byte*)column + column->length + 4);
    }
}


//
// F_BunnyScroll
//
void F_BunnyScroll(void)
{
    int scrolled;
    int x;
    patch_t* p1;
    patch_t* p2;
    char name[10];
    int stage;
    static int laststage;

    p1 = W_CacheLumpName("PFUB2", PU_LEVEL);
    p2 = W_CacheLumpName("PFUB1", PU_LEVEL);

    V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    scrolled = 320 - (finalecount - 230) / 2;
    if (scrolled > 320)
        scrolled = 320;
    if (scrolled < 0)
        scrolled = 0;

    for (x = 0; x < SCREENWIDTH; x++)
    {
        if (x + scrolled < 320)
            F_DrawPatchCol(x, p1, x + scrolled);
        else
            F_DrawPatchCol(x, p2, x + scrolled - 320);
    }

    if (finalecount < 1130)
        return;
    if (finalecount < 1180)
    {
        V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
                    (SCREENHEIGHT - 8 * 8) / 2, 0, W_CacheLumpName("END0", PU_CACHE));
        laststage = 0;
        return;
    }

    stage = (finalecount - 1180) / 5;
    if (stage > 6)
        stage = 6;
    if (stage > laststage)
    {
        S_StartSound(0, sfx_pistol);
        laststage = stage;
    }

    //doom_sprintf(name, "END%i", stage);
    doom_strcpy(name, "END");
    doom_concat(name, doom_itoa(stage, 10));
    V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, (SCREENHEIGHT - 8 * 8) / 2, 0, W_CacheLumpName(name, PU_CACHE));
}


//
// F_Drawer
//
void F_Drawer(void)
{
    if (finalestage == 2)
    {
        F_CastDrawer();
        return;
    }

    if (!finalestage)
        F_TextWrite();
    else
    {
        switch (gameepisode)
        {
            case 1:
                if (gamemode == retail)
                    V_DrawPatch(0, 0, 0,
                                W_CacheLumpName("CREDIT", PU_CACHE));
                else
                    V_DrawPatch(0, 0, 0,
                                W_CacheLumpName("HELP2", PU_CACHE));
                break;
            case 2:
                V_DrawPatch(0, 0, 0,
                            W_CacheLumpName("VICTORY2", PU_CACHE));
                break;
            case 3:
                F_BunnyScroll();
                break;
            case 4:
                V_DrawPatch(0, 0, 0,
                            W_CacheLumpName("ENDPIC", PU_CACHE));
                break;
        }
    }
}
static doom_boolean go = 0;

static byte* wipe_scr_start;
static byte* wipe_scr_end;
static byte* wipe_scr;

static int* y;


void wipe_shittyColMajorXform(short* array, int width, int height)
{
    int x;
    int y;
    short* dest;

    dest = (short*)Z_Malloc(width * height * sizeof(short), PU_STATIC, 0);

    for (y = 0; y < height; y++)
        for (x = 0; x < width; x++)
            dest[x * height + y] = array[y * width + x];

    doom_memcpy(array, dest, width * height * 2);

    Z_Free(dest);
}


int wipe_initColorXForm(int width, int height, int ticks)
{
    doom_memcpy(wipe_scr, wipe_scr_start, width * height);
    return 0;
}


int wipe_doColorXForm(int width, int height, int ticks)
{
    doom_boolean changed;
    byte* w;
    byte* e;
    int newval;

    changed = false;
    w = wipe_scr;
    e = wipe_scr_end;

    while (w != wipe_scr + width * height)
    {
        if (*w != *e)
        {
            if (*w > *e)
            {
                newval = *w - ticks;
                if (newval < *e)
                    *w = *e;
                else
                    *w = newval;
                changed = true;
            }
            else if (*w < *e)
            {
                newval = *w + ticks;
                if (newval > *e)
                    *w = *e;
                else
                    *w = newval;
                changed = true;
            }
        }
        w++;
        e++;
    }

    return !changed;
}


int wipe_exitColorXForm(int width, int height, int ticks)
{
    return 0;
}


int wipe_initMelt(int width, int height, int ticks)
{
    int i, r;

    // copy start screen to main screen
    doom_memcpy(wipe_scr, wipe_scr_start, width * height);

    // makes this wipe faster (in theory)
    // to have stuff in column-major format
    wipe_shittyColMajorXform((short*)wipe_scr_start, width / 2, height);
    wipe_shittyColMajorXform((short*)wipe_scr_end, width / 2, height);

    // setup initial column positions
    // (y<0 => not ready to scroll yet)
    y = (int*)Z_Malloc(width * sizeof(int), PU_STATIC, 0);
    y[0] = -(M_Random() % 16);
    for (i = 1; i < width; i++)
    {
        r = (M_Random() % 3) - 1;
        y[i] = y[i - 1] + r;
        if (y[i] > 0) y[i] = 0;
        else if (y[i] == -16) y[i] = -15;
    }

    return 0;
}


int wipe_doMelt(int width, int height, int ticks)
{
    int i;
    int j;
    int dy;
    int idx;

    short* s;
    short* d;
    doom_boolean done = true;

    width /= 2;

    while (ticks--)
    {
        for (i = 0; i < width; i++)
        {
            if (y[i] < 0)
            {
                y[i]++; done = false;
            }
            else if (y[i] < height)
            {
                dy = (y[i] < 16) ? y[i] + 1 : 8;
                if (y[i] + dy >= height) dy = height - y[i];
                s = &((short*)wipe_scr_end)[i * height + y[i]];
                d = &((short*)wipe_scr)[y[i] * width + i];
                idx = 0;
                for (j = dy; j; j--)
                {
                    d[idx] = *(s++);
                    idx += width;
                }
                y[i] += dy;
                s = &((short*)wipe_scr_start)[i * height];
                d = &((short*)wipe_scr)[y[i] * width + i];
                idx = 0;
                for (j = height - y[i]; j; j--)
                {
                    d[idx] = *(s++);
                    idx += width;
                }
                done = false;
            }
        }
    }

    return done;
}


int wipe_exitMelt(int width, int height, int ticks)
{
    Z_Free(y);
    return 0;
}


int wipe_StartScreen(int x, int y, int width, int height)
{
    wipe_scr_start = screens[2];
    I_ReadScreen(wipe_scr_start);
    return 0;
}


int wipe_EndScreen(int x, int y, int width, int height)
{
    wipe_scr_end = screens[3];
    I_ReadScreen(wipe_scr_end);
    V_DrawBlock(x, y, 0, width, height, wipe_scr_start); // restore start scr.
    return 0;
}


int wipe_ScreenWipe(int wipeno, int x, int y, int width, int height, int ticks)
{
    int rc;
    static int (*wipes[])(int, int, int) =
    {
        wipe_initColorXForm, wipe_doColorXForm, wipe_exitColorXForm,
        wipe_initMelt, wipe_doMelt, wipe_exitMelt
    };

    void V_MarkRect(int, int, int, int);

    // initial stuff
    if (!go)
    {
        go = 1;
        // wipe_scr = (byte *) Z_Malloc(width*height, PU_STATIC, 0); // DEBUG
        wipe_scr = screens[0];
        (*wipes[wipeno * 3])(width, height, ticks);
    }

    // do a piece of wipe-in
    V_MarkRect(0, 0, width, height);
    rc = (*wipes[wipeno * 3 + 1])(width, height, ticks);
    //  V_DrawBlock(x, y, 0, width, height, wipe_scr); // DEBUG

    // final stuff
    if (rc)
    {
        go = 0;
        (*wipes[wipeno * 3 + 2])(width, height, ticks);
    }

    return !go;
}
#define SAVEGAMESIZE    0x2c000
#define SAVESTRINGSIZE  24
#define MAXPLMOVE       (forwardmove[1])
#define TURBOTHRESHOLD  0x32
#define SLOWTURNTICS    6
#define NUMKEYS         256
#define BODYQUESIZE     32
#define VERSIONSIZE     16
#define DEMOMARKER      0x80


// Prototypes
doom_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_DoWorldDone(void);
void G_DoSaveGame(void);
void P_SpawnPlayer(mapthing_t* mthing);
void R_ExecuteSetViewSize(void);


gameaction_t gameaction;
gamestate_t gamestate;
skill_t gameskill;
doom_boolean respawnmonsters;
int gameepisode;
int gamemap;

doom_boolean paused;
doom_boolean sendpause; // send a pause event next tic 
doom_boolean sendsave; // send a save event next tic 
doom_boolean usergame; // ok to save / end game 

doom_boolean timingdemo; // if true, exit with report on completion 
doom_boolean nodrawers; // for comparative timing purposes 
doom_boolean noblit; // for comparative timing purposes 
int starttime; // for comparative timing purposes           

doom_boolean viewactive;

doom_boolean deathmatch; // only if started as net death 
doom_boolean netgame; // only true if packets are broadcast 
doom_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];
doom_boolean demorecording;
doom_boolean demoplayback;
doom_boolean netdemo;
byte* demobuffer;
byte* demo_p;
byte* demoend;
doom_boolean singledemo; // quit after playing a demo from cmdline 

doom_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 mousebfire;
int mousebstrafe;
int mousebforward;
int mousemove;

int joybfire;
int joybstrafe;
int joybuse;
int joybspeed;

fixed_t forwardmove[2] = { 0x19, 0x32 };
fixed_t sidemove[2] = { 0x18, 0x28 };
fixed_t angleturn[3] = { 640, 1280, 320 }; // + slow turn 

doom_boolean gamekeydown[NUMKEYS];
int turnheld; // for accelerative turning 

doom_boolean mousearray[4];
doom_boolean* mousebuttons = &mousearray[1]; // allow [-1]

// mouse values are used once 
int mousex;
int mousey;

int dclicktime;
int dclickstate;
int dclicks;
int dclicktime2;
int dclickstate2;
int dclicks2;

// joystick values are repeated 
int joyxmove;
int joyymove;
doom_boolean joyarray[5];
doom_boolean* joybuttons = &joyarray[1]; // allow [-1] 

int savegameslot;
char savedescription[32];

mobj_t* bodyque[BODYQUESIZE];
int bodyqueslot;

void* statcopy; // for statistics driver

// 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
};

doom_boolean secretexit;

char savename[256];

skill_t d_skill;
int d_episode;
int d_map;

char* defdemoname;


extern gamestate_t wipegamestate;
extern char* pagename;
extern doom_boolean setsizeneeded;

// The sky texture to be used instead of the F_SKY1 dummy.
extern int skytexture;


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;
    doom_boolean    strafe;
    doom_boolean    bstrafe;
    int             speed;
    int             tspeed;
    int             forward;
    int             side;

    ticcmd_t* base;

    base = I_BaseTiccmd();                // empty, or external driver
    doom_memcpy(cmd, base, sizeof(*cmd));

    cmd->consistancy =
        consistancy[consoleplayer][maketic % BACKUPTICS];


    strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe]
        || joybuttons[joybstrafe];

    extern int always_run;
    doom_boolean running = always_run ? (gamekeydown[key_speed] ? false : true) : (gamekeydown[key_speed] ? true : false);
    speed = running || 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])
        {
            side += sidemove[speed];
        }
        if (gamekeydown[key_left])
        {
            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])
    {
        forward += forwardmove[speed];
    }
    if (gamekeydown[key_down])
    {
        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;
        // clear double clicks if hit use button 
        dclicks = 0;
    }

    // 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[mousebforward])
        forward += forwardmove[speed];

    // forward double click
    if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1)
    {
        dclickstate = mousebuttons[mousebforward];
        if (dclickstate)
            dclicks++;
        if (dclicks == 2)
        {
            cmd->buttons |= BT_USE;
            dclicks = 0;
        }
        else
            dclicktime = 0;
    }
    else
    {
        dclicktime += ticdup;
        if (dclicktime > 20)
        {
            dclicks = 0;
            dclickstate = 0;
        }
    }

    // strafe double click
    bstrafe =
        mousebuttons[mousebstrafe]
        || joybuttons[joybstrafe];
    if (bstrafe != dclickstate2 && dclicktime2 > 1)
    {
        dclickstate2 = bstrafe;
        if (dclickstate2)
            dclicks2++;
        if (dclicks2 == 2)
        {
            cmd->buttons |= BT_USE;
            dclicks2 = 0;
        }
        else
            dclicktime2 = 0;
    }
    else
    {
        dclicktime2 += ticdup;
        if (dclicktime2 > 20)
        {
            dclicks2 = 0;
            dclickstate2 = 0;
        }
    }

    if (mousemove)
        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 
//
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;
        doom_memset(players[i].frags, 0, sizeof(players[i].frags));
    }

    P_SetupLevel(gameepisode, gamemap, 0, gameskill);
    displayplayer = consoleplayer;                // view the guy you are playing    
    starttime = I_GetTime();
    gameaction = ga_nothing;
    Z_CheckHeap();

    // clear cmd building stuff
    doom_memset(gamekeydown, 0, sizeof(gamekeydown));
    joyxmove = joyymove = 0;
    mousex = mousey = 0;
    sendpause = sendsave = paused = false;
    doom_memset(mousebuttons, 0, sizeof(*mousebuttons) * 3);
    doom_memset(joybuttons, 0, sizeof(*joybuttons) * 4);
}


//
// G_Responder  
// Get info needed to make ticcmd_ts for the players.
// 
doom_boolean G_Responder(event_t* ev)
{
    // allow spy mode changes even during the demo
    if (gamestate == GS_LEVEL && ev->type == ev_keydown
        && ev->data1 == KEY_F12 && (singledemo || !deathmatch))
    {
        // spy mode 
        do
        {
            displayplayer++;
            if (displayplayer == MAXPLAYERS)
                displayplayer = 0;
        } while (!playeringame[displayplayer] && displayplayer != consoleplayer);
        return true;
    }

    // any other key pops up menu if in demos
    if (gameaction == ga_nothing && !singledemo &&
        (demoplayback || gamestate == GS_DEMOSCREEN)
        )
    {
        if (ev->type == ev_keydown ||
            (ev->type == ev_mouse && ev->data1) ||
            (ev->type == ev_joystick && ev->data1))
        {
            M_StartControlPanel();
            return true;
        }
        return false;
    }

    if (gamestate == GS_LEVEL)
    {
#if 0 
        if (devparm && ev->type == ev_keydown && ev->data1 == ';')
        {
            G_DeathMatchSpawnPlayer(0);
            return true;
        }
#endif 
        if (HU_Responder(ev))
            return true;        // chat ate the event 
        if (ST_Responder(ev))
            return true;        // status window ate it 
        if (AM_Responder(ev))
            return true;        // automap ate it 
    }

    if (gamestate == GS_FINALE)
    {
        if (F_Responder(ev))
            return true;        // finale ate the event 
    }

    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:
            mousebuttons[0] = ev->data1 & 1;
            mousebuttons[1] = ev->data1 & 2;
            mousebuttons[2] = ev->data1 & 4;
            mousex = ev->data2 * (mouseSensitivity + 5) / 10;
            mousey = ev->data3 * (mouseSensitivity + 5) / 10;
            return true;    // eat events 

        case ev_joystick:
            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;

            doom_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];
                //doom_sprintf(turbomessage, "%s is turbo!", player_names[i]);
                doom_strcpy(turbomessage, player_names[i]);
                doom_concat(turbomessage, " is turbo!");
                players[consoleplayer].message = turbomessage;
            }

            if (netgame && !netdemo && !(gametic % ticdup))
            {
                if (gametic > BACKUPTICS
                    && consistancy[i][buf] != cmd->consistancy)
                {
                    //I_Error("Error: consistency failure (%i should be %i)",
                    //        cmd->consistancy, consistancy[i][buf]);
                    
                    doom_strcpy(error_buf, "Error: consistency failure (");
                    doom_concat(error_buf, doom_itoa(cmd->consistancy, 10));
                    doom_concat(error_buf, " should be ");
                    doom_concat(error_buf, doom_itoa(consistancy[i][buf], 10));
                    doom_concat(error_buf, ")");
                    I_Error(error_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])
                            doom_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)
{
    player_t* p;

    // set up the saved info         
    p = &players[player];

    // clear everything else to defaults 
    G_PlayerReborn(player);
}


//
// G_PlayerFinishLevel
// Can when a player completes a level.
//
void G_PlayerFinishLevel(int player)
{
    player_t* p;

    p = &players[player];

    doom_memset(p->powers, 0, sizeof(p->powers));
    doom_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;

    doom_memcpy(frags, players[player].frags, sizeof(frags));
    killcount = players[player].killcount;
    itemcount = players[player].itemcount;
    secretcount = players[player].secretcount;

    p = &players[player];
    doom_memset(p, 0, sizeof(*p));

    doom_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 
//

doom_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 * (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 = (int)(deathmatch_p - deathmatchstarts);
    if (selections < 4)
    {
        //I_Error("Error: Only %i deathmatch spots, 4 required", selections);
        
        doom_strcpy(error_buf, "Error: Only ");
        doom_concat(error_buf, doom_itoa(selections, 10));
        doom_concat(error_buf, " deathmatch spots, 4 required");
        I_Error(error_buf);
    }

    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 = 0;

        // 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;
}


//
// G_DoCompleted 
//
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 ((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;
    }


    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;
        doom_memcpy(wminfo.plyr[i].frags, players[i].frags
               , sizeof(wminfo.plyr[i].frags));
    }

    gamestate = GS_INTERMISSION;
    viewactive = false;
    automapactive = false;

    if (statcopy)
        doom_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. 
//

void G_LoadGame(char* name)
{
    doom_strcpy(savename, name);
    gameaction = ga_loadgame;
}


void G_DoLoadGame(void)
{
    int length;
    int i;
    int a, b, c;
    char vcheck[VERSIONSIZE];

    gameaction = ga_nothing;

    length = M_ReadFile(savename, &savebuffer);
    save_p = savebuffer + SAVESTRINGSIZE;

    // skip the description field 
    doom_memset(vcheck, 0, sizeof(vcheck));
    //doom_sprintf(vcheck, "version %i", VERSION);
    doom_strcpy(vcheck, "version ");
    doom_concat(vcheck, doom_itoa(VERSION, 10));
    if (doom_strcmp((const char*)save_p, (const char*)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("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;
    doom_strcpy(savedescription, description);
    sendsave = true;
}

void G_DoSaveGame(void)
{
    char  name[100];
    char  name2[VERSIONSIZE];
    char* description;
    int   length;
    int   i;

#if 0
    if (M_CheckParm("-cdrom"))
        doom_sprintf(name, "c:\\doomdata\\"SAVEGAMENAME"%d.dsg", savegameslot);
    else
#endif
    {
        //doom_sprintf(name, SAVEGAMENAME"%d.dsg", savegameslot);
        doom_strcpy(name, SAVEGAMENAME);
        doom_concat(name, doom_itoa(savegameslot, 10));
        doom_concat(name, ".dsg");
    }
    description = savedescription;

    save_p = savebuffer = screens[1] + 0x4000;

    doom_memcpy(save_p, description, SAVESTRINGSIZE);
    save_p += SAVESTRINGSIZE;
    doom_memset(name2, 0, sizeof(name2));
    //doom_sprintf(name2, "version %i", VERSION);
    doom_strcpy(name2, "version ");
    doom_concat(name2, doom_itoa(VERSION, 10));
    doom_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 = (int)(save_p - savebuffer);
    if (length > SAVEGAMESIZE)
        I_Error("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();
}


//
// G_InitNew
// Can be called by the startup code or the menu task,
// consoleplayer, displayplayer, playeringame[] should be set. 
//

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;
}

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;

    viewactive = true;

    // 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 
// 

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;
    doom_strcpy(demoname, name);
    doom_concat(demoname, ".lmp");
    maxsize = 0x20000;
    i = M_CheckParm("-maxdemo");
    if (i && i < myargc - 1)
        maxsize = doom_atoi(myargv[i + 1]) * 1024;
    demobuffer = Z_Malloc(maxsize, PU_STATIC, 0);
    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 
//


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);
    byte demo_version = *demo_p++;
    if (demo_version != VERSION &&
        demo_version != 109) // Demos seem to run fine with version 109
    {
        //doom_print("Demo is from a different game version! Demo Verson = %i, this version = %i\n", (int)demo_version, VERSION);
        doom_print("Demo is from a different game version! Demo Verson = ");
        doom_print(doom_itoa((int)demo_version, 10));
        doom_print(", this version = ");
        doom_print(doom_itoa(VERSION, 10));
        doom_print("\n");
        gameaction = ga_nothing;
        return;
    }

    skill = *demo_p++;
    episode = *demo_p++;
    map = *demo_p++;
    deathmatch = *demo_p++;
    respawnparm = *demo_p++;
    fastparm = *demo_p++;
    nomonsters = *demo_p++;
    consoleplayer = *demo_p++;

    for (i = 0; i < MAXPLAYERS; i++)
        playeringame[i] = *demo_p++;
    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
===================
*/

doom_boolean G_CheckDemoStatus(void)
{
    int endtime;

    if (timingdemo)
    {
        endtime = I_GetTime();
        //I_Error("Error: timed %i gametics in %i realtics", gametic
        //        , endtime - starttime);
        
        doom_strcpy(error_buf, "Error: timed ");
        doom_concat(error_buf, doom_itoa(gametic, 10));
        doom_concat(error_buf, " gametics in ");
        doom_concat(error_buf, doom_itoa(endtime - starttime, 10));
        doom_concat(error_buf, " realtics");
        I_Error(error_buf);
    }

    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, (int)(demo_p - demobuffer));
        Z_Free(demobuffer);
        demorecording = false;
        //I_Error("Error: Demo %s recorded", demoname);
        
        doom_strcpy(error_buf, "Error: Demo ");
        doom_concat(error_buf, demoname);
        doom_concat(error_buf, " recorded");
        I_Error(error_buf);
    }

    return false;
}
#define noterased viewwindowx


extern doom_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);
}


doom_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;
    }
}


doom_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, doom_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 = doom_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 doom_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,
                     doom_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,
                     doom_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
doom_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;
}
#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
#define QUEUESIZE 128


static player_t* plr;
static hu_textline_t w_title;
static hu_itext_t w_chat;
static doom_boolean always_off = false;
static char chat_dest[MAXPLAYERS];
static hu_itext_t w_inputbuffer[MAXPLAYERS];
static doom_boolean message_on;
static doom_boolean message_nottobefuckedwith;
static hu_stext_t w_message;
static int message_counter;
static doom_boolean headsupactive = false;
static char chatchars[QUEUESIZE];
static int head = 0;
static int tail = 0;

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
};

const char* shiftxform;

const char french_shiftxform[] =
{
    0,
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
    31,
    ' ', '!', '"', '#', '$', '%', '&',
    '"', // shift-'
    '(', ')', '*', '+',
    '?', // shift-,
    '_', // shift--
    '>', // shift-.
    '?', // shift-/
    '0', // shift-0
    '1', // shift-1
    '2', // shift-2
    '3', // shift-3
    '4', // shift-4
    '5', // shift-5
    '6', // shift-6
    '7', // shift-7
    '8', // shift-8
    '9', // shift-9
    '/',
    '.', // shift-;
    '<',
    '+', // shift-=
    '>', '?', '@',
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    '[', // shift-[
    '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK
    ']', // shift-]
    '"', '_',
    '\'', // shift-`
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    '{', '|', '}', '~', 127

};

const char english_shiftxform[] =
{
    0,
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
    31,
    ' ', '!', '"', '#', '$', '%', '&',
    '"', // shift-'
    '(', ')', '*', '+',
    '<', // shift-,
    '_', // shift--
    '>', // shift-.
    '?', // shift-/
    ')', // shift-0
    '!', // shift-1
    '@', // shift-2
    '#', // shift-3
    '$', // shift-4
    '%', // shift-5
    '^', // shift-6
    '&', // shift-7
    '*', // shift-8
    '(', // shift-9
    ':',
    ':', // shift-;
    '<',
    '+', // shift-=
    '>', '?', '@',
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    '[', // shift-[
    '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK
    ']', // shift-]
    '"', '_',
    '\'', // shift-`
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    '{', '|', '}', '~', 127
};

char frenchKeyMap[128] =
{
    0,
    1,2,3,4,5,6,7,8,9,10,
    11,12,13,14,15,16,17,18,19,20,
    21,22,23,24,25,26,27,28,29,30,
    31,
    ' ','!','"','#','$','%','&','%','(',')','*','+',';','-',':','!',
    '0','1','2','3','4','5','6','7','8','9',':','M','<','=','>','?',
    '@','Q','B','C','D','E','F','G','H','I','J','K','L',',','N','O',
    'P','A','R','S','T','U','V','Z','X','Y','W','^','\\','$','^','_',
    '@','Q','B','C','D','E','F','G','H','I','J','K','L',',','N','O',
    'P','A','R','S','T','U','V','Z','X','Y','W','^','\\','$','^',127
};

char chat_char; // remove later.
patch_t* hu_font[HU_FONTSIZE];
doom_boolean chat_on;
doom_boolean message_dontfuckwithme;

extern int showMessages;
extern doom_boolean automapactive;


//
// 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
};

char ForeignTranslation(unsigned char ch)
{
    return ch < 128 ? frenchKeyMap[ch] : ch;
}

void HU_Init(void)
{

    int i;
    int j;
    char buffer[9];

    if (french)
        shiftxform = french_shiftxform;
    else
        shiftxform = english_shiftxform;

    // load the heads-up font
    j = HU_FONTSTART;
    for (i = 0; i < HU_FONTSIZE; i++)
    {
        //if (j == 40) __debugbreak();
        //doom_sprintf(buffer, "STCFN%.3d", j++);
        doom_strcpy(buffer, "STCFN");
        if (j < 100) doom_concat(buffer, "0");
        if (j < 10) doom_concat(buffer, "0");
        doom_concat(buffer, doom_itoa(j++, 10));
        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
                {
                    if (c >= 'a' && c <= 'z')
                        c = (char)shiftxform[(unsigned char)c];
                    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;
            }
        }
    }
}

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;
}

doom_boolean HU_Responder(event_t* ev)
{

    static char lastmessage[HU_MAXLINELENGTH + 1];
    char* macromessage;
    doom_boolean eatkey = false;
    static doom_boolean shiftdown = false;
    static doom_boolean altdown = false;
    unsigned char c;
    int i;
    int numplayers;

    static char destination_keys[MAXPLAYERS] =
    {
        HUSTR_KEYGREEN,
        HUSTR_KEYINDIGO,
        HUSTR_KEYBROWN,
        HUSTR_KEYRED
    };

    static int num_nobrainers = 0;

    numplayers = 0;
    for (i = 0; i < MAXPLAYERS; i++)
        numplayers += playeringame[i];

    if (ev->data1 == KEY_RSHIFT)
    {
        shiftdown = ev->type == ev_keydown;
        return false;
    }
    else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT)
    {
        altdown = ev->type == ev_keydown;
        return false;
    }

    if (ev->type != ev_keydown)
        return false;

    if (!chat_on)
    {
        if (ev->data1 == 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 > 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;
            doom_strcpy(lastmessage, chat_macros[c]);
            plr->message = lastmessage;
            eatkey = true;
        }
        else
        {
            if (french)
                c = ForeignTranslation(c);
            if (shiftdown || (c >= 'a' && c <= 'z'))
                c = shiftxform[c];
            eatkey = HUlib_keyInIText(&w_chat, c);
            if (eatkey)
            {
                HU_queueChatChar(c);
            }
            if (c == KEY_ENTER)
            {
                chat_on = false;
                if (w_chat.l.len)
                {
                    doom_strcpy(lastmessage, w_chat.l.l);
                    plr->message = lastmessage;
                }
            }
            else if (c == KEY_ESCAPE)
                chat_on = false;
        }
    }

    return eatkey;
}
#if defined(I_NET_ENABLED)
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
//#include "doom_config.h"

#if defined(I_NET_ENABLED)
#if defined(DOOM_WIN32)
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
//#include <WinSock2.h>
//#include <winsock.h>
#define IPPORT_USERRESERVED 5000
#pragma comment(lib, "ws2_32.lib")
#else
 //#include <sys/socket.h>
 //#include <netinet/in.h>
 //#include <arpa/inet.h>
 //#include <sys/ioctl.h>
#define SOCKET int
#endif
#else
#define IPPORT_USERRESERVED 5000
#endif

//#include "i_system.h"
//#include "d_event.h"
//#include "d_net.h"
//#include "m_argv.h"
//#include "doomstat.h"
//#include "i_net.h"


// For some odd reason...
#if !defined(DOOM_APPLE) // It doesn't complain on Win32? O_o
#define ntohl(x) \
        ((unsigned long int)((((unsigned long int)(x) & 0x000000ffU) << 24) | \
                             (((unsigned long int)(x) & 0x0000ff00U) <<  8) | \
                             (((unsigned long int)(x) & 0x00ff0000U) >>  8) | \
                             (((unsigned long int)(x) & 0xff000000U) >> 24)))

#define ntohs(x) \
        ((unsigned short int)((((unsigned short int)(x) & 0x00ff) << 8) | \
                              (((unsigned short int)(x) & 0xff00) >> 8))) \

#define htonl(x) ntohl(x)
#define htons(x) ntohs(x)
#endif


//
// NETWORKING
//

int DOOMPORT = (IPPORT_USERRESERVED + 0x1d);
int DOOMPORT_SEND = (IPPORT_USERRESERVED + 0x1e);

#if defined(I_NET_ENABLED)
SOCKET sendsocket;
SOCKET insocket;

struct sockaddr_in sendaddress[MAXNETNODES];
#endif

void (*netget) (void);
void (*netsend) (void);


void NetSend(void);
doom_boolean NetListen(void);


//
// UDPsocket
//
#if defined(I_NET_ENABLED)
SOCKET UDPsocket(void)
{
    SOCKET s;

    // allocate a socket
    s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s < 0)
    {
        //I_Error("Error: can't create socket: %s", strerror(errno));
        
        doom_strcpy(error_buf, "Error: can't create socket: ");
        doom_concat(error_buf, strerror(errno));
        I_Error(error_buf);
    }

    return s;
}
#endif


//
// BindToLocalPort
//
#if defined(I_NET_ENABLED)
void BindToLocalPort(SOCKET s, int port)
{
    int v;
    struct sockaddr_in address;

    doom_memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = port;

    v = bind(s, (void*)&address, sizeof(address));
    if (v == -1)
    {
        //I_Error("Error: BindToPort: bind: %s", strerror(errno));
        
        doom_strcpy(error_buf, "Error: BindToPort: bind: ");
        doom_concat(error_buf, strerror(errno));
        I_Error(error_buf);
    }
}
#endif


//
// PacketSend
//
void PacketSend(void)
{
#if defined(I_NET_ENABLED)
    int c;
    doomdata_t sw;

    // byte swap
    sw.checksum = htonl(netbuffer->checksum);
    sw.player = netbuffer->player;
    sw.retransmitfrom = netbuffer->retransmitfrom;
    sw.starttic = netbuffer->starttic;
    sw.numtics = netbuffer->numtics;
    for (c = 0; c < netbuffer->numtics; c++)
    {
        sw.cmds[c].forwardmove = netbuffer->cmds[c].forwardmove;
        sw.cmds[c].sidemove = netbuffer->cmds[c].sidemove;
        sw.cmds[c].angleturn = htons(netbuffer->cmds[c].angleturn);
        sw.cmds[c].consistancy = htons(netbuffer->cmds[c].consistancy);
        sw.cmds[c].chatchar = netbuffer->cmds[c].chatchar;
        sw.cmds[c].buttons = netbuffer->cmds[c].buttons;
    }

    //doom_print ("sending %i\n",gametic);                
    c = sendto(sendsocket, (const char*)&sw, doomcom->datalength
               , 0, (void*)&sendaddress[doomcom->remotenode]
               , sizeof(sendaddress[doomcom->remotenode]));
#endif
}


//
// PacketGet
//
void PacketGet(void)
{
#if defined(I_NET_ENABLED)
    int i;
    int c;
    struct sockaddr_in fromaddress;
#if defined(__APPLE__)
    socklen_t fromlen;
#else
    int fromlen;
#endif
    doomdata_t sw;

    fromlen = sizeof(fromaddress);
    c = recvfrom(insocket, (char*)&sw, sizeof(sw), 0
                 , (struct sockaddr*)&fromaddress, &fromlen);
    if (c == -1)
    {
#if defined(DOOM_WIN32)
        int r = WSAGetLastError();
        if (r != WSAEWOULDBLOCK)
        {
            //I_Error("Error: GetPacket: %i", r);
            
            doom_strcpy(error_buf, "Error: GetPacket: ");
            doom_concat(error_buf, doom_itoa(r, 10));
            I_Error(error_buf);
        }
#else
        if (errno != EWOULDBLOCK)
        {
            //I_Error("Error: GetPacket: %s", strerror(errno));
            
            doom_strcpy(error_buf, "Error: GetPacket: ");
            doom_concat(error_buf, strerror(errno));
            I_Error(error_buf);
        }
#endif
        doomcom->remotenode = -1;                // no packet
        return;
    }

    {
        static int first = 1;
        if (first)
        {
            //doom_print("len=%d:p=[0x%x 0x%x] \n", c, *(int*)&sw, *((int*)&sw + 1));
            doom_print("len=");
            doom_print(doom_itoa(c, 10));
            doom_print(":p=[0x");
            doom_print(doom_itoa(*(int*)&sw, 16));
            doom_print(" 0x");
            doom_print(doom_itoa(*((int*)&sw + 1), 16));
            doom_print("] \n");
        }
        first = 0;
    }

    // find remote node number
    for (i = 0; i < doomcom->numnodes; i++)
        if (fromaddress.sin_addr.s_addr == sendaddress[i].sin_addr.s_addr)
            break;

    if (i == doomcom->numnodes)
    {
        // packet is not from one of the players (new game broadcast)
        doomcom->remotenode = -1; // no packet
        return;
    }

    doomcom->remotenode = i; // good packet from a game player
    doomcom->datalength = c;

    // byte swap
    netbuffer->checksum = ntohl(sw.checksum);
    netbuffer->player = sw.player;
    netbuffer->retransmitfrom = sw.retransmitfrom;
    netbuffer->starttic = sw.starttic;
    netbuffer->numtics = sw.numtics;

    for (c = 0; c < netbuffer->numtics; c++)
    {
        netbuffer->cmds[c].forwardmove = sw.cmds[c].forwardmove;
        netbuffer->cmds[c].sidemove = sw.cmds[c].sidemove;
        netbuffer->cmds[c].angleturn = ntohs(sw.cmds[c].angleturn);
        netbuffer->cmds[c].consistancy = ntohs(sw.cmds[c].consistancy);
        netbuffer->cmds[c].chatchar = sw.cmds[c].chatchar;
        netbuffer->cmds[c].buttons = sw.cmds[c].buttons;
    }
#endif
}


int GetLocalAddress(void)
{
#if defined(I_NET_ENABLED)
    char hostname[1024];
    struct hostent* hostentry; // host information entry
    int v;

    // get local address
    v = gethostname(hostname, sizeof(hostname));
    if (v == -1)
    {
        //I_Error("Error: GetLocalAddress : gethostname: errno %d", errno);
        
        doom_strcpy(error_buf, "Error: GetLocalAddress : gethostname: errno ");
        doom_concat(error_buf, strerror(errno));
        I_Error(error_buf);
    }

    hostentry = gethostbyname(hostname);
    if (!hostentry)
    {
        I_Error("Error: GetLocalAddress : gethostbyname: couldn't get local host");
    }

    return *(int*)hostentry->h_addr_list[0];
#else
    return 0;
#endif
}


//
// I_InitNetwork
//
void I_InitNetwork(void)
{
#if defined(I_NET_ENABLED)
#if defined(DOOM_WIN32)
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    u_long trueval = 1;
#else
    doom_boolean trueval = true;
#endif
#endif
    int i;
    int p;
    struct hostent* hostentry;        // host information entry

    doomcom = doom_malloc(sizeof(*doomcom));
    doom_memset(doomcom, 0, sizeof(*doomcom));

    // set up for network
    i = M_CheckParm("-dup");
    if (i && i < myargc - 1)
    {
        doomcom->ticdup = myargv[i + 1][0] - '0';
        if (doomcom->ticdup < 1)
            doomcom->ticdup = 1;
        if (doomcom->ticdup > 9)
            doomcom->ticdup = 9;
    }
    else
        doomcom->ticdup = 1;

    if (M_CheckParm("-extratic"))
        doomcom->extratics = 1;
    else
        doomcom->extratics = 0;

    p = M_CheckParm("-port");
    if (p && p < myargc - 1)
    {
        DOOMPORT = doom_atoi(myargv[p + 1]);
        //doom_print("using alternate port %i\n", DOOMPORT);
        doom_print("using alternate port ");
        doom_print(doom_itoa(DOOMPORT, 10));
        doom_print("\n");
    }

    p = M_CheckParm("-sendport");
    if (p && p < myargc - 1)
    {
        DOOMPORT_SEND = doom_atoi(myargv[p + 1]);
        //doom_print("using alternate send port %i\n", DOOMPORT_SEND);
        doom_print("using alternate send port ");
        doom_print(doom_itoa(DOOMPORT_SEND, 10));
        doom_print("\n");
    }

    // parse network game options,
    //  -net <consoleplayer> <host> <host> ...
    i = M_CheckParm("-net");
    if (!i)
    {
        // single player game
        netgame = false;
        doomcom->id = DOOMCOM_ID;
        doomcom->numplayers = doomcom->numnodes = 1;
        doomcom->deathmatch = false;
        doomcom->consoleplayer = 0;
        return;
    }

#if defined(I_NET_ENABLED)
    netsend = PacketSend;
    netget = PacketGet;
    netgame = true;

    // parse player number and host list
    doomcom->consoleplayer = myargv[i + 1][0] - '1';

    doomcom->numnodes = 1;        // this node for sure

    i++;
    while (++i < myargc && myargv[i][0] != '-')
    {
        sendaddress[doomcom->numnodes].sin_family = AF_INET;
        sendaddress[doomcom->numnodes].sin_port = htons(DOOMPORT);
        if (myargv[i][0] == '.')
        {
            sendaddress[doomcom->numnodes].sin_addr.s_addr
                = inet_addr(myargv[i] + 1);
        }
        else
        {
            hostentry = gethostbyname(myargv[i]);
            if (!hostentry)
            {
                //I_Error("Error: gethostbyname: couldn't find %s", myargv[i]);
                
                doom_strcpy(error_buf, "Error: gethostbyname: couldn't find ");
                doom_concat(error_buf, myargv[i]);
                I_Error(error_buf);
            }
            sendaddress[doomcom->numnodes].sin_addr.s_addr
                = *(int*)hostentry->h_addr_list[0];
        }
        doomcom->numnodes++;
    }

    doomcom->id = DOOMCOM_ID;
    doomcom->numplayers = doomcom->numnodes;

    // build message to receive
    insocket = UDPsocket();
    BindToLocalPort(insocket, htons(DOOMPORT));
#if defined(DOOM_WIN32)
    ioctlsocket(insocket, FIONBIO, &trueval);
#else
    ioctl(insocket, FIONBIO, &trueval);
#endif

    sendsocket = UDPsocket();
#endif
}


void I_NetCmd(void)
{
#if defined(I_NET_ENABLED)
    if (doomcom->command == CMD_SEND)
    {
        netsend();
    }
    else if (doomcom->command == CMD_GET)
    {
        netget();
    }
    else
    {
        //I_Error("Error: Bad net cmd: %i\n", doomcom->command);
        
        doom_strcpy(error_buf, "Error: Bad net cmd: ");
        doom_concat(error_buf, doom_itoa(doomcom->command, 10));
        I_Error(error_buf);
    }
#endif
}
#define SAMPLECOUNT 512
#define NUM_CHANNELS 8
// It is 2 for 16bit, and 2 for two channels.
#define BUFMUL 4
#define MIXBUFFERSIZE (SAMPLECOUNT*BUFMUL)

#define SAMPLERATE 11025 // Hz
#define SAMPLESIZE 2 // 16bit

#define MAX_QUEUED_MIDI_MSGS 256

#define EVENT_RELEASE_NOTE 0
#define EVENT_PLAY_NOTE 1
#define EVENT_PITCH_BEND 2
#define EVENT_SYSTEM_EVENT 3
#define EVENT_CONTROLLER 4
#define EVENT_END_OF_MEASURE 5
#define EVENT_FINISH 6
#define EVENT_UNUSED 7

#define CONTROLLER_EVENT_ALL_SOUNDS_OFF 10
#define CONTROLLER_EVENT_ALL_NOTES_OFF 11
#define CONTROLLER_EVENT_MONO 12
#define CONTROLLER_EVENT_POLY 13
#define CONTROLLER_EVENT_RESET_ALL_CONTROLLERS 14
#define CONTROLLER_EVENT_EVENT 15

#define CONTROLLER_CHANGE_INSTRUMENT 0
#define CONTROLLER_BANK_SELECT 1
#define CONTROLLER_MODULATION 2
#define CONTROLLER_VOLUME 3
#define CONTROLLER_PAN 4
#define CONTROLLER_EXPRESSION 5
#define CONTROLLER_REVERB 6
#define CONTROLLER_CHORUS 7
#define CONTROLLER_SUSTAIN 8
#define CONTROLLER_SOFT 9


typedef struct
{
    char ID[4];
    unsigned short scoreLen;
    unsigned short scoreStart;
    unsigned short channels;
    unsigned short sec_channels;
    unsigned short instrCnt;
    unsigned short dummy;
} mus_header_t;


// A quick hack to establish a protocol between
// synchronous mix buffer updates and asynchronous
// audio writes. Probably redundant with gametic.
static int flag = 0;

static unsigned char* mus_data = 0;
static mus_header_t mus_header;
static int mus_offset = 0;
static int mus_delay = 0;
static doom_boolean mus_loop = false;
static doom_boolean mus_playing = false;
static int mus_volume = 127;
static int mus_channel_volumes[16] = { 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127 };

static int looping = 0;
static int musicdies = -1;


// The number of internal mixing channels,
//  the samples calculated for each mixing step,
//  the size of the 16bit, 2 hardware channel (stereo)
//  mixing buffer, and the samplerate of the raw data.

// The actual lengths of all sound effects.
int lengths[NUMSFX];

// The actual output device.
int audio_fd;

// The global mixing buffer.
// Basically, samples from all active internal channels
//  are modifed and added, and stored in the buffer
//  that is submitted to the audio device.
signed short mixbuffer[MIXBUFFERSIZE];


// The channel step amount...
unsigned int channelstep[NUM_CHANNELS];
// ... and a 0.16 bit remainder of last step.
unsigned int channelstepremainder[NUM_CHANNELS];

// The channel data pointers, start and end.
unsigned char* channels[NUM_CHANNELS];
unsigned char* channelsend[NUM_CHANNELS];

// Time/gametic that the channel started playing,
//  used to determine oldest, which automatically
//  has lowest priority.
// In case number of active sounds exceeds
//  available channels.
int channelstart[NUM_CHANNELS];

// The sound in channel handles,
//  determined on registration,
//  might be used to unregister/stop/modify,
//  currently unused.
int channelhandles[NUM_CHANNELS];

// SFX id of the playing sound effect.
// Used to catch duplicates (like chainsaw).
int channelids[NUM_CHANNELS];

// Pitch to stepping lookup, unused.
int steptable[256];

// Volume lookups.
int vol_lookup[128 * 256];

// Hardware left and right channel volume lookup.
int* channelleftvol_lookup[NUM_CHANNELS];
int* channelrightvol_lookup[NUM_CHANNELS];

unsigned long queued_midi_msgs[MAX_QUEUED_MIDI_MSGS];
int queue_midi_head = 0;
int queue_midi_tail = 0;


void TickSong();


//
// This function loads the sound data from the WAD lump,
//  for single sound.
//
void* getsfx(char* sfxname, int* len)
{
    unsigned char* sfx;
    unsigned char* paddedsfx;
    int i;
    int size;
    int paddedsize;
    char name[20];
    int sfxlump;

    // Get the sound data from the WAD, allocate lump
    //  in zone memory.
    //doom_sprintf(name, "ds%s", sfxname);
    doom_strcpy(name, "ds");
    doom_concat(name, sfxname);

    // Now, there is a severe problem with the
    //  sound handling, in it is not (yet/anymore)
    //  gamemode aware. That means, sounds from
    //  DOOM II will be requested even with DOOM
    //  shareware.
    // The sound list is wired into sounds.c,
    //  which sets the external variable.
    // I do not do runtime patches to that
    //  variable. Instead, we will use a
    //  default sound for replacement.
    if (W_CheckNumForName(name) == -1)
        sfxlump = W_GetNumForName("dspistol");
    else
        sfxlump = W_GetNumForName(name);

    size = W_LumpLength(sfxlump);

    sfx = (unsigned char*)W_CacheLumpNum(sfxlump, PU_STATIC);

    // Pads the sound effect out to the mixing buffer size.
    // The original realloc would interfere with zone memory.
    paddedsize = ((size - 8 + (SAMPLECOUNT - 1)) / SAMPLECOUNT) * SAMPLECOUNT;

    // Allocate from zone memory.
    paddedsfx = (unsigned char*)Z_Malloc(paddedsize + 8, PU_STATIC, 0);
    // ddt: (unsigned char *) realloc(sfx, paddedsize+8);
    // This should interfere with zone memory handling,
    //  which does not kick in in the soundserver.

    // Now copy and pad.
    doom_memcpy(paddedsfx, sfx, size);
    for (i = size; i < paddedsize + 8; i++)
        paddedsfx[i] = 128;

    // Remove the cached lump.
    Z_Free(sfx);

    // Preserve padded length.
    *len = paddedsize;

    // Return allocated padded data.
    return (void*)(paddedsfx + 8);
}


//
// This function adds a sound to the
//  list of currently active sounds,
//  which is maintained as a given number
//  (eight, usually) of internal channels.
// Returns a handle.
//
int addsfx(int sfxid, int volume, int step, int seperation)
{
    static unsigned short handlenums = 0;

    int i;
    int rc = -1;

    int oldest = gametic;
    int oldestnum = 0;
    int slot;

    int rightvol;
    int leftvol;

    // Chainsaw troubles.
    // Play these sound effects only one at a time.
    if (sfxid == sfx_sawup
        || sfxid == sfx_sawidl
        || sfxid == sfx_sawful
        || sfxid == sfx_sawhit
        || sfxid == sfx_stnmov
        || sfxid == sfx_pistol)
    {
        // Loop all channels, check.
        for (i = 0; i < NUM_CHANNELS; i++)
        {
            // Active, and using the same SFX?
            if ((channels[i])
                && (channelids[i] == sfxid))
            {
                // Reset.
                channels[i] = 0;
                // We are sure that iff,
                //  there will only be one.
                break;
            }
        }
    }

    // Loop all channels to find oldest SFX.
    for (i = 0; (i < NUM_CHANNELS) && (channels[i]); i++)
    {
        if (channelstart[i] < oldest)
        {
            oldestnum = i;
            oldest = channelstart[i];
        }
    }

    // Tales from the cryptic.
    // If we found a channel, fine.
    // If not, we simply overwrite the first one, 0.
    // Probably only happens at startup.
    if (i == NUM_CHANNELS)
        slot = oldestnum;
    else
        slot = i;

    // Okay, in the less recent channel,
    //  we will handle the new SFX.
    // Set pointer to raw data.
    channels[slot] = (unsigned char*)S_sfx[sfxid].data;
    // Set pointer to end of raw data.
    channelsend[slot] = channels[slot] + lengths[sfxid];

    // Reset current handle number, limited to 0..100.
    if (!handlenums)
        handlenums = 100;

    // Assign current handle number.
    // Preserved so sounds could be stopped (unused).
    channelhandles[slot] = rc = handlenums++;

    // Set stepping???
    // Kinda getting the impression this is never used.
    channelstep[slot] = step;
    // ???
    channelstepremainder[slot] = 0;
    // Should be gametic, I presume.
    channelstart[slot] = gametic;

    // Separation, that is, orientation/stereo.
    //  range is: 1 - 256
    seperation += 1;

    // Per left/right channel.
    //  x^2 seperation,
    //  adjust volume properly.
    leftvol =
        volume - ((volume * seperation * seperation) >> 16); ///(256*256);
    seperation = seperation - 257;
    rightvol =
        volume - ((volume * seperation * seperation) >> 16);

    // Sanity check, clamp volume.
    if (rightvol < 0 || rightvol > 127)
        I_Error("Error: rightvol out of bounds");

    if (leftvol < 0 || leftvol > 127)
        I_Error("Error: leftvol out of bounds");

    // Get the proper lookup table piece
    //  for this volume level???
    channelleftvol_lookup[slot] = &vol_lookup[leftvol * 256];
    channelrightvol_lookup[slot] = &vol_lookup[rightvol * 256];

    // Preserve sound SFX id,
    //  e.g. for avoiding duplicates of chainsaw.
    channelids[slot] = sfxid;

    // You tell me.
    return rc;
}


//
// SFX API
// Note: this was called by S_Init.
// However, whatever they did in the
// old DPMS based DOS version, this
// were simply dummies in the Linux
// version.
// See soundserver initdata().
//
void I_SetChannels()
{
    // Init internal lookups (raw data, mixing buffer, channels).
    // This function sets up internal lookups used during
    //  the mixing process. 
    int i;
    int j;

    int* steptablemid = steptable + 128;

    // Okay, reset internal mixing channels to zero.
    /*for (i=0; i<NUM_CHANNELS; i++)
    {
      channels[i] = 0;
    }*/

    // This table provides step widths for pitch parameters.
    // I fail to see that this is currently used.
#if 0
    for (i = -128; i < 128; i++)
    {
        doom_print("steptablemid[");
        doom_print(doom_itoa(i, 10));
        doom_print("] = ");
        doom_print(doom_itoa((int)(pow(2.0, (i / 64.0)) * 65536.0), 10));
        doom_print(";\n");
        //steptablemid[i] = (int)(pow(2.0, (i / 64.0)) * 65536.0);
    }
#endif

    steptablemid[-128] = 16384;
    steptablemid[-127] = 16562;
    steptablemid[-126] = 16742;
    steptablemid[-125] = 16925;
    steptablemid[-124] = 17109;
    steptablemid[-123] = 17295;
    steptablemid[-122] = 17484;
    steptablemid[-121] = 17674;
    steptablemid[-120] = 17866;
    steptablemid[-119] = 18061;
    steptablemid[-118] = 18258;
    steptablemid[-117] = 18456;
    steptablemid[-116] = 18657;
    steptablemid[-115] = 18861;
    steptablemid[-114] = 19066;
    steptablemid[-113] = 19274;
    steptablemid[-112] = 19483;
    steptablemid[-111] = 19696;
    steptablemid[-110] = 19910;
    steptablemid[-109] = 20127;
    steptablemid[-108] = 20346;
    steptablemid[-107] = 20568;
    steptablemid[-106] = 20792;
    steptablemid[-105] = 21018;
    steptablemid[-104] = 21247;
    steptablemid[-103] = 21478;
    steptablemid[-102] = 21712;
    steptablemid[-101] = 21949;
    steptablemid[-100] = 22188;
    steptablemid[-99] = 22429;
    steptablemid[-98] = 22673;
    steptablemid[-97] = 22920;
    steptablemid[-96] = 23170;
    steptablemid[-95] = 23422;
    steptablemid[-94] = 23677;
    steptablemid[-93] = 23935;
    steptablemid[-92] = 24196;
    steptablemid[-91] = 24459;
    steptablemid[-90] = 24726;
    steptablemid[-89] = 24995;
    steptablemid[-88] = 25267;
    steptablemid[-87] = 25542;
    steptablemid[-86] = 25820;
    steptablemid[-85] = 26102;
    steptablemid[-84] = 26386;
    steptablemid[-83] = 26673;
    steptablemid[-82] = 26964;
    steptablemid[-81] = 27257;
    steptablemid[-80] = 27554;
    steptablemid[-79] = 27854;
    steptablemid[-78] = 28157;
    steptablemid[-77] = 28464;
    steptablemid[-76] = 28774;
    steptablemid[-75] = 29087;
    steptablemid[-74] = 29404;
    steptablemid[-73] = 29724;
    steptablemid[-72] = 30048;
    steptablemid[-71] = 30375;
    steptablemid[-70] = 30706;
    steptablemid[-69] = 31040;
    steptablemid[-68] = 31378;
    steptablemid[-67] = 31720;
    steptablemid[-66] = 32065;
    steptablemid[-65] = 32415;
    steptablemid[-64] = 32768;
    steptablemid[-63] = 33124;
    steptablemid[-62] = 33485;
    steptablemid[-61] = 33850;
    steptablemid[-60] = 34218;
    steptablemid[-59] = 34591;
    steptablemid[-58] = 34968;
    steptablemid[-57] = 35348;
    steptablemid[-56] = 35733;
    steptablemid[-55] = 36122;
    steptablemid[-54] = 36516;
    steptablemid[-53] = 36913;
    steptablemid[-52] = 37315;
    steptablemid[-51] = 37722;
    steptablemid[-50] = 38132;
    steptablemid[-49] = 38548;
    steptablemid[-48] = 38967;
    steptablemid[-47] = 39392;
    steptablemid[-46] = 39821;
    steptablemid[-45] = 40254;
    steptablemid[-44] = 40693;
    steptablemid[-43] = 41136;
    steptablemid[-42] = 41584;
    steptablemid[-41] = 42037;
    steptablemid[-40] = 42494;
    steptablemid[-39] = 42957;
    steptablemid[-38] = 43425;
    steptablemid[-37] = 43898;
    steptablemid[-36] = 44376;
    steptablemid[-35] = 44859;
    steptablemid[-34] = 45347;
    steptablemid[-33] = 45841;
    steptablemid[-32] = 46340;
    steptablemid[-31] = 46845;
    steptablemid[-30] = 47355;
    steptablemid[-29] = 47871;
    steptablemid[-28] = 48392;
    steptablemid[-27] = 48919;
    steptablemid[-26] = 49452;
    steptablemid[-25] = 49990;
    steptablemid[-24] = 50535;
    steptablemid[-23] = 51085;
    steptablemid[-22] = 51641;
    steptablemid[-21] = 52204;
    steptablemid[-20] = 52772;
    steptablemid[-19] = 53347;
    steptablemid[-18] = 53928;
    steptablemid[-17] = 54515;
    steptablemid[-16] = 55108;
    steptablemid[-15] = 55709;
    steptablemid[-14] = 56315;
    steptablemid[-13] = 56928;
    steptablemid[-12] = 57548;
    steptablemid[-11] = 58175;
    steptablemid[-10] = 58809;
    steptablemid[-9] = 59449;
    steptablemid[-8] = 60096;
    steptablemid[-7] = 60751;
    steptablemid[-6] = 61412;
    steptablemid[-5] = 62081;
    steptablemid[-4] = 62757;
    steptablemid[-3] = 63440;
    steptablemid[-2] = 64131;
    steptablemid[-1] = 64830;
    steptablemid[0] = 65536;
    steptablemid[1] = 66249;
    steptablemid[2] = 66971;
    steptablemid[3] = 67700;
    steptablemid[4] = 68437;
    steptablemid[5] = 69182;
    steptablemid[6] = 69936;
    steptablemid[7] = 70697;
    steptablemid[8] = 71467;
    steptablemid[9] = 72245;
    steptablemid[10] = 73032;
    steptablemid[11] = 73827;
    steptablemid[12] = 74631;
    steptablemid[13] = 75444;
    steptablemid[14] = 76265;
    steptablemid[15] = 77096;
    steptablemid[16] = 77935;
    steptablemid[17] = 78784;
    steptablemid[18] = 79642;
    steptablemid[19] = 80509;
    steptablemid[20] = 81386;
    steptablemid[21] = 82272;
    steptablemid[22] = 83168;
    steptablemid[23] = 84074;
    steptablemid[24] = 84989;
    steptablemid[25] = 85915;
    steptablemid[26] = 86850;
    steptablemid[27] = 87796;
    steptablemid[28] = 88752;
    steptablemid[29] = 89718;
    steptablemid[30] = 90695;
    steptablemid[31] = 91683;
    steptablemid[32] = 92681;
    steptablemid[33] = 93691;
    steptablemid[34] = 94711;
    steptablemid[35] = 95742;
    steptablemid[36] = 96785;
    steptablemid[37] = 97839;
    steptablemid[38] = 98904;
    steptablemid[39] = 99981;
    steptablemid[40] = 101070;
    steptablemid[41] = 102170;
    steptablemid[42] = 103283;
    steptablemid[43] = 104408;
    steptablemid[44] = 105545;
    steptablemid[45] = 106694;
    steptablemid[46] = 107856;
    steptablemid[47] = 109030;
    steptablemid[48] = 110217;
    steptablemid[49] = 111418;
    steptablemid[50] = 112631;
    steptablemid[51] = 113857;
    steptablemid[52] = 115097;
    steptablemid[53] = 116351;
    steptablemid[54] = 117618;
    steptablemid[55] = 118898;
    steptablemid[56] = 120193;
    steptablemid[57] = 121502;
    steptablemid[58] = 122825;
    steptablemid[59] = 124162;
    steptablemid[60] = 125514;
    steptablemid[61] = 126881;
    steptablemid[62] = 128263;
    steptablemid[63] = 129660;
    steptablemid[64] = 131072;
    steptablemid[65] = 132499;
    steptablemid[66] = 133942;
    steptablemid[67] = 135400;
    steptablemid[68] = 136875;
    steptablemid[69] = 138365;
    steptablemid[70] = 139872;
    steptablemid[71] = 141395;
    steptablemid[72] = 142935;
    steptablemid[73] = 144491;
    steptablemid[74] = 146064;
    steptablemid[75] = 147655;
    steptablemid[76] = 149263;
    steptablemid[77] = 150888;
    steptablemid[78] = 152531;
    steptablemid[79] = 154192;
    steptablemid[80] = 155871;
    steptablemid[81] = 157569;
    steptablemid[82] = 159284;
    steptablemid[83] = 161019;
    steptablemid[84] = 162772;
    steptablemid[85] = 164545;
    steptablemid[86] = 166337;
    steptablemid[87] = 168148;
    steptablemid[88] = 169979;
    steptablemid[89] = 171830;
    steptablemid[90] = 173701;
    steptablemid[91] = 175592;
    steptablemid[92] = 177504;
    steptablemid[93] = 179437;
    steptablemid[94] = 181391;
    steptablemid[95] = 183367;
    steptablemid[96] = 185363;
    steptablemid[97] = 187382;
    steptablemid[98] = 189422;
    steptablemid[99] = 191485;
    steptablemid[100] = 193570;
    steptablemid[101] = 195678;
    steptablemid[102] = 197809;
    steptablemid[103] = 199963;
    steptablemid[104] = 202140;
    steptablemid[105] = 204341;
    steptablemid[106] = 206566;
    steptablemid[107] = 208816;
    steptablemid[108] = 211090;
    steptablemid[109] = 213388;
    steptablemid[110] = 215712;
    steptablemid[111] = 218061;
    steptablemid[112] = 220435;
    steptablemid[113] = 222836;
    steptablemid[114] = 225262;
    steptablemid[115] = 227715;
    steptablemid[116] = 230195;
    steptablemid[117] = 232702;
    steptablemid[118] = 235236;
    steptablemid[119] = 237797;
    steptablemid[120] = 240387;
    steptablemid[121] = 243004;
    steptablemid[122] = 245650;
    steptablemid[123] = 248325;
    steptablemid[124] = 251029;
    steptablemid[125] = 253763;
    steptablemid[126] = 256526;
    steptablemid[127] = 259320;

    // Generates volume lookup tables
    //  which also turn the unsigned samples
    //  into signed samples.
    for (i = 0; i < 128; i++)
        for (j = 0; j < 256; j++)
            vol_lookup[i * 256 + j] = (i * (j - 128) * 256) / 127;
}


void I_SetSfxVolume(int volume)
{
    // Identical to DOS.
    // Basically, this should propagate
    //  the menu/config file setting
    //  to the state variable used in
    //  the mixing.
    snd_SfxVolume = volume;
}


// MUSIC API - dummy. Some code from DOS version.
void I_SetMusicVolume(int volume)
{
    snd_MusicVolume = volume;
    mus_volume = snd_MusicVolume * 8;

    for (int i = 0; i < 16; ++i)
    {
        queued_midi_msgs[(queue_midi_tail++) % MAX_QUEUED_MIDI_MSGS] = (0x000000B0 | i | 0x0700 | (((mus_channel_volumes[i] * mus_volume) / 127) << 16));
    }
}


//
// Retrieve the raw data lump index
//  for a given SFX name.
//
int I_GetSfxLumpNum(sfxinfo_t* sfx)
{
    char namebuf[9];
    //doom_sprintf(namebuf, "ds%s", sfx->name);
    doom_strcpy(namebuf, "ds");
    doom_concat(namebuf, sfx->name);
    return W_GetNumForName(namebuf);
}


//
// Starting a sound means adding it
//  to the current list of active sounds
//  in the internal channels.
// As the SFX info struct contains
//  e.g. a pointer to the raw data,
//  it is ignored.
// As our sound handling does not handle
//  priority, it is ignored.
// Pitching (that is, increased speed of playback)
//  is set, but currently not used by mixing.
//
int I_StartSound(int id, int vol, int sep, int pitch, int priority)
{
    // Returns a handle (not used).
    id = addsfx(id, vol, steptable[pitch], sep);
    return id;
}


void I_StopSound(int handle)
{
    // You need the handle returned by StartSound.
    // Would be looping all channels,
    //  tracking down the handle,
    //  an setting the channel to zero.
}


int I_SoundIsPlaying(int handle)
{
    // Ouch.
    return gametic < handle;
}


//
// This function loops all active (internal) sound
//  channels, retrieves a given number of samples
//  from the raw sound data, modifies it according
//  to the current (internal) channel parameters,
//  mixes the per channel samples into the global
//  mixbuffer, clamping it to the allowed range,
//  and sets up everything for transferring the
//  contents of the mixbuffer to the (two)
//  hardware channels (left and right, that is).
//
// This function currently supports only 16bit.
//
void I_UpdateSound(void)
{
    static int song_tick_progress = 0;

    // Mix current sound data.
    // Data, from raw sound, for right and left.
    register unsigned int sample;
    register int dl;
    register int dr;

    // Pointers in global mixbuffer, left, right, end.
    signed short* leftout;
    signed short* rightout;
    signed short* leftend;
    // Step in mixbuffer, left and right, thus two.
    int step;

    // Mixing channel index.
    int chan;

    // Do music first. [dsl] TODO: if we have embedded synth
      //static int song_progress = 0;
      //song_progress += 512;
      //while (song_progress > 0)
      //{
      //    TickSong(); // About 7 music ticks per sound sampling (X for doubt)
      //    song_progress -= 78;
      //}

    // Left and right channel
    //  are in global mixbuffer, alternating.
    leftout = mixbuffer;
    rightout = mixbuffer + 1;
    step = 2;

    // Determine end, for left channel only
    //  (right channel is implicit).
    leftend = mixbuffer + SAMPLECOUNT * step;

    // Mix sounds into the mixing buffer.
    // Loop over step*SAMPLECOUNT,
    //  that is 512 values for two channels.
    while (leftout != leftend)
    {
        // Reset left/right value. 
        dl = 0;
        dr = 0;

        // Love thy L2 chache - made this a loop.
        // Now more channels could be set at compile time
        //  as well. Thus loop those  channels.
        for (chan = 0; chan < NUM_CHANNELS; chan++)
        {
            // Check channel, if active.
            if (channels[chan])
            {
                // Get the raw data from the channel. 
                sample = *channels[chan];
                // Add left and right part
                //  for this channel (sound)
                //  to the current data.
                // Adjust volume accordingly.
                dl += channelleftvol_lookup[chan][sample];
                dr += channelrightvol_lookup[chan][sample];
                // Increment index ???
                channelstepremainder[chan] += channelstep[chan];
                // MSB is next sample???
                channels[chan] += channelstepremainder[chan] >> 16;
                // Limit to LSB???
                channelstepremainder[chan] &= 65536 - 1;

                // Check whether we are done.
                if (channels[chan] >= channelsend[chan])
                    channels[chan] = 0;
            }
        }

        // Clamp to range. Left hardware channel.
        // Has been char instead of short.
        // if (dl > 127) *leftout = 127;
        // else if (dl < -128) *leftout = -128;
        // else *leftout = dl;

        if (dl > 0x7fff)
            *leftout = 0x7fff;
        else if (dl < -0x8000)
            *leftout = -0x8000;
        else
            *leftout = dl;

        // Same for right hardware channel.
        if (dr > 0x7fff)
            *rightout = 0x7fff;
        else if (dr < -0x8000)
            *rightout = -0x8000;
        else
            *rightout = dr;

        // Increment current pointers in mixbuffer.
        leftout += step;
        rightout += step;
    }
}


//extern doom_sound_callbacks_t doom_sound_callbacks;
//extern doom_boolean skip_next_sound_update;

// 
// This would be used to write out the mixbuffer
//  during each game loop update.
// Updates sound buffer and audio device at runtime. 
// It is called during Timer interrupt with SNDINTR.
// Mixing now done synchronous, and
//  only output be done asynchronous?
//
void I_SubmitSound(void)
{
}


void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
{
    // I fail too see that this is used.
    // Would be using the handle to identify
    //  on which channel the sound might be active,
    //  and resetting the channel parameters.
}


void I_ShutdownSound(void)
{
    // Wait till all pending sounds are finished.
    int done = 0;
    int i;

    // FIXME (below).
    doom_print("I_ShutdownSound: NOT finishing pending sounds\n");

    while (!done)
    {
        for (i = 0; i < 8 && !channels[i]; i++);

        // FIXME. No proper channel output.
        //if (i==8)
        done = 1;
    }

    // Done.
    return;
}


void I_InitSound()
{
    int i;

    // Secure and configure sound device first.
    doom_print("I_InitSound: ");

    // Initialize external data (all sounds) at start, keep static.
    doom_print("I_InitSound: ");

    for (i = 1; i < NUMSFX; i++)
    {
        // Alias? Example is the chaingun sound linked to pistol.
        if (!S_sfx[i].link)
        {
            // Load data from WAD file.
            S_sfx[i].data = getsfx(S_sfx[i].name, &lengths[i]);
        }
        else
        {
            // Previously loaded already?
            S_sfx[i].data = S_sfx[i].link->data;
            lengths[i] = lengths[(S_sfx[i].link - S_sfx) / sizeof(sfxinfo_t)];
        }
    }

    doom_print(" pre-cached all sound data\n");

    // Now initialize mixbuffer with zero.
    for (i = 0; i < MIXBUFFERSIZE; i++)
        mixbuffer[i] = 0;

    // Finished initialization.
    doom_print("I_InitSound: sound module ready\n");
}


//
// MUSIC API.
//
void I_InitMusic(void)
{
}


void I_ShutdownMusic(void)
{
}


void I_PlaySong(int handle, int looping)
{
    musicdies = gametic + TICRATE * 30;

    mus_loop = looping ? true : false;
    mus_playing = true;
}


void I_PauseSong(int handle)
{
    mus_playing = false;
}


void I_ResumeSong(int handle)
{
    if (mus_data) mus_playing = true;
}


static void reset_all_channels()
{
    for (int i = 0; i < 16; ++i)
        queued_midi_msgs[(queue_midi_tail++) % MAX_QUEUED_MIDI_MSGS] = 0b10110000 | i | (123 << 8);
}


void I_StopSong(int handle)
{
    mus_data = 0;
    mus_delay = 0;
    mus_offset = 0;
    mus_playing = false;

    reset_all_channels();
}


void I_UnRegisterSong(int handle)
{
    I_StopSong(handle);
}


int I_RegisterSong(void* data)
{
    doom_memcpy(&mus_header, data, sizeof(mus_header_t));
    if (doom_strncmp(mus_header.ID, "MUS", 3) != 0 || mus_header.ID[3] != 0x1A) return 0;

    mus_data = (unsigned char*)data;
    mus_delay = 0;
    mus_offset = mus_header.scoreStart;
    mus_playing = false;

    return 1;
}


// Is the song playing?
int I_QrySongPlaying(int handle)
{
    return mus_playing;
}


unsigned long I_TickSong()
{
    unsigned long midi_event = 0;

    // Dequeue MIDI events
    if (queue_midi_head != queue_midi_tail)
        return queued_midi_msgs[(queue_midi_head++) % MAX_QUEUED_MIDI_MSGS];

    if (!mus_playing || !mus_data) return 0;

    if (mus_delay <= 0)
    {
        int event = (int)mus_data[mus_offset++];
        int type = (event & 0b01110000) >> 4;
        int channel = event & 0b00001111;

        if (channel == 15) channel = 9; // Percussion is 9 on GM
        else if (channel == 9) channel = 15;

        switch (type)
        {
            case EVENT_RELEASE_NOTE:
            {
                int note = (int)mus_data[mus_offset++] & 0b01111111;
                midi_event = (0x00000080 | channel | (note << 8));
                break;
            }
            case EVENT_PLAY_NOTE:
            {
                int note_bytes = (int)mus_data[mus_offset++];
                int note = note_bytes & 0b01111111;
                int vol = 127;
                if (note_bytes & 0b10000000) vol = (int)mus_data[mus_offset++] & 0b01111111;
                midi_event = (0x00000090 | channel | (note << 8) | (vol << 16));
                break;
            }
            case EVENT_PITCH_BEND:
            {
                int bend_amount = (int)mus_data[mus_offset++] * 64;
                int l = bend_amount & 0b01111111;
                int m = (bend_amount & 0b1111111110000000) >> 7;
                midi_event = (0x000000E0 | channel | (l << 8) | (m << 16));
                break;
            }
            case EVENT_SYSTEM_EVENT:
            {
                int controller = (int)mus_data[mus_offset++] & 0b01111111;
                switch (controller)
                {
                    case CONTROLLER_EVENT_ALL_SOUNDS_OFF:
                        midi_event = (0x000000B0 | channel | (120 << 8));
                        break;
                    case CONTROLLER_EVENT_ALL_NOTES_OFF:
                        midi_event = (0x000000B0 | channel | (123 << 8));
                        break;
                    case CONTROLLER_EVENT_MONO:
                        midi_event = (0x000000B0 | channel | (126 << 8));
                        break;
                    case CONTROLLER_EVENT_POLY:
                        midi_event = (0x000000B0 | channel | (127 << 8));
                        break;
                    case CONTROLLER_EVENT_RESET_ALL_CONTROLLERS:
                        midi_event = (0x000000B0 | channel | (121 << 8));
                        break;
                    case CONTROLLER_EVENT_EVENT: // Doom never implemented
                        break;
                }
                break;
            }
            case EVENT_CONTROLLER:
            {
                int controller = (int)mus_data[mus_offset++] & 0b01111111;
                int value = (int)mus_data[mus_offset++] & 0b01111111;
                switch (controller)
                {
                    case CONTROLLER_CHANGE_INSTRUMENT:
                        midi_event = (0x000000C0 | channel | (value << 8));
                        break;
                    case CONTROLLER_BANK_SELECT:
                        midi_event = (0x000000B0 | channel | 0x2000 | (value << 16));
                        break;
                    case CONTROLLER_MODULATION:
                        midi_event = (0x000000B0 | channel | 0x0100 | (value << 16));
                        break;
                    case CONTROLLER_VOLUME:
                        mus_channel_volumes[channel] = value;
                        midi_event = (0x000000B0 | channel | 0x0700 | (((mus_channel_volumes[channel] * mus_volume) / 127) << 16));
                        break;
                    case CONTROLLER_PAN:
                        midi_event = (0x000000B0 | channel | 0x0A00 | (value << 16));
                        break;
                    case CONTROLLER_EXPRESSION:
                        midi_event = (0x000000B0 | channel | 0x0B00 | (value << 16));
                        break;
                    case CONTROLLER_REVERB:
                        midi_event = (0x000000B0 | channel | 0x5B00 | (value << 16));
                        break;
                    case CONTROLLER_CHORUS:
                        midi_event = (0x000000B0 | channel | 0x5D00 | (value << 16));
                        break;
                    case CONTROLLER_SUSTAIN:
                        midi_event = (0x000000B0 | channel | 0x4000 | (value << 16));
                        break;
                    case CONTROLLER_SOFT:
                        midi_event = (0x000000B0 | channel | 0x4300 | (value << 16));
                        break;
                }
                break;
            }
            case EVENT_END_OF_MEASURE:
            {
                break;
            }
            case EVENT_FINISH:
            {
                // Loop
                if (mus_loop)
                {
                    mus_delay = 0;
                    mus_offset = mus_header.scoreStart;
                    break;
                }
                else
                {
                    mus_playing = false;
                    return 0;
                }
            }
            case EVENT_UNUSED:
            {
                int dummy = (int)mus_data[mus_offset++];
                break;
            }
        }

        if (event & 0b10000000) // Followed by delay
        {
            mus_delay = 0;
            int delay_byte = 0;
            do
            {
                delay_byte = mus_data[mus_offset++];
                mus_delay = mus_delay * 128 + delay_byte & 0b01111111;
            } while (delay_byte & 0b10000000);

            return midi_event;
        }
    }

    mus_delay--;

    return midi_event;
}
int mb_used = 6 * (sizeof(void*) / 4);
ticcmd_t emptycmd;


extern doom_boolean demorecording;


void I_Tactile(int on, int off, int total)
{
}


ticcmd_t* I_BaseTiccmd(void)
{
    return &emptycmd;
}


int I_GetHeapSize(void)
{
    return mb_used * 1024 * 1024;
}


byte* I_ZoneBase(int* size)
{
    *size = mb_used * 1024 * 1024;
    return (byte*)doom_malloc(*size);
}


//
// I_GetTime
// returns time in 1/70th second tics
//
int I_GetTime(void)
{
    int sec, usec;
    int newtics;
    static int basetime = 0;

    doom_gettime(&sec, &usec);
    if (!basetime)
        basetime = sec;
    newtics = (sec - basetime) * TICRATE + usec * TICRATE / 1000000;
    return newtics;
}


//
// I_Init
//
void I_Init(void)
{
    I_InitSound();
}


//
// I_Quit
//
void I_Quit(void)
{
    D_QuitNetGame();
    I_ShutdownSound();
    I_ShutdownMusic();
    M_SaveDefaults();
    I_ShutdownGraphics();
    doom_exit(0);
}


void I_WaitVBL(int count)
{
#if 0 // [pd] Never sleep in main thread
#ifdef SGI
    sginap(1);
#else
#ifdef SUN
    sleep(0);
#else
    usleep(count * (1000000 / 70));
#endif
#endif
#endif
}


void I_BeginRead(void)
{
}


void I_EndRead(void)
{
}


byte* I_AllocLow(int length)
{
    byte* mem;

    mem = (byte*)doom_malloc(length);
    doom_memset(mem, 0, length);
    return mem;
}


//
// I_Error
//
void I_Error(char* error)
{
    // Message first.
    if (error) doom_print(error);
    doom_print("\n");

    // Shutdown. Here might be other errors.
    if (demorecording)
        G_CheckDemoStatus();

    D_QuitNetGame();
    I_ShutdownGraphics();

    doom_exit(-1);
}
#define POINTER_WARP_COUNTDOWN 1


// Fake mouse handling.
// This cannot work properly w/o DGA.
// Needs an invisible mouse cursor at least.
doom_boolean grabMouse;
int doPointerWarp = POINTER_WARP_COUNTDOWN;

unsigned char screen_palette[256 * 3];

doom_boolean mousemoved = false;
doom_boolean shmFinished;


// Blocky mode,
// replace each 320x200 pixel with multiply*multiply pixels.
// According to Dave Taylor, it still is a bonehead thing
// to use ....
static int multiply = 1;
static int lastmousex = 0;
static int lastmousey = 0;


void I_ShutdownGraphics(void)
{
}


//
// I_StartFrame
//
void I_StartFrame(void)
{
}


void I_GetEvent(void)
{
}


//
// I_StartTic
//
void I_StartTic(void)
{
}


//
// I_UpdateNoBlit
//
void I_UpdateNoBlit(void)
{
    // what is this?
}


//
// I_FinishUpdate
//
void I_FinishUpdate(void)
{
    static int lasttic;
    int tics;
    int i;

    // draws little dots on the bottom of the screen
    if (devparm)
    {
        i = I_GetTime();
        tics = i - lasttic;
        lasttic = i;
        if (tics > 20) tics = 20;

        for (i = 0; i < tics * 2; i += 2)
            screens[0][(SCREENHEIGHT - 1) * SCREENWIDTH + i] = 0xff;
        for (; i < 20 * 2; i += 2)
            screens[0][(SCREENHEIGHT - 1) * SCREENWIDTH + i] = 0x0;

    }
}


//
// I_ReadScreen
//
void I_ReadScreen(byte* scr)
{
    doom_memcpy(scr, screens[0], SCREENWIDTH * SCREENHEIGHT);
}


//
// I_SetPalette
//
void I_SetPalette(byte* palette)
{
    doom_memcpy(screen_palette, palette, 256 * 3);
}


void I_InitGraphics(void)
{
    screens[0] = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT);
}
char* sprnames[NUMSPRITES + 1] = {
    "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG",
    "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2",
    "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS",
    "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG",
    "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR",
    "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN",
    "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI",
    "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO",
    "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW",
    "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4",
    "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2",
    "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU",
    "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3",
    "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2",0
};


void A_Light0();
void A_WeaponReady();
void A_Lower();
void A_Raise();
void A_Punch();
void A_ReFire();
void A_FirePistol();
void A_Light1();
void A_FireShotgun();
void A_Light2();
void A_FireShotgun2();
void A_CheckReload();
void A_OpenShotgun2();
void A_LoadShotgun2();
void A_CloseShotgun2();
void A_FireCGun();
void A_GunFlash();
void A_FireMissile();
void A_Saw();
void A_FirePlasma();
void A_BFGsound();
void A_FireBFG();
void A_BFGSpray();
void A_Explode();
void A_Pain();
void A_PlayerScream();
void A_Fall();
void A_XScream();
void A_Look();
void A_Chase();
void A_FaceTarget();
void A_PosAttack();
void A_Scream();
void A_SPosAttack();
void A_VileChase();
void A_VileStart();
void A_VileTarget();
void A_VileAttack();
void A_StartFire();
void A_Fire();
void A_FireCrackle();
void A_Tracer();
void A_SkelWhoosh();
void A_SkelFist();
void A_SkelMissile();
void A_FatRaise();
void A_FatAttack1();
void A_FatAttack2();
void A_FatAttack3();
void A_BossDeath();
void A_CPosAttack();
void A_CPosRefire();
void A_TroopAttack();
void A_SargAttack();
void A_HeadAttack();
void A_BruisAttack();
void A_SkullAttack();
void A_Metal();
void A_SpidRefire();
void A_BabyMetal();
void A_BspiAttack();
void A_Hoof();
void A_CyberAttack();
void A_PainAttack();
void A_PainDie();
void A_KeenDie();
void A_BrainPain();
void A_BrainScream();
void A_BrainDie();
void A_BrainAwake();
void A_BrainSpit();
void A_SpawnSound();
void A_SpawnFly();
void A_BrainExplode();


state_t states[NUMSTATES] = {
    {SPR_TROO,0,-1,{0},S_NULL,0,0}, // S_NULL
    {SPR_SHTG,4,0,{(actionf_p1)A_Light0},S_NULL,0,0}, // S_LIGHTDONE
    {SPR_PUNG,0,1,{(actionf_p1)A_WeaponReady},S_PUNCH,0,0}, // S_PUNCH
    {SPR_PUNG,0,1,{(actionf_p1)A_Lower},S_PUNCHDOWN,0,0}, // S_PUNCHDOWN
    {SPR_PUNG,0,1,{(actionf_p1)A_Raise},S_PUNCHUP,0,0}, // S_PUNCHUP
    {SPR_PUNG,1,4,{0},S_PUNCH2,0,0}, // S_PUNCH1
    {SPR_PUNG,2,4,{(actionf_p1)A_Punch},S_PUNCH3,0,0}, // S_PUNCH2
    {SPR_PUNG,3,5,{0},S_PUNCH4,0,0}, // S_PUNCH3
    {SPR_PUNG,2,4,{0},S_PUNCH5,0,0}, // S_PUNCH4
    {SPR_PUNG,1,5,{(actionf_p1)A_ReFire},S_PUNCH,0,0}, // S_PUNCH5
    {SPR_PISG,0,1,{(actionf_p1)A_WeaponReady},S_PISTOL,0,0},// S_PISTOL
    {SPR_PISG,0,1,{(actionf_p1)A_Lower},S_PISTOLDOWN,0,0}, // S_PISTOLDOWN
    {SPR_PISG,0,1,{(actionf_p1)A_Raise},S_PISTOLUP,0,0}, // S_PISTOLUP
    {SPR_PISG,0,4,{0},S_PISTOL2,0,0}, // S_PISTOL1
    {SPR_PISG,1,6,{(actionf_p1)A_FirePistol},S_PISTOL3,0,0},// S_PISTOL2
    {SPR_PISG,2,4,{0},S_PISTOL4,0,0}, // S_PISTOL3
    {SPR_PISG,1,5,{(actionf_p1)A_ReFire},S_PISTOL,0,0}, // S_PISTOL4
    {SPR_PISF,32768,7,{(actionf_p1)A_Light1},S_LIGHTDONE,0,0}, // S_PISTOLFLASH
    {SPR_SHTG,0,1,{(actionf_p1)A_WeaponReady},S_SGUN,0,0}, // S_SGUN
    {SPR_SHTG,0,1,{(actionf_p1)A_Lower},S_SGUNDOWN,0,0}, // S_SGUNDOWN
    {SPR_SHTG,0,1,{(actionf_p1)A_Raise},S_SGUNUP,0,0}, // S_SGUNUP
    {SPR_SHTG,0,3,{0},S_SGUN2,0,0}, // S_SGUN1
    {SPR_SHTG,0,7,{(actionf_p1)A_FireShotgun},S_SGUN3,0,0}, // S_SGUN2
    {SPR_SHTG,1,5,{0},S_SGUN4,0,0}, // S_SGUN3
    {SPR_SHTG,2,5,{0},S_SGUN5,0,0}, // S_SGUN4
    {SPR_SHTG,3,4,{0},S_SGUN6,0,0}, // S_SGUN5
    {SPR_SHTG,2,5,{0},S_SGUN7,0,0}, // S_SGUN6
    {SPR_SHTG,1,5,{0},S_SGUN8,0,0}, // S_SGUN7
    {SPR_SHTG,0,3,{0},S_SGUN9,0,0}, // S_SGUN8
    {SPR_SHTG,0,7,{(actionf_p1)A_ReFire},S_SGUN,0,0}, // S_SGUN9
    {SPR_SHTF,32768,4,{(actionf_p1)A_Light1},S_SGUNFLASH2,0,0}, // S_SGUNFLASH1
    {SPR_SHTF,32769,3,{(actionf_p1)A_Light2},S_LIGHTDONE,0,0}, // S_SGUNFLASH2
    {SPR_SHT2,0,1,{(actionf_p1)A_WeaponReady},S_DSGUN,0,0}, // S_DSGUN
    {SPR_SHT2,0,1,{(actionf_p1)A_Lower},S_DSGUNDOWN,0,0}, // S_DSGUNDOWN
    {SPR_SHT2,0,1,{(actionf_p1)A_Raise},S_DSGUNUP,0,0}, // S_DSGUNUP
    {SPR_SHT2,0,3,{0},S_DSGUN2,0,0}, // S_DSGUN1
    {SPR_SHT2,0,7,{(actionf_p1)A_FireShotgun2},S_DSGUN3,0,0}, // S_DSGUN2
    {SPR_SHT2,1,7,{0},S_DSGUN4,0,0}, // S_DSGUN3
    {SPR_SHT2,2,7,{(actionf_p1)A_CheckReload},S_DSGUN5,0,0}, // S_DSGUN4
    {SPR_SHT2,3,7,{(actionf_p1)A_OpenShotgun2},S_DSGUN6,0,0}, // S_DSGUN5
    {SPR_SHT2,4,7,{0},S_DSGUN7,0,0}, // S_DSGUN6
    {SPR_SHT2,5,7,{(actionf_p1)A_LoadShotgun2},S_DSGUN8,0,0}, // S_DSGUN7
    {SPR_SHT2,6,6,{0},S_DSGUN9,0,0}, // S_DSGUN8
    {SPR_SHT2,7,6,{(actionf_p1)A_CloseShotgun2},S_DSGUN10,0,0}, // S_DSGUN9
    {SPR_SHT2,0,5,{(actionf_p1)A_ReFire},S_DSGUN,0,0}, // S_DSGUN10
    {SPR_SHT2,1,7,{0},S_DSNR2,0,0}, // S_DSNR1
    {SPR_SHT2,0,3,{0},S_DSGUNDOWN,0,0}, // S_DSNR2
    {SPR_SHT2,32776,5,{(actionf_p1)A_Light1},S_DSGUNFLASH2,0,0}, // S_DSGUNFLASH1
    {SPR_SHT2,32777,4,{(actionf_p1)A_Light2},S_LIGHTDONE,0,0}, // S_DSGUNFLASH2
    {SPR_CHGG,0,1,{(actionf_p1)A_WeaponReady},S_CHAIN,0,0}, // S_CHAIN
    {SPR_CHGG,0,1,{(actionf_p1)A_Lower},S_CHAINDOWN,0,0}, // S_CHAINDOWN
    {SPR_CHGG,0,1,{(actionf_p1)A_Raise},S_CHAINUP,0,0}, // S_CHAINUP
    {SPR_CHGG,0,4,{(actionf_p1)A_FireCGun},S_CHAIN2,0,0}, // S_CHAIN1
    {SPR_CHGG,1,4,{(actionf_p1)A_FireCGun},S_CHAIN3,0,0}, // S_CHAIN2
    {SPR_CHGG,1,0,{(actionf_p1)A_ReFire},S_CHAIN,0,0}, // S_CHAIN3
    {SPR_CHGF,32768,5,{(actionf_p1)A_Light1},S_LIGHTDONE,0,0}, // S_CHAINFLASH1
    {SPR_CHGF,32769,5,{(actionf_p1)A_Light2},S_LIGHTDONE,0,0}, // S_CHAINFLASH2
    {SPR_MISG,0,1,{(actionf_p1)A_WeaponReady},S_MISSILE,0,0}, // S_MISSILE
    {SPR_MISG,0,1,{(actionf_p1)A_Lower},S_MISSILEDOWN,0,0}, // S_MISSILEDOWN
    {SPR_MISG,0,1,{(actionf_p1)A_Raise},S_MISSILEUP,0,0}, // S_MISSILEUP
    {SPR_MISG,1,8,{(actionf_p1)A_GunFlash},S_MISSILE2,0,0}, // S_MISSILE1
    {SPR_MISG,1,12,{(actionf_p1)A_FireMissile},S_MISSILE3,0,0}, // S_MISSILE2
    {SPR_MISG,1,0,{(actionf_p1)A_ReFire},S_MISSILE,0,0}, // S_MISSILE3
    {SPR_MISF,32768,3,{(actionf_p1)A_Light1},S_MISSILEFLASH2,0,0}, // S_MISSILEFLASH1
    {SPR_MISF,32769,4,{0},S_MISSILEFLASH3,0,0}, // S_MISSILEFLASH2
    {SPR_MISF,32770,4,{(actionf_p1)A_Light2},S_MISSILEFLASH4,0,0}, // S_MISSILEFLASH3
    {SPR_MISF,32771,4,{(actionf_p1)A_Light2},S_LIGHTDONE,0,0}, // S_MISSILEFLASH4
    {SPR_SAWG,2,4,{(actionf_p1)A_WeaponReady},S_SAWB,0,0}, // S_SAW
    {SPR_SAWG,3,4,{(actionf_p1)A_WeaponReady},S_SAW,0,0}, // S_SAWB
    {SPR_SAWG,2,1,{(actionf_p1)A_Lower},S_SAWDOWN,0,0}, // S_SAWDOWN
    {SPR_SAWG,2,1,{(actionf_p1)A_Raise},S_SAWUP,0,0}, // S_SAWUP
    {SPR_SAWG,0,4,{(actionf_p1)A_Saw},S_SAW2,0,0}, // S_SAW1
    {SPR_SAWG,1,4,{(actionf_p1)A_Saw},S_SAW3,0,0}, // S_SAW2
    {SPR_SAWG,1,0,{(actionf_p1)A_ReFire},S_SAW,0,0}, // S_SAW3
    {SPR_PLSG,0,1,{(actionf_p1)A_WeaponReady},S_PLASMA,0,0}, // S_PLASMA
    {SPR_PLSG,0,1,{(actionf_p1)A_Lower},S_PLASMADOWN,0,0}, // S_PLASMADOWN
    {SPR_PLSG,0,1,{(actionf_p1)A_Raise},S_PLASMAUP,0,0}, // S_PLASMAUP
    {SPR_PLSG,0,3,{(actionf_p1)A_FirePlasma},S_PLASMA2,0,0}, // S_PLASMA1
    {SPR_PLSG,1,20,{(actionf_p1)A_ReFire},S_PLASMA,0,0}, // S_PLASMA2
    {SPR_PLSF,32768,4,{(actionf_p1)A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH1
    {SPR_PLSF,32769,4,{(actionf_p1)A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH2
    {SPR_BFGG,0,1,{(actionf_p1)A_WeaponReady},S_BFG,0,0}, // S_BFG
    {SPR_BFGG,0,1,{(actionf_p1)A_Lower},S_BFGDOWN,0,0}, // S_BFGDOWN
    {SPR_BFGG,0,1,{(actionf_p1)A_Raise},S_BFGUP,0,0}, // S_BFGUP
    {SPR_BFGG,0,20,{(actionf_p1)A_BFGsound},S_BFG2,0,0}, // S_BFG1
    {SPR_BFGG,1,10,{(actionf_p1)A_GunFlash},S_BFG3,0,0}, // S_BFG2
    {SPR_BFGG,1,10,{(actionf_p1)A_FireBFG},S_BFG4,0,0}, // S_BFG3
    {SPR_BFGG,1,20,{(actionf_p1)A_ReFire},S_BFG,0,0}, // S_BFG4
    {SPR_BFGF,32768,11,{(actionf_p1)A_Light1},S_BFGFLASH2,0,0}, // S_BFGFLASH1
    {SPR_BFGF,32769,6,{(actionf_p1)A_Light2},S_LIGHTDONE,0,0}, // S_BFGFLASH2
    {SPR_BLUD,2,8,{0},S_BLOOD2,0,0}, // S_BLOOD1
    {SPR_BLUD,1,8,{0},S_BLOOD3,0,0}, // S_BLOOD2
    {SPR_BLUD,0,8,{0},S_NULL,0,0}, // S_BLOOD3
    {SPR_PUFF,32768,4,{0},S_PUFF2,0,0}, // S_PUFF1
    {SPR_PUFF,1,4,{0},S_PUFF3,0,0}, // S_PUFF2
    {SPR_PUFF,2,4,{0},S_PUFF4,0,0}, // S_PUFF3
    {SPR_PUFF,3,4,{0},S_NULL,0,0}, // S_PUFF4
    {SPR_BAL1,32768,4,{0},S_TBALL2,0,0}, // S_TBALL1
    {SPR_BAL1,32769,4,{0},S_TBALL1,0,0}, // S_TBALL2
    {SPR_BAL1,32770,6,{0},S_TBALLX2,0,0}, // S_TBALLX1
    {SPR_BAL1,32771,6,{0},S_TBALLX3,0,0}, // S_TBALLX2
    {SPR_BAL1,32772,6,{0},S_NULL,0,0}, // S_TBALLX3
    {SPR_BAL2,32768,4,{0},S_RBALL2,0,0}, // S_RBALL1
    {SPR_BAL2,32769,4,{0},S_RBALL1,0,0}, // S_RBALL2
    {SPR_BAL2,32770,6,{0},S_RBALLX2,0,0}, // S_RBALLX1
    {SPR_BAL2,32771,6,{0},S_RBALLX3,0,0}, // S_RBALLX2
    {SPR_BAL2,32772,6,{0},S_NULL,0,0}, // S_RBALLX3
    {SPR_PLSS,32768,6,{0},S_PLASBALL2,0,0}, // S_PLASBALL
    {SPR_PLSS,32769,6,{0},S_PLASBALL,0,0}, // S_PLASBALL2
    {SPR_PLSE,32768,4,{0},S_PLASEXP2,0,0}, // S_PLASEXP
    {SPR_PLSE,32769,4,{0},S_PLASEXP3,0,0}, // S_PLASEXP2
    {SPR_PLSE,32770,4,{0},S_PLASEXP4,0,0}, // S_PLASEXP3
    {SPR_PLSE,32771,4,{0},S_PLASEXP5,0,0}, // S_PLASEXP4
    {SPR_PLSE,32772,4,{0},S_NULL,0,0}, // S_PLASEXP5
    {SPR_MISL,32768,1,{0},S_ROCKET,0,0}, // S_ROCKET
    {SPR_BFS1,32768,4,{0},S_BFGSHOT2,0,0}, // S_BFGSHOT
    {SPR_BFS1,32769,4,{0},S_BFGSHOT,0,0}, // S_BFGSHOT2
    {SPR_BFE1,32768,8,{0},S_BFGLAND2,0,0}, // S_BFGLAND
    {SPR_BFE1,32769,8,{0},S_BFGLAND3,0,0}, // S_BFGLAND2
    {SPR_BFE1,32770,8,{(actionf_p1)A_BFGSpray},S_BFGLAND4,0,0}, // S_BFGLAND3
    {SPR_BFE1,32771,8,{0},S_BFGLAND5,0,0}, // S_BFGLAND4
    {SPR_BFE1,32772,8,{0},S_BFGLAND6,0,0}, // S_BFGLAND5
    {SPR_BFE1,32773,8,{0},S_NULL,0,0}, // S_BFGLAND6
    {SPR_BFE2,32768,8,{0},S_BFGEXP2,0,0}, // S_BFGEXP
    {SPR_BFE2,32769,8,{0},S_BFGEXP3,0,0}, // S_BFGEXP2
    {SPR_BFE2,32770,8,{0},S_BFGEXP4,0,0}, // S_BFGEXP3
    {SPR_BFE2,32771,8,{0},S_NULL,0,0}, // S_BFGEXP4
    {SPR_MISL,32769,8,{(actionf_p1)A_Explode},S_EXPLODE2,0,0}, // S_EXPLODE1
    {SPR_MISL,32770,6,{0},S_EXPLODE3,0,0}, // S_EXPLODE2
    {SPR_MISL,32771,4,{0},S_NULL,0,0}, // S_EXPLODE3
    {SPR_TFOG,32768,6,{0},S_TFOG01,0,0}, // S_TFOG
    {SPR_TFOG,32769,6,{0},S_TFOG02,0,0}, // S_TFOG01
    {SPR_TFOG,32768,6,{0},S_TFOG2,0,0}, // S_TFOG02
    {SPR_TFOG,32769,6,{0},S_TFOG3,0,0}, // S_TFOG2
    {SPR_TFOG,32770,6,{0},S_TFOG4,0,0}, // S_TFOG3
    {SPR_TFOG,32771,6,{0},S_TFOG5,0,0}, // S_TFOG4
    {SPR_TFOG,32772,6,{0},S_TFOG6,0,0}, // S_TFOG5
    {SPR_TFOG,32773,6,{0},S_TFOG7,0,0}, // S_TFOG6
    {SPR_TFOG,32774,6,{0},S_TFOG8,0,0}, // S_TFOG7
    {SPR_TFOG,32775,6,{0},S_TFOG9,0,0}, // S_TFOG8
    {SPR_TFOG,32776,6,{0},S_TFOG10,0,0}, // S_TFOG9
    {SPR_TFOG,32777,6,{0},S_NULL,0,0}, // S_TFOG10
    {SPR_IFOG,32768,6,{0},S_IFOG01,0,0}, // S_IFOG
    {SPR_IFOG,32769,6,{0},S_IFOG02,0,0}, // S_IFOG01
    {SPR_IFOG,32768,6,{0},S_IFOG2,0,0}, // S_IFOG02
    {SPR_IFOG,32769,6,{0},S_IFOG3,0,0}, // S_IFOG2
    {SPR_IFOG,32770,6,{0},S_IFOG4,0,0}, // S_IFOG3
    {SPR_IFOG,32771,6,{0},S_IFOG5,0,0}, // S_IFOG4
    {SPR_IFOG,32772,6,{0},S_NULL,0,0}, // S_IFOG5
    {SPR_PLAY,0,-1,{0},S_NULL,0,0}, // S_PLAY
    {SPR_PLAY,0,4,{0},S_PLAY_RUN2,0,0}, // S_PLAY_RUN1
    {SPR_PLAY,1,4,{0},S_PLAY_RUN3,0,0}, // S_PLAY_RUN2
    {SPR_PLAY,2,4,{0},S_PLAY_RUN4,0,0}, // S_PLAY_RUN3
    {SPR_PLAY,3,4,{0},S_PLAY_RUN1,0,0}, // S_PLAY_RUN4
    {SPR_PLAY,4,12,{0},S_PLAY,0,0}, // S_PLAY_ATK1
    {SPR_PLAY,32773,6,{0},S_PLAY_ATK1,0,0}, // S_PLAY_ATK2
    {SPR_PLAY,6,4,{0},S_PLAY_PAIN2,0,0}, // S_PLAY_PAIN
    {SPR_PLAY,6,4,{(actionf_p1)A_Pain},S_PLAY,0,0}, // S_PLAY_PAIN2
    {SPR_PLAY,7,10,{0},S_PLAY_DIE2,0,0}, // S_PLAY_DIE1
    {SPR_PLAY,8,10,{(actionf_p1)A_PlayerScream},S_PLAY_DIE3,0,0}, // S_PLAY_DIE2
    {SPR_PLAY,9,10,{(actionf_p1)A_Fall},S_PLAY_DIE4,0,0}, // S_PLAY_DIE3
    {SPR_PLAY,10,10,{0},S_PLAY_DIE5,0,0}, // S_PLAY_DIE4
    {SPR_PLAY,11,10,{0},S_PLAY_DIE6,0,0}, // S_PLAY_DIE5
    {SPR_PLAY,12,10,{0},S_PLAY_DIE7,0,0}, // S_PLAY_DIE6
    {SPR_PLAY,13,-1,{0},S_NULL,0,0}, // S_PLAY_DIE7
    {SPR_PLAY,14,5,{0},S_PLAY_XDIE2,0,0}, // S_PLAY_XDIE1
    {SPR_PLAY,15,5,{(actionf_p1)A_XScream},S_PLAY_XDIE3,0,0}, // S_PLAY_XDIE2
    {SPR_PLAY,16,5,{(actionf_p1)A_Fall},S_PLAY_XDIE4,0,0}, // S_PLAY_XDIE3
    {SPR_PLAY,17,5,{0},S_PLAY_XDIE5,0,0}, // S_PLAY_XDIE4
    {SPR_PLAY,18,5,{0},S_PLAY_XDIE6,0,0}, // S_PLAY_XDIE5
    {SPR_PLAY,19,5,{0},S_PLAY_XDIE7,0,0}, // S_PLAY_XDIE6
    {SPR_PLAY,20,5,{0},S_PLAY_XDIE8,0,0}, // S_PLAY_XDIE7
    {SPR_PLAY,21,5,{0},S_PLAY_XDIE9,0,0}, // S_PLAY_XDIE8
    {SPR_PLAY,22,-1,{0},S_NULL,0,0}, // S_PLAY_XDIE9
    {SPR_POSS,0,10,{(actionf_p1)A_Look},S_POSS_STND2,0,0}, // S_POSS_STND
    {SPR_POSS,1,10,{(actionf_p1)A_Look},S_POSS_STND,0,0}, // S_POSS_STND2
    {SPR_POSS,0,4,{(actionf_p1)A_Chase},S_POSS_RUN2,0,0}, // S_POSS_RUN1
    {SPR_POSS,0,4,{(actionf_p1)A_Chase},S_POSS_RUN3,0,0}, // S_POSS_RUN2
    {SPR_POSS,1,4,{(actionf_p1)A_Chase},S_POSS_RUN4,0,0}, // S_POSS_RUN3
    {SPR_POSS,1,4,{(actionf_p1)A_Chase},S_POSS_RUN5,0,0}, // S_POSS_RUN4
    {SPR_POSS,2,4,{(actionf_p1)A_Chase},S_POSS_RUN6,0,0}, // S_POSS_RUN5
    {SPR_POSS,2,4,{(actionf_p1)A_Chase},S_POSS_RUN7,0,0}, // S_POSS_RUN6
    {SPR_POSS,3,4,{(actionf_p1)A_Chase},S_POSS_RUN8,0,0}, // S_POSS_RUN7
    {SPR_POSS,3,4,{(actionf_p1)A_Chase},S_POSS_RUN1,0,0}, // S_POSS_RUN8
    {SPR_POSS,4,10,{(actionf_p1)A_FaceTarget},S_POSS_ATK2,0,0}, // S_POSS_ATK1
    {SPR_POSS,5,8,{(actionf_p1)A_PosAttack},S_POSS_ATK3,0,0}, // S_POSS_ATK2
    {SPR_POSS,4,8,{0},S_POSS_RUN1,0,0}, // S_POSS_ATK3
    {SPR_POSS,6,3,{0},S_POSS_PAIN2,0,0}, // S_POSS_PAIN
    {SPR_POSS,6,3,{(actionf_p1)A_Pain},S_POSS_RUN1,0,0}, // S_POSS_PAIN2
    {SPR_POSS,7,5,{0},S_POSS_DIE2,0,0}, // S_POSS_DIE1
    {SPR_POSS,8,5,{(actionf_p1)A_Scream},S_POSS_DIE3,0,0}, // S_POSS_DIE2
    {SPR_POSS,9,5,{(actionf_p1)A_Fall},S_POSS_DIE4,0,0}, // S_POSS_DIE3
    {SPR_POSS,10,5,{0},S_POSS_DIE5,0,0}, // S_POSS_DIE4
    {SPR_POSS,11,-1,{0},S_NULL,0,0}, // S_POSS_DIE5
    {SPR_POSS,12,5,{0},S_POSS_XDIE2,0,0}, // S_POSS_XDIE1
    {SPR_POSS,13,5,{(actionf_p1)A_XScream},S_POSS_XDIE3,0,0}, // S_POSS_XDIE2
    {SPR_POSS,14,5,{(actionf_p1)A_Fall},S_POSS_XDIE4,0,0}, // S_POSS_XDIE3
    {SPR_POSS,15,5,{0},S_POSS_XDIE5,0,0}, // S_POSS_XDIE4
    {SPR_POSS,16,5,{0},S_POSS_XDIE6,0,0}, // S_POSS_XDIE5
    {SPR_POSS,17,5,{0},S_POSS_XDIE7,0,0}, // S_POSS_XDIE6
    {SPR_POSS,18,5,{0},S_POSS_XDIE8,0,0}, // S_POSS_XDIE7
    {SPR_POSS,19,5,{0},S_POSS_XDIE9,0,0}, // S_POSS_XDIE8
    {SPR_POSS,20,-1,{0},S_NULL,0,0}, // S_POSS_XDIE9
    {SPR_POSS,10,5,{0},S_POSS_RAISE2,0,0}, // S_POSS_RAISE1
    {SPR_POSS,9,5,{0},S_POSS_RAISE3,0,0}, // S_POSS_RAISE2
    {SPR_POSS,8,5,{0},S_POSS_RAISE4,0,0}, // S_POSS_RAISE3
    {SPR_POSS,7,5,{0},S_POSS_RUN1,0,0}, // S_POSS_RAISE4
    {SPR_SPOS,0,10,{(actionf_p1)A_Look},S_SPOS_STND2,0,0}, // S_SPOS_STND
    {SPR_SPOS,1,10,{(actionf_p1)A_Look},S_SPOS_STND,0,0}, // S_SPOS_STND2
    {SPR_SPOS,0,3,{(actionf_p1)A_Chase},S_SPOS_RUN2,0,0}, // S_SPOS_RUN1
    {SPR_SPOS,0,3,{(actionf_p1)A_Chase},S_SPOS_RUN3,0,0}, // S_SPOS_RUN2
    {SPR_SPOS,1,3,{(actionf_p1)A_Chase},S_SPOS_RUN4,0,0}, // S_SPOS_RUN3
    {SPR_SPOS,1,3,{(actionf_p1)A_Chase},S_SPOS_RUN5,0,0}, // S_SPOS_RUN4
    {SPR_SPOS,2,3,{(actionf_p1)A_Chase},S_SPOS_RUN6,0,0}, // S_SPOS_RUN5
    {SPR_SPOS,2,3,{(actionf_p1)A_Chase},S_SPOS_RUN7,0,0}, // S_SPOS_RUN6
    {SPR_SPOS,3,3,{(actionf_p1)A_Chase},S_SPOS_RUN8,0,0}, // S_SPOS_RUN7
    {SPR_SPOS,3,3,{(actionf_p1)A_Chase},S_SPOS_RUN1,0,0}, // S_SPOS_RUN8
    {SPR_SPOS,4,10,{(actionf_p1)A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
    {SPR_SPOS,32773,10,{(actionf_p1)A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
    {SPR_SPOS,4,10,{0},S_SPOS_RUN1,0,0}, // S_SPOS_ATK3
    {SPR_SPOS,6,3,{0},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN
    {SPR_SPOS,6,3,{(actionf_p1)A_Pain},S_SPOS_RUN1,0,0}, // S_SPOS_PAIN2
    {SPR_SPOS,7,5,{0},S_SPOS_DIE2,0,0}, // S_SPOS_DIE1
    {SPR_SPOS,8,5,{(actionf_p1)A_Scream},S_SPOS_DIE3,0,0}, // S_SPOS_DIE2
    {SPR_SPOS,9,5,{(actionf_p1)A_Fall},S_SPOS_DIE4,0,0}, // S_SPOS_DIE3
    {SPR_SPOS,10,5,{0},S_SPOS_DIE5,0,0}, // S_SPOS_DIE4
    {SPR_SPOS,11,-1,{0},S_NULL,0,0}, // S_SPOS_DIE5
    {SPR_SPOS,12,5,{0},S_SPOS_XDIE2,0,0}, // S_SPOS_XDIE1
    {SPR_SPOS,13,5,{(actionf_p1)A_XScream},S_SPOS_XDIE3,0,0}, // S_SPOS_XDIE2
    {SPR_SPOS,14,5,{(actionf_p1)A_Fall},S_SPOS_XDIE4,0,0}, // S_SPOS_XDIE3
    {SPR_SPOS,15,5,{0},S_SPOS_XDIE5,0,0}, // S_SPOS_XDIE4
    {SPR_SPOS,16,5,{0},S_SPOS_XDIE6,0,0}, // S_SPOS_XDIE5
    {SPR_SPOS,17,5,{0},S_SPOS_XDIE7,0,0}, // S_SPOS_XDIE6
    {SPR_SPOS,18,5,{0},S_SPOS_XDIE8,0,0}, // S_SPOS_XDIE7
    {SPR_SPOS,19,5,{0},S_SPOS_XDIE9,0,0}, // S_SPOS_XDIE8
    {SPR_SPOS,20,-1,{0},S_NULL,0,0}, // S_SPOS_XDIE9
    {SPR_SPOS,11,5,{0},S_SPOS_RAISE2,0,0}, // S_SPOS_RAISE1
    {SPR_SPOS,10,5,{0},S_SPOS_RAISE3,0,0}, // S_SPOS_RAISE2
    {SPR_SPOS,9,5,{0},S_SPOS_RAISE4,0,0}, // S_SPOS_RAISE3
    {SPR_SPOS,8,5,{0},S_SPOS_RAISE5,0,0}, // S_SPOS_RAISE4
    {SPR_SPOS,7,5,{0},S_SPOS_RUN1,0,0}, // S_SPOS_RAISE5
    {SPR_VILE,0,10,{(actionf_p1)A_Look},S_VILE_STND2,0,0}, // S_VILE_STND
    {SPR_VILE,1,10,{(actionf_p1)A_Look},S_VILE_STND,0,0}, // S_VILE_STND2
    {SPR_VILE,0,2,{(actionf_p1)A_VileChase},S_VILE_RUN2,0,0}, // S_VILE_RUN1
    {SPR_VILE,0,2,{(actionf_p1)A_VileChase},S_VILE_RUN3,0,0}, // S_VILE_RUN2
    {SPR_VILE,1,2,{(actionf_p1)A_VileChase},S_VILE_RUN4,0,0}, // S_VILE_RUN3
    {SPR_VILE,1,2,{(actionf_p1)A_VileChase},S_VILE_RUN5,0,0}, // S_VILE_RUN4
    {SPR_VILE,2,2,{(actionf_p1)A_VileChase},S_VILE_RUN6,0,0}, // S_VILE_RUN5
    {SPR_VILE,2,2,{(actionf_p1)A_VileChase},S_VILE_RUN7,0,0}, // S_VILE_RUN6
    {SPR_VILE,3,2,{(actionf_p1)A_VileChase},S_VILE_RUN8,0,0}, // S_VILE_RUN7
    {SPR_VILE,3,2,{(actionf_p1)A_VileChase},S_VILE_RUN9,0,0}, // S_VILE_RUN8
    {SPR_VILE,4,2,{(actionf_p1)A_VileChase},S_VILE_RUN10,0,0}, // S_VILE_RUN9
    {SPR_VILE,4,2,{(actionf_p1)A_VileChase},S_VILE_RUN11,0,0}, // S_VILE_RUN10
    {SPR_VILE,5,2,{(actionf_p1)A_VileChase},S_VILE_RUN12,0,0}, // S_VILE_RUN11
    {SPR_VILE,5,2,{(actionf_p1)A_VileChase},S_VILE_RUN1,0,0}, // S_VILE_RUN12
    {SPR_VILE,32774,0,{(actionf_p1)A_VileStart},S_VILE_ATK2,0,0}, // S_VILE_ATK1
    {SPR_VILE,32774,10,{(actionf_p1)A_FaceTarget},S_VILE_ATK3,0,0}, // S_VILE_ATK2
    {SPR_VILE,32775,8,{(actionf_p1)A_VileTarget},S_VILE_ATK4,0,0}, // S_VILE_ATK3
    {SPR_VILE,32776,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK5,0,0}, // S_VILE_ATK4
    {SPR_VILE,32777,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK6,0,0}, // S_VILE_ATK5
    {SPR_VILE,32778,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK7,0,0}, // S_VILE_ATK6
    {SPR_VILE,32779,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK8,0,0}, // S_VILE_ATK7
    {SPR_VILE,32780,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK9,0,0}, // S_VILE_ATK8
    {SPR_VILE,32781,8,{(actionf_p1)A_FaceTarget},S_VILE_ATK10,0,0}, // S_VILE_ATK9
    {SPR_VILE,32782,8,{(actionf_p1)A_VileAttack},S_VILE_ATK11,0,0}, // S_VILE_ATK10
    {SPR_VILE,32783,20,{0},S_VILE_RUN1,0,0}, // S_VILE_ATK11
    {SPR_VILE,32794,10,{0},S_VILE_HEAL2,0,0}, // S_VILE_HEAL1
    {SPR_VILE,32795,10,{0},S_VILE_HEAL3,0,0}, // S_VILE_HEAL2
    {SPR_VILE,32796,10,{0},S_VILE_RUN1,0,0}, // S_VILE_HEAL3
    {SPR_VILE,16,5,{0},S_VILE_PAIN2,0,0}, // S_VILE_PAIN
    {SPR_VILE,16,5,{(actionf_p1)A_Pain},S_VILE_RUN1,0,0}, // S_VILE_PAIN2
    {SPR_VILE,16,7,{0},S_VILE_DIE2,0,0}, // S_VILE_DIE1
    {SPR_VILE,17,7,{(actionf_p1)A_Scream},S_VILE_DIE3,0,0}, // S_VILE_DIE2
    {SPR_VILE,18,7,{(actionf_p1)A_Fall},S_VILE_DIE4,0,0}, // S_VILE_DIE3
    {SPR_VILE,19,7,{0},S_VILE_DIE5,0,0}, // S_VILE_DIE4
    {SPR_VILE,20,7,{0},S_VILE_DIE6,0,0}, // S_VILE_DIE5
    {SPR_VILE,21,7,{0},S_VILE_DIE7,0,0}, // S_VILE_DIE6
    {SPR_VILE,22,7,{0},S_VILE_DIE8,0,0}, // S_VILE_DIE7
    {SPR_VILE,23,5,{0},S_VILE_DIE9,0,0}, // S_VILE_DIE8
    {SPR_VILE,24,5,{0},S_VILE_DIE10,0,0}, // S_VILE_DIE9
    {SPR_VILE,25,-1,{0},S_NULL,0,0}, // S_VILE_DIE10
    {SPR_FIRE,32768,2,{(actionf_p1)A_StartFire},S_FIRE2,0,0}, // S_FIRE1
    {SPR_FIRE,32769,2,{(actionf_p1)A_Fire},S_FIRE3,0,0}, // S_FIRE2
    {SPR_FIRE,32768,2,{(actionf_p1)A_Fire},S_FIRE4,0,0}, // S_FIRE3
    {SPR_FIRE,32769,2,{(actionf_p1)A_Fire},S_FIRE5,0,0}, // S_FIRE4
    {SPR_FIRE,32770,2,{(actionf_p1)A_FireCrackle},S_FIRE6,0,0}, // S_FIRE5
    {SPR_FIRE,32769,2,{(actionf_p1)A_Fire},S_FIRE7,0,0}, // S_FIRE6
    {SPR_FIRE,32770,2,{(actionf_p1)A_Fire},S_FIRE8,0,0}, // S_FIRE7
    {SPR_FIRE,32769,2,{(actionf_p1)A_Fire},S_FIRE9,0,0}, // S_FIRE8
    {SPR_FIRE,32770,2,{(actionf_p1)A_Fire},S_FIRE10,0,0}, // S_FIRE9
    {SPR_FIRE,32771,2,{(actionf_p1)A_Fire},S_FIRE11,0,0}, // S_FIRE10
    {SPR_FIRE,32770,2,{(actionf_p1)A_Fire},S_FIRE12,0,0}, // S_FIRE11
    {SPR_FIRE,32771,2,{(actionf_p1)A_Fire},S_FIRE13,0,0}, // S_FIRE12
    {SPR_FIRE,32770,2,{(actionf_p1)A_Fire},S_FIRE14,0,0}, // S_FIRE13
    {SPR_FIRE,32771,2,{(actionf_p1)A_Fire},S_FIRE15,0,0}, // S_FIRE14
    {SPR_FIRE,32772,2,{(actionf_p1)A_Fire},S_FIRE16,0,0}, // S_FIRE15
    {SPR_FIRE,32771,2,{(actionf_p1)A_Fire},S_FIRE17,0,0}, // S_FIRE16
    {SPR_FIRE,32772,2,{(actionf_p1)A_Fire},S_FIRE18,0,0}, // S_FIRE17
    {SPR_FIRE,32771,2,{(actionf_p1)A_Fire},S_FIRE19,0,0}, // S_FIRE18
    {SPR_FIRE,32772,2,{(actionf_p1)A_FireCrackle},S_FIRE20,0,0}, // S_FIRE19
    {SPR_FIRE,32773,2,{(actionf_p1)A_Fire},S_FIRE21,0,0}, // S_FIRE20
    {SPR_FIRE,32772,2,{(actionf_p1)A_Fire},S_FIRE22,0,0}, // S_FIRE21
    {SPR_FIRE,32773,2,{(actionf_p1)A_Fire},S_FIRE23,0,0}, // S_FIRE22
    {SPR_FIRE,32772,2,{(actionf_p1)A_Fire},S_FIRE24,0,0}, // S_FIRE23
    {SPR_FIRE,32773,2,{(actionf_p1)A_Fire},S_FIRE25,0,0}, // S_FIRE24
    {SPR_FIRE,32774,2,{(actionf_p1)A_Fire},S_FIRE26,0,0}, // S_FIRE25
    {SPR_FIRE,32775,2,{(actionf_p1)A_Fire},S_FIRE27,0,0}, // S_FIRE26
    {SPR_FIRE,32774,2,{(actionf_p1)A_Fire},S_FIRE28,0,0}, // S_FIRE27
    {SPR_FIRE,32775,2,{(actionf_p1)A_Fire},S_FIRE29,0,0}, // S_FIRE28
    {SPR_FIRE,32774,2,{(actionf_p1)A_Fire},S_FIRE30,0,0}, // S_FIRE29
    {SPR_FIRE,32775,2,{(actionf_p1)A_Fire},S_NULL,0,0}, // S_FIRE30
    {SPR_PUFF,1,4,{0},S_SMOKE2,0,0}, // S_SMOKE1
    {SPR_PUFF,2,4,{0},S_SMOKE3,0,0}, // S_SMOKE2
    {SPR_PUFF,1,4,{0},S_SMOKE4,0,0}, // S_SMOKE3
    {SPR_PUFF,2,4,{0},S_SMOKE5,0,0}, // S_SMOKE4
    {SPR_PUFF,3,4,{0},S_NULL,0,0}, // S_SMOKE5
    {SPR_FATB,32768,2,{(actionf_p1)A_Tracer},S_TRACER2,0,0}, // S_TRACER
    {SPR_FATB,32769,2,{(actionf_p1)A_Tracer},S_TRACER,0,0}, // S_TRACER2
    {SPR_FBXP,32768,8,{0},S_TRACEEXP2,0,0}, // S_TRACEEXP1
    {SPR_FBXP,32769,6,{0},S_TRACEEXP3,0,0}, // S_TRACEEXP2
    {SPR_FBXP,32770,4,{0},S_NULL,0,0}, // S_TRACEEXP3
    {SPR_SKEL,0,10,{(actionf_p1)A_Look},S_SKEL_STND2,0,0}, // S_SKEL_STND
    {SPR_SKEL,1,10,{(actionf_p1)A_Look},S_SKEL_STND,0,0}, // S_SKEL_STND2
    {SPR_SKEL,0,2,{(actionf_p1)A_Chase},S_SKEL_RUN2,0,0}, // S_SKEL_RUN1
    {SPR_SKEL,0,2,{(actionf_p1)A_Chase},S_SKEL_RUN3,0,0}, // S_SKEL_RUN2
    {SPR_SKEL,1,2,{(actionf_p1)A_Chase},S_SKEL_RUN4,0,0}, // S_SKEL_RUN3
    {SPR_SKEL,1,2,{(actionf_p1)A_Chase},S_SKEL_RUN5,0,0}, // S_SKEL_RUN4
    {SPR_SKEL,2,2,{(actionf_p1)A_Chase},S_SKEL_RUN6,0,0}, // S_SKEL_RUN5
    {SPR_SKEL,2,2,{(actionf_p1)A_Chase},S_SKEL_RUN7,0,0}, // S_SKEL_RUN6
    {SPR_SKEL,3,2,{(actionf_p1)A_Chase},S_SKEL_RUN8,0,0}, // S_SKEL_RUN7
    {SPR_SKEL,3,2,{(actionf_p1)A_Chase},S_SKEL_RUN9,0,0}, // S_SKEL_RUN8
    {SPR_SKEL,4,2,{(actionf_p1)A_Chase},S_SKEL_RUN10,0,0}, // S_SKEL_RUN9
    {SPR_SKEL,4,2,{(actionf_p1)A_Chase},S_SKEL_RUN11,0,0}, // S_SKEL_RUN10
    {SPR_SKEL,5,2,{(actionf_p1)A_Chase},S_SKEL_RUN12,0,0}, // S_SKEL_RUN11
    {SPR_SKEL,5,2,{(actionf_p1)A_Chase},S_SKEL_RUN1,0,0}, // S_SKEL_RUN12
    {SPR_SKEL,6,0,{(actionf_p1)A_FaceTarget},S_SKEL_FIST2,0,0}, // S_SKEL_FIST1
    {SPR_SKEL,6,6,{(actionf_p1)A_SkelWhoosh},S_SKEL_FIST3,0,0}, // S_SKEL_FIST2
    {SPR_SKEL,7,6,{(actionf_p1)A_FaceTarget},S_SKEL_FIST4,0,0}, // S_SKEL_FIST3
    {SPR_SKEL,8,6,{(actionf_p1)A_SkelFist},S_SKEL_RUN1,0,0}, // S_SKEL_FIST4
    {SPR_SKEL,32777,0,{(actionf_p1)A_FaceTarget},S_SKEL_MISS2,0,0}, // S_SKEL_MISS1
    {SPR_SKEL,32777,10,{(actionf_p1)A_FaceTarget},S_SKEL_MISS3,0,0}, // S_SKEL_MISS2
    {SPR_SKEL,10,10,{(actionf_p1)A_SkelMissile},S_SKEL_MISS4,0,0}, // S_SKEL_MISS3
    {SPR_SKEL,10,10,{(actionf_p1)A_FaceTarget},S_SKEL_RUN1,0,0}, // S_SKEL_MISS4
    {SPR_SKEL,11,5,{0},S_SKEL_PAIN2,0,0}, // S_SKEL_PAIN
    {SPR_SKEL,11,5,{(actionf_p1)A_Pain},S_SKEL_RUN1,0,0}, // S_SKEL_PAIN2
    {SPR_SKEL,11,7,{0},S_SKEL_DIE2,0,0}, // S_SKEL_DIE1
    {SPR_SKEL,12,7,{0},S_SKEL_DIE3,0,0}, // S_SKEL_DIE2
    {SPR_SKEL,13,7,{(actionf_p1)A_Scream},S_SKEL_DIE4,0,0}, // S_SKEL_DIE3
    {SPR_SKEL,14,7,{(actionf_p1)A_Fall},S_SKEL_DIE5,0,0}, // S_SKEL_DIE4
    {SPR_SKEL,15,7,{0},S_SKEL_DIE6,0,0}, // S_SKEL_DIE5
    {SPR_SKEL,16,-1,{0},S_NULL,0,0}, // S_SKEL_DIE6
    {SPR_SKEL,16,5,{0},S_SKEL_RAISE2,0,0}, // S_SKEL_RAISE1
    {SPR_SKEL,15,5,{0},S_SKEL_RAISE3,0,0}, // S_SKEL_RAISE2
    {SPR_SKEL,14,5,{0},S_SKEL_RAISE4,0,0}, // S_SKEL_RAISE3
    {SPR_SKEL,13,5,{0},S_SKEL_RAISE5,0,0}, // S_SKEL_RAISE4
    {SPR_SKEL,12,5,{0},S_SKEL_RAISE6,0,0}, // S_SKEL_RAISE5
    {SPR_SKEL,11,5,{0},S_SKEL_RUN1,0,0}, // S_SKEL_RAISE6
    {SPR_MANF,32768,4,{0},S_FATSHOT2,0,0}, // S_FATSHOT1
    {SPR_MANF,32769,4,{0},S_FATSHOT1,0,0}, // S_FATSHOT2
    {SPR_MISL,32769,8,{0},S_FATSHOTX2,0,0}, // S_FATSHOTX1
    {SPR_MISL,32770,6,{0},S_FATSHOTX3,0,0}, // S_FATSHOTX2
    {SPR_MISL,32771,4,{0},S_NULL,0,0}, // S_FATSHOTX3
    {SPR_FATT,0,15,{(actionf_p1)A_Look},S_FATT_STND2,0,0}, // S_FATT_STND
    {SPR_FATT,1,15,{(actionf_p1)A_Look},S_FATT_STND,0,0}, // S_FATT_STND2
    {SPR_FATT,0,4,{(actionf_p1)A_Chase},S_FATT_RUN2,0,0}, // S_FATT_RUN1
    {SPR_FATT,0,4,{(actionf_p1)A_Chase},S_FATT_RUN3,0,0}, // S_FATT_RUN2
    {SPR_FATT,1,4,{(actionf_p1)A_Chase},S_FATT_RUN4,0,0}, // S_FATT_RUN3
    {SPR_FATT,1,4,{(actionf_p1)A_Chase},S_FATT_RUN5,0,0}, // S_FATT_RUN4
    {SPR_FATT,2,4,{(actionf_p1)A_Chase},S_FATT_RUN6,0,0}, // S_FATT_RUN5
    {SPR_FATT,2,4,{(actionf_p1)A_Chase},S_FATT_RUN7,0,0}, // S_FATT_RUN6
    {SPR_FATT,3,4,{(actionf_p1)A_Chase},S_FATT_RUN8,0,0}, // S_FATT_RUN7
    {SPR_FATT,3,4,{(actionf_p1)A_Chase},S_FATT_RUN9,0,0}, // S_FATT_RUN8
    {SPR_FATT,4,4,{(actionf_p1)A_Chase},S_FATT_RUN10,0,0}, // S_FATT_RUN9
    {SPR_FATT,4,4,{(actionf_p1)A_Chase},S_FATT_RUN11,0,0}, // S_FATT_RUN10
    {SPR_FATT,5,4,{(actionf_p1)A_Chase},S_FATT_RUN12,0,0}, // S_FATT_RUN11
    {SPR_FATT,5,4,{(actionf_p1)A_Chase},S_FATT_RUN1,0,0}, // S_FATT_RUN12
    {SPR_FATT,6,20,{(actionf_p1)A_FatRaise},S_FATT_ATK2,0,0}, // S_FATT_ATK1
    {SPR_FATT,32775,10,{(actionf_p1)A_FatAttack1},S_FATT_ATK3,0,0}, // S_FATT_ATK2
    {SPR_FATT,8,5,{(actionf_p1)A_FaceTarget},S_FATT_ATK4,0,0}, // S_FATT_ATK3
    {SPR_FATT,6,5,{(actionf_p1)A_FaceTarget},S_FATT_ATK5,0,0}, // S_FATT_ATK4
    {SPR_FATT,32775,10,{(actionf_p1)A_FatAttack2},S_FATT_ATK6,0,0}, // S_FATT_ATK5
    {SPR_FATT,8,5,{(actionf_p1)A_FaceTarget},S_FATT_ATK7,0,0}, // S_FATT_ATK6
    {SPR_FATT,6,5,{(actionf_p1)A_FaceTarget},S_FATT_ATK8,0,0}, // S_FATT_ATK7
    {SPR_FATT,32775,10,{(actionf_p1)A_FatAttack3},S_FATT_ATK9,0,0}, // S_FATT_ATK8
    {SPR_FATT,8,5,{(actionf_p1)A_FaceTarget},S_FATT_ATK10,0,0}, // S_FATT_ATK9
    {SPR_FATT,6,5,{(actionf_p1)A_FaceTarget},S_FATT_RUN1,0,0}, // S_FATT_ATK10
    {SPR_FATT,9,3,{0},S_FATT_PAIN2,0,0}, // S_FATT_PAIN
    {SPR_FATT,9,3,{(actionf_p1)A_Pain},S_FATT_RUN1,0,0}, // S_FATT_PAIN2
    {SPR_FATT,10,6,{0},S_FATT_DIE2,0,0}, // S_FATT_DIE1
    {SPR_FATT,11,6,{(actionf_p1)A_Scream},S_FATT_DIE3,0,0}, // S_FATT_DIE2
    {SPR_FATT,12,6,{(actionf_p1)A_Fall},S_FATT_DIE4,0,0}, // S_FATT_DIE3
    {SPR_FATT,13,6,{0},S_FATT_DIE5,0,0}, // S_FATT_DIE4
    {SPR_FATT,14,6,{0},S_FATT_DIE6,0,0}, // S_FATT_DIE5
    {SPR_FATT,15,6,{0},S_FATT_DIE7,0,0}, // S_FATT_DIE6
    {SPR_FATT,16,6,{0},S_FATT_DIE8,0,0}, // S_FATT_DIE7
    {SPR_FATT,17,6,{0},S_FATT_DIE9,0,0}, // S_FATT_DIE8
    {SPR_FATT,18,6,{0},S_FATT_DIE10,0,0}, // S_FATT_DIE9
    {SPR_FATT,19,-1,{(actionf_p1)A_BossDeath},S_NULL,0,0}, // S_FATT_DIE10
    {SPR_FATT,17,5,{0},S_FATT_RAISE2,0,0}, // S_FATT_RAISE1
    {SPR_FATT,16,5,{0},S_FATT_RAISE3,0,0}, // S_FATT_RAISE2
    {SPR_FATT,15,5,{0},S_FATT_RAISE4,0,0}, // S_FATT_RAISE3
    {SPR_FATT,14,5,{0},S_FATT_RAISE5,0,0}, // S_FATT_RAISE4
    {SPR_FATT,13,5,{0},S_FATT_RAISE6,0,0}, // S_FATT_RAISE5
    {SPR_FATT,12,5,{0},S_FATT_RAISE7,0,0}, // S_FATT_RAISE6
    {SPR_FATT,11,5,{0},S_FATT_RAISE8,0,0}, // S_FATT_RAISE7
    {SPR_FATT,10,5,{0},S_FATT_RUN1,0,0}, // S_FATT_RAISE8
    {SPR_CPOS,0,10,{(actionf_p1)A_Look},S_CPOS_STND2,0,0}, // S_CPOS_STND
    {SPR_CPOS,1,10,{(actionf_p1)A_Look},S_CPOS_STND,0,0}, // S_CPOS_STND2
    {SPR_CPOS,0,3,{(actionf_p1)A_Chase},S_CPOS_RUN2,0,0}, // S_CPOS_RUN1
    {SPR_CPOS,0,3,{(actionf_p1)A_Chase},S_CPOS_RUN3,0,0}, // S_CPOS_RUN2
    {SPR_CPOS,1,3,{(actionf_p1)A_Chase},S_CPOS_RUN4,0,0}, // S_CPOS_RUN3
    {SPR_CPOS,1,3,{(actionf_p1)A_Chase},S_CPOS_RUN5,0,0}, // S_CPOS_RUN4
    {SPR_CPOS,2,3,{(actionf_p1)A_Chase},S_CPOS_RUN6,0,0}, // S_CPOS_RUN5
    {SPR_CPOS,2,3,{(actionf_p1)A_Chase},S_CPOS_RUN7,0,0}, // S_CPOS_RUN6
    {SPR_CPOS,3,3,{(actionf_p1)A_Chase},S_CPOS_RUN8,0,0}, // S_CPOS_RUN7
    {SPR_CPOS,3,3,{(actionf_p1)A_Chase},S_CPOS_RUN1,0,0}, // S_CPOS_RUN8
    {SPR_CPOS,4,10,{(actionf_p1)A_FaceTarget},S_CPOS_ATK2,0,0}, // S_CPOS_ATK1
    {SPR_CPOS,32773,4,{(actionf_p1)A_CPosAttack},S_CPOS_ATK3,0,0}, // S_CPOS_ATK2
    {SPR_CPOS,32772,4,{(actionf_p1)A_CPosAttack},S_CPOS_ATK4,0,0}, // S_CPOS_ATK3
    {SPR_CPOS,5,1,{(actionf_p1)A_CPosRefire},S_CPOS_ATK2,0,0}, // S_CPOS_ATK4
    {SPR_CPOS,6,3,{0},S_CPOS_PAIN2,0,0}, // S_CPOS_PAIN
    {SPR_CPOS,6,3,{(actionf_p1)A_Pain},S_CPOS_RUN1,0,0}, // S_CPOS_PAIN2
    {SPR_CPOS,7,5,{0},S_CPOS_DIE2,0,0}, // S_CPOS_DIE1
    {SPR_CPOS,8,5,{(actionf_p1)A_Scream},S_CPOS_DIE3,0,0}, // S_CPOS_DIE2
    {SPR_CPOS,9,5,{(actionf_p1)A_Fall},S_CPOS_DIE4,0,0}, // S_CPOS_DIE3
    {SPR_CPOS,10,5,{0},S_CPOS_DIE5,0,0}, // S_CPOS_DIE4
    {SPR_CPOS,11,5,{0},S_CPOS_DIE6,0,0}, // S_CPOS_DIE5
    {SPR_CPOS,12,5,{0},S_CPOS_DIE7,0,0}, // S_CPOS_DIE6
    {SPR_CPOS,13,-1,{0},S_NULL,0,0}, // S_CPOS_DIE7
    {SPR_CPOS,14,5,{0},S_CPOS_XDIE2,0,0}, // S_CPOS_XDIE1
    {SPR_CPOS,15,5,{(actionf_p1)A_XScream},S_CPOS_XDIE3,0,0}, // S_CPOS_XDIE2
    {SPR_CPOS,16,5,{(actionf_p1)A_Fall},S_CPOS_XDIE4,0,0}, // S_CPOS_XDIE3
    {SPR_CPOS,17,5,{0},S_CPOS_XDIE5,0,0}, // S_CPOS_XDIE4
    {SPR_CPOS,18,5,{0},S_CPOS_XDIE6,0,0}, // S_CPOS_XDIE5
    {SPR_CPOS,19,-1,{0},S_NULL,0,0}, // S_CPOS_XDIE6
    {SPR_CPOS,13,5,{0},S_CPOS_RAISE2,0,0}, // S_CPOS_RAISE1
    {SPR_CPOS,12,5,{0},S_CPOS_RAISE3,0,0}, // S_CPOS_RAISE2
    {SPR_CPOS,11,5,{0},S_CPOS_RAISE4,0,0}, // S_CPOS_RAISE3
    {SPR_CPOS,10,5,{0},S_CPOS_RAISE5,0,0}, // S_CPOS_RAISE4
    {SPR_CPOS,9,5,{0},S_CPOS_RAISE6,0,0}, // S_CPOS_RAISE5
    {SPR_CPOS,8,5,{0},S_CPOS_RAISE7,0,0}, // S_CPOS_RAISE6
    {SPR_CPOS,7,5,{0},S_CPOS_RUN1,0,0}, // S_CPOS_RAISE7
    {SPR_TROO,0,10,{(actionf_p1)A_Look},S_TROO_STND2,0,0}, // S_TROO_STND
    {SPR_TROO,1,10,{(actionf_p1)A_Look},S_TROO_STND,0,0}, // S_TROO_STND2
    {SPR_TROO,0,3,{(actionf_p1)A_Chase},S_TROO_RUN2,0,0}, // S_TROO_RUN1
    {SPR_TROO,0,3,{(actionf_p1)A_Chase},S_TROO_RUN3,0,0}, // S_TROO_RUN2
    {SPR_TROO,1,3,{(actionf_p1)A_Chase},S_TROO_RUN4,0,0}, // S_TROO_RUN3
    {SPR_TROO,1,3,{(actionf_p1)A_Chase},S_TROO_RUN5,0,0}, // S_TROO_RUN4
    {SPR_TROO,2,3,{(actionf_p1)A_Chase},S_TROO_RUN6,0,0}, // S_TROO_RUN5
    {SPR_TROO,2,3,{(actionf_p1)A_Chase},S_TROO_RUN7,0,0}, // S_TROO_RUN6
    {SPR_TROO,3,3,{(actionf_p1)A_Chase},S_TROO_RUN8,0,0}, // S_TROO_RUN7
    {SPR_TROO,3,3,{(actionf_p1)A_Chase},S_TROO_RUN1,0,0}, // S_TROO_RUN8
    {SPR_TROO,4,8,{(actionf_p1)A_FaceTarget},S_TROO_ATK2,0,0}, // S_TROO_ATK1
    {SPR_TROO,5,8,{(actionf_p1)A_FaceTarget},S_TROO_ATK3,0,0}, // S_TROO_ATK2
    {SPR_TROO,6,6,{(actionf_p1)A_TroopAttack},S_TROO_RUN1,0,0}, // S_TROO_ATK3
    {SPR_TROO,7,2,{0},S_TROO_PAIN2,0,0}, // S_TROO_PAIN
    {SPR_TROO,7,2,{(actionf_p1)A_Pain},S_TROO_RUN1,0,0}, // S_TROO_PAIN2
    {SPR_TROO,8,8,{0},S_TROO_DIE2,0,0}, // S_TROO_DIE1
    {SPR_TROO,9,8,{(actionf_p1)A_Scream},S_TROO_DIE3,0,0}, // S_TROO_DIE2
    {SPR_TROO,10,6,{0},S_TROO_DIE4,0,0}, // S_TROO_DIE3
    {SPR_TROO,11,6,{(actionf_p1)A_Fall},S_TROO_DIE5,0,0}, // S_TROO_DIE4
    {SPR_TROO,12,-1,{0},S_NULL,0,0}, // S_TROO_DIE5
    {SPR_TROO,13,5,{0},S_TROO_XDIE2,0,0}, // S_TROO_XDIE1
    {SPR_TROO,14,5,{(actionf_p1)A_XScream},S_TROO_XDIE3,0,0}, // S_TROO_XDIE2
    {SPR_TROO,15,5,{0},S_TROO_XDIE4,0,0}, // S_TROO_XDIE3
    {SPR_TROO,16,5,{(actionf_p1)A_Fall},S_TROO_XDIE5,0,0}, // S_TROO_XDIE4
    {SPR_TROO,17,5,{0},S_TROO_XDIE6,0,0}, // S_TROO_XDIE5
    {SPR_TROO,18,5,{0},S_TROO_XDIE7,0,0}, // S_TROO_XDIE6
    {SPR_TROO,19,5,{0},S_TROO_XDIE8,0,0}, // S_TROO_XDIE7
    {SPR_TROO,20,-1,{0},S_NULL,0,0}, // S_TROO_XDIE8
    {SPR_TROO,12,8,{0},S_TROO_RAISE2,0,0}, // S_TROO_RAISE1
    {SPR_TROO,11,8,{0},S_TROO_RAISE3,0,0}, // S_TROO_RAISE2
    {SPR_TROO,10,6,{0},S_TROO_RAISE4,0,0}, // S_TROO_RAISE3
    {SPR_TROO,9,6,{0},S_TROO_RAISE5,0,0}, // S_TROO_RAISE4
    {SPR_TROO,8,6,{0},S_TROO_RUN1,0,0}, // S_TROO_RAISE5
    {SPR_SARG,0,10,{(actionf_p1)A_Look},S_SARG_STND2,0,0}, // S_SARG_STND
    {SPR_SARG,1,10,{(actionf_p1)A_Look},S_SARG_STND,0,0}, // S_SARG_STND2
    {SPR_SARG,0,2,{(actionf_p1)A_Chase},S_SARG_RUN2,0,0}, // S_SARG_RUN1
    {SPR_SARG,0,2,{(actionf_p1)A_Chase},S_SARG_RUN3,0,0}, // S_SARG_RUN2
    {SPR_SARG,1,2,{(actionf_p1)A_Chase},S_SARG_RUN4,0,0}, // S_SARG_RUN3
    {SPR_SARG,1,2,{(actionf_p1)A_Chase},S_SARG_RUN5,0,0}, // S_SARG_RUN4
    {SPR_SARG,2,2,{(actionf_p1)A_Chase},S_SARG_RUN6,0,0}, // S_SARG_RUN5
    {SPR_SARG,2,2,{(actionf_p1)A_Chase},S_SARG_RUN7,0,0}, // S_SARG_RUN6
    {SPR_SARG,3,2,{(actionf_p1)A_Chase},S_SARG_RUN8,0,0}, // S_SARG_RUN7
    {SPR_SARG,3,2,{(actionf_p1)A_Chase},S_SARG_RUN1,0,0}, // S_SARG_RUN8
    {SPR_SARG,4,8,{(actionf_p1)A_FaceTarget},S_SARG_ATK2,0,0}, // S_SARG_ATK1
    {SPR_SARG,5,8,{(actionf_p1)A_FaceTarget},S_SARG_ATK3,0,0}, // S_SARG_ATK2
    {SPR_SARG,6,8,{(actionf_p1)A_SargAttack},S_SARG_RUN1,0,0}, // S_SARG_ATK3
    {SPR_SARG,7,2,{0},S_SARG_PAIN2,0,0}, // S_SARG_PAIN
    {SPR_SARG,7,2,{(actionf_p1)A_Pain},S_SARG_RUN1,0,0}, // S_SARG_PAIN2
    {SPR_SARG,8,8,{0},S_SARG_DIE2,0,0}, // S_SARG_DIE1
    {SPR_SARG,9,8,{(actionf_p1)A_Scream},S_SARG_DIE3,0,0}, // S_SARG_DIE2
    {SPR_SARG,10,4,{0},S_SARG_DIE4,0,0}, // S_SARG_DIE3
    {SPR_SARG,11,4,{(actionf_p1)A_Fall},S_SARG_DIE5,0,0}, // S_SARG_DIE4
    {SPR_SARG,12,4,{0},S_SARG_DIE6,0,0}, // S_SARG_DIE5
    {SPR_SARG,13,-1,{0},S_NULL,0,0}, // S_SARG_DIE6
    {SPR_SARG,13,5,{0},S_SARG_RAISE2,0,0}, // S_SARG_RAISE1
    {SPR_SARG,12,5,{0},S_SARG_RAISE3,0,0}, // S_SARG_RAISE2
    {SPR_SARG,11,5,{0},S_SARG_RAISE4,0,0}, // S_SARG_RAISE3
    {SPR_SARG,10,5,{0},S_SARG_RAISE5,0,0}, // S_SARG_RAISE4
    {SPR_SARG,9,5,{0},S_SARG_RAISE6,0,0}, // S_SARG_RAISE5
    {SPR_SARG,8,5,{0},S_SARG_RUN1,0,0}, // S_SARG_RAISE6
    {SPR_HEAD,0,10,{(actionf_p1)A_Look},S_HEAD_STND,0,0}, // S_HEAD_STND
    {SPR_HEAD,0,3,{(actionf_p1)A_Chase},S_HEAD_RUN1,0,0}, // S_HEAD_RUN1
    {SPR_HEAD,1,5,{(actionf_p1)A_FaceTarget},S_HEAD_ATK2,0,0}, // S_HEAD_ATK1
    {SPR_HEAD,2,5,{(actionf_p1)A_FaceTarget},S_HEAD_ATK3,0,0}, // S_HEAD_ATK2
    {SPR_HEAD,32771,5,{(actionf_p1)A_HeadAttack},S_HEAD_RUN1,0,0}, // S_HEAD_ATK3
    {SPR_HEAD,4,3,{0},S_HEAD_PAIN2,0,0}, // S_HEAD_PAIN
    {SPR_HEAD,4,3,{(actionf_p1)A_Pain},S_HEAD_PAIN3,0,0}, // S_HEAD_PAIN2
    {SPR_HEAD,5,6,{0},S_HEAD_RUN1,0,0}, // S_HEAD_PAIN3
    {SPR_HEAD,6,8,{0},S_HEAD_DIE2,0,0}, // S_HEAD_DIE1
    {SPR_HEAD,7,8,{(actionf_p1)A_Scream},S_HEAD_DIE3,0,0}, // S_HEAD_DIE2
    {SPR_HEAD,8,8,{0},S_HEAD_DIE4,0,0}, // S_HEAD_DIE3
    {SPR_HEAD,9,8,{0},S_HEAD_DIE5,0,0}, // S_HEAD_DIE4
    {SPR_HEAD,10,8,{(actionf_p1)A_Fall},S_HEAD_DIE6,0,0}, // S_HEAD_DIE5
    {SPR_HEAD,11,-1,{0},S_NULL,0,0}, // S_HEAD_DIE6
    {SPR_HEAD,11,8,{0},S_HEAD_RAISE2,0,0}, // S_HEAD_RAISE1
    {SPR_HEAD,10,8,{0},S_HEAD_RAISE3,0,0}, // S_HEAD_RAISE2
    {SPR_HEAD,9,8,{0},S_HEAD_RAISE4,0,0}, // S_HEAD_RAISE3
    {SPR_HEAD,8,8,{0},S_HEAD_RAISE5,0,0}, // S_HEAD_RAISE4
    {SPR_HEAD,7,8,{0},S_HEAD_RAISE6,0,0}, // S_HEAD_RAISE5
    {SPR_HEAD,6,8,{0},S_HEAD_RUN1,0,0}, // S_HEAD_RAISE6
    {SPR_BAL7,32768,4,{0},S_BRBALL2,0,0}, // S_BRBALL1
    {SPR_BAL7,32769,4,{0},S_BRBALL1,0,0}, // S_BRBALL2
    {SPR_BAL7,32770,6,{0},S_BRBALLX2,0,0}, // S_BRBALLX1
    {SPR_BAL7,32771,6,{0},S_BRBALLX3,0,0}, // S_BRBALLX2
    {SPR_BAL7,32772,6,{0},S_NULL,0,0}, // S_BRBALLX3
    {SPR_BOSS,0,10,{(actionf_p1)A_Look},S_BOSS_STND2,0,0}, // S_BOSS_STND
    {SPR_BOSS,1,10,{(actionf_p1)A_Look},S_BOSS_STND,0,0}, // S_BOSS_STND2
    {SPR_BOSS,0,3,{(actionf_p1)A_Chase},S_BOSS_RUN2,0,0}, // S_BOSS_RUN1
    {SPR_BOSS,0,3,{(actionf_p1)A_Chase},S_BOSS_RUN3,0,0}, // S_BOSS_RUN2
    {SPR_BOSS,1,3,{(actionf_p1)A_Chase},S_BOSS_RUN4,0,0}, // S_BOSS_RUN3
    {SPR_BOSS,1,3,{(actionf_p1)A_Chase},S_BOSS_RUN5,0,0}, // S_BOSS_RUN4
    {SPR_BOSS,2,3,{(actionf_p1)A_Chase},S_BOSS_RUN6,0,0}, // S_BOSS_RUN5
    {SPR_BOSS,2,3,{(actionf_p1)A_Chase},S_BOSS_RUN7,0,0}, // S_BOSS_RUN6
    {SPR_BOSS,3,3,{(actionf_p1)A_Chase},S_BOSS_RUN8,0,0}, // S_BOSS_RUN7
    {SPR_BOSS,3,3,{(actionf_p1)A_Chase},S_BOSS_RUN1,0,0}, // S_BOSS_RUN8
    {SPR_BOSS,4,8,{(actionf_p1)A_FaceTarget},S_BOSS_ATK2,0,0}, // S_BOSS_ATK1
    {SPR_BOSS,5,8,{(actionf_p1)A_FaceTarget},S_BOSS_ATK3,0,0}, // S_BOSS_ATK2
    {SPR_BOSS,6,8,{(actionf_p1)A_BruisAttack},S_BOSS_RUN1,0,0}, // S_BOSS_ATK3
    {SPR_BOSS,7,2,{0},S_BOSS_PAIN2,0,0}, // S_BOSS_PAIN
    {SPR_BOSS,7,2,{(actionf_p1)A_Pain},S_BOSS_RUN1,0,0}, // S_BOSS_PAIN2
    {SPR_BOSS,8,8,{0},S_BOSS_DIE2,0,0}, // S_BOSS_DIE1
    {SPR_BOSS,9,8,{(actionf_p1)A_Scream},S_BOSS_DIE3,0,0}, // S_BOSS_DIE2
    {SPR_BOSS,10,8,{0},S_BOSS_DIE4,0,0}, // S_BOSS_DIE3
    {SPR_BOSS,11,8,{(actionf_p1)A_Fall},S_BOSS_DIE5,0,0}, // S_BOSS_DIE4
    {SPR_BOSS,12,8,{0},S_BOSS_DIE6,0,0}, // S_BOSS_DIE5
    {SPR_BOSS,13,8,{0},S_BOSS_DIE7,0,0}, // S_BOSS_DIE6
    {SPR_BOSS,14,-1,{(actionf_p1)A_BossDeath},S_NULL,0,0}, // S_BOSS_DIE7
    {SPR_BOSS,14,8,{0},S_BOSS_RAISE2,0,0}, // S_BOSS_RAISE1
    {SPR_BOSS,13,8,{0},S_BOSS_RAISE3,0,0}, // S_BOSS_RAISE2
    {SPR_BOSS,12,8,{0},S_BOSS_RAISE4,0,0}, // S_BOSS_RAISE3
    {SPR_BOSS,11,8,{0},S_BOSS_RAISE5,0,0}, // S_BOSS_RAISE4
    {SPR_BOSS,10,8,{0},S_BOSS_RAISE6,0,0}, // S_BOSS_RAISE5
    {SPR_BOSS,9,8,{0},S_BOSS_RAISE7,0,0}, // S_BOSS_RAISE6
    {SPR_BOSS,8,8,{0},S_BOSS_RUN1,0,0}, // S_BOSS_RAISE7
    {SPR_BOS2,0,10,{(actionf_p1)A_Look},S_BOS2_STND2,0,0}, // S_BOS2_STND
    {SPR_BOS2,1,10,{(actionf_p1)A_Look},S_BOS2_STND,0,0}, // S_BOS2_STND2
    {SPR_BOS2,0,3,{(actionf_p1)A_Chase},S_BOS2_RUN2,0,0}, // S_BOS2_RUN1
    {SPR_BOS2,0,3,{(actionf_p1)A_Chase},S_BOS2_RUN3,0,0}, // S_BOS2_RUN2
    {SPR_BOS2,1,3,{(actionf_p1)A_Chase},S_BOS2_RUN4,0,0}, // S_BOS2_RUN3
    {SPR_BOS2,1,3,{(actionf_p1)A_Chase},S_BOS2_RUN5,0,0}, // S_BOS2_RUN4
    {SPR_BOS2,2,3,{(actionf_p1)A_Chase},S_BOS2_RUN6,0,0}, // S_BOS2_RUN5
    {SPR_BOS2,2,3,{(actionf_p1)A_Chase},S_BOS2_RUN7,0,0}, // S_BOS2_RUN6
    {SPR_BOS2,3,3,{(actionf_p1)A_Chase},S_BOS2_RUN8,0,0}, // S_BOS2_RUN7
    {SPR_BOS2,3,3,{(actionf_p1)A_Chase},S_BOS2_RUN1,0,0}, // S_BOS2_RUN8
    {SPR_BOS2,4,8,{(actionf_p1)A_FaceTarget},S_BOS2_ATK2,0,0}, // S_BOS2_ATK1
    {SPR_BOS2,5,8,{(actionf_p1)A_FaceTarget},S_BOS2_ATK3,0,0}, // S_BOS2_ATK2
    {SPR_BOS2,6,8,{(actionf_p1)A_BruisAttack},S_BOS2_RUN1,0,0}, // S_BOS2_ATK3
    {SPR_BOS2,7,2,{0},S_BOS2_PAIN2,0,0}, // S_BOS2_PAIN
    {SPR_BOS2,7,2,{(actionf_p1)A_Pain},S_BOS2_RUN1,0,0}, // S_BOS2_PAIN2
    {SPR_BOS2,8,8,{0},S_BOS2_DIE2,0,0}, // S_BOS2_DIE1
    {SPR_BOS2,9,8,{(actionf_p1)A_Scream},S_BOS2_DIE3,0,0}, // S_BOS2_DIE2
    {SPR_BOS2,10,8,{0},S_BOS2_DIE4,0,0}, // S_BOS2_DIE3
    {SPR_BOS2,11,8,{(actionf_p1)A_Fall},S_BOS2_DIE5,0,0}, // S_BOS2_DIE4
    {SPR_BOS2,12,8,{0},S_BOS2_DIE6,0,0}, // S_BOS2_DIE5
    {SPR_BOS2,13,8,{0},S_BOS2_DIE7,0,0}, // S_BOS2_DIE6
    {SPR_BOS2,14,-1,{0},S_NULL,0,0}, // S_BOS2_DIE7
    {SPR_BOS2,14,8,{0},S_BOS2_RAISE2,0,0}, // S_BOS2_RAISE1
    {SPR_BOS2,13,8,{0},S_BOS2_RAISE3,0,0}, // S_BOS2_RAISE2
    {SPR_BOS2,12,8,{0},S_BOS2_RAISE4,0,0}, // S_BOS2_RAISE3
    {SPR_BOS2,11,8,{0},S_BOS2_RAISE5,0,0}, // S_BOS2_RAISE4
    {SPR_BOS2,10,8,{0},S_BOS2_RAISE6,0,0}, // S_BOS2_RAISE5
    {SPR_BOS2,9,8,{0},S_BOS2_RAISE7,0,0}, // S_BOS2_RAISE6
    {SPR_BOS2,8,8,{0},S_BOS2_RUN1,0,0}, // S_BOS2_RAISE7
    {SPR_SKUL,32768,10,{(actionf_p1)A_Look},S_SKULL_STND2,0,0}, // S_SKULL_STND
    {SPR_SKUL,32769,10,{(actionf_p1)A_Look},S_SKULL_STND,0,0}, // S_SKULL_STND2
    {SPR_SKUL,32768,6,{(actionf_p1)A_Chase},S_SKULL_RUN2,0,0}, // S_SKULL_RUN1
    {SPR_SKUL,32769,6,{(actionf_p1)A_Chase},S_SKULL_RUN1,0,0}, // S_SKULL_RUN2
    {SPR_SKUL,32770,10,{(actionf_p1)A_FaceTarget},S_SKULL_ATK2,0,0}, // S_SKULL_ATK1
    {SPR_SKUL,32771,4,{(actionf_p1)A_SkullAttack},S_SKULL_ATK3,0,0}, // S_SKULL_ATK2
    {SPR_SKUL,32770,4,{0},S_SKULL_ATK4,0,0}, // S_SKULL_ATK3
    {SPR_SKUL,32771,4,{0},S_SKULL_ATK3,0,0}, // S_SKULL_ATK4
    {SPR_SKUL,32772,3,{0},S_SKULL_PAIN2,0,0}, // S_SKULL_PAIN
    {SPR_SKUL,32772,3,{(actionf_p1)A_Pain},S_SKULL_RUN1,0,0}, // S_SKULL_PAIN2
    {SPR_SKUL,32773,6,{0},S_SKULL_DIE2,0,0}, // S_SKULL_DIE1
    {SPR_SKUL,32774,6,{(actionf_p1)A_Scream},S_SKULL_DIE3,0,0}, // S_SKULL_DIE2
    {SPR_SKUL,32775,6,{0},S_SKULL_DIE4,0,0}, // S_SKULL_DIE3
    {SPR_SKUL,32776,6,{(actionf_p1)A_Fall},S_SKULL_DIE5,0,0}, // S_SKULL_DIE4
    {SPR_SKUL,9,6,{0},S_SKULL_DIE6,0,0}, // S_SKULL_DIE5
    {SPR_SKUL,10,6,{0},S_NULL,0,0}, // S_SKULL_DIE6
    {SPR_SPID,0,10,{(actionf_p1)A_Look},S_SPID_STND2,0,0}, // S_SPID_STND
    {SPR_SPID,1,10,{(actionf_p1)A_Look},S_SPID_STND,0,0}, // S_SPID_STND2
    {SPR_SPID,0,3,{(actionf_p1)A_Metal},S_SPID_RUN2,0,0}, // S_SPID_RUN1
    {SPR_SPID,0,3,{(actionf_p1)A_Chase},S_SPID_RUN3,0,0}, // S_SPID_RUN2
    {SPR_SPID,1,3,{(actionf_p1)A_Chase},S_SPID_RUN4,0,0}, // S_SPID_RUN3
    {SPR_SPID,1,3,{(actionf_p1)A_Chase},S_SPID_RUN5,0,0}, // S_SPID_RUN4
    {SPR_SPID,2,3,{(actionf_p1)A_Metal},S_SPID_RUN6,0,0}, // S_SPID_RUN5
    {SPR_SPID,2,3,{(actionf_p1)A_Chase},S_SPID_RUN7,0,0}, // S_SPID_RUN6
    {SPR_SPID,3,3,{(actionf_p1)A_Chase},S_SPID_RUN8,0,0}, // S_SPID_RUN7
    {SPR_SPID,3,3,{(actionf_p1)A_Chase},S_SPID_RUN9,0,0}, // S_SPID_RUN8
    {SPR_SPID,4,3,{(actionf_p1)A_Metal},S_SPID_RUN10,0,0}, // S_SPID_RUN9
    {SPR_SPID,4,3,{(actionf_p1)A_Chase},S_SPID_RUN11,0,0}, // S_SPID_RUN10
    {SPR_SPID,5,3,{(actionf_p1)A_Chase},S_SPID_RUN12,0,0}, // S_SPID_RUN11
    {SPR_SPID,5,3,{(actionf_p1)A_Chase},S_SPID_RUN1,0,0}, // S_SPID_RUN12
    {SPR_SPID,32768,20,{(actionf_p1)A_FaceTarget},S_SPID_ATK2,0,0}, // S_SPID_ATK1
    {SPR_SPID,32774,4,{(actionf_p1)A_SPosAttack},S_SPID_ATK3,0,0}, // S_SPID_ATK2
    {SPR_SPID,32775,4,{(actionf_p1)A_SPosAttack},S_SPID_ATK4,0,0}, // S_SPID_ATK3
    {SPR_SPID,32775,1,{(actionf_p1)A_SpidRefire},S_SPID_ATK2,0,0}, // S_SPID_ATK4
    {SPR_SPID,8,3,{0},S_SPID_PAIN2,0,0}, // S_SPID_PAIN
    {SPR_SPID,8,3,{(actionf_p1)A_Pain},S_SPID_RUN1,0,0}, // S_SPID_PAIN2
    {SPR_SPID,9,20,{(actionf_p1)A_Scream},S_SPID_DIE2,0,0}, // S_SPID_DIE1
    {SPR_SPID,10,10,{(actionf_p1)A_Fall},S_SPID_DIE3,0,0}, // S_SPID_DIE2
    {SPR_SPID,11,10,{0},S_SPID_DIE4,0,0}, // S_SPID_DIE3
    {SPR_SPID,12,10,{0},S_SPID_DIE5,0,0}, // S_SPID_DIE4
    {SPR_SPID,13,10,{0},S_SPID_DIE6,0,0}, // S_SPID_DIE5
    {SPR_SPID,14,10,{0},S_SPID_DIE7,0,0}, // S_SPID_DIE6
    {SPR_SPID,15,10,{0},S_SPID_DIE8,0,0}, // S_SPID_DIE7
    {SPR_SPID,16,10,{0},S_SPID_DIE9,0,0}, // S_SPID_DIE8
    {SPR_SPID,17,10,{0},S_SPID_DIE10,0,0}, // S_SPID_DIE9
    {SPR_SPID,18,30,{0},S_SPID_DIE11,0,0}, // S_SPID_DIE10
    {SPR_SPID,18,-1,{(actionf_p1)A_BossDeath},S_NULL,0,0}, // S_SPID_DIE11
    {SPR_BSPI,0,10,{(actionf_p1)A_Look},S_BSPI_STND2,0,0}, // S_BSPI_STND
    {SPR_BSPI,1,10,{(actionf_p1)A_Look},S_BSPI_STND,0,0}, // S_BSPI_STND2
    {SPR_BSPI,0,20,{0},S_BSPI_RUN1,0,0}, // S_BSPI_SIGHT
    {SPR_BSPI,0,3,{(actionf_p1)A_BabyMetal},S_BSPI_RUN2,0,0}, // S_BSPI_RUN1
    {SPR_BSPI,0,3,{(actionf_p1)A_Chase},S_BSPI_RUN3,0,0}, // S_BSPI_RUN2
    {SPR_BSPI,1,3,{(actionf_p1)A_Chase},S_BSPI_RUN4,0,0}, // S_BSPI_RUN3
    {SPR_BSPI,1,3,{(actionf_p1)A_Chase},S_BSPI_RUN5,0,0}, // S_BSPI_RUN4
    {SPR_BSPI,2,3,{(actionf_p1)A_Chase},S_BSPI_RUN6,0,0}, // S_BSPI_RUN5
    {SPR_BSPI,2,3,{(actionf_p1)A_Chase},S_BSPI_RUN7,0,0}, // S_BSPI_RUN6
    {SPR_BSPI,3,3,{(actionf_p1)A_BabyMetal},S_BSPI_RUN8,0,0}, // S_BSPI_RUN7
    {SPR_BSPI,3,3,{(actionf_p1)A_Chase},S_BSPI_RUN9,0,0}, // S_BSPI_RUN8
    {SPR_BSPI,4,3,{(actionf_p1)A_Chase},S_BSPI_RUN10,0,0}, // S_BSPI_RUN9
    {SPR_BSPI,4,3,{(actionf_p1)A_Chase},S_BSPI_RUN11,0,0}, // S_BSPI_RUN10
    {SPR_BSPI,5,3,{(actionf_p1)A_Chase},S_BSPI_RUN12,0,0}, // S_BSPI_RUN11
    {SPR_BSPI,5,3,{(actionf_p1)A_Chase},S_BSPI_RUN1,0,0}, // S_BSPI_RUN12
    {SPR_BSPI,32768,20,{(actionf_p1)A_FaceTarget},S_BSPI_ATK2,0,0}, // S_BSPI_ATK1
    {SPR_BSPI,32774,4,{(actionf_p1)A_BspiAttack},S_BSPI_ATK3,0,0}, // S_BSPI_ATK2
    {SPR_BSPI,32775,4,{0},S_BSPI_ATK4,0,0}, // S_BSPI_ATK3
    {SPR_BSPI,32775,1,{(actionf_p1)A_SpidRefire},S_BSPI_ATK2,0,0}, // S_BSPI_ATK4
    {SPR_BSPI,8,3,{0},S_BSPI_PAIN2,0,0}, // S_BSPI_PAIN
    {SPR_BSPI,8,3,{(actionf_p1)A_Pain},S_BSPI_RUN1,0,0}, // S_BSPI_PAIN2
    {SPR_BSPI,9,20,{(actionf_p1)A_Scream},S_BSPI_DIE2,0,0}, // S_BSPI_DIE1
    {SPR_BSPI,10,7,{(actionf_p1)A_Fall},S_BSPI_DIE3,0,0}, // S_BSPI_DIE2
    {SPR_BSPI,11,7,{0},S_BSPI_DIE4,0,0}, // S_BSPI_DIE3
    {SPR_BSPI,12,7,{0},S_BSPI_DIE5,0,0}, // S_BSPI_DIE4
    {SPR_BSPI,13,7,{0},S_BSPI_DIE6,0,0}, // S_BSPI_DIE5
    {SPR_BSPI,14,7,{0},S_BSPI_DIE7,0,0}, // S_BSPI_DIE6
    {SPR_BSPI,15,-1,{(actionf_p1)A_BossDeath},S_NULL,0,0}, // S_BSPI_DIE7
    {SPR_BSPI,15,5,{0},S_BSPI_RAISE2,0,0}, // S_BSPI_RAISE1
    {SPR_BSPI,14,5,{0},S_BSPI_RAISE3,0,0}, // S_BSPI_RAISE2
    {SPR_BSPI,13,5,{0},S_BSPI_RAISE4,0,0}, // S_BSPI_RAISE3
    {SPR_BSPI,12,5,{0},S_BSPI_RAISE5,0,0}, // S_BSPI_RAISE4
    {SPR_BSPI,11,5,{0},S_BSPI_RAISE6,0,0}, // S_BSPI_RAISE5
    {SPR_BSPI,10,5,{0},S_BSPI_RAISE7,0,0}, // S_BSPI_RAISE6
    {SPR_BSPI,9,5,{0},S_BSPI_RUN1,0,0}, // S_BSPI_RAISE7
    {SPR_APLS,32768,5,{0},S_ARACH_PLAZ2,0,0}, // S_ARACH_PLAZ
    {SPR_APLS,32769,5,{0},S_ARACH_PLAZ,0,0}, // S_ARACH_PLAZ2
    {SPR_APBX,32768,5,{0},S_ARACH_PLEX2,0,0}, // S_ARACH_PLEX
    {SPR_APBX,32769,5,{0},S_ARACH_PLEX3,0,0}, // S_ARACH_PLEX2
    {SPR_APBX,32770,5,{0},S_ARACH_PLEX4,0,0}, // S_ARACH_PLEX3
    {SPR_APBX,32771,5,{0},S_ARACH_PLEX5,0,0}, // S_ARACH_PLEX4
    {SPR_APBX,32772,5,{0},S_NULL,0,0}, // S_ARACH_PLEX5
    {SPR_CYBR,0,10,{(actionf_p1)A_Look},S_CYBER_STND2,0,0}, // S_CYBER_STND
    {SPR_CYBR,1,10,{(actionf_p1)A_Look},S_CYBER_STND,0,0}, // S_CYBER_STND2
    {SPR_CYBR,0,3,{(actionf_p1)A_Hoof},S_CYBER_RUN2,0,0}, // S_CYBER_RUN1
    {SPR_CYBR,0,3,{(actionf_p1)A_Chase},S_CYBER_RUN3,0,0}, // S_CYBER_RUN2
    {SPR_CYBR,1,3,{(actionf_p1)A_Chase},S_CYBER_RUN4,0,0}, // S_CYBER_RUN3
    {SPR_CYBR,1,3,{(actionf_p1)A_Chase},S_CYBER_RUN5,0,0}, // S_CYBER_RUN4
    {SPR_CYBR,2,3,{(actionf_p1)A_Chase},S_CYBER_RUN6,0,0}, // S_CYBER_RUN5
    {SPR_CYBR,2,3,{(actionf_p1)A_Chase},S_CYBER_RUN7,0,0}, // S_CYBER_RUN6
    {SPR_CYBR,3,3,{(actionf_p1)A_Metal},S_CYBER_RUN8,0,0}, // S_CYBER_RUN7
    {SPR_CYBR,3,3,{(actionf_p1)A_Chase},S_CYBER_RUN1,0,0}, // S_CYBER_RUN8
    {SPR_CYBR,4,6,{(actionf_p1)A_FaceTarget},S_CYBER_ATK2,0,0}, // S_CYBER_ATK1
    {SPR_CYBR,5,12,{(actionf_p1)A_CyberAttack},S_CYBER_ATK3,0,0}, // S_CYBER_ATK2
    {SPR_CYBR,4,12,{(actionf_p1)A_FaceTarget},S_CYBER_ATK4,0,0}, // S_CYBER_ATK3
    {SPR_CYBR,5,12,{(actionf_p1)A_CyberAttack},S_CYBER_ATK5,0,0}, // S_CYBER_ATK4
    {SPR_CYBR,4,12,{(actionf_p1)A_FaceTarget},S_CYBER_ATK6,0,0}, // S_CYBER_ATK5
    {SPR_CYBR,5,12,{(actionf_p1)A_CyberAttack},S_CYBER_RUN1,0,0}, // S_CYBER_ATK6
    {SPR_CYBR,6,10,{(actionf_p1)A_Pain},S_CYBER_RUN1,0,0}, // S_CYBER_PAIN
    {SPR_CYBR,7,10,{0},S_CYBER_DIE2,0,0}, // S_CYBER_DIE1
    {SPR_CYBR,8,10,{(actionf_p1)A_Scream},S_CYBER_DIE3,0,0}, // S_CYBER_DIE2
    {SPR_CYBR,9,10,{0},S_CYBER_DIE4,0,0}, // S_CYBER_DIE3
    {SPR_CYBR,10,10,{0},S_CYBER_DIE5,0,0}, // S_CYBER_DIE4
    {SPR_CYBR,11,10,{0},S_CYBER_DIE6,0,0}, // S_CYBER_DIE5
    {SPR_CYBR,12,10,{(actionf_p1)A_Fall},S_CYBER_DIE7,0,0}, // S_CYBER_DIE6
    {SPR_CYBR,13,10,{0},S_CYBER_DIE8,0,0}, // S_CYBER_DIE7
    {SPR_CYBR,14,10,{0},S_CYBER_DIE9,0,0}, // S_CYBER_DIE8
    {SPR_CYBR,15,30,{0},S_CYBER_DIE10,0,0}, // S_CYBER_DIE9
    {SPR_CYBR,15,-1,{(actionf_p1)A_BossDeath},S_NULL,0,0}, // S_CYBER_DIE10
    {SPR_PAIN,0,10,{(actionf_p1)A_Look},S_PAIN_STND,0,0}, // S_PAIN_STND
    {SPR_PAIN,0,3,{(actionf_p1)A_Chase},S_PAIN_RUN2,0,0}, // S_PAIN_RUN1
    {SPR_PAIN,0,3,{(actionf_p1)A_Chase},S_PAIN_RUN3,0,0}, // S_PAIN_RUN2
    {SPR_PAIN,1,3,{(actionf_p1)A_Chase},S_PAIN_RUN4,0,0}, // S_PAIN_RUN3
    {SPR_PAIN,1,3,{(actionf_p1)A_Chase},S_PAIN_RUN5,0,0}, // S_PAIN_RUN4
    {SPR_PAIN,2,3,{(actionf_p1)A_Chase},S_PAIN_RUN6,0,0}, // S_PAIN_RUN5
    {SPR_PAIN,2,3,{(actionf_p1)A_Chase},S_PAIN_RUN1,0,0}, // S_PAIN_RUN6
    {SPR_PAIN,3,5,{(actionf_p1)A_FaceTarget},S_PAIN_ATK2,0,0}, // S_PAIN_ATK1
    {SPR_PAIN,4,5,{(actionf_p1)A_FaceTarget},S_PAIN_ATK3,0,0}, // S_PAIN_ATK2
    {SPR_PAIN,32773,5,{(actionf_p1)A_FaceTarget},S_PAIN_ATK4,0,0}, // S_PAIN_ATK3
    {SPR_PAIN,32773,0,{(actionf_p1)A_PainAttack},S_PAIN_RUN1,0,0}, // S_PAIN_ATK4
    {SPR_PAIN,6,6,{0},S_PAIN_PAIN2,0,0}, // S_PAIN_PAIN
    {SPR_PAIN,6,6,{(actionf_p1)A_Pain},S_PAIN_RUN1,0,0}, // S_PAIN_PAIN2
    {SPR_PAIN,32775,8,{0},S_PAIN_DIE2,0,0}, // S_PAIN_DIE1
    {SPR_PAIN,32776,8,{(actionf_p1)A_Scream},S_PAIN_DIE3,0,0}, // S_PAIN_DIE2
    {SPR_PAIN,32777,8,{0},S_PAIN_DIE4,0,0}, // S_PAIN_DIE3
    {SPR_PAIN,32778,8,{0},S_PAIN_DIE5,0,0}, // S_PAIN_DIE4
    {SPR_PAIN,32779,8,{(actionf_p1)A_PainDie},S_PAIN_DIE6,0,0}, // S_PAIN_DIE5
    {SPR_PAIN,32780,8,{0},S_NULL,0,0}, // S_PAIN_DIE6
    {SPR_PAIN,12,8,{0},S_PAIN_RAISE2,0,0}, // S_PAIN_RAISE1
    {SPR_PAIN,11,8,{0},S_PAIN_RAISE3,0,0}, // S_PAIN_RAISE2
    {SPR_PAIN,10,8,{0},S_PAIN_RAISE4,0,0}, // S_PAIN_RAISE3
    {SPR_PAIN,9,8,{0},S_PAIN_RAISE5,0,0}, // S_PAIN_RAISE4
    {SPR_PAIN,8,8,{0},S_PAIN_RAISE6,0,0}, // S_PAIN_RAISE5
    {SPR_PAIN,7,8,{0},S_PAIN_RUN1,0,0}, // S_PAIN_RAISE6
    {SPR_SSWV,0,10,{(actionf_p1)A_Look},S_SSWV_STND2,0,0}, // S_SSWV_STND
    {SPR_SSWV,1,10,{(actionf_p1)A_Look},S_SSWV_STND,0,0}, // S_SSWV_STND2
    {SPR_SSWV,0,3,{(actionf_p1)A_Chase},S_SSWV_RUN2,0,0}, // S_SSWV_RUN1
    {SPR_SSWV,0,3,{(actionf_p1)A_Chase},S_SSWV_RUN3,0,0}, // S_SSWV_RUN2
    {SPR_SSWV,1,3,{(actionf_p1)A_Chase},S_SSWV_RUN4,0,0}, // S_SSWV_RUN3
    {SPR_SSWV,1,3,{(actionf_p1)A_Chase},S_SSWV_RUN5,0,0}, // S_SSWV_RUN4
    {SPR_SSWV,2,3,{(actionf_p1)A_Chase},S_SSWV_RUN6,0,0}, // S_SSWV_RUN5
    {SPR_SSWV,2,3,{(actionf_p1)A_Chase},S_SSWV_RUN7,0,0}, // S_SSWV_RUN6
    {SPR_SSWV,3,3,{(actionf_p1)A_Chase},S_SSWV_RUN8,0,0}, // S_SSWV_RUN7
    {SPR_SSWV,3,3,{(actionf_p1)A_Chase},S_SSWV_RUN1,0,0}, // S_SSWV_RUN8
    {SPR_SSWV,4,10,{(actionf_p1)A_FaceTarget},S_SSWV_ATK2,0,0}, // S_SSWV_ATK1
    {SPR_SSWV,5,10,{(actionf_p1)A_FaceTarget},S_SSWV_ATK3,0,0}, // S_SSWV_ATK2
    {SPR_SSWV,32774,4,{(actionf_p1)A_CPosAttack},S_SSWV_ATK4,0,0}, // S_SSWV_ATK3
    {SPR_SSWV,5,6,{(actionf_p1)A_FaceTarget},S_SSWV_ATK5,0,0}, // S_SSWV_ATK4
    {SPR_SSWV,32774,4,{(actionf_p1)A_CPosAttack},S_SSWV_ATK6,0,0}, // S_SSWV_ATK5
    {SPR_SSWV,5,1,{(actionf_p1)A_CPosRefire},S_SSWV_ATK2,0,0}, // S_SSWV_ATK6
    {SPR_SSWV,7,3,{0},S_SSWV_PAIN2,0,0}, // S_SSWV_PAIN
    {SPR_SSWV,7,3,{(actionf_p1)A_Pain},S_SSWV_RUN1,0,0}, // S_SSWV_PAIN2
    {SPR_SSWV,8,5,{0},S_SSWV_DIE2,0,0}, // S_SSWV_DIE1
    {SPR_SSWV,9,5,{(actionf_p1)A_Scream},S_SSWV_DIE3,0,0}, // S_SSWV_DIE2
    {SPR_SSWV,10,5,{(actionf_p1)A_Fall},S_SSWV_DIE4,0,0}, // S_SSWV_DIE3
    {SPR_SSWV,11,5,{0},S_SSWV_DIE5,0,0}, // S_SSWV_DIE4
    {SPR_SSWV,12,-1,{0},S_NULL,0,0}, // S_SSWV_DIE5
    {SPR_SSWV,13,5,{0},S_SSWV_XDIE2,0,0}, // S_SSWV_XDIE1
    {SPR_SSWV,14,5,{(actionf_p1)A_XScream},S_SSWV_XDIE3,0,0}, // S_SSWV_XDIE2
    {SPR_SSWV,15,5,{(actionf_p1)A_Fall},S_SSWV_XDIE4,0,0}, // S_SSWV_XDIE3
    {SPR_SSWV,16,5,{0},S_SSWV_XDIE5,0,0}, // S_SSWV_XDIE4
    {SPR_SSWV,17,5,{0},S_SSWV_XDIE6,0,0}, // S_SSWV_XDIE5
    {SPR_SSWV,18,5,{0},S_SSWV_XDIE7,0,0}, // S_SSWV_XDIE6
    {SPR_SSWV,19,5,{0},S_SSWV_XDIE8,0,0}, // S_SSWV_XDIE7
    {SPR_SSWV,20,5,{0},S_SSWV_XDIE9,0,0}, // S_SSWV_XDIE8
    {SPR_SSWV,21,-1,{0},S_NULL,0,0}, // S_SSWV_XDIE9
    {SPR_SSWV,12,5,{0},S_SSWV_RAISE2,0,0}, // S_SSWV_RAISE1
    {SPR_SSWV,11,5,{0},S_SSWV_RAISE3,0,0}, // S_SSWV_RAISE2
    {SPR_SSWV,10,5,{0},S_SSWV_RAISE4,0,0}, // S_SSWV_RAISE3
    {SPR_SSWV,9,5,{0},S_SSWV_RAISE5,0,0}, // S_SSWV_RAISE4
    {SPR_SSWV,8,5,{0},S_SSWV_RUN1,0,0}, // S_SSWV_RAISE5
    {SPR_KEEN,0,-1,{0},S_KEENSTND,0,0}, // S_KEENSTND
    {SPR_KEEN,0,6,{0},S_COMMKEEN2,0,0}, // S_COMMKEEN
    {SPR_KEEN,1,6,{0},S_COMMKEEN3,0,0}, // S_COMMKEEN2
    {SPR_KEEN,2,6,{(actionf_p1)A_Scream},S_COMMKEEN4,0,0}, // S_COMMKEEN3
    {SPR_KEEN,3,6,{0},S_COMMKEEN5,0,0}, // S_COMMKEEN4
    {SPR_KEEN,4,6,{0},S_COMMKEEN6,0,0}, // S_COMMKEEN5
    {SPR_KEEN,5,6,{0},S_COMMKEEN7,0,0}, // S_COMMKEEN6
    {SPR_KEEN,6,6,{0},S_COMMKEEN8,0,0}, // S_COMMKEEN7
    {SPR_KEEN,7,6,{0},S_COMMKEEN9,0,0}, // S_COMMKEEN8
    {SPR_KEEN,8,6,{0},S_COMMKEEN10,0,0}, // S_COMMKEEN9
    {SPR_KEEN,9,6,{0},S_COMMKEEN11,0,0}, // S_COMMKEEN10
    {SPR_KEEN,10,6,{(actionf_p1)A_KeenDie},S_COMMKEEN12,0,0},// S_COMMKEEN11
    {SPR_KEEN,11,-1,{0},S_NULL,0,0}, // S_COMMKEEN12
    {SPR_KEEN,12,4,{0},S_KEENPAIN2,0,0}, // S_KEENPAIN
    {SPR_KEEN,12,8,{(actionf_p1)A_Pain},S_KEENSTND,0,0}, // S_KEENPAIN2
    {SPR_BBRN,0,-1,{0},S_NULL,0,0}, // S_BRAIN
    {SPR_BBRN,1,36,{(actionf_p1)A_BrainPain},S_BRAIN,0,0}, // S_BRAIN_PAIN
    {SPR_BBRN,0,100,{(actionf_p1)A_BrainScream},S_BRAIN_DIE2,0,0}, // S_BRAIN_DIE1
    {SPR_BBRN,0,10,{0},S_BRAIN_DIE3,0,0}, // S_BRAIN_DIE2
    {SPR_BBRN,0,10,{0},S_BRAIN_DIE4,0,0}, // S_BRAIN_DIE3
    {SPR_BBRN,0,-1,{(actionf_p1)A_BrainDie},S_NULL,0,0}, // S_BRAIN_DIE4
    {SPR_SSWV,0,10,{(actionf_p1)A_Look},S_BRAINEYE,0,0}, // S_BRAINEYE
    {SPR_SSWV,0,181,{(actionf_p1)A_BrainAwake},S_BRAINEYE1,0,0}, // S_BRAINEYESEE
    {SPR_SSWV,0,150,{(actionf_p1)A_BrainSpit},S_BRAINEYE1,0,0}, // S_BRAINEYE1
    {SPR_BOSF,32768,3,{(actionf_p1)A_SpawnSound},S_SPAWN2,0,0}, // S_SPAWN1
    {SPR_BOSF,32769,3,{(actionf_p1)A_SpawnFly},S_SPAWN3,0,0}, // S_SPAWN2
    {SPR_BOSF,32770,3,{(actionf_p1)A_SpawnFly},S_SPAWN4,0,0}, // S_SPAWN3
    {SPR_BOSF,32771,3,{(actionf_p1)A_SpawnFly},S_SPAWN1,0,0}, // S_SPAWN4
    {SPR_FIRE,32768,4,{(actionf_p1)A_Fire},S_SPAWNFIRE2,0,0}, // S_SPAWNFIRE1
    {SPR_FIRE,32769,4,{(actionf_p1)A_Fire},S_SPAWNFIRE3,0,0}, // S_SPAWNFIRE2
    {SPR_FIRE,32770,4,{(actionf_p1)A_Fire},S_SPAWNFIRE4,0,0}, // S_SPAWNFIRE3
    {SPR_FIRE,32771,4,{(actionf_p1)A_Fire},S_SPAWNFIRE5,0,0}, // S_SPAWNFIRE4
    {SPR_FIRE,32772,4,{(actionf_p1)A_Fire},S_SPAWNFIRE6,0,0}, // S_SPAWNFIRE5
    {SPR_FIRE,32773,4,{(actionf_p1)A_Fire},S_SPAWNFIRE7,0,0}, // S_SPAWNFIRE6
    {SPR_FIRE,32774,4,{(actionf_p1)A_Fire},S_SPAWNFIRE8,0,0}, // S_SPAWNFIRE7
    {SPR_FIRE,32775,4,{(actionf_p1)A_Fire},S_NULL,0,0}, // S_SPAWNFIRE8
    {SPR_MISL,32769,10,{0},S_BRAINEXPLODE2,0,0}, // S_BRAINEXPLODE1
    {SPR_MISL,32770,10,{0},S_BRAINEXPLODE3,0,0}, // S_BRAINEXPLODE2
    {SPR_MISL,32771,10,{(actionf_p1)A_BrainExplode},S_NULL,0,0}, // S_BRAINEXPLODE3
    {SPR_ARM1,0,6,{0},S_ARM1A,0,0}, // S_ARM1
    {SPR_ARM1,32769,7,{0},S_ARM1,0,0}, // S_ARM1A
    {SPR_ARM2,0,6,{0},S_ARM2A,0,0}, // S_ARM2
    {SPR_ARM2,32769,6,{0},S_ARM2,0,0}, // S_ARM2A
    {SPR_BAR1,0,6,{0},S_BAR2,0,0}, // S_BAR1
    {SPR_BAR1,1,6,{0},S_BAR1,0,0}, // S_BAR2
    {SPR_BEXP,32768,5,{0},S_BEXP2,0,0}, // S_BEXP
    {SPR_BEXP,32769,5,{(actionf_p1)A_Scream},S_BEXP3,0,0}, // S_BEXP2
    {SPR_BEXP,32770,5,{0},S_BEXP4,0,0}, // S_BEXP3
    {SPR_BEXP,32771,10,{(actionf_p1)A_Explode},S_BEXP5,0,0}, // S_BEXP4
    {SPR_BEXP,32772,10,{0},S_NULL,0,0}, // S_BEXP5
    {SPR_FCAN,32768,4,{0},S_BBAR2,0,0}, // S_BBAR1
    {SPR_FCAN,32769,4,{0},S_BBAR3,0,0}, // S_BBAR2
    {SPR_FCAN,32770,4,{0},S_BBAR1,0,0}, // S_BBAR3
    {SPR_BON1,0,6,{0},S_BON1A,0,0}, // S_BON1
    {SPR_BON1,1,6,{0},S_BON1B,0,0}, // S_BON1A
    {SPR_BON1,2,6,{0},S_BON1C,0,0}, // S_BON1B
    {SPR_BON1,3,6,{0},S_BON1D,0,0}, // S_BON1C
    {SPR_BON1,2,6,{0},S_BON1E,0,0}, // S_BON1D
    {SPR_BON1,1,6,{0},S_BON1,0,0}, // S_BON1E
    {SPR_BON2,0,6,{0},S_BON2A,0,0}, // S_BON2
    {SPR_BON2,1,6,{0},S_BON2B,0,0}, // S_BON2A
    {SPR_BON2,2,6,{0},S_BON2C,0,0}, // S_BON2B
    {SPR_BON2,3,6,{0},S_BON2D,0,0}, // S_BON2C
    {SPR_BON2,2,6,{0},S_BON2E,0,0}, // S_BON2D
    {SPR_BON2,1,6,{0},S_BON2,0,0}, // S_BON2E
    {SPR_BKEY,0,10,{0},S_BKEY2,0,0}, // S_BKEY
    {SPR_BKEY,32769,10,{0},S_BKEY,0,0}, // S_BKEY2
    {SPR_RKEY,0,10,{0},S_RKEY2,0,0}, // S_RKEY
    {SPR_RKEY,32769,10,{0},S_RKEY,0,0}, // S_RKEY2
    {SPR_YKEY,0,10,{0},S_YKEY2,0,0}, // S_YKEY
    {SPR_YKEY,32769,10,{0},S_YKEY,0,0}, // S_YKEY2
    {SPR_BSKU,0,10,{0},S_BSKULL2,0,0}, // S_BSKULL
    {SPR_BSKU,32769,10,{0},S_BSKULL,0,0}, // S_BSKULL2
    {SPR_RSKU,0,10,{0},S_RSKULL2,0,0}, // S_RSKULL
    {SPR_RSKU,32769,10,{0},S_RSKULL,0,0}, // S_RSKULL2
    {SPR_YSKU,0,10,{0},S_YSKULL2,0,0}, // S_YSKULL
    {SPR_YSKU,32769,10,{0},S_YSKULL,0,0}, // S_YSKULL2
    {SPR_STIM,0,-1,{0},S_NULL,0,0}, // S_STIM
    {SPR_MEDI,0,-1,{0},S_NULL,0,0}, // S_MEDI
    {SPR_SOUL,32768,6,{0},S_SOUL2,0,0}, // S_SOUL
    {SPR_SOUL,32769,6,{0},S_SOUL3,0,0}, // S_SOUL2
    {SPR_SOUL,32770,6,{0},S_SOUL4,0,0}, // S_SOUL3
    {SPR_SOUL,32771,6,{0},S_SOUL5,0,0}, // S_SOUL4
    {SPR_SOUL,32770,6,{0},S_SOUL6,0,0}, // S_SOUL5
    {SPR_SOUL,32769,6,{0},S_SOUL,0,0}, // S_SOUL6
    {SPR_PINV,32768,6,{0},S_PINV2,0,0}, // S_PINV
    {SPR_PINV,32769,6,{0},S_PINV3,0,0}, // S_PINV2
    {SPR_PINV,32770,6,{0},S_PINV4,0,0}, // S_PINV3
    {SPR_PINV,32771,6,{0},S_PINV,0,0}, // S_PINV4
    {SPR_PSTR,32768,-1,{0},S_NULL,0,0}, // S_PSTR
    {SPR_PINS,32768,6,{0},S_PINS2,0,0}, // S_PINS
    {SPR_PINS,32769,6,{0},S_PINS3,0,0}, // S_PINS2
    {SPR_PINS,32770,6,{0},S_PINS4,0,0}, // S_PINS3
    {SPR_PINS,32771,6,{0},S_PINS,0,0}, // S_PINS4
    {SPR_MEGA,32768,6,{0},S_MEGA2,0,0}, // S_MEGA
    {SPR_MEGA,32769,6,{0},S_MEGA3,0,0}, // S_MEGA2
    {SPR_MEGA,32770,6,{0},S_MEGA4,0,0}, // S_MEGA3
    {SPR_MEGA,32771,6,{0},S_MEGA,0,0}, // S_MEGA4
    {SPR_SUIT,32768,-1,{0},S_NULL,0,0}, // S_SUIT
    {SPR_PMAP,32768,6,{0},S_PMAP2,0,0}, // S_PMAP
    {SPR_PMAP,32769,6,{0},S_PMAP3,0,0}, // S_PMAP2
    {SPR_PMAP,32770,6,{0},S_PMAP4,0,0}, // S_PMAP3
    {SPR_PMAP,32771,6,{0},S_PMAP5,0,0}, // S_PMAP4
    {SPR_PMAP,32770,6,{0},S_PMAP6,0,0}, // S_PMAP5
    {SPR_PMAP,32769,6,{0},S_PMAP,0,0}, // S_PMAP6
    {SPR_PVIS,32768,6,{0},S_PVIS2,0,0}, // S_PVIS
    {SPR_PVIS,1,6,{0},S_PVIS,0,0}, // S_PVIS2
    {SPR_CLIP,0,-1,{0},S_NULL,0,0}, // S_CLIP
    {SPR_AMMO,0,-1,{0},S_NULL,0,0}, // S_AMMO
    {SPR_ROCK,0,-1,{0},S_NULL,0,0}, // S_ROCK
    {SPR_BROK,0,-1,{0},S_NULL,0,0}, // S_BROK
    {SPR_CELL,0,-1,{0},S_NULL,0,0}, // S_CELL
    {SPR_CELP,0,-1,{0},S_NULL,0,0}, // S_CELP
    {SPR_SHEL,0,-1,{0},S_NULL,0,0}, // S_SHEL
    {SPR_SBOX,0,-1,{0},S_NULL,0,0}, // S_SBOX
    {SPR_BPAK,0,-1,{0},S_NULL,0,0}, // S_BPAK
    {SPR_BFUG,0,-1,{0},S_NULL,0,0}, // S_BFUG
    {SPR_MGUN,0,-1,{0},S_NULL,0,0}, // S_MGUN
    {SPR_CSAW,0,-1,{0},S_NULL,0,0}, // S_CSAW
    {SPR_LAUN,0,-1,{0},S_NULL,0,0}, // S_LAUN
    {SPR_PLAS,0,-1,{0},S_NULL,0,0}, // S_PLAS
    {SPR_SHOT,0,-1,{0},S_NULL,0,0}, // S_SHOT
    {SPR_SGN2,0,-1,{0},S_NULL,0,0}, // S_SHOT2
    {SPR_COLU,32768,-1,{0},S_NULL,0,0}, // S_COLU
    {SPR_SMT2,0,-1,{0},S_NULL,0,0}, // S_STALAG
    {SPR_GOR1,0,10,{0},S_BLOODYTWITCH2,0,0}, // S_BLOODYTWITCH
    {SPR_GOR1,1,15,{0},S_BLOODYTWITCH3,0,0}, // S_BLOODYTWITCH2
    {SPR_GOR1,2,8,{0},S_BLOODYTWITCH4,0,0}, // S_BLOODYTWITCH3
    {SPR_GOR1,1,6,{0},S_BLOODYTWITCH,0,0}, // S_BLOODYTWITCH4
    {SPR_PLAY,13,-1,{0},S_NULL,0,0}, // S_DEADTORSO
    {SPR_PLAY,18,-1,{0},S_NULL,0,0}, // S_DEADBOTTOM
    {SPR_POL2,0,-1,{0},S_NULL,0,0}, // S_HEADSONSTICK
    {SPR_POL5,0,-1,{0},S_NULL,0,0}, // S_GIBS
    {SPR_POL4,0,-1,{0},S_NULL,0,0}, // S_HEADONASTICK
    {SPR_POL3,32768,6,{0},S_HEADCANDLES2,0,0}, // S_HEADCANDLES
    {SPR_POL3,32769,6,{0},S_HEADCANDLES,0,0}, // S_HEADCANDLES2
    {SPR_POL1,0,-1,{0},S_NULL,0,0}, // S_DEADSTICK
    {SPR_POL6,0,6,{0},S_LIVESTICK2,0,0}, // S_LIVESTICK
    {SPR_POL6,1,8,{0},S_LIVESTICK,0,0}, // S_LIVESTICK2
    {SPR_GOR2,0,-1,{0},S_NULL,0,0}, // S_MEAT2
    {SPR_GOR3,0,-1,{0},S_NULL,0,0}, // S_MEAT3
    {SPR_GOR4,0,-1,{0},S_NULL,0,0}, // S_MEAT4
    {SPR_GOR5,0,-1,{0},S_NULL,0,0}, // S_MEAT5
    {SPR_SMIT,0,-1,{0},S_NULL,0,0}, // S_STALAGTITE
    {SPR_COL1,0,-1,{0},S_NULL,0,0}, // S_TALLGRNCOL
    {SPR_COL2,0,-1,{0},S_NULL,0,0}, // S_SHRTGRNCOL
    {SPR_COL3,0,-1,{0},S_NULL,0,0}, // S_TALLREDCOL
    {SPR_COL4,0,-1,{0},S_NULL,0,0}, // S_SHRTREDCOL
    {SPR_CAND,32768,-1,{0},S_NULL,0,0}, // S_CANDLESTIK
    {SPR_CBRA,32768,-1,{0},S_NULL,0,0}, // S_CANDELABRA
    {SPR_COL6,0,-1,{0},S_NULL,0,0}, // S_SKULLCOL
    {SPR_TRE1,0,-1,{0},S_NULL,0,0}, // S_TORCHTREE
    {SPR_TRE2,0,-1,{0},S_NULL,0,0}, // S_BIGTREE
    {SPR_ELEC,0,-1,{0},S_NULL,0,0}, // S_TECHPILLAR
    {SPR_CEYE,32768,6,{0},S_EVILEYE2,0,0}, // S_EVILEYE
    {SPR_CEYE,32769,6,{0},S_EVILEYE3,0,0}, // S_EVILEYE2
    {SPR_CEYE,32770,6,{0},S_EVILEYE4,0,0}, // S_EVILEYE3
    {SPR_CEYE,32769,6,{0},S_EVILEYE,0,0}, // S_EVILEYE4
    {SPR_FSKU,32768,6,{0},S_FLOATSKULL2,0,0}, // S_FLOATSKULL
    {SPR_FSKU,32769,6,{0},S_FLOATSKULL3,0,0}, // S_FLOATSKULL2
    {SPR_FSKU,32770,6,{0},S_FLOATSKULL,0,0}, // S_FLOATSKULL3
    {SPR_COL5,0,14,{0},S_HEARTCOL2,0,0}, // S_HEARTCOL
    {SPR_COL5,1,14,{0},S_HEARTCOL,0,0}, // S_HEARTCOL2
    {SPR_TBLU,32768,4,{0},S_BLUETORCH2,0,0}, // S_BLUETORCH
    {SPR_TBLU,32769,4,{0},S_BLUETORCH3,0,0}, // S_BLUETORCH2
    {SPR_TBLU,32770,4,{0},S_BLUETORCH4,0,0}, // S_BLUETORCH3
    {SPR_TBLU,32771,4,{0},S_BLUETORCH,0,0}, // S_BLUETORCH4
    {SPR_TGRN,32768,4,{0},S_GREENTORCH2,0,0}, // S_GREENTORCH
    {SPR_TGRN,32769,4,{0},S_GREENTORCH3,0,0}, // S_GREENTORCH2
    {SPR_TGRN,32770,4,{0},S_GREENTORCH4,0,0}, // S_GREENTORCH3
    {SPR_TGRN,32771,4,{0},S_GREENTORCH,0,0}, // S_GREENTORCH4
    {SPR_TRED,32768,4,{0},S_REDTORCH2,0,0}, // S_REDTORCH
    {SPR_TRED,32769,4,{0},S_REDTORCH3,0,0}, // S_REDTORCH2
    {SPR_TRED,32770,4,{0},S_REDTORCH4,0,0}, // S_REDTORCH3
    {SPR_TRED,32771,4,{0},S_REDTORCH,0,0}, // S_REDTORCH4
    {SPR_SMBT,32768,4,{0},S_BTORCHSHRT2,0,0}, // S_BTORCHSHRT
    {SPR_SMBT,32769,4,{0},S_BTORCHSHRT3,0,0}, // S_BTORCHSHRT2
    {SPR_SMBT,32770,4,{0},S_BTORCHSHRT4,0,0}, // S_BTORCHSHRT3
    {SPR_SMBT,32771,4,{0},S_BTORCHSHRT,0,0}, // S_BTORCHSHRT4
    {SPR_SMGT,32768,4,{0},S_GTORCHSHRT2,0,0}, // S_GTORCHSHRT
    {SPR_SMGT,32769,4,{0},S_GTORCHSHRT3,0,0}, // S_GTORCHSHRT2
    {SPR_SMGT,32770,4,{0},S_GTORCHSHRT4,0,0}, // S_GTORCHSHRT3
    {SPR_SMGT,32771,4,{0},S_GTORCHSHRT,0,0}, // S_GTORCHSHRT4
    {SPR_SMRT,32768,4,{0},S_RTORCHSHRT2,0,0}, // S_RTORCHSHRT
    {SPR_SMRT,32769,4,{0},S_RTORCHSHRT3,0,0}, // S_RTORCHSHRT2
    {SPR_SMRT,32770,4,{0},S_RTORCHSHRT4,0,0}, // S_RTORCHSHRT3
    {SPR_SMRT,32771,4,{0},S_RTORCHSHRT,0,0}, // S_RTORCHSHRT4
    {SPR_HDB1,0,-1,{0},S_NULL,0,0}, // S_HANGNOGUTS
    {SPR_HDB2,0,-1,{0},S_NULL,0,0}, // S_HANGBNOBRAIN
    {SPR_HDB3,0,-1,{0},S_NULL,0,0}, // S_HANGTLOOKDN
    {SPR_HDB4,0,-1,{0},S_NULL,0,0}, // S_HANGTSKULL
    {SPR_HDB5,0,-1,{0},S_NULL,0,0}, // S_HANGTLOOKUP
    {SPR_HDB6,0,-1,{0},S_NULL,0,0}, // S_HANGTNOBRAIN
    {SPR_POB1,0,-1,{0},S_NULL,0,0}, // S_COLONGIBS
    {SPR_POB2,0,-1,{0},S_NULL,0,0}, // S_SMALLPOOL
    {SPR_BRS1,0,-1,{0},S_NULL,0,0}, // S_BRAINSTEM
    {SPR_TLMP,32768,4,{0},S_TECHLAMP2,0,0}, // S_TECHLAMP
    {SPR_TLMP,32769,4,{0},S_TECHLAMP3,0,0}, // S_TECHLAMP2
    {SPR_TLMP,32770,4,{0},S_TECHLAMP4,0,0}, // S_TECHLAMP3
    {SPR_TLMP,32771,4,{0},S_TECHLAMP,0,0}, // S_TECHLAMP4
    {SPR_TLP2,32768,4,{0},S_TECH2LAMP2,0,0}, // S_TECH2LAMP
    {SPR_TLP2,32769,4,{0},S_TECH2LAMP3,0,0}, // S_TECH2LAMP2
    {SPR_TLP2,32770,4,{0},S_TECH2LAMP4,0,0}, // S_TECH2LAMP3
    {SPR_TLP2,32771,4,{0},S_TECH2LAMP,0,0}        // S_TECH2LAMP4
};


mobjinfo_t mobjinfo[NUMMOBJTYPES] = {

    { // MT_PLAYER
        -1, // doomednum
        S_PLAY, // spawnstate
        100, // spawnhealth
        S_PLAY_RUN1, // seestate
        sfx_None, // seesound
        0, // reactiontime
        sfx_None, // attacksound
        S_PLAY_PAIN, // painstate
        255, // painchance
        sfx_plpain, // painsound
        S_NULL, // meleestate
        S_PLAY_ATK1, // missilestate
        S_PLAY_DIE1, // deathstate
        S_PLAY_XDIE1, // xdeathstate
        sfx_pldeth, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_DROPOFF | MF_PICKUP | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_POSSESSED
        3004, // doomednum
        S_POSS_STND, // spawnstate
        20, // spawnhealth
        S_POSS_RUN1, // seestate
        sfx_posit1, // seesound
        8, // reactiontime
        sfx_pistol, // attacksound
        S_POSS_PAIN, // painstate
        200, // painchance
        sfx_popain, // painsound
        0, // meleestate
        S_POSS_ATK1, // missilestate
        S_POSS_DIE1, // deathstate
        S_POSS_XDIE1, // xdeathstate
        sfx_podth1, // deathsound
        8, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_posact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_POSS_RAISE1                // raisestate
    },

    { // MT_SHOTGUY
        9, // doomednum
        S_SPOS_STND, // spawnstate
        30, // spawnhealth
        S_SPOS_RUN1, // seestate
        sfx_posit2, // seesound
        8, // reactiontime
        0, // attacksound
        S_SPOS_PAIN, // painstate
        170, // painchance
        sfx_popain, // painsound
        0, // meleestate
        S_SPOS_ATK1, // missilestate
        S_SPOS_DIE1, // deathstate
        S_SPOS_XDIE1, // xdeathstate
        sfx_podth2, // deathsound
        8, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_posact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_SPOS_RAISE1                // raisestate
    },

    { // MT_VILE
        64, // doomednum
        S_VILE_STND, // spawnstate
        700, // spawnhealth
        S_VILE_RUN1, // seestate
        sfx_vilsit, // seesound
        8, // reactiontime
        0, // attacksound
        S_VILE_PAIN, // painstate
        10, // painchance
        sfx_vipain, // painsound
        0, // meleestate
        S_VILE_ATK1, // missilestate
        S_VILE_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_vildth, // deathsound
        15, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        500, // mass
        0, // damage
        sfx_vilact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_NULL                // raisestate
    },

    { // MT_FIRE
        -1, // doomednum
        S_FIRE1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_UNDEAD
        66, // doomednum
        S_SKEL_STND, // spawnstate
        300, // spawnhealth
        S_SKEL_RUN1, // seestate
        sfx_skesit, // seesound
        8, // reactiontime
        0, // attacksound
        S_SKEL_PAIN, // painstate
        100, // painchance
        sfx_popain, // painsound
        S_SKEL_FIST1, // meleestate
        S_SKEL_MISS1, // missilestate
        S_SKEL_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_skedth, // deathsound
        10, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        500, // mass
        0, // damage
        sfx_skeact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_SKEL_RAISE1                // raisestate
    },

    { // MT_TRACER
        -1, // doomednum
        S_TRACER, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_skeatk, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_TRACEEXP1, // deathstate
        S_NULL, // xdeathstate
        sfx_barexp, // deathsound
        10 * FRACUNIT, // speed
        11 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        10, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_SMOKE
        -1, // doomednum
        S_SMOKE1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_FATSO
        67, // doomednum
        S_FATT_STND, // spawnstate
        600, // spawnhealth
        S_FATT_RUN1, // seestate
        sfx_mansit, // seesound
        8, // reactiontime
        0, // attacksound
        S_FATT_PAIN, // painstate
        80, // painchance
        sfx_mnpain, // painsound
        0, // meleestate
        S_FATT_ATK1, // missilestate
        S_FATT_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_mandth, // deathsound
        8, // speed
        48 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        1000, // mass
        0, // damage
        sfx_posact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_FATT_RAISE1                // raisestate
    },

    { // MT_FATSHOT
        -1, // doomednum
        S_FATSHOT1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_firsht, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_FATSHOTX1, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        20 * FRACUNIT, // speed
        6 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        8, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_CHAINGUY
        65, // doomednum
        S_CPOS_STND, // spawnstate
        70, // spawnhealth
        S_CPOS_RUN1, // seestate
        sfx_posit2, // seesound
        8, // reactiontime
        0, // attacksound
        S_CPOS_PAIN, // painstate
        170, // painchance
        sfx_popain, // painsound
        0, // meleestate
        S_CPOS_ATK1, // missilestate
        S_CPOS_DIE1, // deathstate
        S_CPOS_XDIE1, // xdeathstate
        sfx_podth2, // deathsound
        8, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_posact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_CPOS_RAISE1                // raisestate
    },

    { // MT_TROOP
        3001, // doomednum
        S_TROO_STND, // spawnstate
        60, // spawnhealth
        S_TROO_RUN1, // seestate
        sfx_bgsit1, // seesound
        8, // reactiontime
        0, // attacksound
        S_TROO_PAIN, // painstate
        200, // painchance
        sfx_popain, // painsound
        S_TROO_ATK1, // meleestate
        S_TROO_ATK1, // missilestate
        S_TROO_DIE1, // deathstate
        S_TROO_XDIE1, // xdeathstate
        sfx_bgdth1, // deathsound
        8, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_bgact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_TROO_RAISE1                // raisestate
    },

    { // MT_SERGEANT
        3002, // doomednum
        S_SARG_STND, // spawnstate
        150, // spawnhealth
        S_SARG_RUN1, // seestate
        sfx_sgtsit, // seesound
        8, // reactiontime
        sfx_sgtatk, // attacksound
        S_SARG_PAIN, // painstate
        180, // painchance
        sfx_dmpain, // painsound
        S_SARG_ATK1, // meleestate
        0, // missilestate
        S_SARG_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_sgtdth, // deathsound
        10, // speed
        30 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        400, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_SARG_RAISE1                // raisestate
    },

    { // MT_SHADOWS
        58, // doomednum
        S_SARG_STND, // spawnstate
        150, // spawnhealth
        S_SARG_RUN1, // seestate
        sfx_sgtsit, // seesound
        8, // reactiontime
        sfx_sgtatk, // attacksound
        S_SARG_PAIN, // painstate
        180, // painchance
        sfx_dmpain, // painsound
        S_SARG_ATK1, // meleestate
        0, // missilestate
        S_SARG_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_sgtdth, // deathsound
        10, // speed
        30 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        400, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_SHADOW | MF_COUNTKILL, // flags
        S_SARG_RAISE1                // raisestate
    },

    { // MT_HEAD
        3005, // doomednum
        S_HEAD_STND, // spawnstate
        400, // spawnhealth
        S_HEAD_RUN1, // seestate
        sfx_cacsit, // seesound
        8, // reactiontime
        0, // attacksound
        S_HEAD_PAIN, // painstate
        128, // painchance
        sfx_dmpain, // painsound
        0, // meleestate
        S_HEAD_ATK1, // missilestate
        S_HEAD_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_cacdth, // deathsound
        8, // speed
        31 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        400, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | MF_COUNTKILL, // flags
        S_HEAD_RAISE1                // raisestate
    },

    { // MT_BRUISER
        3003, // doomednum
        S_BOSS_STND, // spawnstate
        1000, // spawnhealth
        S_BOSS_RUN1, // seestate
        sfx_brssit, // seesound
        8, // reactiontime
        0, // attacksound
        S_BOSS_PAIN, // painstate
        50, // painchance
        sfx_dmpain, // painsound
        S_BOSS_ATK1, // meleestate
        S_BOSS_ATK1, // missilestate
        S_BOSS_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_brsdth, // deathsound
        8, // speed
        24 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        1000, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_BOSS_RAISE1                // raisestate
    },

    { // MT_BRUISERSHOT
        -1, // doomednum
        S_BRBALL1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_firsht, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_BRBALLX1, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        15 * FRACUNIT, // speed
        6 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        8, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_KNIGHT
        69, // doomednum
        S_BOS2_STND, // spawnstate
        500, // spawnhealth
        S_BOS2_RUN1, // seestate
        sfx_kntsit, // seesound
        8, // reactiontime
        0, // attacksound
        S_BOS2_PAIN, // painstate
        50, // painchance
        sfx_dmpain, // painsound
        S_BOS2_ATK1, // meleestate
        S_BOS2_ATK1, // missilestate
        S_BOS2_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_kntdth, // deathsound
        8, // speed
        24 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        1000, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_BOS2_RAISE1                // raisestate
    },

    { // MT_SKULL
        3006, // doomednum
        S_SKULL_STND, // spawnstate
        100, // spawnhealth
        S_SKULL_RUN1, // seestate
        0, // seesound
        8, // reactiontime
        sfx_sklatk, // attacksound
        S_SKULL_PAIN, // painstate
        256, // painchance
        sfx_dmpain, // painsound
        0, // meleestate
        S_SKULL_ATK1, // missilestate
        S_SKULL_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        8, // speed
        16 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        50, // mass
        3, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_SPIDER
        7, // doomednum
        S_SPID_STND, // spawnstate
        3000, // spawnhealth
        S_SPID_RUN1, // seestate
        sfx_spisit, // seesound
        8, // reactiontime
        sfx_shotgn, // attacksound
        S_SPID_PAIN, // painstate
        40, // painchance
        sfx_dmpain, // painsound
        0, // meleestate
        S_SPID_ATK1, // missilestate
        S_SPID_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_spidth, // deathsound
        12, // speed
        128 * FRACUNIT, // radius
        100 * FRACUNIT, // height
        1000, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_NULL                // raisestate
    },

    { // MT_BABY
        68, // doomednum
        S_BSPI_STND, // spawnstate
        500, // spawnhealth
        S_BSPI_SIGHT, // seestate
        sfx_bspsit, // seesound
        8, // reactiontime
        0, // attacksound
        S_BSPI_PAIN, // painstate
        128, // painchance
        sfx_dmpain, // painsound
        0, // meleestate
        S_BSPI_ATK1, // missilestate
        S_BSPI_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_bspdth, // deathsound
        12, // speed
        64 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        600, // mass
        0, // damage
        sfx_bspact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_BSPI_RAISE1                // raisestate
    },

    { // MT_CYBORG
        16, // doomednum
        S_CYBER_STND, // spawnstate
        4000, // spawnhealth
        S_CYBER_RUN1, // seestate
        sfx_cybsit, // seesound
        8, // reactiontime
        0, // attacksound
        S_CYBER_PAIN, // painstate
        20, // painchance
        sfx_dmpain, // painsound
        0, // meleestate
        S_CYBER_ATK1, // missilestate
        S_CYBER_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_cybdth, // deathsound
        16, // speed
        40 * FRACUNIT, // radius
        110 * FRACUNIT, // height
        1000, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_NULL                // raisestate
    },

    { // MT_PAIN
        71, // doomednum
        S_PAIN_STND, // spawnstate
        400, // spawnhealth
        S_PAIN_RUN1, // seestate
        sfx_pesit, // seesound
        8, // reactiontime
        0, // attacksound
        S_PAIN_PAIN, // painstate
        128, // painchance
        sfx_pepain, // painsound
        0, // meleestate
        S_PAIN_ATK1, // missilestate
        S_PAIN_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_pedth, // deathsound
        8, // speed
        31 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        400, // mass
        0, // damage
        sfx_dmact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | MF_COUNTKILL, // flags
        S_PAIN_RAISE1                // raisestate
    },

    { // MT_WOLFSS
        84, // doomednum
        S_SSWV_STND, // spawnstate
        50, // spawnhealth
        S_SSWV_RUN1, // seestate
        sfx_sssit, // seesound
        8, // reactiontime
        0, // attacksound
        S_SSWV_PAIN, // painstate
        170, // painchance
        sfx_popain, // painsound
        0, // meleestate
        S_SSWV_ATK1, // missilestate
        S_SSWV_DIE1, // deathstate
        S_SSWV_XDIE1, // xdeathstate
        sfx_ssdth, // deathsound
        8, // speed
        20 * FRACUNIT, // radius
        56 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_posact, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_SSWV_RAISE1                // raisestate
    },

    { // MT_KEEN
        72, // doomednum
        S_KEENSTND, // spawnstate
        100, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_KEENPAIN, // painstate
        256, // painchance
        sfx_keenpn, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_COMMKEEN, // deathstate
        S_NULL, // xdeathstate
        sfx_keendt, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        72 * FRACUNIT, // height
        10000000, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY | MF_SHOOTABLE | MF_COUNTKILL, // flags
        S_NULL                // raisestate
    },

    { // MT_BOSSBRAIN
        88, // doomednum
        S_BRAIN, // spawnstate
        250, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_BRAIN_PAIN, // painstate
        255, // painchance
        sfx_bospn, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_BRAIN_DIE1, // deathstate
        S_NULL, // xdeathstate
        sfx_bosdth, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        10000000, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SHOOTABLE, // flags
        S_NULL                // raisestate
    },

    { // MT_BOSSSPIT
        89, // doomednum
        S_BRAINEYE, // spawnstate
        1000, // spawnhealth
        S_BRAINEYESEE, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        32 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOSECTOR, // flags
        S_NULL                // raisestate
    },

    { // MT_BOSSTARGET
        87, // doomednum
        S_NULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        32 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOSECTOR, // flags
        S_NULL                // raisestate
    },

    { // MT_SPAWNSHOT
        -1, // doomednum
        S_SPAWN1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_bospit, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        10 * FRACUNIT, // speed
        6 * FRACUNIT, // radius
        32 * FRACUNIT, // height
        100, // mass
        3, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY | MF_NOCLIP, // flags
        S_NULL                // raisestate
    },

    { // MT_SPAWNFIRE
        -1, // doomednum
        S_SPAWNFIRE1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_BARREL
        2035, // doomednum
        S_BAR1, // spawnstate
        20, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_BEXP, // deathstate
        S_NULL, // xdeathstate
        sfx_barexp, // deathsound
        0, // speed
        10 * FRACUNIT, // radius
        42 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SHOOTABLE | MF_NOBLOOD, // flags
        S_NULL                // raisestate
    },

    { // MT_TROOPSHOT
        -1, // doomednum
        S_TBALL1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_firsht, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_TBALLX1, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        10 * FRACUNIT, // speed
        6 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        3, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_HEADSHOT
        -1, // doomednum
        S_RBALL1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_firsht, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_RBALLX1, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        10 * FRACUNIT, // speed
        6 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        5, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_ROCKET
        -1, // doomednum
        S_ROCKET, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_rlaunc, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_EXPLODE1, // deathstate
        S_NULL, // xdeathstate
        sfx_barexp, // deathsound
        20 * FRACUNIT, // speed
        11 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        20, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_PLASMA
        -1, // doomednum
        S_PLASBALL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_plasma, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_PLASEXP, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        25 * FRACUNIT, // speed
        13 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        5, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_BFG
        -1, // doomednum
        S_BFGSHOT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        0, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_BFGLAND, // deathstate
        S_NULL, // xdeathstate
        sfx_rxplod, // deathsound
        25 * FRACUNIT, // speed
        13 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        100, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_ARACHPLAZ
        -1, // doomednum
        S_ARACH_PLAZ, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_plasma, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_ARACH_PLEX, // deathstate
        S_NULL, // xdeathstate
        sfx_firxpl, // deathsound
        25 * FRACUNIT, // speed
        13 * FRACUNIT, // radius
        8 * FRACUNIT, // height
        100, // mass
        5, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_PUFF
        -1, // doomednum
        S_PUFF1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_BLOOD
        -1, // doomednum
        S_BLOOD1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP, // flags
        S_NULL                // raisestate
    },

    { // MT_TFOG
        -1, // doomednum
        S_TFOG, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_IFOG
        -1, // doomednum
        S_IFOG, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_TELEPORTMAN
        14, // doomednum
        S_NULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOSECTOR, // flags
        S_NULL                // raisestate
    },

    { // MT_EXTRABFG
        -1, // doomednum
        S_BFGEXP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC0
        2018, // doomednum
        S_ARM1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC1
        2019, // doomednum
        S_ARM2, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC2
        2014, // doomednum
        S_BON1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC3
        2015, // doomednum
        S_BON2, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC4
        5, // doomednum
        S_BKEY, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC5
        13, // doomednum
        S_RKEY, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC6
        6, // doomednum
        S_YKEY, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC7
        39, // doomednum
        S_YSKULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC8
        38, // doomednum
        S_RSKULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC9
        40, // doomednum
        S_BSKULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_NOTDMATCH, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC10
        2011, // doomednum
        S_STIM, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC11
        2012, // doomednum
        S_MEDI, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC12
        2013, // doomednum
        S_SOUL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_INV
        2022, // doomednum
        S_PINV, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC13
        2023, // doomednum
        S_PSTR, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_INS
        2024, // doomednum
        S_PINS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC14
        2025, // doomednum
        S_SUIT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC15
        2026, // doomednum
        S_PMAP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC16
        2045, // doomednum
        S_PVIS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_MEGA
        83, // doomednum
        S_MEGA, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL | MF_COUNTITEM, // flags
        S_NULL                // raisestate
    },

    { // MT_CLIP
        2007, // doomednum
        S_CLIP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC17
        2048, // doomednum
        S_AMMO, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC18
        2010, // doomednum
        S_ROCK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC19
        2046, // doomednum
        S_BROK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC20
        2047, // doomednum
        S_CELL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC21
        17, // doomednum
        S_CELP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC22
        2008, // doomednum
        S_SHEL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC23
        2049, // doomednum
        S_SBOX, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC24
        8, // doomednum
        S_BPAK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC25
        2006, // doomednum
        S_BFUG, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_CHAINGUN
        2002, // doomednum
        S_MGUN, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC26
        2005, // doomednum
        S_CSAW, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC27
        2003, // doomednum
        S_LAUN, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC28
        2004, // doomednum
        S_PLAS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_SHOTGUN
        2001, // doomednum
        S_SHOT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_SUPERSHOTGUN
        82, // doomednum
        S_SHOT2, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPECIAL, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC29
        85, // doomednum
        S_TECHLAMP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC30
        86, // doomednum
        S_TECH2LAMP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC31
        2028, // doomednum
        S_COLU, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC32
        30, // doomednum
        S_TALLGRNCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC33
        31, // doomednum
        S_SHRTGRNCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC34
        32, // doomednum
        S_TALLREDCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC35
        33, // doomednum
        S_SHRTREDCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC36
        37, // doomednum
        S_SKULLCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC37
        36, // doomednum
        S_HEARTCOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC38
        41, // doomednum
        S_EVILEYE, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC39
        42, // doomednum
        S_FLOATSKULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC40
        43, // doomednum
        S_TORCHTREE, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC41
        44, // doomednum
        S_BLUETORCH, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC42
        45, // doomednum
        S_GREENTORCH, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC43
        46, // doomednum
        S_REDTORCH, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC44
        55, // doomednum
        S_BTORCHSHRT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC45
        56, // doomednum
        S_GTORCHSHRT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC46
        57, // doomednum
        S_RTORCHSHRT, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC47
        47, // doomednum
        S_STALAGTITE, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC48
        48, // doomednum
        S_TECHPILLAR, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC49
        34, // doomednum
        S_CANDLESTIK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC50
        35, // doomednum
        S_CANDELABRA, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC51
        49, // doomednum
        S_BLOODYTWITCH, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        68 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC52
        50, // doomednum
        S_MEAT2, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        84 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC53
        51, // doomednum
        S_MEAT3, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        84 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC54
        52, // doomednum
        S_MEAT4, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        68 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC55
        53, // doomednum
        S_MEAT5, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        52 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC56
        59, // doomednum
        S_MEAT2, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        84 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC57
        60, // doomednum
        S_MEAT4, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        68 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC58
        61, // doomednum
        S_MEAT3, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        52 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC59
        62, // doomednum
        S_MEAT5, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        52 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC60
        63, // doomednum
        S_BLOODYTWITCH, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        68 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC61
        22, // doomednum
        S_HEAD_DIE6, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC62
        15, // doomednum
        S_PLAY_DIE7, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC63
        18, // doomednum
        S_POSS_DIE5, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC64
        21, // doomednum
        S_SARG_DIE6, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC65
        23, // doomednum
        S_SKULL_DIE6, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC66
        20, // doomednum
        S_TROO_DIE5, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC67
        19, // doomednum
        S_SPOS_DIE5, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC68
        10, // doomednum
        S_PLAY_XDIE9, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC69
        12, // doomednum
        S_PLAY_XDIE9, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC70
        28, // doomednum
        S_HEADSONSTICK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC71
        24, // doomednum
        S_GIBS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        0, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC72
        27, // doomednum
        S_HEADONASTICK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC73
        29, // doomednum
        S_HEADCANDLES, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC74
        25, // doomednum
        S_DEADSTICK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC75
        26, // doomednum
        S_LIVESTICK, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC76
        54, // doomednum
        S_BIGTREE, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        32 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC77
        70, // doomednum
        S_BBAR1, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC78
        73, // doomednum
        S_HANGNOGUTS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        88 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC79
        74, // doomednum
        S_HANGBNOBRAIN, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        88 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC80
        75, // doomednum
        S_HANGTLOOKDN, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC81
        76, // doomednum
        S_HANGTSKULL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC82
        77, // doomednum
        S_HANGTLOOKUP, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC83
        78, // doomednum
        S_HANGTNOBRAIN, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        16 * FRACUNIT, // radius
        64 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC84
        79, // doomednum
        S_COLONGIBS, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC85
        80, // doomednum
        S_SMALLPOOL, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP, // flags
        S_NULL                // raisestate
    },

    { // MT_MISC86
        81, // doomednum
        S_BRAINSTEM, // spawnstate
        1000, // spawnhealth
        S_NULL, // seestate
        sfx_None, // seesound
        8, // reactiontime
        sfx_None, // attacksound
        S_NULL, // painstate
        0, // painchance
        sfx_None, // painsound
        S_NULL, // meleestate
        S_NULL, // missilestate
        S_NULL, // deathstate
        S_NULL, // xdeathstate
        sfx_None, // deathsound
        0, // speed
        20 * FRACUNIT, // radius
        16 * FRACUNIT, // height
        100, // mass
        0, // damage
        sfx_None, // activesound
        MF_NOBLOCKMAP, // flags
        S_NULL                // raisestate
    }
};
int myargc;
char** myargv;


//
// M_CheckParm
// Checks for the given parameter
// in the program's command line arguments.
// Returns the argument number (1 to argc-1)
// or 0 if not present
int M_CheckParm(char* check)
{
    int i;

    for (i = 1; i < myargc; i++)
    {
        if (!doom_strcasecmp(check, myargv[i]))
            return i;
    }

    return 0;
}
void M_ClearBox(fixed_t* box)
{
    box[BOXTOP] = box[BOXRIGHT] = DOOM_MININT;
    box[BOXBOTTOM] = box[BOXLEFT] = DOOM_MAXINT;
}


void M_AddToBox(fixed_t* box, fixed_t x, fixed_t y)
{
    if (x < box[BOXLEFT])
        box[BOXLEFT] = x;
    else if (x > box[BOXRIGHT])
        box[BOXRIGHT] = x;
    if (y < box[BOXBOTTOM])
        box[BOXBOTTOM] = y;
    else if (y > box[BOXTOP])
        box[BOXTOP] = y;
}
static int firsttime = 1;
static unsigned char cheat_xlate_table[256];


//
// Called in st_stuff module, which handles the input.
// Returns a 1 if the cheat was successful, 0 if failed.
//
int cht_CheckCheat(cheatseq_t* cht, char key)
{
    int i;
    int rc = 0;

    if (firsttime)
    {
        firsttime = 0;
        for (i = 0; i < 256; i++) cheat_xlate_table[i] = SCRAMBLE(i);
    }

    if (!cht->p)
        cht->p = cht->sequence; // initialize if first time

    if (*cht->p == 0)
        *(cht->p++) = key;
    else if
        (cheat_xlate_table[(unsigned char)key] == *cht->p) cht->p++;
    else
        cht->p = cht->sequence;

    if (*cht->p == 1)
        cht->p++;
    else if (*cht->p == 0xff) // end of sequence character
    {
        cht->p = cht->sequence;
        rc = 1;
    }

    return rc;
}


void cht_GetParam(cheatseq_t* cht, char* buffer)
{
    unsigned char* p, c;

    p = cht->sequence;
    while (*(p++) != 1);

    do
    {
        c = *p;
        *(buffer++) = c;
        *(p++) = 0;
    } while (c && *p != 0xff);

    if (*p == 0xff)
        *buffer = 0;
}
fixed_t FixedMul(fixed_t a, fixed_t b)
{
    return ((long long)a * (long long)b) >> FRACBITS;
}


//
// FixedDiv, C version.
//
fixed_t FixedDiv(fixed_t a, fixed_t b)
{
    if ((doom_abs(a) >> 14) >= doom_abs(b))
        return (a ^ b) < 0 ? DOOM_MININT : DOOM_MAXINT;
    return FixedDiv2(a, b);
}


fixed_t FixedDiv2(fixed_t a, fixed_t b)
{
    double c;

    c = ((double)a) / ((double)b) * FRACUNIT;

    if (c >= 2147483648.0 || c < -2147483648.0)
        I_Error("Error: FixedDiv: divide by zero");
    return (fixed_t)c;
}
#define SAVESTRINGSIZE 24
#define SKULLXOFF -32
#define LINEHEIGHT 16


//
// MENU TYPEDEFS
//
typedef struct
{
    // 0 = no cursor here, 1 = ok, 2 = arrows ok
    short status;

    char name[10];

    // choice = menu item #.
    // if status = 2,
    //   choice=0:leftarrow,1:rightarrow
    void (*routine)(int choice);

    // hotkey in menu
    char alphaKey;
} menuitem_t;


typedef struct menu_s
{
    short numitems; // # of menu items
    struct menu_s* prevMenu; // previous menu
    menuitem_t* menuitems; // menu items
    void (*routine)(); // draw routine
    short x;
    short y; // x,y of menu
    short lastOn; // last item user was on in menu
} menu_t;


typedef struct
{
    char* lump;
    int x, w;
    int offx;
    int offy;
} menu_custom_text_seg_t;


typedef struct
{
    char* name;
    menu_custom_text_seg_t segs[16];
} menu_custom_text_t;


extern int doom_flags;

extern patch_t* hu_font[HU_FONTSIZE];
extern doom_boolean message_dontfuckwithme;

extern doom_boolean chat_on; // in heads-up code

extern int mousemove;


//
// defaulted values
//
int mouseSensitivity; // has default

// Show messages has default, 0 = off, 1 = on
int showMessages;


// Blocky mode, has default, 0 = high, 1 = normal
int detailLevel;
int screenblocks; // has default

// temp for screenblocks (0-9)
int screenSize;

// -1 = no quicksave slot picked!
int quickSaveSlot;

// 1 = message to be printed
int messageToPrint;
// ...and here is the message string!
char* messageString;

// message x & y
int messx;
int messy;
int messageLastMenuActive;

// timed message = no input from user
doom_boolean messageNeedsInput;

void (*messageRoutine)(int response);

char gammamsg[5][26] =
{
    GAMMALVL0,
    GAMMALVL1,
    GAMMALVL2,
    GAMMALVL3,
    GAMMALVL4
};

// we are going to be entering a savegame string
int saveStringEnter;
int saveSlot; // which slot to save in
int saveCharIndex; // which char we're editing
// old save description before edit
char saveOldString[SAVESTRINGSIZE];

doom_boolean inhelpscreens;
doom_boolean menuactive;

extern doom_boolean sendpause;
char savegamestrings[10][SAVESTRINGSIZE];

char endstring[160];

short itemOn; // menu item skull is on
short skullAnimCounter; // skull animation counter
short whichSkull; // which skull to draw

// graphic name of skulls
// warning: initializer-string for array of chars is too long
char skullName[2][/*8*/9] = { "M_SKULL1","M_SKULL2" };

// current menudef
menu_t* currentMenu;

// We create new menu text by cutting into existing graphics and pasting them to create the new text.
// This way we don't ship code with embeded graphics that come from WAD files.
menu_custom_text_t menu_custom_texts[] =
{
    {"TXT_MMOV", {
        {"M_MSENS", 0, 74, 0, 0}, // Mouse
        {"M_MSENS", 0, 31, 83, 0}, // Mo
        {"M_MSENS", 160, 14, 83 + 31, 0}, // v
        {"M_MSENS", 60, 14, 83 + 31 + 14, 0}, // e
        {"M_DETAIL", 169, 5, 83 + 31 + 14 + 14, 0}, // :
        {0}
    }},
    {"TXT_MOPT", {
        {"M_MSENS", 0, 74, 0, 0}, // Mouse
        {"M_OPTION", 0, 92, 74 + 9, 0}, // Options
        {0}
    }},
    {"TXT_CROS", {
        {"M_SKILL", 0, 16, 0, 0}, // C
        {"M_DETAIL", 14, 15, 16, 0}, // r
        {"M_SKILL", 46, 30, 16 + 15, 0}, // os
        {"M_SKILL", 62, 14, 16 + 15 + 30, 0}, // s
        {"M_SKILL", 16, 15, 16 + 15 + 30 + 14, 0}, // h
        {"M_DETAIL", 140, 19, 16 + 15 + 30 + 14 + 15, 0}, // ai
        {"M_DETAIL", 14, 15, 16 + 15 + 30 + 14 + 15 + 19, 0}, // r
        {"M_DETAIL", 169, 5, 16 + 15 + 30 + 14 + 15 + 19 + 15, 0}, // :
        {0}
    }},
    {"TXT_ARUN", {
        {"M_SGTTL", 90, 17, 0, 0}, // A
        {"M_GDLOW", 0, 10, 17, 3}, // l
        {"M_GDLOW", 26, 16, 17 + 10, 3}, // 
        {"M_DISP", 57, 30, 17 + 10 + 16, 0}, // ay
        {"M_RDTHIS", 99, 14, 17 + 10 + 16 + 30, 0}, // s
        {"M_RDTHIS", 0, 16, 17 + 10 + 16 + 30 + 14 + 7, 0}, // R
        {"M_SFXVOL", 90, 15, 17 + 10 + 16 + 30 + 14 + 7 + 16, 0}, // u
        {"M_OPTION", 62, 15, 17 + 10 + 16 + 30 + 14 + 7 + 16 + 15, 0}, // n
        {"M_DETAIL", 169, 5, 17 + 10 + 16 + 30 + 14 + 7 + 16 + 15 + 15, 0}, // :
        {0}
    }},
};

const int custom_texts_count = sizeof(menu_custom_texts) / sizeof(menu_custom_text_t);

char tempstring[80];
int epi;
char detailNames[2][9] = { "M_GDHIGH","M_GDLOW" };
char msgNames[2][9] = { "M_MSGOFF","M_MSGON" };

int quitsounds[8] =
{
    sfx_pldeth,
    sfx_dmpain,
    sfx_popain,
    sfx_slop,
    sfx_telept,
    sfx_posit1,
    sfx_posit3,
    sfx_sgtatk
};

int quitsounds2[8] =
{
    sfx_vilact,
    sfx_getpow,
    sfx_boscub,
    sfx_slop,
    sfx_skeswg,
    sfx_kntdth,
    sfx_bspact,
    sfx_sgtatk
};


//
// PROTOTYPES
//
void M_NewGame(int choice);
void M_Episode(int choice);
void M_ChooseSkill(int choice);
void M_LoadGame(int choice);
void M_SaveGame(int choice);
void M_Options(int choice);
void M_EndGame(int choice);
void M_ReadThis(int choice);
void M_ReadThis2(int choice);
void M_QuitDOOM(int choice);

void M_ChangeMessages(int choice);
void M_SfxVol(int choice);
void M_MusicVol(int choice);
void M_ChangeDetail(int choice);
void M_MouseOptions(int choice);
void M_SizeDisplay(int choice);
void M_StartGame(int choice);
void M_Sound(int choice);
void M_ChangeCrosshair(int choice);
void M_ChangeAlwaysRun(int choice);

void M_MouseMove(int choice);
void M_ChangeSensitivity(int choice);

void M_FinishReadThis(int choice);
void M_LoadSelect(int choice);
void M_SaveSelect(int choice);
void M_ReadSaveStrings(void);
void M_QuickSave(void);
void M_QuickLoad(void);

void M_DrawMainMenu(void);
void M_DrawReadThis1(void);
void M_DrawReadThis2(void);
void M_DrawNewGame(void);
void M_DrawEpisode(void);
void M_DrawOptions(void);
void M_DrawSound(void);
void M_DrawLoad(void);
void M_DrawSave(void);

void M_DrawSaveLoadBorder(int x, int y);
void M_SetupNextMenu(menu_t* menudef);
void M_DrawThermo(int x, int y, int thermWidth, int thermDot);
void M_DrawEmptyCell(menu_t* menu, int item);
void M_DrawSelCell(menu_t* menu, int item);
void M_WriteText(int x, int y, char* string);
int  M_StringWidth(char* string);
int  M_StringHeight(char* string);
void M_StartControlPanel(void);
void M_StartMessage(char* string, void* routine, doom_boolean input);
void M_StopMessage(void);
void M_ClearMenus(void);
void M_DrawMouseOptions(void);


//
// DOOM MENU
//
enum
{
    newgame = 0,
    options,
    loadgame,
    savegame,
    readthis,
    quitdoom,
    main_end
} main_e;

menuitem_t MainMenu[] =
{
    {1,"M_NGAME",M_NewGame,'n'},
    {1,"M_OPTION",M_Options,'o'},
    {1,"M_LOADG",M_LoadGame,'l'},
    {1,"M_SAVEG",M_SaveGame,'s'},
    // Another hickup with Special edition.
    {1,"M_RDTHIS",M_ReadThis,'r'},
    {1,"M_QUITG",M_QuitDOOM,'q'}
};

menu_t  MainDef =
{
    main_end,
    0,
    MainMenu,
    M_DrawMainMenu,
    97,64,
    0
};


//
// EPISODE SELECT
//
enum
{
    ep1,
    ep2,
    ep3,
    ep4,
    ep_end
} episodes_e;

menuitem_t EpisodeMenu[] =
{
    {1,"M_EPI1", M_Episode,'k'},
    {1,"M_EPI2", M_Episode,'t'},
    {1,"M_EPI3", M_Episode,'i'},
    {1,"M_EPI4", M_Episode,'t'}
};

menu_t  EpiDef =
{
    ep_end,                // # of menu items
    &MainDef,                // previous menu
    EpisodeMenu,        // menuitem_t ->
    M_DrawEpisode,        // drawing routine ->
    48,63,              // x,y
    ep1                        // lastOn
};


//
// NEW GAME
//
enum
{
    killthings,
    toorough,
    hurtme,
    violence,
    nightmare,
    newg_end
} newgame_e;

menuitem_t NewGameMenu[] =
{
    {1,"M_JKILL",        M_ChooseSkill, 'i'},
    {1,"M_ROUGH",        M_ChooseSkill, 'h'},
    {1,"M_HURT",        M_ChooseSkill, 'h'},
    {1,"M_ULTRA",        M_ChooseSkill, 'u'},
    {1,"M_NMARE",        M_ChooseSkill, 'n'}
};

menu_t  NewDef =
{
    newg_end,                // # of menu items
    &EpiDef,                // previous menu
    NewGameMenu,        // menuitem_t ->
    M_DrawNewGame,        // drawing routine ->
    48,63,              // x,y
    hurtme                // lastOn
};


//
// OPTIONS MENU
//
menuitem_t* OptionsMenu;

enum
{
    endgame,
    messages,
    crosshair_opt,
    always_run_opt,
    //detail, // Details do nothing?
    scrnsize,
    option_empty1,
    mouseoptions,
    soundvol,
    opt_end
} options_e;

menuitem_t OptionsMenuFull[] =
{
    {1,"M_ENDGAM",  M_EndGame,'e'},
    {1,"M_MESSG",   M_ChangeMessages,'m'},
    {1,"TXT_CROS",  M_ChangeCrosshair,'c'},
    {1,"TXT_ARUN",  M_ChangeAlwaysRun,'r'},
    //{1,"M_DETAIL", M_ChangeDetail,'g'},  // Details do nothing?
    {2,"M_SCRNSZ",  M_SizeDisplay,'s'},
    {-1,"",0},
    {1,"TXT_MOPT",  M_MouseOptions,'f'},
    {1,"M_SVOL",    M_Sound,'s'}
};

menu_t  OptionsDef =
{
    opt_end,
    &MainDef,
    OptionsMenuFull,
    M_DrawOptions,
    60,37,
    0
};


enum
{
    endgame_no_mouse,
    messages_no_mouse,
    crosshair_opt_no_mouse,
    always_run_opt_no_mouse,
    //detail_no_mouse, // Details do nothing?
    scrnsize_no_mouse,
    option_empty1_no_mouse,
    soundvol_no_mouse,
    opt_end_no_mouse
} options_e_no_mouse;

menuitem_t OptionsMenuNoMouse[] =
{
    {1,"M_ENDGAM",  M_EndGame,'e'},
    {1,"M_MESSG",   M_ChangeMessages,'m'},
    {1,"TXT_CROS",  M_ChangeCrosshair,'c'},
    {1,"TXT_ARUN",  M_ChangeAlwaysRun,'r'},
    //{1,"M_DETAIL",  M_ChangeDetail,'g'}, // Details do nothing?
    {2,"M_SCRNSZ",  M_SizeDisplay,'s'},
    {-1,"",0},
    {1,"M_SVOL",    M_Sound,'s'}
};

menu_t  OptionsNoMouseDef =
{
    opt_end_no_mouse,
    &MainDef,
    OptionsMenuNoMouse,
    M_DrawOptions,
    60,37,
    0
};


enum
{
    endgame_no_sound,
    messages_no_sound,
    crosshair_opt_no_sound,
    always_run_opt_no_sound,
    //detail_no_sound, // Details do nothing?
    scrnsize_no_sound,
    option_empty1_no_sound,
    mouseoptions_no_sound,
    opt_end_no_sound
} options_e_no_sound;

menuitem_t OptionsMenuNoSound[] =
{
    {1,"M_ENDGAM",  M_EndGame,'e'},
    {1,"M_MESSG",   M_ChangeMessages,'m'},
    {1,"TXT_CROS",  M_ChangeCrosshair,'c'},
    {1,"TXT_ARUN",  M_ChangeAlwaysRun,'r'},
    //{1,"M_DETAIL",  M_ChangeDetail,'g'}, // Details do nothing?
    {2,"M_SCRNSZ",  M_SizeDisplay,'s'},
    {-1,"",0},
    {1,"TXT_MOPT",  M_MouseOptions,'f'}
};

menu_t OptionsNoSoundDef =
{
    opt_end_no_sound,
    &MainDef,
    OptionsMenuNoSound,
    M_DrawOptions,
    60,37,
    0
};


enum
{
    endgame_no_sound_no_mouse,
    messages_no_sound_no_mouse,
    crosshair_opt_no_sound_no_mouse,
    always_run_top_no_sound_no_mouse,
    //detail_no_sound_no_mouse, // Details do nothing?
    scrnsize_no_sound_no_mouse,
    option_empty1_no_sound_no_mouse,
    opt_end_no_sound_no_mouse
} options_e_no_sound_no_mouse;

menuitem_t OptionsMenuNoSoundNoMouse[] =
{
    {1,"M_ENDGAM",  M_EndGame,'e'},
    {1,"M_MESSG",   M_ChangeMessages,'m'},
    {1,"TXT_CROS",  M_ChangeCrosshair,'c'},
    {1,"TXT_ARUN",  M_ChangeAlwaysRun,'r'},
    //{1,"M_DETAIL",  M_ChangeDetail,'g'}, // Details do nothing?
    {2,"M_SCRNSZ",  M_SizeDisplay,'s'},
    {-1,"",0}
};

menu_t OptionsNoSoundNoMouseDef =
{
    opt_end_no_sound_no_mouse,
    &MainDef,
    OptionsMenuNoSoundNoMouse,
    M_DrawOptions,
    60,37,
    0
};


//
// MOUSE OPTIONS
//
enum
{
    mousemov,
    mousesens,
    mouse_option_empty1,
    mouse_opt_end
} mouseoptions_e;

menuitem_t MouseOptionsMenu[] =
{
    {1,"TXT_MMOV", M_MouseMove,'f'},
    {2,"M_MSENS", M_ChangeSensitivity,'m'},
    {-1,"",0},
};

menu_t  MouseOptionsDef =
{
    mouse_opt_end,
    &OptionsDef,
    MouseOptionsMenu,
    M_DrawMouseOptions,
    60,70,
    0
};


//
// Read This! MENU 1 & 2
//
enum
{
    rdthsempty1,
    read1_end
} read_e;

menuitem_t ReadMenu1[] =
{
    {1,"",M_ReadThis2,0}
};

menu_t  ReadDef1 =
{
    read1_end,
    &MainDef,
    ReadMenu1,
    M_DrawReadThis1,
    280,185,
    0
};

enum
{
    rdthsempty2,
    read2_end
} read_e2;

menuitem_t ReadMenu2[] =
{
    {1,"",M_FinishReadThis,0}
};

menu_t  ReadDef2 =
{
    read2_end,
    &ReadDef1,
    ReadMenu2,
    M_DrawReadThis2,
    330,175,
    0
};

//
// SOUND VOLUME MENU
//
menuitem_t* SoundMenu;

enum
{
    sfx_vol,
    sfx_empty1,
    music_vol,
    sfx_empty2,
    sound_end
} sound_e;

menuitem_t SoundMenuFull[] =
{
    {2,"M_SFXVOL",M_SfxVol,'s'},
    {-1,"",0},
    {2,"M_MUSVOL",M_MusicVol,'m'},
    {-1,"",0}
};

menu_t  SoundDef =
{
    sound_end,
    &OptionsDef,
    SoundMenuFull,
    M_DrawSound,
    80,64,
    0
};


enum
{
    music_vol_no_sfx,
    sfx_empty2_no_sfx,
    sound_end_no_sfx
} sound_e_no_sfx;

menuitem_t SoundMenuNoSFX[] =
{
    {2,"M_MUSVOL",M_MusicVol,'m'},
    {-1,"",0}
};

menu_t  SoundNoSFXDef =
{
    sound_end_no_sfx,
    &OptionsDef,
    SoundMenuNoSFX,
    M_DrawSound,
    80,64,
    0
};


enum
{
    sfx_vol_no_music,
    sfx_empty1_no_music,
    sound_end_no_music
} sound_e_no_music;

menuitem_t SoundMenuNoMusic[] =
{
    {2,"M_SFXVOL",M_SfxVol,'s'},
    {-1,"",0}
};

menu_t  SoundNoMusicDef =
{
    sound_end_no_music,
    &OptionsDef,
    SoundMenuNoMusic,
    M_DrawSound,
    80,64,
    0
};


//
// LOAD GAME MENU
//
enum
{
    load1,
    load2,
    load3,
    load4,
    load5,
    load6,
    load_end
} load_e;

menuitem_t DOOM_LoadMenu[] =
{
    {1,"", M_LoadSelect,'1'},
    {1,"", M_LoadSelect,'2'},
    {1,"", M_LoadSelect,'3'},
    {1,"", M_LoadSelect,'4'},
    {1,"", M_LoadSelect,'5'},
    {1,"", M_LoadSelect,'6'}
};

menu_t  LoadDef =
{
    load_end,
    &MainDef,
    DOOM_LoadMenu,
    M_DrawLoad,
    80,54,
    0
};

//
// SAVE GAME MENU
//
menuitem_t SaveMenu[] =
{
    {1,"", M_SaveSelect,'1'},
    {1,"", M_SaveSelect,'2'},
    {1,"", M_SaveSelect,'3'},
    {1,"", M_SaveSelect,'4'},
    {1,"", M_SaveSelect,'5'},
    {1,"", M_SaveSelect,'6'}
};

menu_t  SaveDef =
{
    load_end,
    &MainDef,
    SaveMenu,
    M_DrawSave,
    80,54,
    0
};


//
// M_DrawCustomMenuText
//  Draw several segments of patches to make up new text
//
void M_DrawCustomMenuText(char* name, int x, int y)
{
    for (int i = 0; i < custom_texts_count; ++i)
    {
        menu_custom_text_t* custom_text = menu_custom_texts + i;
        if (doom_strcmp(custom_text->name, name) == 0)
        {
            menu_custom_text_seg_t* seg = custom_text->segs;
            while (seg->lump)
            {
                void* lump = W_CacheLumpName(seg->lump, PU_CACHE);
                V_DrawPatchRectDirect(x + seg->offx, y, 0, lump, seg->x, seg->w);
                ++seg;
            }
            break;
        }
    }
}


//
// M_ReadSaveStrings
//  read the strings from the savegame files
//
void M_ReadSaveStrings(void)
{
    void* handle;
    int count;
    int i;
    char name[256];

    for (i = 0; i < load_end; i++)
    {
#if 0
        if (M_CheckParm("-cdrom"))
            //doom_sprintf(name, "c:\\doomdata\\" SAVEGAMENAME "%d.dsg", i);
        else
#endif
        {
            //doom_sprintf(name, SAVEGAMENAME"%d.dsg", i);
            doom_strcpy(name, SAVEGAMENAME);
            doom_concat(name, doom_itoa(i, 10));
            doom_concat(name, ".dsg");
        }

        handle = doom_open(name, "r");
        if (handle == 0)
        {
            doom_strcpy(&savegamestrings[i][0], EMPTYSTRING);
            DOOM_LoadMenu[i].status = 0;
            continue;
        }
        count = doom_read(handle, &savegamestrings[i], SAVESTRINGSIZE);
        doom_close(handle);
        DOOM_LoadMenu[i].status = 1;
    }
}


//
// M_LoadGame & Cie.
//
void M_DrawLoad(void)
{
    int i;

    V_DrawPatchDirect(72, 28, 0, W_CacheLumpName("M_LOADG", PU_CACHE));
    for (i = 0; i < load_end; i++)
    {
        M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i);
        M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]);
    }
}


//
// Draw border for the savegame description
//
void M_DrawSaveLoadBorder(int x, int y)
{
    int i;

    V_DrawPatchDirect(x - 8, y + 7, 0, W_CacheLumpName("M_LSLEFT", PU_CACHE));

    for (i = 0; i < 24; i++)
    {
        V_DrawPatchDirect(x, y + 7, 0, W_CacheLumpName("M_LSCNTR", PU_CACHE));
        x += 8;
    }

    V_DrawPatchDirect(x, y + 7, 0, W_CacheLumpName("M_LSRGHT", PU_CACHE));
}


//
// User wants to load this game
//
void M_LoadSelect(int choice)
{
    char name[256];

#if 0
    if (M_CheckParm("-cdrom"))
        //doom_sprintf(name, "c:\\doomdata\\"SAVEGAMENAME"%d.dsg", choice);
    else
#endif
    {
        //doom_sprintf(name, SAVEGAMENAME"%d.dsg", choice);
        doom_strcpy(name, SAVEGAMENAME);
        doom_concat(name, doom_itoa(choice, 10));
        doom_concat(name, ".dsg");
    }
    G_LoadGame(name);
    M_ClearMenus();
}


//
// Selected from DOOM menu
//
void M_LoadGame(int choice)
{
    if (netgame)
    {
        M_StartMessage(LOADNET, 0, false);
        return;
    }

    M_SetupNextMenu(&LoadDef);
    M_ReadSaveStrings();
}


//
//  M_SaveGame & Cie.
//
void M_DrawSave(void)
{
    int i;

    V_DrawPatchDirect(72, 28, 0, W_CacheLumpName("M_SAVEG", PU_CACHE));
    for (i = 0; i < load_end; i++)
    {
        M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i);
        M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]);
    }

    if (saveStringEnter)
    {
        i = M_StringWidth(savegamestrings[saveSlot]);
        M_WriteText(LoadDef.x + i, LoadDef.y + LINEHEIGHT * saveSlot, "_");
    }
}


//
// M_Responder calls this when user is finished
//
void M_DoSave(int slot)
{
    G_SaveGame(slot, savegamestrings[slot]);
    M_ClearMenus();

    // PICK QUICKSAVE SLOT YET?
    if (quickSaveSlot == -2)
        quickSaveSlot = slot;
}


//
// User wants to save. Start string input for M_Responder
//
void M_SaveSelect(int choice)
{
    // we are going to be intercepting all chars
    saveStringEnter = 1;

    saveSlot = choice;
    doom_strcpy(saveOldString, savegamestrings[choice]);
    if (!doom_strcmp(savegamestrings[choice], EMPTYSTRING))
        savegamestrings[choice][0] = 0;
    saveCharIndex = (int)doom_strlen(savegamestrings[choice]);
}


//
// Selected from DOOM menu
//
void M_SaveGame(int choice)
{
    if (!usergame)
    {
        M_StartMessage(SAVEDEAD, 0, false);
        return;
    }

    if (gamestate != GS_LEVEL)
        return;

    M_SetupNextMenu(&SaveDef);
    M_ReadSaveStrings();
}


//
// M_QuickSave
//
void M_QuickSaveResponse(int ch)
{
    if (ch == 'y')
    {
        M_DoSave(quickSaveSlot);
        S_StartSound(0, sfx_swtchx);
    }
}


void M_QuickSave(void)
{
    if (!usergame)
    {
        S_StartSound(0, sfx_oof);
        return;
    }

    if (gamestate != GS_LEVEL)
        return;

    if (quickSaveSlot < 0)
    {
        M_StartControlPanel();
        M_ReadSaveStrings();
        M_SetupNextMenu(&SaveDef);
        quickSaveSlot = -2;        // means to pick a slot now
        return;
    }
    //doom_sprintf(tempstring, QSPROMPT, savegamestrings[quickSaveSlot]);
    doom_strcpy(tempstring, QSPROMPT_1);
    doom_concat(tempstring, savegamestrings[quickSaveSlot]);
    doom_strcpy(tempstring, QSPROMPT_2);
    M_StartMessage(tempstring, M_QuickSaveResponse, true);
}


//
// M_QuickLoad
//
void M_QuickLoadResponse(int ch)
{
    if (ch == 'y')
    {
        M_LoadSelect(quickSaveSlot);
        S_StartSound(0, sfx_swtchx);
    }
}


void M_QuickLoad(void)
{
    if (netgame)
    {
        M_StartMessage(QLOADNET, 0, false);
        return;
    }

    if (quickSaveSlot < 0)
    {
        M_StartMessage(QSAVESPOT, 0, false);
        return;
    }
    //doom_sprintf(tempstring, QLPROMPT, savegamestrings[quickSaveSlot]);
    doom_strcpy(tempstring, QLPROMPT_1);
    doom_concat(tempstring, savegamestrings[quickSaveSlot]);
    doom_strcpy(tempstring, QLPROMPT_2);
    M_StartMessage(tempstring, M_QuickLoadResponse, true);
}


//
// Read This Menus
// Had a "quick hack to fix romero bug"
//
void M_DrawReadThis1(void)
{
    inhelpscreens = true;
    switch (gamemode)
    {
        case commercial:
            V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP", PU_CACHE));
            break;
        case shareware:
        case registered:
        case retail:
            V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP1", PU_CACHE));
            break;
        default:
            break;
    }
    return;
}


//
// Read This Menus - optional second page.
//
void M_DrawReadThis2(void)
{
    inhelpscreens = true;
    switch (gamemode)
    {
        case retail:
        case commercial:
            // This hack keeps us from having to change menus.
            V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("CREDIT", PU_CACHE));
            break;
        case shareware:
        case registered:
            V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP2", PU_CACHE));
            break;
        default:
            break;
    }
    return;
}


//
// Change Sfx & Music volumes
//
void M_DrawSound(void)
{
    V_DrawPatchDirect(60, 38, 0, W_CacheLumpName("M_SVOL", PU_CACHE));

    if (!(doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS))
    {
        int offset = (doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS) ? sfx_vol_no_music : sfx_vol;
        M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (offset + 1),
                     16, snd_SfxVolume);
    }

    if (!(doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS))
    {
        int offset = (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS) ? music_vol_no_sfx : music_vol;
        M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (offset + 1),
                     16, snd_MusicVolume);
    }
}


void M_Sound(int choice)
{
    M_SetupNextMenu(&SoundDef);
}


void M_MouseOptions(int choice)
{
    M_SetupNextMenu(&MouseOptionsDef);
}


void M_SfxVol(int choice)
{
    switch (choice)
    {
        case 0:
            if (snd_SfxVolume)
                snd_SfxVolume--;
            break;
        case 1:
            if (snd_SfxVolume < 15)
                snd_SfxVolume++;
            break;
    }

    S_SetSfxVolume(snd_SfxVolume /* *8 */);
}


void M_MusicVol(int choice)
{
    switch (choice)
    {
        case 0:
            if (snd_MusicVolume)
                snd_MusicVolume--;
            break;
        case 1:
            if (snd_MusicVolume < 15)
                snd_MusicVolume++;
            break;
    }

    S_SetMusicVolume(snd_MusicVolume /* *8 */);
}


//
// M_DrawMainMenu
//
void M_DrawMainMenu(void)
{
    V_DrawPatchDirect(94, 2, 0, W_CacheLumpName("M_DOOM", PU_CACHE));
}


//
// M_NewGame
//
void M_DrawNewGame(void)
{
    V_DrawPatchDirect(96, 14, 0, W_CacheLumpName("M_NEWG", PU_CACHE));
    V_DrawPatchDirect(54, 38, 0, W_CacheLumpName("M_SKILL", PU_CACHE));
}


void M_NewGame(int choice)
{
    if (netgame && !demoplayback)
    {
        M_StartMessage(NEWGAME, 0, false);
        return;
    }

    if (gamemode == commercial)
        M_SetupNextMenu(&NewDef);
    else
        M_SetupNextMenu(&EpiDef);
}


//
// M_Episode
//
void M_DrawEpisode(void)
{
    V_DrawPatchDirect(54, 38, 0, W_CacheLumpName("M_EPISOD", PU_CACHE));
}

void M_VerifyNightmare(int ch)
{
    if (ch != 'y')
        return;

    G_DeferedInitNew(nightmare, epi + 1, 1);
    M_ClearMenus();
}

void M_ChooseSkill(int choice)
{
    if (choice == nightmare)
    {
        M_StartMessage(NIGHTMARE, M_VerifyNightmare, true);
        return;
    }

    G_DeferedInitNew(choice, epi + 1, 1);
    M_ClearMenus();
}

void M_Episode(int choice)
{
    if ((gamemode == shareware)
        && choice)
    {
        M_StartMessage(SWSTRING, 0, false);
        M_SetupNextMenu(&ReadDef1);
        return;
    }

    // Yet another hack...
    if ((gamemode == registered)
        && (choice > 2))
    {
        doom_print(
                "M_Episode: 4th episode requires UltimateDOOM\n");
        choice = 0;
    }

    epi = choice;
    M_SetupNextMenu(&NewDef);
}


//
// M_Options
//
void M_DrawOptions(void)
{
    V_DrawPatchDirect(108, 15, 0, W_CacheLumpName("M_OPTTTL", PU_CACHE));

    //V_DrawPatchDirect (OptionsDef.x + 175,OptionsDef.y+LINEHEIGHT*detail,0,
    //                W_CacheLumpName(detailNames[detailLevel],PU_CACHE)); // Details do nothing?

    V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages, 0,
                      W_CacheLumpName(msgNames[showMessages], PU_CACHE));

    extern int crosshair;
    V_DrawPatchDirect(OptionsDef.x + 131, OptionsDef.y + LINEHEIGHT * crosshair_opt, 0,
                      W_CacheLumpName(msgNames[crosshair], PU_CACHE));

    extern int always_run;
    V_DrawPatchDirect(OptionsDef.x + 147, OptionsDef.y + LINEHEIGHT * always_run_opt, 0,
                      W_CacheLumpName(msgNames[always_run], PU_CACHE));

    M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (scrnsize + 1),
                 9, screenSize);
}


void M_DrawMouseOptions(void)
{
    M_DrawCustomMenuText("TXT_MOPT", 74, 45);

    V_DrawPatchDirect(MouseOptionsDef.x + 149, MouseOptionsDef.y + LINEHEIGHT * mousemov, 0,
                      W_CacheLumpName(msgNames[mousemove], PU_CACHE));

    M_DrawThermo(MouseOptionsDef.x, MouseOptionsDef.y + LINEHEIGHT * (mousesens + 1),
                 10, mouseSensitivity);
}


void M_Options(int choice)
{
    M_SetupNextMenu(&OptionsDef);
}


//
// Toggle messages on/off
//
void M_ChangeMessages(int choice)
{
    // warning: unused parameter `int choice'
    choice = 0;
    showMessages = 1 - showMessages;

    if (!showMessages)
        players[consoleplayer].message = MSGOFF;
    else
        players[consoleplayer].message = MSGON;

    message_dontfuckwithme = true;
}


//
// Toggle crosshair on/off
//
void M_ChangeCrosshair(int choice)
{
    extern int crosshair;

    // warning: unused parameter `int choice'
    choice = 0;
    crosshair = 1 - crosshair;

    if (!crosshair)
        players[consoleplayer].message = CROSSOFF;
    else
        players[consoleplayer].message = CROSSON;

    message_dontfuckwithme = true;
}


//
// Toggle always-run on/off
//
void M_ChangeAlwaysRun(int choice)
{
    extern int always_run;

    // warning: unused parameter `int choice'
    choice = 0;
    always_run = 1 - always_run;

    if (!always_run)
        players[consoleplayer].message = ALWAYSRUNOFF;
    else
        players[consoleplayer].message = ALWAYSRUNON;

    message_dontfuckwithme = true;
}


//
// M_EndGame
//
void M_EndGameResponse(int ch)
{
    if (ch != 'y')
        return;

    currentMenu->lastOn = itemOn;
    M_ClearMenus();
    D_StartTitle();
}


void M_EndGame(int choice)
{
    choice = 0;
    if (!usergame)
    {
        S_StartSound(0, sfx_oof);
        return;
    }

    if (netgame)
    {
        M_StartMessage(NETEND, 0, false);
        return;
    }

    M_StartMessage(ENDGAME, M_EndGameResponse, true);
}


//
// M_ReadThis
//
void M_ReadThis(int choice)
{
    choice = 0;
    M_SetupNextMenu(&ReadDef1);
}


void M_ReadThis2(int choice)
{
    choice = 0;
    M_SetupNextMenu(&ReadDef2);
}


void M_FinishReadThis(int choice)
{
    choice = 0;
    M_SetupNextMenu(&MainDef);
}


//
// M_QuitDOOM
//
void M_QuitResponse(int ch)
{
    if (ch != 'y')
        return;
    if (!netgame)
    {
        if (gamemode == commercial)
            S_StartSound(0, quitsounds2[(gametic >> 2) & 7]);
        else
            S_StartSound(0, quitsounds[(gametic >> 2) & 7]);
        I_WaitVBL(105);
    }
    I_Quit();
}


void M_QuitDOOM(int choice)
{
    // We pick index 0 which is language sensitive,
    //  or one at random, between 1 and maximum number.
    if (language != english)
    {
        //doom_sprintf(endstring, "%s\n\n"DOSY, endmsg[0]);
        doom_strcpy(endstring, endmsg[0]);
        doom_concat(endstring, "\n\n" DOSY);
    }
    else
    {
        //doom_sprintf(endstring, "%s\n\n" DOSY, endmsg[gametic % (NUM_QUITMESSAGES - 2) + 1]);
        doom_strcpy(endstring, endmsg[gametic % (NUM_QUITMESSAGES - 2) + 1]);
        doom_concat(endstring, "\n\n" DOSY);
    }

    M_StartMessage(endstring, M_QuitResponse, true);
}


void M_ChangeSensitivity(int choice)
{
    switch (choice)
    {
        case 0:
            if (mouseSensitivity)
                mouseSensitivity--;
            break;
        case 1:
            if (mouseSensitivity < 9)
                mouseSensitivity++;
            break;
    }
}


void M_MouseMove(int choice)
{
    choice = 0;
    mousemove = 1 - mousemove;

    return;
}


void M_ChangeDetail(int choice)
{
    choice = 0;
    detailLevel = 1 - detailLevel;

    // FIXME - does not work. Remove anyway?
    doom_print("M_ChangeDetail: low detail mode n.a.\n");
}


void M_SizeDisplay(int choice)
{
    switch (choice)
    {
        case 0:
            if (screenSize > 0)
            {
                screenblocks--;
                screenSize--;
            }
            break;
        case 1:
            if (screenSize < 8)
            {
                screenblocks++;
                screenSize++;
            }
            break;
    }

    R_SetViewSize(screenblocks, detailLevel);
}


//
// Menu Functions
//
void M_DrawThermo(int x, int y, int thermWidth, int thermDot)
{
    int xx;
    int i;

    xx = x;
    V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERML", PU_CACHE));
    xx += 8;
    for (i = 0; i < thermWidth; i++)
    {
        V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERMM", PU_CACHE));
        xx += 8;
    }
    V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERMR", PU_CACHE));

    V_DrawPatchDirect((x + 8) + thermDot * 8, y,
                      0, W_CacheLumpName("M_THERMO", PU_CACHE));
}


void M_DrawEmptyCell(menu_t* menu, int item)
{
    V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, 0,
                      W_CacheLumpName("M_CELL1", PU_CACHE));
}


void M_DrawSelCell(menu_t* menu, int item)
{
    V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, 0,
                      W_CacheLumpName("M_CELL2", PU_CACHE));
}


void M_StartMessage(char* string, void* routine, doom_boolean input)
{
    messageLastMenuActive = menuactive;
    messageToPrint = 1;
    messageString = string;
    messageRoutine = routine;
    messageNeedsInput = input;
    menuactive = true;
    return;
}


void M_StopMessage(void)
{
    menuactive = messageLastMenuActive;
    messageToPrint = 0;
}


//
// Find string width from hu_font chars
//
int M_StringWidth(char* string)
{
    int i;
    int w = 0;
    int c;

    for (i = 0; i < doom_strlen(string); i++)
    {
        c = doom_toupper(string[i]) - HU_FONTSTART;
        if (c < 0 || c >= HU_FONTSIZE)
            w += 4;
        else
            w += SHORT(hu_font[c]->width);
    }

    return w;
}


//
// Find string height from hu_font chars
//
int M_StringHeight(char* string)
{
    int i;
    int h;
    int height = SHORT(hu_font[0]->height);

    h = height;
    for (i = 0; i < doom_strlen(string); i++)
        if (string[i] == '\n')
            h += height;

    return h;
}


//
// Write a string using the hu_font
//
void M_WriteText(int x, int y, char* string)
{
    int w;
    char* ch;
    int c;
    int cx;
    int cy;

    ch = string;
    cx = x;
    cy = y;

    while (1)
    {
        c = *ch++;
        if (!c)
            break;
        if (c == '\n')
        {
            cx = x;
            cy += 12;
            continue;
        }

        c = doom_toupper(c) - HU_FONTSTART;
        if (c < 0 || c >= HU_FONTSIZE)
        {
            cx += 4;
            continue;
        }

        w = SHORT(hu_font[c]->width);
        if (cx + w > SCREENWIDTH)
            break;
        V_DrawPatchDirect(cx, cy, 0, hu_font[c]);
        cx += w;
    }
}


//
// CONTROL PANEL
//

//
// M_Responder
//
doom_boolean M_Responder(event_t* ev)
{
    int             ch;
    int             i;
    static  int     joywait = 0;
    static  int     mousewait = 0;
    static  int     mousey = 0;
    static  int     lasty = 0;
    static  int     mousex = 0;
    static  int     lastx = 0;

    ch = -1;

    if (ev->type == ev_joystick && joywait < I_GetTime())
    {
        if (ev->data3 == -1)
        {
            ch = KEY_UPARROW;
            joywait = I_GetTime() + 5;
        }
        else if (ev->data3 == 1)
        {
            ch = KEY_DOWNARROW;
            joywait = I_GetTime() + 5;
        }

        if (ev->data2 == -1)
        {
            ch = KEY_LEFTARROW;
            joywait = I_GetTime() + 2;
        }
        else if (ev->data2 == 1)
        {
            ch = KEY_RIGHTARROW;
            joywait = I_GetTime() + 2;
        }

        if (ev->data1 & 1)
        {
            ch = KEY_ENTER;
            joywait = I_GetTime() + 5;
        }
        if (ev->data1 & 2)
        {
            ch = KEY_BACKSPACE;
            joywait = I_GetTime() + 5;
        }
    }
    else
    {
        if (ev->type == ev_mouse && mousewait < I_GetTime())
        {
            mousey += ev->data3;
            if (mousey < lasty - 30)
            {
                ch = KEY_DOWNARROW;
                mousewait = I_GetTime() + 5;
                mousey = lasty -= 30;
            }
            else if (mousey > lasty + 30)
            {
                ch = KEY_UPARROW;
                mousewait = I_GetTime() + 5;
                mousey = lasty += 30;
            }

            mousex += ev->data2;
            if (mousex < lastx - 30)
            {
                ch = KEY_LEFTARROW;
                mousewait = I_GetTime() + 5;
                mousex = lastx -= 30;
            }
            else if (mousex > lastx + 30)
            {
                ch = KEY_RIGHTARROW;
                mousewait = I_GetTime() + 5;
                mousex = lastx += 30;
            }

            if (ev->data1 & 1)
            {
                ch = KEY_ENTER;
                mousewait = I_GetTime() + 15;
            }

            if (ev->data1 & 2)
            {
                ch = KEY_BACKSPACE;
                mousewait = I_GetTime() + 15;
            }
        }
        else
            if (ev->type == ev_keydown)
            {
                ch = ev->data1;
            }
    }

    if (ch == -1)
        return false;


    // Save Game string input
    if (saveStringEnter)
    {
        switch (ch)
        {
            case KEY_BACKSPACE:
                if (saveCharIndex > 0)
                {
                    saveCharIndex--;
                    savegamestrings[saveSlot][saveCharIndex] = 0;
                }
                break;

            case KEY_ESCAPE:
                saveStringEnter = 0;
                doom_strcpy(&savegamestrings[saveSlot][0], saveOldString);
                break;

            case KEY_ENTER:
                saveStringEnter = 0;
                if (savegamestrings[saveSlot][0])
                    M_DoSave(saveSlot);
                break;

            default:
                ch = doom_toupper(ch);
                if (ch != 32)
                    if (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE)
                        break;
                if (ch >= 32 && ch <= 127 &&
                    saveCharIndex < SAVESTRINGSIZE - 1 &&
                    M_StringWidth(savegamestrings[saveSlot]) <
                    (SAVESTRINGSIZE - 2) * 8)
                {
                    savegamestrings[saveSlot][saveCharIndex++] = ch;
                    savegamestrings[saveSlot][saveCharIndex] = 0;
                }
                break;
        }
        return true;
    }

    // Take care of any messages that need input
    if (messageToPrint)
    {
        if (messageNeedsInput == true &&
            !(ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE))
            return false;

        menuactive = messageLastMenuActive;
        messageToPrint = 0;
        if (messageRoutine)
            messageRoutine(ch);

        menuactive = false;
        S_StartSound(0, sfx_swtchx);
        return true;
    }

    if (devparm && ch == KEY_F1)
    {
        G_ScreenShot();
        return true;
    }


    // F-Keys
    if (!menuactive)
        switch (ch)
        {
            case KEY_MINUS:         // Screen size down
                if (automapactive || chat_on)
                    return false;
                M_SizeDisplay(0);
                S_StartSound(0, sfx_stnmov);
                return true;

            case KEY_EQUALS:        // Screen size up
                if (automapactive || chat_on)
                    return false;
                M_SizeDisplay(1);
                S_StartSound(0, sfx_stnmov);
                return true;

            case KEY_F1:            // Help key
                M_StartControlPanel();

                if (gamemode == retail)
                    currentMenu = &ReadDef2;
                else
                    currentMenu = &ReadDef1;

                itemOn = 0;
                S_StartSound(0, sfx_swtchn);
                return true;

            case KEY_F2:            // Save
                M_StartControlPanel();
                S_StartSound(0, sfx_swtchn);
                M_SaveGame(0);
                return true;

            case KEY_F3:            // Load
                M_StartControlPanel();
                S_StartSound(0, sfx_swtchn);
                M_LoadGame(0);
                return true;

            case KEY_F4:            // Sound Volume
                M_StartControlPanel();
                currentMenu = &SoundDef;
                itemOn = sfx_vol;
                S_StartSound(0, sfx_swtchn);
                return true;

            // case KEY_F5:            // Detail toggle
            //     M_ChangeDetail(0);
            //     S_StartSound(0, sfx_swtchn);
            //     return true;

            case KEY_F5:            // Crosshair toggle
                M_ChangeCrosshair(0);
                S_StartSound(0, sfx_swtchn);
                return true;

            case KEY_F6:            // Quicksave
                S_StartSound(0, sfx_swtchn);
                M_QuickSave();
                return true;

            case KEY_F7:            // End game
                S_StartSound(0, sfx_swtchn);
                M_EndGame(0);
                return true;

            case KEY_F8:            // Toggle messages
                M_ChangeMessages(0);
                S_StartSound(0, sfx_swtchn);
                return true;

            case KEY_F9:            // Quickload
                S_StartSound(0, sfx_swtchn);
                M_QuickLoad();
                return true;

            case KEY_F10:           // Quit DOOM
                S_StartSound(0, sfx_swtchn);
                M_QuitDOOM(0);
                return true;

            case KEY_F11:           // gamma toggle
                usegamma++;
                if (usegamma > 4)
                    usegamma = 0;
                players[consoleplayer].message = gammamsg[usegamma];
                I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE));
                return true;

        }


    // Pop-up menu?
    if (!menuactive)
    {
        if (ch == KEY_ESCAPE)
        {
            M_StartControlPanel();
            S_StartSound(0, sfx_swtchn);
            return true;
        }
        return false;
    }


    // Keys usable within menu
    switch (ch)
    {
        case KEY_DOWNARROW:
            do
            {
                if (itemOn + 1 > currentMenu->numitems - 1)
                    itemOn = 0;
                else itemOn++;
                S_StartSound(0, sfx_pstop);
            } while (currentMenu->menuitems[itemOn].status == -1);
            return true;

        case KEY_UPARROW:
            do
            {
                if (!itemOn)
                    itemOn = currentMenu->numitems - 1;
                else itemOn--;
                S_StartSound(0, sfx_pstop);
            } while (currentMenu->menuitems[itemOn].status == -1);
            return true;

        case KEY_LEFTARROW:
            if (currentMenu->menuitems[itemOn].routine &&
                currentMenu->menuitems[itemOn].status == 2)
            {
                S_StartSound(0, sfx_stnmov);
                currentMenu->menuitems[itemOn].routine(0);
            }
            return true;

        case KEY_RIGHTARROW:
            if (currentMenu->menuitems[itemOn].routine &&
                currentMenu->menuitems[itemOn].status == 2)
            {
                S_StartSound(0, sfx_stnmov);
                currentMenu->menuitems[itemOn].routine(1);
            }
            return true;

        case KEY_ENTER:
            if (currentMenu->menuitems[itemOn].routine &&
                currentMenu->menuitems[itemOn].status)
            {
                currentMenu->lastOn = itemOn;
                if (currentMenu->menuitems[itemOn].status == 2)
                {
                    currentMenu->menuitems[itemOn].routine(1);      // right arrow
                    S_StartSound(0, sfx_stnmov);
                }
                else
                {
                    currentMenu->menuitems[itemOn].routine(itemOn);
                    S_StartSound(0, sfx_pistol);
                }
            }
            return true;

        case KEY_ESCAPE:
            currentMenu->lastOn = itemOn;
            M_ClearMenus();
            S_StartSound(0, sfx_swtchx);
            return true;

        case KEY_BACKSPACE:
            currentMenu->lastOn = itemOn;
            if (currentMenu->prevMenu)
            {
                currentMenu = currentMenu->prevMenu;
                itemOn = currentMenu->lastOn;
                S_StartSound(0, sfx_swtchn);
            }
            return true;

        default:
            for (i = itemOn + 1; i < currentMenu->numitems; i++)
                if (currentMenu->menuitems[i].alphaKey == ch)
                {
                    itemOn = i;
                    S_StartSound(0, sfx_pstop);
                    return true;
                }
            for (i = 0; i <= itemOn; i++)
                if (currentMenu->menuitems[i].alphaKey == ch)
                {
                    itemOn = i;
                    S_StartSound(0, sfx_pstop);
                    return true;
                }
            break;

    }

    return false;
}


//
// M_StartControlPanel
//
void M_StartControlPanel(void)
{
    // intro might call this repeatedly
    if (menuactive)
        return;

    menuactive = 1;
    currentMenu = &MainDef;         // JDC
    itemOn = currentMenu->lastOn;   // JDC
}


//
// M_Drawer
// Called after the view has been rendered,
// but before it has been blitted.
//
void M_Drawer(void)
{
    static short x;
    static short y;
    short i;
    short max;
    char string[40];
    int start;

    inhelpscreens = false;


    // Horiz. & Vertically center string and print it.
    if (messageToPrint)
    {
        start = 0;
        y = 100 - M_StringHeight(messageString) / 2;
        while (*(messageString + start))
        {
            for (i = 0; i < doom_strlen(messageString + start); i++)
                if (*(messageString + start + i) == '\n')
                {
                    doom_memset(string, 0, 40);
                    doom_strncpy(string, messageString + start, i);
                    start += i + 1;
                    break;
                }

            if (i == doom_strlen(messageString + start))
            {
                doom_strcpy(string, messageString + start);
                start += i;
            }

            x = 160 - M_StringWidth(string) / 2;
            M_WriteText(x, y, string);
            y += SHORT(hu_font[0]->height);
        }
        return;
    }

    if (!menuactive)
        return;

    // Darken background so the menu is more readable.
    if (doom_flags & DOOM_FLAG_MENU_DARKEN_BG)
    {
        extern byte* screens[5];
        extern unsigned char screen_palette[256 * 3];
        extern lighttable_t* colormaps;
        for (int j = 0, len = SCREENWIDTH * SCREENHEIGHT; j < len; ++j)
        {
            byte color = screens[0][j];
            color = colormaps[color + (20 * 256)];
            screens[0][j] = color;
        }
    }

    if (currentMenu->routine)
        currentMenu->routine();         // call Draw routine

    // DRAW MENU
    x = currentMenu->x;
    y = currentMenu->y;
    max = currentMenu->numitems;

    for (i = 0; i < max; i++)
    {
        menuitem_t* menuitem = currentMenu->menuitems + i;
        if (menuitem->name[0])
        {
            if (doom_strncmp(menuitem->name, "TXT_", 4) == 0)
            {
                M_DrawCustomMenuText(menuitem->name, x, y);
            }
            else
            {
                V_DrawPatchDirect(x, y, 0, W_CacheLumpName(menuitem->name, PU_CACHE));
            }
        }
        y += LINEHEIGHT;
    }


    // DRAW SKULL
    V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn * LINEHEIGHT, 0,
                      W_CacheLumpName(skullName[whichSkull], PU_CACHE));

}


//
// M_ClearMenus
//
void M_ClearMenus(void)
{
    menuactive = 0;
}


//
// M_SetupNextMenu
//
void M_SetupNextMenu(menu_t* menudef)
{
    currentMenu = menudef;
    itemOn = currentMenu->lastOn;
}


//
// M_Ticker
//
void M_Ticker(void)
{
    if (--skullAnimCounter <= 0)
    {
        whichSkull ^= 1;
        skullAnimCounter = 8;
    }
}


//
// M_Init
//
void M_Init(void)
{
    doom_boolean hide_mouse = (doom_flags & DOOM_FLAG_HIDE_MOUSE_OPTIONS) ? true : false;
    doom_boolean hide_sound = ((doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS) && (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS)) ? true : false;

    OptionsMenu = OptionsMenuFull;
    if (hide_mouse && !hide_sound)
    {
        OptionsMenu = OptionsMenuNoMouse;
        doom_memcpy(&OptionsDef, &OptionsNoMouseDef, sizeof(OptionsDef));
    }
    else if (!hide_mouse && hide_sound)
    {
        OptionsMenu = OptionsMenuNoSound;
        doom_memcpy(&OptionsDef, &OptionsNoSoundDef, sizeof(OptionsDef));
    }
    else if (hide_mouse && hide_sound)
    {
        OptionsMenu = OptionsMenuNoSoundNoMouse;
        doom_memcpy(&OptionsDef, &OptionsNoSoundNoMouseDef, sizeof(OptionsDef));
    }

    SoundMenu = SoundMenuFull;
    if (doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS)
    {
        SoundMenu = SoundMenuNoMusic;
        doom_memcpy(&SoundDef, &SoundNoMusicDef, sizeof(SoundDef));
    }
    else if (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS)
    {
        SoundMenu = SoundMenuNoSFX;
        doom_memcpy(&SoundDef, &SoundNoSFXDef, sizeof(SoundDef));
    }

    currentMenu = &MainDef;
    menuactive = 0;
    itemOn = currentMenu->lastOn;
    whichSkull = 0;
    skullAnimCounter = 10;
    screenSize = screenblocks - 3;
    messageToPrint = 0;
    messageString = 0;
    messageLastMenuActive = menuactive;
    quickSaveSlot = -1;

    // Here we could catch other version dependencies,
    //  like HELP1/2, and four episodes.

    switch (gamemode)
    {
        case commercial:
            // This is used because DOOM 2 had only one HELP
            //  page. I use CREDIT as second page now, but
            //  kept this hack for educational purposes.
            MainMenu[readthis] = MainMenu[quitdoom];
            MainDef.numitems--;
            MainDef.y += 8;
            NewDef.prevMenu = &MainDef;
            ReadDef1.routine = M_DrawReadThis1;
            ReadDef1.x = 330;
            ReadDef1.y = 165;
            ReadMenu1[0].routine = M_FinishReadThis;
            break;
        case shareware:
            // Episode 2 and 3 are handled,
            //  branching to an ad screen.
        case registered:
            // We need to remove the fourth episode.
            EpiDef.numitems--;
            break;
        case retail:
            // We are fine.
        default:
            break;
    }
}
#ifndef O_BINARY
#define O_BINARY 0
#endif
#define STRING_VALUE 0xFFFF


//
// SCREEN SHOTS
//
typedef struct
{
    char manufacturer;
    char version;
    char encoding;
    char bits_per_pixel;

    unsigned short xmin;
    unsigned short ymin;
    unsigned short xmax;
    unsigned short ymax;

    unsigned short hres;
    unsigned short vres;

    unsigned char palette[48];

    char reserved;
    char color_planes;
    unsigned short bytes_per_line;
    unsigned short palette_type;

    char filler[58];
    unsigned char data; // unbounded
} pcx_t;


//
// M_DrawText
// Returns the final X coordinate
// HU_Init must have been called to init the font
//
extern patch_t* hu_font[HU_FONTSIZE];


//
// DEFAULTS
//
extern int key_right;
extern int key_left;
extern int key_up;
extern int key_down;

extern int key_strafeleft;
extern int key_straferight;

extern int key_fire;
extern int key_use;
extern int key_strafe;
extern int key_speed;

extern int mousebfire;
extern int mousebstrafe;
extern int mousebforward;
extern int mousemove;

extern int joybfire;
extern int joybstrafe;
extern int joybuse;
extern int joybspeed;

extern int viewwidth;
extern int viewheight;

extern int mouseSensitivity;
extern int showMessages;

extern int detailLevel;

extern int screenblocks;

extern int showMessages;

// machine-independent sound params
extern int numChannels;

extern char* chat_macros[];

extern byte scantokey[128];


int usemouse;
int usejoystick;
int crosshair;
int always_run;


default_t defaults[] =
{
    {"mouse_sensitivity",&mouseSensitivity, 5},
    {"sfx_volume",&snd_SfxVolume, 8},
    {"music_volume",&snd_MusicVolume, 8},
    {"show_messages",&showMessages, 1},

    {"key_right",&key_right, KEY_RIGHTARROW},
    {"key_left",&key_left, KEY_LEFTARROW},
    {"key_up",&key_up, KEY_UPARROW},
    {"key_down",&key_down, KEY_DOWNARROW},
    {"key_strafeleft",&key_strafeleft, ','},
    {"key_straferight",&key_straferight, '.'},

    {"key_fire",&key_fire, KEY_RCTRL},
    {"key_use",&key_use, ' '},
    {"key_strafe",&key_strafe, KEY_RALT},
    {"key_speed",&key_speed, KEY_RSHIFT},

    {"use_mouse",&usemouse, 1},
    {"mouseb_fire",&mousebfire,0},
    {"mouseb_strafe",&mousebstrafe,1},
    {"mouseb_forward",&mousebforward,2},
    {"mouse_move",&mousemove,0},

    {"use_joystick",&usejoystick, 0},
    {"joyb_fire",&joybfire,0},
    {"joyb_strafe",&joybstrafe,1},
    {"joyb_use",&joybuse,3},
    {"joyb_speed",&joybspeed,2},

    {"screenblocks",&screenblocks, 9},
    {"detaillevel",&detailLevel, 0},
    {"crosshair",&crosshair, 0},
    {"always_run",&always_run, 0},

    {"snd_channels",&numChannels, 3},

    {"usegamma",&usegamma, 0},

    {"chatmacro0", 0, STRING_VALUE, 0, 0, &chat_macros[0], HUSTR_CHATMACRO0 },
    {"chatmacro1", 0, STRING_VALUE, 0, 0, &chat_macros[1], HUSTR_CHATMACRO1 },
    {"chatmacro2", 0, STRING_VALUE, 0, 0, &chat_macros[2], HUSTR_CHATMACRO2 },
    {"chatmacro3", 0, STRING_VALUE, 0, 0, &chat_macros[3], HUSTR_CHATMACRO3 },
    {"chatmacro4", 0, STRING_VALUE, 0, 0, &chat_macros[4], HUSTR_CHATMACRO4 },
    {"chatmacro5", 0, STRING_VALUE, 0, 0, &chat_macros[5], HUSTR_CHATMACRO5 },
    {"chatmacro6", 0, STRING_VALUE, 0, 0, &chat_macros[6], HUSTR_CHATMACRO6 },
    {"chatmacro7", 0, STRING_VALUE, 0, 0, &chat_macros[7], HUSTR_CHATMACRO7 },
    {"chatmacro8", 0, STRING_VALUE, 0, 0, &chat_macros[8], HUSTR_CHATMACRO8 },
    {"chatmacro9", 0, STRING_VALUE, 0, 0, &chat_macros[9], HUSTR_CHATMACRO9 }
};


int numdefaults = sizeof(defaults) / sizeof(default_t);;
char* defaultfile;


int M_DrawText(int x, int y, doom_boolean direct, char* string)
{
    int c;
    int w;

    while (*string)
    {
        c = doom_toupper(*string) - HU_FONTSTART;
        string++;
        if (c < 0 || c> HU_FONTSIZE)
        {
            x += 4;
            continue;
        }

        w = SHORT(hu_font[c]->width);
        if (x + w > SCREENWIDTH)
            break;
        if (direct)
            V_DrawPatchDirect(x, y, 0, hu_font[c]);
        else
            V_DrawPatch(x, y, 0, hu_font[c]);
        x += w;
    }

    return x;
}


//
// M_WriteFile
//
doom_boolean M_WriteFile(char const* name, void* source, int length)
{
    void* handle;
    int count;

    handle = doom_open(name, "wb");

    if (handle == 0)
        return false;

    count = doom_write(handle, source, length);
    doom_close(handle);

    if (count < length)
        return false;

    return true;
}


//
// M_ReadFile
//
int M_ReadFile(char const* name, byte** buffer)
{
    void* handle;
    int count, length;
    byte* buf;

    handle = doom_open(name, "rb");
    if (handle == 0)
    {
        //I_Error("Error: Couldn't read file %s", name);
        
        doom_strcpy(error_buf, "Error: Couldn't read file ");
        doom_concat(error_buf, name);
        I_Error(error_buf);
    }
    doom_seek(handle, 0, DOOM_SEEK_END);
    length = doom_tell(handle);
    doom_seek(handle, 0, DOOM_SEEK_SET);
    buf = Z_Malloc(length, PU_STATIC, 0);
    count = doom_read(handle, buf, length);
    doom_close(handle);

    if (count < length)
    {
        //I_Error("Error: Couldn't read file %s", name);
        
        doom_strcpy(error_buf, "Error: Couldn't read file ");
        doom_concat(error_buf, name);
        I_Error(error_buf);
    }

    *buffer = buf;
    return length;
}


//
// M_SaveDefaults
//
void M_SaveDefaults(void)
{
    int i;
    int v;
    void* f;

    f = doom_open(defaultfile, "w");
    if (!f)
        return; // can't write the file, but don't complain

    for (i = 0; i < numdefaults; i++)
    {
        if (defaults[i].defaultvalue > -0xfff
            && defaults[i].defaultvalue < 0xfff)
        {
            v = *defaults[i].location;
            //fprintf(f, "%s\t\t%i\n", defaults[i].name, v);
            doom_fprint(f, defaults[i].name);
            doom_fprint(f, "\t\t");
            doom_fprint(f, doom_itoa(v, 10));
            doom_fprint(f, "\n");
        }
        else
        {
            //fprintf(f, "%s\t\t\"%s\"\n", defaults[i].name,
            //        *(char**)(defaults[i].text_location));
            doom_fprint(f, defaults[i].name);
            doom_fprint(f, "\t\t\"");
            doom_fprint(f, *(char**)(defaults[i].text_location));
            doom_fprint(f, "\"\n");
        }
    }

    doom_close(f);
}


//
// M_LoadDefaults
//
void M_LoadDefaults(void)
{
    int i;
    int len;
    void* f;
    char def[80];
    char strparm[100];
    char* newstring;
    int parm;
    doom_boolean isstring;

    // set everything to base values
    // numdefaults = sizeof(defaults)/sizeof(defaults[0]);
    for (i = 0; i < numdefaults; i++)
    {
        if (defaults[i].defaultvalue == 0xFFFF)
            *defaults[i].text_location = defaults[i].default_text_value;
        else
            *defaults[i].location = (int)defaults[i].defaultvalue;
    }

    // check for a custom default file
    i = M_CheckParm("-config");
    if (i && i < myargc - 1)
    {
        defaultfile = myargv[i + 1];
        //doom_print("        default file: %s\n", defaultfile);
        doom_print("        default file: ");
        doom_print(defaultfile);
        doom_print("\n");
    }
    else
        defaultfile = basedefault;

    // read the file in, overriding any set defaults
    f = doom_open(defaultfile, "r");
    if (f)
    {
        while (!doom_eof(f))
        {
            // def
            int arg_read = 0;
            char c;
            for (i = 0; i < 79; ++i)
            {
                doom_read(f, &c, 1);
                if (c == ' ' || c == '\n' || c == '\t')
                {
                    if (i > 0) arg_read++;
                    break;
                }
                def[i] = c;
            }
            def[i] = '\0';

            // Ignore spaces
            if (c != '\n')
            {
                while (1)
                {
                    doom_read(f, &c, 1);
                    if (c != ' ' && c != '\t') break;
                }

                // strparam
                i = 0;
                if (c != '\n')
                {
                    for (; i < 260;)
                    {
                        strparm[i++] = c;
                        doom_read(f, &c, 1);
                        if (c == '\n')
                        {
                            if (i > 0) arg_read++;
                            break;
                        }
                    }
                }
                strparm[i] = '\0';
            }

            isstring = false;
            //if (fscanf(f, "%79s %[^\n]\n", def, strparm) == 2)
            if (arg_read == 2)
            {
                if (strparm[0] == '"')
                {
                    // get a string default
                    isstring = true;
                    len = (int)doom_strlen(strparm);
                    newstring = (char*)doom_malloc(len);
                    strparm[len - 1] = 0;
                    doom_strcpy(newstring, strparm + 1);
                }
                else if (strparm[0] == '0' && strparm[1] == 'x')
                {
                    //sscanf(strparm + 2, "%x", &parm);
                    parm = doom_atox(strparm + 2);
                }
                else
                {
                    //sscanf(strparm, "%i", &parm);
                    parm = doom_atoi(strparm);
                }
                for (i = 0; i < numdefaults; i++)
                    if (!doom_strcmp(def, defaults[i].name))
                    {
                        if (!isstring)
                            *defaults[i].location = parm;
                        else
                            *defaults[i].text_location = newstring;
                        break;
                    }
            }
        }

        doom_close(f);
    }
}


//
// WritePCXfile
//
void WritePCXfile(char* filename, byte* data, int width, int height, byte* palette)
{
    int i;
    int length;
    pcx_t* pcx;
    byte* pack;

    pcx = Z_Malloc(width * height * 2 + 1000, PU_STATIC, 0);

    pcx->manufacturer = 0x0a; // PCX id
    pcx->version = 5; // 256 color
    pcx->encoding = 1; // uncompressed
    pcx->bits_per_pixel = 8; // 256 color
    pcx->xmin = 0;
    pcx->ymin = 0;
    pcx->xmax = SHORT(width - 1);
    pcx->ymax = SHORT(height - 1);
    pcx->hres = SHORT(width);
    pcx->vres = SHORT(height);
    doom_memset(pcx->palette, 0, sizeof(pcx->palette));
    pcx->color_planes = 1; // chunky image
    pcx->bytes_per_line = SHORT(width);
    pcx->palette_type = SHORT(2); // not a grey scale
    doom_memset(pcx->filler, 0, sizeof(pcx->filler));

    // pack the image
    pack = &pcx->data;

    for (i = 0; i < width * height; i++)
    {
        if ((*data & 0xc0) != 0xc0)
            *pack++ = *data++;
        else
        {
            *pack++ = 0xc1;
            *pack++ = *data++;
        }
    }

    // write the palette
    *pack++ = 0x0c;        // palette ID byte
    for (i = 0; i < 768; i++)
        *pack++ = *palette++;

    // write output file
    length = (int)(pack - (byte*)pcx);
    M_WriteFile(filename, pcx, length);

    Z_Free(pcx);
}


//
// M_ScreenShot
//
void M_ScreenShot(void)
{
    int i;
    byte* linear;
    char lbmname[12];
    void* f;

    // munge planar buffer to linear
    linear = screens[2];
    I_ReadScreen(linear);

    // find a file name to save it to
    doom_strcpy(lbmname, "DOOM00.pcx");

    for (i = 0; i <= 99; i++)
    {
        lbmname[4] = i / 10 + '0';
        lbmname[5] = i % 10 + '0';
        if ((f = doom_open(lbmname, "wb")) == 0)
            break;        // file doesn't exist
        doom_close(f);
    }
    if (i == 100)
        I_Error("Error: M_ScreenShot: Couldn't create a PCX");

    // save the pcx file
    WritePCXfile(lbmname, linear,
                 SCREENWIDTH, SCREENHEIGHT,
                 W_CacheLumpName("PLAYPAL", PU_CACHE));

    players[consoleplayer].message = "screen shot";
}
unsigned char rndtable[256] = {
    0,   8, 109, 220, 222, 241, 149, 107,  75, 248, 254, 140,  16,  66 ,
    74,  21, 211,  47,  80, 242, 154,  27, 205, 128, 161,  89,  77,  36 ,
    95, 110,  85,  48, 212, 140, 211, 249,  22,  79, 200,  50,  28, 188 ,
    52, 140, 202, 120,  68, 145,  62,  70, 184, 190,  91, 197, 152, 224 ,
    149, 104,  25, 178, 252, 182, 202, 182, 141, 197,   4,  81, 181, 242 ,
    145,  42,  39, 227, 156, 198, 225, 193, 219,  93, 122, 175, 249,   0 ,
    175, 143,  70, 239,  46, 246, 163,  53, 163, 109, 168, 135,   2, 235 ,
    25,  92,  20, 145, 138,  77,  69, 166,  78, 176, 173, 212, 166, 113 ,
    94, 161,  41,  50, 239,  49, 111, 164,  70,  60,   2,  37, 171,  75 ,
    136, 156,  11,  56,  42, 146, 138, 229,  73, 146,  77,  61,  98, 196 ,
    135, 106,  63, 197, 195,  86,  96, 203, 113, 101, 170, 247, 181, 113 ,
    80, 250, 108,   7, 255, 237, 129, 226,  79, 107, 112, 166, 103, 241 ,
    24, 223, 239, 120, 198,  58,  60,  82, 128,   3, 184,  66, 143, 224 ,
    145, 224,  81, 206, 163,  45,  63,  90, 168, 114,  59,  33, 159,  95 ,
    28, 139, 123,  98, 125, 196,  15,  70, 194, 253,  54,  14, 109, 226 ,
    71,  17, 161,  93, 186,  87, 244, 138,  20,  52, 123, 251,  26,  36 ,
    17,  46,  52, 231, 232,  76,  31, 221,  84,  37, 216, 165, 212, 106 ,
    197, 242,  98,  43,  39, 175, 254, 145, 190,  84, 118, 222, 187, 136 ,
    120, 163, 236, 249
};


int rndindex = 0;
int prndindex = 0;


// Which one is deterministic?
int P_Random(void)
{
    prndindex = (prndindex + 1) & 0xff;
    return rndtable[prndindex];
}


int M_Random(void)
{
    rndindex = (rndindex + 1) & 0xff;
    return rndtable[rndindex];
}


void M_ClearRandom(void)
{
    rndindex = prndindex = 0;
}
#ifdef __BIG_ENDIAN__


// Swap 16bit, that is, MSB and LSB byte.
unsigned short SwapSHORT(unsigned short x)
{
    // No masking with 0xFF should be necessary. 
    return (x >> 8) | (x << 8);
}


// Swapping 32bit.
unsigned long SwapLONG(unsigned long x)
{
    return
        (x >> 24)
        | ((x >> 8) & 0xff00)
        | ((x << 8) & 0xff0000)
        | (x << 24);
}


#endif
ceiling_t* activeceilings[MAXCEILINGS];


//
// T_MoveCeiling
//
void T_MoveCeiling(ceiling_t* ceiling)
{
    result_e res;

    switch (ceiling->direction)
    {
        case 0:
            // IN STASIS
            break;
        case 1:
            // UP
            res = T_MovePlane(ceiling->sector,
                              ceiling->speed,
                              ceiling->topheight,
                              false, 1, ceiling->direction);

            if (!(leveltime & 7))
            {
                switch (ceiling->type)
                {
                    case silentCrushAndRaise:
                        break;
                    default:
                        S_StartSound((mobj_t*)&ceiling->sector->soundorg,
                                     sfx_stnmov);
                        // ?
                        break;
                }
            }

            if (res == pastdest)
            {
                switch (ceiling->type)
                {
                    case raiseToHighest:
                        P_RemoveActiveCeiling(ceiling);
                        break;

                    case silentCrushAndRaise:
                        S_StartSound((mobj_t*)&ceiling->sector->soundorg,
                                     sfx_pstop);
                    case fastCrushAndRaise:
                    case crushAndRaise:
                        ceiling->direction = -1;
                        break;

                    default:
                        break;
                }

            }
            break;

        case -1:
            // DOWN
            res = T_MovePlane(ceiling->sector,
                              ceiling->speed,
                              ceiling->bottomheight,
                              ceiling->crush, 1, ceiling->direction);

            if (!(leveltime & 7))
            {
                switch (ceiling->type)
                {
                    case silentCrushAndRaise: break;
                    default:
                        S_StartSound((mobj_t*)&ceiling->sector->soundorg,
                                     sfx_stnmov);
                }
            }

            if (res == pastdest)
            {
                switch (ceiling->type)
                {
                    case silentCrushAndRaise:
                        S_StartSound((mobj_t*)&ceiling->sector->soundorg,
                                     sfx_pstop);
                    case crushAndRaise:
                        ceiling->speed = CEILSPEED;
                    case fastCrushAndRaise:
                        ceiling->direction = 1;
                        break;

                    case lowerAndCrush:
                    case lowerToFloor:
                        P_RemoveActiveCeiling(ceiling);
                        break;

                    default:
                        break;
                }
            }
            else // ( res != pastdest )
            {
                if (res == crushed)
                {
                    switch (ceiling->type)
                    {
                        case silentCrushAndRaise:
                        case crushAndRaise:
                        case lowerAndCrush:
                            ceiling->speed = CEILSPEED / 8;
                            break;

                        default:
                            break;
                    }
                }
            }
            break;
    }
}


//
// EV_DoCeiling
// Move a ceiling up/down and all around!
//
int EV_DoCeiling(line_t* line, ceiling_e type)
{
    int secnum;
    int rtn;
    sector_t* sec;
    ceiling_t* ceiling;

    secnum = -1;
    rtn = 0;

    // Reactivate in-stasis ceilings...for certain types.
    switch (type)
    {
        case fastCrushAndRaise:
        case silentCrushAndRaise:
        case crushAndRaise:
            P_ActivateInStasisCeiling(line);
        default:
            break;
    }

    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];
        if (sec->specialdata)
            continue;

        // new door thinker
        rtn = 1;
        ceiling = Z_Malloc(sizeof(*ceiling), PU_LEVSPEC, 0);
        P_AddThinker(&ceiling->thinker);
        sec->specialdata = ceiling;
        ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling;
        ceiling->sector = sec;
        ceiling->crush = false;

        switch (type)
        {
            case fastCrushAndRaise:
                ceiling->crush = true;
                ceiling->topheight = sec->ceilingheight;
                ceiling->bottomheight = sec->floorheight + (8 * FRACUNIT);
                ceiling->direction = -1;
                ceiling->speed = CEILSPEED * 2;
                break;

            case silentCrushAndRaise:
            case crushAndRaise:
                ceiling->crush = true;
                ceiling->topheight = sec->ceilingheight;
            case lowerAndCrush:
            case lowerToFloor:
                ceiling->bottomheight = sec->floorheight;
                if (type != lowerToFloor)
                    ceiling->bottomheight += 8 * FRACUNIT;
                ceiling->direction = -1;
                ceiling->speed = CEILSPEED;
                break;

            case raiseToHighest:
                ceiling->topheight = P_FindHighestCeilingSurrounding(sec);
                ceiling->direction = 1;
                ceiling->speed = CEILSPEED;
                break;
        }

        ceiling->tag = sec->tag;
        ceiling->type = type;
        P_AddActiveCeiling(ceiling);
    }

    return rtn;
}


//
// Add an active ceiling
//
void P_AddActiveCeiling(ceiling_t* c)
{
    int i;

    for (i = 0; i < MAXCEILINGS; i++)
    {
        if (activeceilings[i] == 0)
        {
            activeceilings[i] = c;
            return;
        }
    }
}


//
// Remove a ceiling's thinker
//
void P_RemoveActiveCeiling(ceiling_t* c)
{
    int i;

    for (i = 0; i < MAXCEILINGS; i++)
    {
        if (activeceilings[i] == c)
        {
            activeceilings[i]->sector->specialdata = 0;
            P_RemoveThinker(&activeceilings[i]->thinker);
            activeceilings[i] = 0;
            break;
        }
    }
}


//
// Restart a ceiling that's in-stasis
//
void P_ActivateInStasisCeiling(line_t* line)
{
    int i;

    for (i = 0; i < MAXCEILINGS; i++)
    {
        if (activeceilings[i]
            && (activeceilings[i]->tag == line->tag)
            && (activeceilings[i]->direction == 0))
        {
            activeceilings[i]->direction = activeceilings[i]->olddirection;
            activeceilings[i]->thinker.function.acp1
                = (actionf_p1)T_MoveCeiling;
        }
    }
}


//
// EV_CeilingCrushStop
// Stop a ceiling from crushing!
//
int EV_CeilingCrushStop(line_t* line)
{
    int i;
    int rtn;

    rtn = 0;
    for (i = 0; i < MAXCEILINGS; i++)
    {
        if (activeceilings[i]
            && (activeceilings[i]->tag == line->tag)
            && (activeceilings[i]->direction != 0))
        {
            activeceilings[i]->olddirection = activeceilings[i]->direction;
            activeceilings[i]->thinker.function.acv = (actionf_v)0;
            activeceilings[i]->direction = 0;                // in-stasis
            rtn = 1;
        }
    }

    return rtn;
}
void T_VerticalDoor(vldoor_t* door)
{
    result_e res;

    switch (door->direction)
    {
        case 0:
            // WAITING
            if (!--door->topcountdown)
            {
                switch (door->type)
                {
                    case blazeRaise:
                        door->direction = -1; // time to go back down
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_bdcls);
                        break;

                    case door_normal:
                        door->direction = -1; // time to go back down
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_dorcls);
                        break;

                    case close30ThenOpen:
                        door->direction = 1;
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_doropn);
                        break;

                    default:
                        break;
                }
            }
            break;

        case 2:
            //  INITIAL WAIT
            if (!--door->topcountdown)
            {
                switch (door->type)
                {
                    case raiseIn5Mins:
                        door->direction = 1;
                        door->type = door_normal;
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_doropn);
                        break;

                    default:
                        break;
                }
            }
            break;

        case -1:
            // DOWN
            res = T_MovePlane(door->sector,
                              door->speed,
                              door->sector->floorheight,
                              false, 1, door->direction);
            if (res == pastdest)
            {
                switch (door->type)
                {
                    case blazeRaise:
                    case blazeClose:
                        door->sector->specialdata = 0;
                        P_RemoveThinker(&door->thinker);  // unlink and free
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_bdcls);
                        break;

                    case door_normal:
                    case door_close:
                        door->sector->specialdata = 0;
                        P_RemoveThinker(&door->thinker);  // unlink and free
                        break;

                    case close30ThenOpen:
                        door->direction = 0;
                        door->topcountdown = 35 * 30;
                        break;

                    default:
                        break;
                }
            }
            else if (res == crushed)
            {
                switch (door->type)
                {
                    case blazeClose:
                    case door_close:                // DO NOT GO BACK UP!
                        break;

                    default:
                        door->direction = 1;
                        S_StartSound((mobj_t*)&door->sector->soundorg,
                                     sfx_doropn);
                        break;
                }
            }
            break;

        case 1:
            // UP
            res = T_MovePlane(door->sector,
                              door->speed,
                              door->topheight,
                              false, 1, door->direction);

            if (res == pastdest)
            {
                switch (door->type)
                {
                    case blazeRaise:
                    case door_normal:
                        door->direction = 0; // wait at top
                        door->topcountdown = door->topwait;
                        break;

                    case close30ThenOpen:
                    case blazeOpen:
                    case door_open:
                        door->sector->specialdata = 0;
                        P_RemoveThinker(&door->thinker);  // unlink and free
                        break;

                    default:
                        break;
                }
            }
            break;
    }
}


//
// EV_DoLockedDoor
// Move a locked door up/down
//
int EV_DoLockedDoor(line_t* line, vldoor_e type, mobj_t* thing)
{
    player_t* p;

    p = thing->player;

    if (!p)
        return 0;

    switch (line->special)
    {
        case 99:        // Blue Lock
        case 133:
            if (!p)
                return 0;
            if (!p->cards[it_bluecard] && !p->cards[it_blueskull])
            {
                p->message = PD_BLUEO;
                S_StartSound(0, sfx_oof);
                return 0;
            }
            break;

        case 134: // Red Lock
        case 135:
            if (!p)
                return 0;
            if (!p->cards[it_redcard] && !p->cards[it_redskull])
            {
                p->message = PD_REDO;
                S_StartSound(0, sfx_oof);
                return 0;
            }
            break;

        case 136:        // Yellow Lock
        case 137:
            if (!p)
                return 0;
            if (!p->cards[it_yellowcard] &&
                !p->cards[it_yellowskull])
            {
                p->message = PD_YELLOWO;
                S_StartSound(0, sfx_oof);
                return 0;
            }
            break;
    }

    return EV_DoDoor(line, type);
}


int EV_DoDoor(line_t* line, vldoor_e type)
{
    int secnum, rtn;
    sector_t* sec;
    vldoor_t* door;

    secnum = -1;
    rtn = 0;

    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];
        if (sec->specialdata)
            continue;


        // new door thinker
        rtn = 1;
        door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0);
        P_AddThinker(&door->thinker);
        sec->specialdata = door;

        door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
        door->sector = sec;
        door->type = type;
        door->topwait = VDOORWAIT;
        door->speed = VDOORSPEED;

        switch (type)
        {
            case blazeClose:
                door->topheight = P_FindLowestCeilingSurrounding(sec);
                door->topheight -= 4 * FRACUNIT;
                door->direction = -1;
                door->speed = VDOORSPEED * 4;
                S_StartSound((mobj_t*)&door->sector->soundorg,
                             sfx_bdcls);
                break;

            case door_close:
                door->topheight = P_FindLowestCeilingSurrounding(sec);
                door->topheight -= 4 * FRACUNIT;
                door->direction = -1;
                S_StartSound((mobj_t*)&door->sector->soundorg,
                             sfx_dorcls);
                break;

            case close30ThenOpen:
                door->topheight = sec->ceilingheight;
                door->direction = -1;
                S_StartSound((mobj_t*)&door->sector->soundorg,
                             sfx_dorcls);
                break;

            case blazeRaise:
            case blazeOpen:
                door->direction = 1;
                door->topheight = P_FindLowestCeilingSurrounding(sec);
                door->topheight -= 4 * FRACUNIT;
                door->speed = VDOORSPEED * 4;
                if (door->topheight != sec->ceilingheight)
                    S_StartSound((mobj_t*)&door->sector->soundorg,
                                 sfx_bdopn);
                break;

            case door_normal:
            case door_open:
                door->direction = 1;
                door->topheight = P_FindLowestCeilingSurrounding(sec);
                door->topheight -= 4 * FRACUNIT;
                if (door->topheight != sec->ceilingheight)
                    S_StartSound((mobj_t*)&door->sector->soundorg,
                                 sfx_doropn);
                break;

            default:
                break;
        }
    }

    return rtn;
}


//
// EV_VerticalDoor : open a door manually, no tag value
//
void EV_VerticalDoor(line_t* line, mobj_t* thing)
{
    player_t* player;
    int secnum;
    sector_t* sec;
    vldoor_t* door;
    int side;

    side = 0; // only front sides can be used

    // Check for locks
    player = thing->player;

    switch (line->special)
    {
        case 26: // Blue Lock
        case 32:
            if (!player)
                return;

            if (!player->cards[it_bluecard] && !player->cards[it_blueskull])
            {
                player->message = PD_BLUEK;
                S_StartSound(0, sfx_oof);
                return;
            }
            break;

        case 27: // Yellow Lock
        case 34:
            if (!player)
                return;

            if (!player->cards[it_yellowcard] &&
                !player->cards[it_yellowskull])
            {
                player->message = PD_YELLOWK;
                S_StartSound(0, sfx_oof);
                return;
            }
            break;

        case 28: // Red Lock
        case 33:
            if (!player)
                return;

            if (!player->cards[it_redcard] && !player->cards[it_redskull])
            {
                player->message = PD_REDK;
                S_StartSound(0, sfx_oof);
                return;
            }
            break;
    }

    // if the sector has an active thinker, use it
    sec = sides[line->sidenum[side ^ 1]].sector;
    secnum = (int)(sec - sectors);

    if (sec->specialdata)
    {
        door = sec->specialdata;
        switch (line->special)
        {
            case        1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s
            case        26:
            case        27:
            case        28:
            case        117:
                if (door->direction == -1)
                    door->direction = 1;        // go back up
                else
                {
                    if (!thing->player)
                        return;                // JDC: bad guys never close doors

                    door->direction = -1;        // start going down immediately
                }
                return;
        }
    }

    // for proper sound
    switch (line->special)
    {
        case 117:        // BLAZING DOOR RAISE
        case 118:        // BLAZING DOOR OPEN
            S_StartSound((mobj_t*)&sec->soundorg, sfx_bdopn);
            break;

        case 1:        // NORMAL DOOR SOUND
        case 31:
            S_StartSound((mobj_t*)&sec->soundorg, sfx_doropn);
            break;

        default:        // LOCKED DOOR SOUND
            S_StartSound((mobj_t*)&sec->soundorg, sfx_doropn);
            break;
    }


    // new door thinker
    door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0);
    P_AddThinker(&door->thinker);
    sec->specialdata = door;
    door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
    door->sector = sec;
    door->direction = 1;
    door->speed = VDOORSPEED;
    door->topwait = VDOORWAIT;

    switch (line->special)
    {
        case 1:
        case 26:
        case 27:
        case 28:
            door->type = door_normal;
            break;

        case 31:
        case 32:
        case 33:
        case 34:
            door->type = door_open;
            line->special = 0;
            break;

        case 117:        // blazing door raise
            door->type = blazeRaise;
            door->speed = VDOORSPEED * 4;
            break;
        case 118:        // blazing door open
            door->type = blazeOpen;
            line->special = 0;
            door->speed = VDOORSPEED * 4;
            break;
    }

    // find the top and bottom of the movement range
    door->topheight = P_FindLowestCeilingSurrounding(sec);
    door->topheight -= 4 * FRACUNIT;
}


//
// Spawn a door that closes after 30 seconds
//
void P_SpawnDoorCloseIn30(sector_t* sec)
{
    vldoor_t* door;

    door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0);

    P_AddThinker(&door->thinker);

    sec->specialdata = door;
    sec->special = 0;

    door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
    door->sector = sec;
    door->direction = 0;
    door->type = door_normal;
    door->speed = VDOORSPEED;
    door->topcountdown = 30 * 35;
}


//
// Spawn a door that opens after 5 minutes
//
void P_SpawnDoorRaiseIn5Mins(sector_t* sec, int secnum)
{
    vldoor_t* door;

    door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0);

    P_AddThinker(&door->thinker);

    sec->specialdata = door;
    sec->special = 0;

    door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
    door->sector = sec;
    door->direction = 2;
    door->type = raiseIn5Mins;
    door->speed = VDOORSPEED;
    door->topheight = P_FindLowestCeilingSurrounding(sec);
    door->topheight -= 4 * FRACUNIT;
    door->topwait = VDOORWAIT;
    door->topcountdown = 5 * 60 * 35;
}
#define MAXSPECIALCROSS 8
#define FATSPREAD (ANG90/8)
#define SKULLSPEED (20*FRACUNIT)


typedef enum
{
    DI_EAST,
    DI_NORTHEAST,
    DI_NORTH,
    DI_NORTHWEST,
    DI_WEST,
    DI_SOUTHWEST,
    DI_SOUTH,
    DI_SOUTHEAST,
    DI_NODIR,
    NUMDIRS
} dirtype_t;


//
// P_NewChaseDir related LUT.
//
dirtype_t opposite[] =
{
  DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST,
  DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR
};

dirtype_t diags[] =
{
    DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST
};

mobj_t* soundtarget;
fixed_t xspeed[8] = { FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000 };
fixed_t yspeed[8] = { 0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000 };
int TRACEANGLE = 0xc000000;
mobj_t* corpsehit;
mobj_t* vileobj;
fixed_t viletryx;
fixed_t viletryy;
mobj_t* braintargets[32];
int numbraintargets;
int braintargeton;


extern line_t* spechit[MAXSPECIALCROSS];
extern int numspechit;


void A_Fall(mobj_t* actor);
void A_Fire(mobj_t* actor);
void A_ReFire(player_t* player, pspdef_t* psp);
void A_SpawnFly(mobj_t* mo);


//
// ENEMY THINKING
// Enemies are allways spawned
// with targetplayer = -1, threshold = 0
// Most monsters are spawned unaware of all players,
// but some can be made preaware
//


//
// Called by P_NoiseAlert.
// Recursively traverse adjacent sectors,
// sound blocking lines cut off traversal.
//
void P_RecursiveSound(sector_t* sec, int soundblocks)
{
    int i;
    line_t* check;
    sector_t* other;

    // wake up all monsters in this sector
    if (sec->validcount == validcount
        && sec->soundtraversed <= soundblocks + 1)
    {
        return; // already flooded
    }

    sec->validcount = validcount;
    sec->soundtraversed = soundblocks + 1;
    sec->soundtarget = soundtarget;

    for (i = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        if (!(check->flags & ML_TWOSIDED))
            continue;

        P_LineOpening(check);

        if (openrange <= 0)
            continue; // closed door

        if (sides[check->sidenum[0]].sector == sec)
            other = sides[check->sidenum[1]].sector;
        else
            other = sides[check->sidenum[0]].sector;

        if (check->flags & ML_SOUNDBLOCK)
        {
            if (!soundblocks)
                P_RecursiveSound(other, 1);
        }
        else
            P_RecursiveSound(other, soundblocks);
    }
}


//
// P_NoiseAlert
// If a monster yells at a player,
// it will alert other monsters to the player.
//
void P_NoiseAlert(mobj_t* target, mobj_t* emmiter)
{
    soundtarget = target;
    validcount++;
    P_RecursiveSound(emmiter->subsector->sector, 0);
}


//
// P_CheckMeleeRange
//
doom_boolean P_CheckMeleeRange(mobj_t* actor)
{
    mobj_t* pl;
    fixed_t dist;

    if (!actor->target)
        return false;

    pl = actor->target;
    dist = P_AproxDistance(pl->x - actor->x, pl->y - actor->y);

    if (dist >= MELEERANGE - 20 * FRACUNIT + pl->info->radius)
        return false;

    if (!P_CheckSight(actor, actor->target))
        return false;

    return true;
}


//
// P_CheckMissileRange
//
doom_boolean P_CheckMissileRange(mobj_t* actor)
{
    fixed_t dist;

    if (!P_CheckSight(actor, actor->target))
        return false;

    if (actor->flags & MF_JUSTHIT)
    {
        // the target just hit the enemy,
        // so fight back!
        actor->flags &= ~MF_JUSTHIT;
        return true;
    }

    if (actor->reactiontime)
        return false; // do not attack yet

    // OPTIMIZE: get this from a global checksight
    dist = P_AproxDistance(actor->x - actor->target->x,
                           actor->y - actor->target->y) - 64 * FRACUNIT;

    if (!actor->info->meleestate)
        dist -= 128 * FRACUNIT; // no melee attack, so fire more

    dist >>= 16;

    if (actor->type == MT_VILE)
    {
        if (dist > 14 * 64)
            return false; // too far away
    }


    if (actor->type == MT_UNDEAD)
    {
        if (dist < 196)
            return false; // close for fist attack
        dist >>= 1;
    }

    if (actor->type == MT_CYBORG
        || actor->type == MT_SPIDER
        || actor->type == MT_SKULL)
    {
        dist >>= 1;
    }

    if (dist > 200)
        dist = 200;

    if (actor->type == MT_CYBORG && dist > 160)
        dist = 160;

    if (P_Random() < dist)
        return false;

    return true;
}


//
// P_Move
// Move in the current direction,
// returns false if the move is blocked.
//
doom_boolean P_Move(mobj_t* actor)
{
    fixed_t tryx;
    fixed_t tryy;

    line_t* ld;

    // warning: 'catch', 'throw', and 'try'
    // are all C++ reserved words
    doom_boolean try_ok;
    doom_boolean good;

    if (actor->movedir == DI_NODIR)
        return false;

    if ((unsigned)actor->movedir >= 8)
        I_Error("Error: Weird actor->movedir!");

    tryx = actor->x + actor->info->speed * xspeed[actor->movedir];
    tryy = actor->y + actor->info->speed * yspeed[actor->movedir];

    try_ok = P_TryMove(actor, tryx, tryy);

    if (!try_ok)
    {
        // open any specials
        if (actor->flags & MF_FLOAT && floatok)
        {
            // must adjust height
            if (actor->z < tmfloorz)
                actor->z += FLOATSPEED;
            else
                actor->z -= FLOATSPEED;

            actor->flags |= MF_INFLOAT;
            return true;
        }

        if (!numspechit)
            return false;

        actor->movedir = DI_NODIR;
        good = false;
        while (numspechit--)
        {
            ld = spechit[numspechit];
            // if the special is not a door
            // that can be opened,
            // return false
            if (P_UseSpecialLine(actor, ld, 0))
                good = true;
        }
        return good;
    }
    else
    {
        actor->flags &= ~MF_INFLOAT;
    }

    if (!(actor->flags & MF_FLOAT))
        actor->z = actor->floorz;

    return true;
}


//
// TryWalk
// Attempts to move actor on
// in its current (ob->moveangle) direction.
// If blocked by either a wall or an actor
// returns FALSE
// If move is either clear or blocked only by a door,
// returns TRUE and sets...
// If a door is in the way,
// an OpenDoor call is made to start it opening.
//
doom_boolean P_TryWalk(mobj_t* actor)
{
    if (!P_Move(actor))
    {
        return false;
    }

    actor->movecount = P_Random() & 15;
    return true;
}


void P_NewChaseDir(mobj_t* actor)
{
    fixed_t deltax;
    fixed_t deltay;

    dirtype_t d[3];

    int tdir;
    dirtype_t olddir;

    dirtype_t turnaround;

    if (!actor->target)
        I_Error("Error: P_NewChaseDir: called with no target");

    olddir = actor->movedir;
    turnaround = opposite[olddir];

    deltax = actor->target->x - actor->x;
    deltay = actor->target->y - actor->y;

    if (deltax > 10 * FRACUNIT)
        d[1] = DI_EAST;
    else if (deltax < -10 * FRACUNIT)
        d[1] = DI_WEST;
    else
        d[1] = DI_NODIR;

    if (deltay < -10 * FRACUNIT)
        d[2] = DI_SOUTH;
    else if (deltay > 10 * FRACUNIT)
        d[2] = DI_NORTH;
    else
        d[2] = DI_NODIR;

    // try direct route
    if (d[1] != DI_NODIR
        && d[2] != DI_NODIR)
    {
        actor->movedir = diags[((deltay < 0) << 1) + (deltax > 0)];
        if (actor->movedir != turnaround && P_TryWalk(actor))
            return;
    }

    // try other directions
    if (P_Random() > 200
        || doom_abs(deltay) > doom_abs(deltax))
    {
        tdir = d[1];
        d[1] = d[2];
        d[2] = tdir;
    }

    if (d[1] == turnaround)
        d[1] = DI_NODIR;
    if (d[2] == turnaround)
        d[2] = DI_NODIR;

    if (d[1] != DI_NODIR)
    {
        actor->movedir = d[1];
        if (P_TryWalk(actor))
        {
            // either moved forward or attacked
            return;
        }
    }

    if (d[2] != DI_NODIR)
    {
        actor->movedir = d[2];

        if (P_TryWalk(actor))
            return;
    }

    // there is no direct path to the player,
    // so pick another direction.
    if (olddir != DI_NODIR)
    {
        actor->movedir = olddir;

        if (P_TryWalk(actor))
            return;
    }

    // randomly determine direction of search
    if (P_Random() & 1)
    {
        for (tdir = DI_EAST;
             tdir <= DI_SOUTHEAST;
             tdir++)
        {
            if (tdir != turnaround)
            {
                actor->movedir = tdir;

                if (P_TryWalk(actor))
                    return;
            }
        }
    }
    else
    {
        for (tdir = DI_SOUTHEAST;
             tdir != (DI_EAST - 1);
             tdir--)
        {
            if (tdir != turnaround)
            {
                actor->movedir = tdir;

                if (P_TryWalk(actor))
                    return;
            }
        }
    }

    if (turnaround != DI_NODIR)
    {
        actor->movedir = turnaround;
        if (P_TryWalk(actor))
            return;
    }

    actor->movedir = DI_NODIR; // can not move
}


//
// P_LookForPlayers
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//
doom_boolean P_LookForPlayers(mobj_t* actor, doom_boolean allaround)
{
    int c;
    int stop;
    player_t* player;
    sector_t* sector;
    angle_t an;
    fixed_t dist;

    sector = actor->subsector->sector;

    c = 0;
    stop = (actor->lastlook - 1) & 3;

    for (; ; actor->lastlook = (actor->lastlook + 1) & 3)
    {
        if (!playeringame[actor->lastlook])
            continue;

        if (c++ == 2
            || actor->lastlook == stop)
        {
            // done looking
            return false;
        }

        player = &players[actor->lastlook];

        if (player->health <= 0)
            continue;                // dead

        if (!P_CheckSight(actor, player->mo))
            continue;                // out of sight

        if (!allaround)
        {
            an = R_PointToAngle2(actor->x,
                                 actor->y,
                                 player->mo->x,
                                 player->mo->y)
                - actor->angle;

            if (an > ANG90 && an < ANG270)
            {
                dist = P_AproxDistance(player->mo->x - actor->x,
                                       player->mo->y - actor->y);
                // if real close, react anyway
                if (dist > MELEERANGE)
                    continue;        // behind back
            }
        }

        actor->target = player->mo;
        return true;
    }

    return false;
}


//
// A_KeenDie
// DOOM II special, map 32.
// Uses special tag 666.
//
void A_KeenDie(mobj_t* mo)
{
    thinker_t* th;
    mobj_t* mo2;
    line_t junk;

    A_Fall(mo);

    // scan the remaining thinkers
    // to see if all Keens are dead
    for (th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if (th->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;

        mo2 = (mobj_t*)th;
        if (mo2 != mo
            && mo2->type == mo->type
            && mo2->health > 0)
        {
            // other Keen not dead
            return;
        }
    }

    junk.tag = 666;
    EV_DoDoor(&junk, door_open);
}


//
// ACTION ROUTINES
//

//
// A_Look
// Stay in state until a player is sighted.
//
void A_Look(mobj_t* actor)
{
    mobj_t* targ;

    actor->threshold = 0; // any shot will wake up
    targ = actor->subsector->sector->soundtarget;

    if (targ
        && (targ->flags & MF_SHOOTABLE))
    {
        actor->target = targ;

        if (actor->flags & MF_AMBUSH)
        {
            if (P_CheckSight(actor, actor->target))
                goto seeyou;
        }
        else
            goto seeyou;
    }


    if (!P_LookForPlayers(actor, false))
        return;

    // go into chase state
seeyou:
    if (actor->info->seesound)
    {
        int sound;

        switch (actor->info->seesound)
        {
            case sfx_posit1:
            case sfx_posit2:
            case sfx_posit3:
                sound = sfx_posit1 + P_Random() % 3;
                break;

            case sfx_bgsit1:
            case sfx_bgsit2:
                sound = sfx_bgsit1 + P_Random() % 2;
                break;

            default:
                sound = actor->info->seesound;
                break;
        }

        if (actor->type == MT_SPIDER
            || actor->type == MT_CYBORG)
        {
            // full volume
            S_StartSound(0, sound);
        }
        else
            S_StartSound(actor, sound);
    }

    P_SetMobjState(actor, actor->info->seestate);
}


//
// A_Chase
// Actor has a melee attack,
// so it tries to close as fast as possible
//
void A_Chase(mobj_t* actor)
{
    int delta;

    if (actor->reactiontime)
        actor->reactiontime--;

    // modify target threshold
    if (actor->threshold)
    {
        if (!actor->target
            || actor->target->health <= 0)
        {
            actor->threshold = 0;
        }
        else
            actor->threshold--;
    }

    // turn towards movement direction if not there yet
    if (actor->movedir < 8)
    {
        actor->angle &= (7 << 29);
        delta = actor->angle - (actor->movedir << 29);

        if (delta > 0)
            actor->angle -= ANG90 / 2;
        else if (delta < 0)
            actor->angle += ANG90 / 2;
    }

    if (!actor->target
        || !(actor->target->flags & MF_SHOOTABLE))
    {
        // look for a new target
        if (P_LookForPlayers(actor, true))
            return;         // got a new target

        P_SetMobjState(actor, actor->info->spawnstate);
        return;
    }

    // do not attack twice in a row
    if (actor->flags & MF_JUSTATTACKED)
    {
        actor->flags &= ~MF_JUSTATTACKED;
        if (gameskill != sk_nightmare && !fastparm)
            P_NewChaseDir(actor);
        return;
    }

    // check for melee attack
    if (actor->info->meleestate
        && P_CheckMeleeRange(actor))
    {
        if (actor->info->attacksound)
            S_StartSound(actor, actor->info->attacksound);

        P_SetMobjState(actor, actor->info->meleestate);
        return;
    }

    // check for missile attack
    if (actor->info->missilestate)
    {
        if (gameskill < sk_nightmare
            && !fastparm && actor->movecount)
        {
            goto nomissile;
        }

        if (!P_CheckMissileRange(actor))
            goto nomissile;

        P_SetMobjState(actor, actor->info->missilestate);
        actor->flags |= MF_JUSTATTACKED;
        return;
    }

    // ?
nomissile:
    // possibly choose another target
    if (netgame
        && !actor->threshold
        && !P_CheckSight(actor, actor->target))
    {
        if (P_LookForPlayers(actor, true))
            return;        // got a new target
    }

    // chase towards player
    if (--actor->movecount < 0
        || !P_Move(actor))
    {
        P_NewChaseDir(actor);
    }

    // make active sound
    if (actor->info->activesound
        && P_Random() < 3)
    {
        S_StartSound(actor, actor->info->activesound);
    }
}


//
// A_FaceTarget
//
void A_FaceTarget(mobj_t* actor)
{
    if (!actor->target)
        return;

    actor->flags &= ~MF_AMBUSH;

    actor->angle = R_PointToAngle2(actor->x,
                                   actor->y,
                                   actor->target->x,
                                   actor->target->y);

    if (actor->target->flags & MF_SHADOW)
        actor->angle += (P_Random() - P_Random()) << 21;
}


//
// A_PosAttack
//
void A_PosAttack(mobj_t* actor)
{
    int angle;
    int damage;
    int slope;

    if (!actor->target)
        return;

    A_FaceTarget(actor);
    angle = actor->angle;
    slope = P_AimLineAttack(actor, angle, MISSILERANGE);

    S_StartSound(actor, sfx_pistol);
    angle += (P_Random() - P_Random()) << 20;
    damage = ((P_Random() % 5) + 1) * 3;
    P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}


void A_SPosAttack(mobj_t* actor)
{
    int i;
    int angle;
    int bangle;
    int damage;
    int slope;

    if (!actor->target)
        return;

    S_StartSound(actor, sfx_shotgn);
    A_FaceTarget(actor);
    bangle = actor->angle;
    slope = P_AimLineAttack(actor, bangle, MISSILERANGE);

    for (i = 0; i < 3; i++)
    {
        angle = bangle + ((P_Random() - P_Random()) << 20);
        damage = ((P_Random() % 5) + 1) * 3;
        P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
    }
}


void A_CPosAttack(mobj_t* actor)
{
    int angle;
    int bangle;
    int damage;
    int slope;

    if (!actor->target)
        return;

    S_StartSound(actor, sfx_shotgn);
    A_FaceTarget(actor);
    bangle = actor->angle;
    slope = P_AimLineAttack(actor, bangle, MISSILERANGE);

    angle = bangle + ((P_Random() - P_Random()) << 20);
    damage = ((P_Random() % 5) + 1) * 3;
    P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}


void A_CPosRefire(mobj_t* actor)
{
    // keep firing unless target got out of sight
    A_FaceTarget(actor);

    if (P_Random() < 40)
        return;

    if (!actor->target
        || actor->target->health <= 0
        || !P_CheckSight(actor, actor->target))
    {
        P_SetMobjState(actor, actor->info->seestate);
    }
}


void A_SpidRefire(mobj_t* actor)
{
    // keep firing unless target got out of sight
    A_FaceTarget(actor);

    if (P_Random() < 10)
        return;

    if (!actor->target
        || actor->target->health <= 0
        || !P_CheckSight(actor, actor->target))
    {
        P_SetMobjState(actor, actor->info->seestate);
    }
}


void A_BspiAttack(mobj_t* actor)
{
    if (!actor->target)
        return;

    A_FaceTarget(actor);

    // launch a missile
    P_SpawnMissile(actor, actor->target, MT_ARACHPLAZ);
}


//
// A_TroopAttack
//
void A_TroopAttack(mobj_t* actor)
{
    int damage;

    if (!actor->target)
        return;

    A_FaceTarget(actor);
    if (P_CheckMeleeRange(actor))
    {
        S_StartSound(actor, sfx_claw);
        damage = (P_Random() % 8 + 1) * 3;
        P_DamageMobj(actor->target, actor, actor, damage);
        return;
    }

    // launch a missile
    P_SpawnMissile(actor, actor->target, MT_TROOPSHOT);
}


void A_SargAttack(mobj_t* actor)
{
    int damage;

    if (!actor->target)
        return;

    A_FaceTarget(actor);
    if (P_CheckMeleeRange(actor))
    {
        damage = ((P_Random() % 10) + 1) * 4;
        P_DamageMobj(actor->target, actor, actor, damage);
    }
}


void A_HeadAttack(mobj_t* actor)
{
    int damage;

    if (!actor->target)
        return;

    A_FaceTarget(actor);
    if (P_CheckMeleeRange(actor))
    {
        damage = (P_Random() % 6 + 1) * 10;
        P_DamageMobj(actor->target, actor, actor, damage);
        return;
    }

    // launch a missile
    P_SpawnMissile(actor, actor->target, MT_HEADSHOT);
}


void A_CyberAttack(mobj_t* actor)
{
    if (!actor->target)
        return;

    A_FaceTarget(actor);
    P_SpawnMissile(actor, actor->target, MT_ROCKET);
}


void A_BruisAttack(mobj_t* actor)
{
    int damage;

    if (!actor->target)
        return;

    if (P_CheckMeleeRange(actor))
    {
        S_StartSound(actor, sfx_claw);
        damage = (P_Random() % 8 + 1) * 10;
        P_DamageMobj(actor->target, actor, actor, damage);
        return;
    }

    // launch a missile
    P_SpawnMissile(actor, actor->target, MT_BRUISERSHOT);
}


//
// A_SkelMissile
//
void A_SkelMissile(mobj_t* actor)
{
    mobj_t* mo;

    if (!actor->target)
        return;

    A_FaceTarget(actor);
    actor->z += 16 * FRACUNIT; // so missile spawns higher
    mo = P_SpawnMissile(actor, actor->target, MT_TRACER);
    actor->z -= 16 * FRACUNIT; // back to normal

    mo->x += mo->momx;
    mo->y += mo->momy;
    mo->tracer = actor->target;
}


void A_Tracer(mobj_t* actor)
{
    angle_t exact;
    fixed_t dist;
    fixed_t slope;
    mobj_t* dest;
    mobj_t* th;

    if (gametic & 3)
        return;

    // spawn a puff of smoke behind the rocket                
    P_SpawnPuff(actor->x, actor->y, actor->z);

    th = P_SpawnMobj(actor->x - actor->momx,
                     actor->y - actor->momy,
                     actor->z, MT_SMOKE);

    th->momz = FRACUNIT;
    th->tics -= P_Random() & 3;
    if (th->tics < 1)
        th->tics = 1;

    // adjust direction
    dest = actor->tracer;

    if (!dest || dest->health <= 0)
        return;

    // change angle        
    exact = R_PointToAngle2(actor->x,
                            actor->y,
                            dest->x,
                            dest->y);

    if (exact != actor->angle)
    {
        if (exact - actor->angle > 0x80000000)
        {
            actor->angle -= TRACEANGLE;
            if (exact - actor->angle < 0x80000000)
                actor->angle = exact;
        }
        else
        {
            actor->angle += TRACEANGLE;
            if (exact - actor->angle > 0x80000000)
                actor->angle = exact;
        }
    }

    exact = actor->angle >> ANGLETOFINESHIFT;
    actor->momx = FixedMul(actor->info->speed, finecosine[exact]);
    actor->momy = FixedMul(actor->info->speed, finesine[exact]);

    // change slope
    dist = P_AproxDistance(dest->x - actor->x,
                           dest->y - actor->y);

    dist = dist / actor->info->speed;

    if (dist < 1)
        dist = 1;
    slope = (dest->z + 40 * FRACUNIT - actor->z) / dist;

    if (slope < actor->momz)
        actor->momz -= FRACUNIT / 8;
    else
        actor->momz += FRACUNIT / 8;
}


void A_SkelWhoosh(mobj_t* actor)
{
    if (!actor->target)
        return;

    A_FaceTarget(actor);
    S_StartSound(actor, sfx_skeswg);
}


void A_SkelFist(mobj_t* actor)
{
    int damage;

    if (!actor->target)
        return;

    A_FaceTarget(actor);

    if (P_CheckMeleeRange(actor))
    {
        damage = ((P_Random() % 10) + 1) * 6;
        S_StartSound(actor, sfx_skepch);
        P_DamageMobj(actor->target, actor, actor, damage);
    }
}


//
// PIT_VileCheck
// Detect a corpse that could be raised.
//
doom_boolean PIT_VileCheck(mobj_t* thing)
{
    int maxdist;
    doom_boolean check;

    if (!(thing->flags & MF_CORPSE))
        return true; // not a monster

    if (thing->tics != -1)
        return true; // not lying still yet

    if (thing->info->raisestate == S_NULL)
        return true; // monster doesn't have a raise state

    maxdist = thing->info->radius + mobjinfo[MT_VILE].radius;

    if (doom_abs(thing->x - viletryx) > maxdist
        || doom_abs(thing->y - viletryy) > maxdist)
        return true; // not actually touching

    corpsehit = thing;
    corpsehit->momx = corpsehit->momy = 0;
    corpsehit->height <<= 2;
    check = P_CheckPosition(corpsehit, corpsehit->x, corpsehit->y);
    corpsehit->height >>= 2;

    if (!check)
        return true; // doesn't fit here

    return false; // got one, so stop checking
}


//
// A_VileChase
// Check for ressurecting a body
//
void A_VileChase(mobj_t* actor)
{
    int xl;
    int xh;
    int yl;
    int yh;

    int bx;
    int by;

    mobjinfo_t* info;
    mobj_t* temp;

    if (actor->movedir != DI_NODIR)
    {
        // check for corpses to raise
        viletryx =
            actor->x + actor->info->speed * xspeed[actor->movedir];
        viletryy =
            actor->y + actor->info->speed * yspeed[actor->movedir];

        xl = (viletryx - bmaporgx - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
        xh = (viletryx - bmaporgx + MAXRADIUS * 2) >> MAPBLOCKSHIFT;
        yl = (viletryy - bmaporgy - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
        yh = (viletryy - bmaporgy + MAXRADIUS * 2) >> MAPBLOCKSHIFT;

        vileobj = actor;
        for (bx = xl; bx <= xh; bx++)
        {
            for (by = yl; by <= yh; by++)
            {
                // Call PIT_VileCheck to check
                // whether object is a corpse
                // that canbe raised.
                if (!P_BlockThingsIterator(bx, by, PIT_VileCheck))
                {
                    // got one!
                    temp = actor->target;
                    actor->target = corpsehit;
                    A_FaceTarget(actor);
                    actor->target = temp;

                    P_SetMobjState(actor, S_VILE_HEAL1);
                    S_StartSound(corpsehit, sfx_slop);
                    info = corpsehit->info;

                    P_SetMobjState(corpsehit, info->raisestate);
                    corpsehit->height <<= 2;
                    corpsehit->flags = info->flags;
                    corpsehit->health = info->spawnhealth;
                    corpsehit->target = 0;

                    return;
                }
            }
        }
    }

    // Return to normal attack.
    A_Chase(actor);
}


//
// A_VileStart
//
void A_VileStart(mobj_t* actor)
{
    S_StartSound(actor, sfx_vilatk);
}


//
// A_Fire
// Keep fire in front of player unless out of sight
//
void A_StartFire(mobj_t* actor)
{
    S_StartSound(actor, sfx_flamst);
    A_Fire(actor);
}


void A_FireCrackle(mobj_t* actor)
{
    S_StartSound(actor, sfx_flame);
    A_Fire(actor);
}


void A_Fire(mobj_t* actor)
{
    mobj_t* dest;
    unsigned        an;

    dest = actor->tracer;
    if (!dest)
        return;

    // don't move it if the vile lost sight
    if (!P_CheckSight(actor->target, dest))
        return;

    an = dest->angle >> ANGLETOFINESHIFT;

    P_UnsetThingPosition(actor);
    actor->x = dest->x + FixedMul(24 * FRACUNIT, finecosine[an]);
    actor->y = dest->y + FixedMul(24 * FRACUNIT, finesine[an]);
    actor->z = dest->z;
    P_SetThingPosition(actor);
}


//
// A_VileTarget
// Spawn the hellfire
//
void A_VileTarget(mobj_t* actor)
{
    mobj_t* fog;

    if (!actor->target)
        return;

    A_FaceTarget(actor);

    fog = P_SpawnMobj(actor->target->x,
                      actor->target->x,
                      actor->target->z, MT_FIRE);

    actor->tracer = fog;
    fog->target = actor;
    fog->tracer = actor->target;
    A_Fire(fog);
}


//
// A_VileAttack
//
void A_VileAttack(mobj_t* actor)
{
    mobj_t* fire;
    int                an;

    if (!actor->target)
        return;

    A_FaceTarget(actor);

    if (!P_CheckSight(actor, actor->target))
        return;

    S_StartSound(actor, sfx_barexp);
    P_DamageMobj(actor->target, actor, actor, 20);
    actor->target->momz = 1000 * FRACUNIT / actor->target->info->mass;

    an = actor->angle >> ANGLETOFINESHIFT;

    fire = actor->tracer;

    if (!fire)
        return;

    // move the fire between the vile and the player
    fire->x = actor->target->x - FixedMul(24 * FRACUNIT, finecosine[an]);
    fire->y = actor->target->y - FixedMul(24 * FRACUNIT, finesine[an]);
    P_RadiusAttack(fire, actor, 70);
}


//
// Mancubus attack,
// firing three missiles (bruisers)
// in three different directions?
// Doesn't look like it. 
//
void A_FatRaise(mobj_t* actor)
{
    A_FaceTarget(actor);
    S_StartSound(actor, sfx_manatk);
}


void A_FatAttack1(mobj_t* actor)
{
    mobj_t* mo;
    int an;

    A_FaceTarget(actor);
    // Change direction  to ...
    actor->angle += FATSPREAD;
    P_SpawnMissile(actor, actor->target, MT_FATSHOT);

    mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
    mo->angle += FATSPREAD;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul(mo->info->speed, finecosine[an]);
    mo->momy = FixedMul(mo->info->speed, finesine[an]);
}


void A_FatAttack2(mobj_t* actor)
{
    mobj_t* mo;
    int an;

    A_FaceTarget(actor);
    // Now here choose opposite deviation.
    actor->angle -= FATSPREAD;
    P_SpawnMissile(actor, actor->target, MT_FATSHOT);

    mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
    mo->angle -= FATSPREAD * 2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul(mo->info->speed, finecosine[an]);
    mo->momy = FixedMul(mo->info->speed, finesine[an]);
}


void A_FatAttack3(mobj_t* actor)
{
    mobj_t* mo;
    int an;

    A_FaceTarget(actor);

    mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
    mo->angle -= FATSPREAD / 2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul(mo->info->speed, finecosine[an]);
    mo->momy = FixedMul(mo->info->speed, finesine[an]);

    mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
    mo->angle += FATSPREAD / 2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul(mo->info->speed, finecosine[an]);
    mo->momy = FixedMul(mo->info->speed, finesine[an]);
}


//
// SkullAttack
// Fly at the player like a missile.
//
void A_SkullAttack(mobj_t* actor)
{
    mobj_t* dest;
    angle_t an;
    int dist;

    if (!actor->target)
        return;

    dest = actor->target;
    actor->flags |= MF_SKULLFLY;

    S_StartSound(actor, actor->info->attacksound);
    A_FaceTarget(actor);
    an = actor->angle >> ANGLETOFINESHIFT;
    actor->momx = FixedMul(SKULLSPEED, finecosine[an]);
    actor->momy = FixedMul(SKULLSPEED, finesine[an]);
    dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
    dist = dist / SKULLSPEED;

    if (dist < 1)
        dist = 1;
    actor->momz = (dest->z + (dest->height >> 1) - actor->z) / dist;
}


//
// A_PainShootSkull
// Spawn a lost soul and launch it at the target
//
void A_PainShootSkull(mobj_t* actor, angle_t angle)
{
    fixed_t x;
    fixed_t y;
    fixed_t z;

    mobj_t* newmobj;
    angle_t an;
    int prestep;
    int count;
    thinker_t* currentthinker;

    // count total number of skull currently on the level
    count = 0;

    currentthinker = thinkercap.next;
    while (currentthinker != &thinkercap)
    {
        if ((currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
            && ((mobj_t*)currentthinker)->type == MT_SKULL)
            count++;
        currentthinker = currentthinker->next;
    }

    // if there are allready 20 skulls on the level,
    // don't spit another one
    if (count > 20)
        return;

    // okay, there's playe for another one
    an = angle >> ANGLETOFINESHIFT;

    prestep =
        4 * FRACUNIT
        + 3 * (actor->info->radius + mobjinfo[MT_SKULL].radius) / 2;

    x = actor->x + FixedMul(prestep, finecosine[an]);
    y = actor->y + FixedMul(prestep, finesine[an]);
    z = actor->z + 8 * FRACUNIT;

    newmobj = P_SpawnMobj(x, y, z, MT_SKULL);

    // Check for movements.
    if (!P_TryMove(newmobj, newmobj->x, newmobj->y))
    {
        // kill it immediately
        P_DamageMobj(newmobj, actor, actor, 10000);
        return;
    }

    newmobj->target = actor->target;
    A_SkullAttack(newmobj);
}


//
// A_PainAttack
// Spawn a lost soul and launch it at the target
// 
void A_PainAttack(mobj_t* actor)
{
    if (!actor->target)
        return;

    A_FaceTarget(actor);
    A_PainShootSkull(actor, actor->angle);
}


void A_PainDie(mobj_t* actor)
{
    A_Fall(actor);
    A_PainShootSkull(actor, actor->angle + ANG90);
    A_PainShootSkull(actor, actor->angle + ANG180);
    A_PainShootSkull(actor, actor->angle + ANG270);
}


void A_Scream(mobj_t* actor)
{
    int sound;

    switch (actor->info->deathsound)
    {
        case 0:
            return;

        case sfx_podth1:
        case sfx_podth2:
        case sfx_podth3:
            sound = sfx_podth1 + P_Random() % 3;
            break;

        case sfx_bgdth1:
        case sfx_bgdth2:
            sound = sfx_bgdth1 + P_Random() % 2;
            break;

        default:
            sound = actor->info->deathsound;
            break;
    }

    // Check for bosses.
    if (actor->type == MT_SPIDER
        || actor->type == MT_CYBORG)
    {
        // full volume
        S_StartSound(0, sound);
    }
    else
        S_StartSound(actor, sound);
}


void A_XScream(mobj_t* actor)
{
    S_StartSound(actor, sfx_slop);
}


void A_Pain(mobj_t* actor)
{
    if (actor->info->painsound)
        S_StartSound(actor, actor->info->painsound);
}


void A_Fall(mobj_t* actor)
{
    // actor is on ground, it can be walked over
    actor->flags &= ~MF_SOLID;

    // So change this if corpse objects
    // are meant to be obstacles.
}


//
// A_Explode
//
void A_Explode(mobj_t* thingy)
{
    P_RadiusAttack(thingy, thingy->target, 128);
}


//
// A_BossDeath
// Possibly trigger special effects
// if on first boss level
//
void A_BossDeath(mobj_t* mo)
{
    thinker_t* th;
    mobj_t* mo2;
    line_t junk;
    int i;

    if (gamemode == commercial)
    {
        if (gamemap != 7)
            return;

        if ((mo->type != MT_FATSO)
            && (mo->type != MT_BABY))
            return;
    }
    else
    {
        switch (gameepisode)
        {
            case 1:
                if (gamemap != 8)
                    return;

                if (mo->type != MT_BRUISER)
                    return;
                break;

            case 2:
                if (gamemap != 8)
                    return;

                if (mo->type != MT_CYBORG)
                    return;
                break;

            case 3:
                if (gamemap != 8)
                    return;

                if (mo->type != MT_SPIDER)
                    return;

                break;

            case 4:
                switch (gamemap)
                {
                    case 6:
                        if (mo->type != MT_CYBORG)
                            return;
                        break;

                    case 8:
                        if (mo->type != MT_SPIDER)
                            return;
                        break;

                    default:
                        return;
                        break;
                }
                break;

            default:
                if (gamemap != 8)
                    return;
                break;
        }
    }

    // make sure there is a player alive for victory
    for (i = 0; i < MAXPLAYERS; i++)
        if (playeringame[i] && players[i].health > 0)
            break;

    if (i == MAXPLAYERS)
        return;        // no one left alive, so do not end game

    // scan the remaining thinkers to see
    // if all bosses are dead
    for (th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if (th->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;

        mo2 = (mobj_t*)th;
        if (mo2 != mo
            && mo2->type == mo->type
            && mo2->health > 0)
        {
            // other boss not dead
            return;
        }
    }

    // victory!
    if (gamemode == commercial)
    {
        if (gamemap == 7)
        {
            if (mo->type == MT_FATSO)
            {
                junk.tag = 666;
                EV_DoFloor(&junk, lowerFloorToLowest);
                return;
            }

            if (mo->type == MT_BABY)
            {
                junk.tag = 667;
                EV_DoFloor(&junk, raiseToTexture);
                return;
            }
        }
    }
    else
    {
        switch (gameepisode)
        {
            case 1:
                junk.tag = 666;
                EV_DoFloor(&junk, lowerFloorToLowest);
                return;
                break;

            case 4:
                switch (gamemap)
                {
                    case 6:
                        junk.tag = 666;
                        EV_DoDoor(&junk, blazeOpen);
                        return;
                        break;

                    case 8:
                        junk.tag = 666;
                        EV_DoFloor(&junk, lowerFloorToLowest);
                        return;
                        break;
                }
        }
    }

    G_ExitLevel();
}


void A_Hoof(mobj_t* mo)
{
    S_StartSound(mo, sfx_hoof);
    A_Chase(mo);
}


void A_Metal(mobj_t* mo)
{
    S_StartSound(mo, sfx_metal);
    A_Chase(mo);
}


void A_BabyMetal(mobj_t* mo)
{
    S_StartSound(mo, sfx_bspwlk);
    A_Chase(mo);
}


void A_OpenShotgun2(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_dbopn);
}


void A_LoadShotgun2(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_dbload);
}


void A_CloseShotgun2(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_dbcls);
    A_ReFire(player, psp);
}


void A_BrainAwake(mobj_t* mo)
{
    thinker_t* thinker;
    mobj_t* m;

    // find all the target spots
    numbraintargets = 0;
    braintargeton = 0;

    thinker = thinkercap.next;
    for (thinker = thinkercap.next;
         thinker != &thinkercap;
         thinker = thinker->next)
    {
        if (thinker->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;        // not a mobj

        m = (mobj_t*)thinker;

        if (m->type == MT_BOSSTARGET)
        {
            braintargets[numbraintargets] = m;
            numbraintargets++;
        }
    }

    S_StartSound(0, sfx_bossit);
}


void A_BrainPain(mobj_t* mo)
{
    S_StartSound(0, sfx_bospn);
}


void A_BrainScream(mobj_t* mo)
{
    int x;
    int y;
    int z;
    mobj_t* th;

    for (x = mo->x - 196 * FRACUNIT; x < mo->x + 320 * FRACUNIT; x += FRACUNIT * 8)
    {
        y = mo->y - 320 * FRACUNIT;
        z = 128 + P_Random() * 2 * FRACUNIT;
        th = P_SpawnMobj(x, y, z, MT_ROCKET);
        th->momz = P_Random() * 512;

        P_SetMobjState(th, S_BRAINEXPLODE1);

        th->tics -= P_Random() & 7;
        if (th->tics < 1)
            th->tics = 1;
    }

    S_StartSound(0, sfx_bosdth);
}


void A_BrainExplode(mobj_t* mo)
{
    int x;
    int y;
    int z;
    mobj_t* th;

    x = mo->x + (P_Random() - P_Random()) * 2048;
    y = mo->y;
    z = 128 + P_Random() * 2 * FRACUNIT;
    th = P_SpawnMobj(x, y, z, MT_ROCKET);
    th->momz = P_Random() * 512;

    P_SetMobjState(th, S_BRAINEXPLODE1);

    th->tics -= P_Random() & 7;
    if (th->tics < 1)
        th->tics = 1;
}


void A_BrainDie(mobj_t* mo)
{
    G_ExitLevel();
}


void A_BrainSpit(mobj_t* mo)
{
    mobj_t* targ;
    mobj_t* newmobj;

    static int easy = 0;

    easy ^= 1;
    if (gameskill <= sk_easy && (!easy))
        return;

    // shoot a cube at current target
    targ = braintargets[braintargeton];
    braintargeton = (braintargeton + 1) % numbraintargets;

    // spawn brain missile
    newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT);
    newmobj->target = targ;
    newmobj->reactiontime =
        ((targ->y - mo->y) / newmobj->momy) / newmobj->state->tics;

    S_StartSound(0, sfx_bospit);
}


// travelling cube sound
void A_SpawnSound(mobj_t* mo)
{
    S_StartSound(mo, sfx_boscub);
    A_SpawnFly(mo);
}


void A_SpawnFly(mobj_t* mo)
{
    mobj_t* newmobj;
    mobj_t* fog;
    mobj_t* targ;
    int r;
    mobjtype_t type;

    if (--mo->reactiontime)
        return;        // still flying

    targ = mo->target;

    // First spawn teleport fog.
    fog = P_SpawnMobj(targ->x, targ->y, targ->z, MT_SPAWNFIRE);
    S_StartSound(fog, sfx_telept);

    // Randomly select monster to spawn.
    r = P_Random();

    // Probability distribution (kind of :),
    // decreasing likelihood.
    if (r < 50)
        type = MT_TROOP;
    else if (r < 90)
        type = MT_SERGEANT;
    else if (r < 120)
        type = MT_SHADOWS;
    else if (r < 130)
        type = MT_PAIN;
    else if (r < 160)
        type = MT_HEAD;
    else if (r < 162)
        type = MT_VILE;
    else if (r < 172)
        type = MT_UNDEAD;
    else if (r < 192)
        type = MT_BABY;
    else if (r < 222)
        type = MT_FATSO;
    else if (r < 246)
        type = MT_KNIGHT;
    else
        type = MT_BRUISER;

    newmobj = P_SpawnMobj(targ->x, targ->y, targ->z, type);
    if (P_LookForPlayers(newmobj, true))
        P_SetMobjState(newmobj, newmobj->info->seestate);

    // telefrag anything in this spot
    P_TeleportMove(newmobj, newmobj->x, newmobj->y);

    // remove self (i.e., cube).
    P_RemoveMobj(mo);
}


void A_PlayerScream(mobj_t* mo)
{
    // Default death sound.
    int sound = sfx_pldeth;

    if ((gamemode == commercial)
        && (mo->health < -50))
    {
        // IF THE PLAYER DIES
        // LESS THAN -50% WITHOUT GIBBING
        sound = sfx_pdiehi;
    }

    S_StartSound(mo, sound);
}
result_e T_MovePlane(sector_t* sector,
                     fixed_t speed,
                     fixed_t dest,
                     doom_boolean crush,
                     int floorOrCeiling,
                     int direction)
{
    doom_boolean flag;
    fixed_t lastpos;

    switch (floorOrCeiling)
    {
        case 0:
            // FLOOR
            switch (direction)
            {
                case -1:
                    // DOWN
                    if (sector->floorheight - speed < dest)
                    {
                        lastpos = sector->floorheight;
                        sector->floorheight = dest;
                        flag = P_ChangeSector(sector, crush);
                        if (flag == true)
                        {
                            sector->floorheight = lastpos;
                            P_ChangeSector(sector, crush);
                            //return crushed;
                        }
                        return pastdest;
                    }
                    else
                    {
                        lastpos = sector->floorheight;
                        sector->floorheight -= speed;
                        flag = P_ChangeSector(sector, crush);
                        if (flag == true)
                        {
                            sector->floorheight = lastpos;
                            P_ChangeSector(sector, crush);
                            return crushed;
                        }
                    }
                    break;

                case 1:
                    // UP
                    if (sector->floorheight + speed > dest)
                    {
                        lastpos = sector->floorheight;
                        sector->floorheight = dest;
                        flag = P_ChangeSector(sector, crush);
                        if (flag == true)
                        {
                            sector->floorheight = lastpos;
                            P_ChangeSector(sector, crush);
                            //return crushed;
                        }
                        return pastdest;
                    }
                    else
                    {
                        // COULD GET CRUSHED
                        lastpos = sector->floorheight;
                        sector->floorheight += speed;
                        flag = P_ChangeSector(sector, crush);
                        if (flag == true)
                        {
                            if (crush == true)
                                return crushed;
                            sector->floorheight = lastpos;
                            P_ChangeSector(sector, crush);
                            return crushed;
                        }
                    }
                    break;
            }
            break;

        case 1:
            // CEILING
            switch (direction)
            {
                case -1:
                    // DOWN
                    if (sector->ceilingheight - speed < dest)
                    {
                        lastpos = sector->ceilingheight;
                        sector->ceilingheight = dest;
                        flag = P_ChangeSector(sector, crush);

                        if (flag == true)
                        {
                            sector->ceilingheight = lastpos;
                            P_ChangeSector(sector, crush);
                            //return crushed;
                        }
                        return pastdest;
                    }
                    else
                    {
                        // COULD GET CRUSHED
                        lastpos = sector->ceilingheight;
                        sector->ceilingheight -= speed;
                        flag = P_ChangeSector(sector, crush);

                        if (flag == true)
                        {
                            if (crush == true)
                                return crushed;
                            sector->ceilingheight = lastpos;
                            P_ChangeSector(sector, crush);
                            return crushed;
                        }
                    }
                    break;

                case 1:
                    // UP
                    if (sector->ceilingheight + speed > dest)
                    {
                        lastpos = sector->ceilingheight;
                        sector->ceilingheight = dest;
                        flag = P_ChangeSector(sector, crush);
                        if (flag == true)
                        {
                            sector->ceilingheight = lastpos;
                            P_ChangeSector(sector, crush);
                            //return crushed;
                        }
                        return pastdest;
                    }
                    else
                    {
                        lastpos = sector->ceilingheight;
                        sector->ceilingheight += speed;
                        flag = P_ChangeSector(sector, crush);
                        // UNUSED
#if 0
                        if (flag == true)
                        {
                            sector->ceilingheight = lastpos;
                            P_ChangeSector(sector, crush);
                            return crushed;
                        }
#endif
                    }
                    break;
            }
            break;
    }

    return ok;
}


//
// MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN)
//
void T_MoveFloor(floormove_t* floor)
{
    result_e res;

    res = T_MovePlane(floor->sector,
                      floor->speed,
                      floor->floordestheight,
                      floor->crush, 0, floor->direction);

    if (!(leveltime & 7))
        S_StartSound((mobj_t*)&floor->sector->soundorg,
                     sfx_stnmov);

    if (res == pastdest)
    {
        floor->sector->specialdata = 0;

        if (floor->direction == 1)
        {
            switch (floor->type)
            {
                case donutRaise:
                    floor->sector->special = floor->newspecial;
                    floor->sector->floorpic = floor->texture;
                default:
                    break;
            }
        }
        else if (floor->direction == -1)
        {
            switch (floor->type)
            {
                case lowerAndChange:
                    floor->sector->special = floor->newspecial;
                    floor->sector->floorpic = floor->texture;
                default:
                    break;
            }
        }
        P_RemoveThinker(&floor->thinker);

        S_StartSound((mobj_t*)&floor->sector->soundorg,
                     sfx_pstop);
    }
}


//
// HANDLE FLOOR TYPES
//
int EV_DoFloor(line_t* line, floor_e floortype)
{
    int secnum;
    int rtn;
    int i;
    sector_t* sec;
    floormove_t* floor;

    secnum = -1;
    rtn = 0;
    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];

        // ALREADY MOVING?  IF SO, KEEP GOING...
        if (sec->specialdata)
            continue;

        // new floor thinker
        rtn = 1;
        floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
        P_AddThinker(&floor->thinker);
        sec->specialdata = floor;
        floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
        floor->type = floortype;
        floor->crush = false;

        switch (floortype)
        {
            case lowerFloor:
                floor->direction = -1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight =
                    P_FindHighestFloorSurrounding(sec);
                break;

            case lowerFloorToLowest:
                floor->direction = -1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight =
                    P_FindLowestFloorSurrounding(sec);
                break;

            case turboLower:
                floor->direction = -1;
                floor->sector = sec;
                floor->speed = FLOORSPEED * 4;
                floor->floordestheight =
                    P_FindHighestFloorSurrounding(sec);
                if (floor->floordestheight != sec->floorheight)
                    floor->floordestheight += 8 * FRACUNIT;
                break;

            case raiseFloorCrush:
                floor->crush = true;
            case raiseFloor:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight =
                    P_FindLowestCeilingSurrounding(sec);
                if (floor->floordestheight > sec->ceilingheight)
                    floor->floordestheight = sec->ceilingheight;
                floor->floordestheight -= (8 * FRACUNIT) *
                    (floortype == raiseFloorCrush);
                break;

            case raiseFloorTurbo:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED * 4;
                floor->floordestheight =
                    P_FindNextHighestFloor(sec, sec->floorheight);
                break;

            case raiseFloorToNearest:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight =
                    P_FindNextHighestFloor(sec, sec->floorheight);
                break;

            case raiseFloor24:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight = floor->sector->floorheight +
                    24 * FRACUNIT;
                break;
            case raiseFloor512:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight = floor->sector->floorheight +
                    512 * FRACUNIT;
                break;

            case raiseFloor24AndChange:
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight = floor->sector->floorheight +
                    24 * FRACUNIT;
                sec->floorpic = line->frontsector->floorpic;
                sec->special = line->frontsector->special;
                break;

            case raiseToTexture:
            {
                int        minsize = DOOM_MAXINT;
                side_t* side;

                floor->direction = 1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                for (i = 0; i < sec->linecount; i++)
                {
                    if (twoSided(secnum, i))
                    {
                        side = getSide(secnum, i, 0);
                        if (side->bottomtexture >= 0)
                            if (textureheight[side->bottomtexture] <
                                minsize)
                                minsize =
                                textureheight[side->bottomtexture];
                        side = getSide(secnum, i, 1);
                        if (side->bottomtexture >= 0)
                            if (textureheight[side->bottomtexture] <
                                minsize)
                                minsize =
                                textureheight[side->bottomtexture];
                    }
                }
                floor->floordestheight =
                    floor->sector->floorheight + minsize;
            }
            break;

            case lowerAndChange:
                floor->direction = -1;
                floor->sector = sec;
                floor->speed = FLOORSPEED;
                floor->floordestheight =
                    P_FindLowestFloorSurrounding(sec);
                floor->texture = sec->floorpic;

                for (i = 0; i < sec->linecount; i++)
                {
                    if (twoSided(secnum, i))
                    {
                        if (getSide(secnum, i, 0)->sector - sectors == secnum)
                        {
                            sec = getSector(secnum, i, 1);

                            if (sec->floorheight == floor->floordestheight)
                            {
                                floor->texture = sec->floorpic;
                                floor->newspecial = sec->special;
                                break;
                            }
                        }
                        else
                        {
                            sec = getSector(secnum, i, 0);

                            if (sec->floorheight == floor->floordestheight)
                            {
                                floor->texture = sec->floorpic;
                                floor->newspecial = sec->special;
                                break;
                            }
                        }
                    }
                }
            default:
                break;
        }
    }

    return rtn;
}


//
// BUILD A STAIRCASE!
//
int EV_BuildStairs(line_t* line, stair_e type)
{
    int secnum;
    int height;
    int i;
    int newsecnum;
    int texture;
    int ok;
    int rtn;

    sector_t* sec;
    sector_t* tsec;

    floormove_t* floor;

    fixed_t stairsize;
    fixed_t speed;

    secnum = -1;
    rtn = 0;
    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];

        // ALREADY MOVING?  IF SO, KEEP GOING...
        if (sec->specialdata)
            continue;

        // new floor thinker
        rtn = 1;
        floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
        P_AddThinker(&floor->thinker);
        sec->specialdata = floor;
        floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
        floor->direction = 1;
        floor->sector = sec;
        switch (type)
        {
            case build8:
                speed = FLOORSPEED / 4;
                stairsize = 8 * FRACUNIT;
                break;
            case turbo16:
                speed = FLOORSPEED * 4;
                stairsize = 16 * FRACUNIT;
                break;
        }
        floor->speed = speed;
        height = sec->floorheight + stairsize;
        floor->floordestheight = height;

        texture = sec->floorpic;

        // Find next sector to raise
        // 1.        Find 2-sided line with same sector side[0]
        // 2.        Other side is the next sector to raise
        do
        {
            ok = 0;
            for (i = 0; i < sec->linecount; i++)
            {
                if (!((sec->lines[i])->flags & ML_TWOSIDED))
                    continue;

                tsec = (sec->lines[i])->frontsector;
                newsecnum = (int)(tsec - sectors);

                if (secnum != newsecnum)
                    continue;

                tsec = (sec->lines[i])->backsector;
                newsecnum = (int)(tsec - sectors);

                if (tsec->floorpic != texture)
                    continue;

                height += stairsize;

                if (tsec->specialdata)
                    continue;

                sec = tsec;
                secnum = newsecnum;
                floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);

                P_AddThinker(&floor->thinker);

                sec->specialdata = floor;
                floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
                floor->direction = 1;
                floor->sector = sec;
                floor->speed = speed;
                floor->floordestheight = height;
                ok = 1;
                break;
            }
        } while (ok);
    }

    return rtn;
}
#ifdef __GNUG__
#pragma implementation "p_inter.h"
#endif
//#include "p_inter.h"


#define BONUSADD        6




// a weapon is found with two clip loads,
// a big item has five clip loads
int        maxammo[NUMAMMO] = {200, 50, 300, 50};
int        clipammo[NUMAMMO] = {10, 4, 20, 1};


//
// GET STUFF
//

//
// P_GiveAmmo
// Num is the number of clip loads,
// not the individual count (0= 1/2 clip).
// Returns false if the ammo can't be picked up at all
//

doom_boolean
P_GiveAmmo
( player_t*        player,
  ammotype_t        ammo,
  int                num )
{
    int                oldammo;
        
    if (ammo == am_noammo)
        return false;
                
    if (ammo < 0 || ammo > NUMAMMO)
    {
        //I_Error ("P_GiveAmmo: bad type %i", ammo);
        
        doom_strcpy(error_buf, "P_GiveAmmo: bad type ");
        doom_concat(error_buf, doom_itoa(ammo, 10));
        I_Error(error_buf);
    }
                
    if ( player->ammo[ammo] == player->maxammo[ammo]  )
        return false;
                
    if (num)
        num *= clipammo[ammo];
    else
        num = clipammo[ammo]/2;
    
    if (gameskill == sk_baby
        || gameskill == sk_nightmare)
    {
        // give double ammo in trainer mode,
        // you'll need in nightmare
        num <<= 1;
    }
    
                
    oldammo = player->ammo[ammo];
    player->ammo[ammo] += num;

    if (player->ammo[ammo] > player->maxammo[ammo])
        player->ammo[ammo] = player->maxammo[ammo];

    // If non zero ammo, 
    // don't change up weapons,
    // player was lower on purpose.
    if (oldammo)
        return true;        

    // We were down to zero,
    // so select a new weapon.
    // Preferences are not user selectable.
    switch (ammo)
    {
      case am_clip:
        if (player->readyweapon == wp_fist)
        {
            if (player->weaponowned[wp_chaingun])
                player->pendingweapon = wp_chaingun;
            else
                player->pendingweapon = wp_pistol;
        }
        break;
        
      case am_shell:
        if (player->readyweapon == wp_fist
            || player->readyweapon == wp_pistol)
        {
            if (player->weaponowned[wp_shotgun])
                player->pendingweapon = wp_shotgun;
        }
        break;
        
      case am_cell:
        if (player->readyweapon == wp_fist
            || player->readyweapon == wp_pistol)
        {
            if (player->weaponowned[wp_plasma])
                player->pendingweapon = wp_plasma;
        }
        break;
        
      case am_misl:
        if (player->readyweapon == wp_fist)
        {
            if (player->weaponowned[wp_missile])
                player->pendingweapon = wp_missile;
        }
      default:
        break;
    }
        
    return true;
}


//
// P_GiveWeapon
// The weapon name may have a MF_DROPPED flag ored in.
//
doom_boolean
P_GiveWeapon
( player_t*        player,
  weapontype_t        weapon,
  doom_boolean        dropped )
{
    doom_boolean        gaveammo;
    doom_boolean        gaveweapon;
        
    if (netgame
        && (deathmatch!=2)
         && !dropped )
    {
        // leave placed weapons forever on net games
        if (player->weaponowned[weapon])
            return false;

        player->bonuscount += BONUSADD;
        player->weaponowned[weapon] = true;

        if (deathmatch)
            P_GiveAmmo (player, weaponinfo[weapon].ammo, 5);
        else
            P_GiveAmmo (player, weaponinfo[weapon].ammo, 2);
        player->pendingweapon = weapon;

        if (player == &players[consoleplayer])
            S_StartSound (0, sfx_wpnup);
        return false;
    }
        
    if (weaponinfo[weapon].ammo != am_noammo)
    {
        // give one clip with a dropped weapon,
        // two clips with a found weapon
        if (dropped)
            gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 1);
        else
            gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 2);
    }
    else
        gaveammo = false;
        
    if (player->weaponowned[weapon])
        gaveweapon = false;
    else
    {
        gaveweapon = true;
        player->weaponowned[weapon] = true;
        player->pendingweapon = weapon;
    }
        
    return (gaveweapon || gaveammo);
}

 

//
// P_GiveBody
// Returns false if the body isn't needed at all
//
doom_boolean
P_GiveBody
( player_t*        player,
  int                num )
{
    if (player->health >= MAXHEALTH)
        return false;
                
    player->health += num;
    if (player->health > MAXHEALTH)
        player->health = MAXHEALTH;
    player->mo->health = player->health;
        
    return true;
}



//
// P_GiveArmor
// Returns false if the armor is worse
// than the current armor.
//
doom_boolean
P_GiveArmor
( player_t*        player,
  int                armortype )
{
    int                hits;
        
    hits = armortype*100;
    if (player->armorpoints >= hits)
        return false;        // don't pick up
                
    player->armortype = armortype;
    player->armorpoints = hits;
        
    return true;
}



//
// P_GiveCard
//
void
P_GiveCard
( player_t*        player,
  card_t        card )
{
    if (player->cards[card])
        return;
    
    player->bonuscount = BONUSADD;
    player->cards[card] = 1;
}


//
// P_GivePower
//
doom_boolean
P_GivePower
( player_t*        player,
  int /*powertype_t*/        power )
{
    if (power == pw_invulnerability)
    {
        player->powers[power] = INVULNTICS;
        return true;
    }
    
    if (power == pw_invisibility)
    {
        player->powers[power] = INVISTICS;
        player->mo->flags |= MF_SHADOW;
        return true;
    }
    
    if (power == pw_infrared)
    {
        player->powers[power] = INFRATICS;
        return true;
    }
    
    if (power == pw_ironfeet)
    {
        player->powers[power] = IRONTICS;
        return true;
    }
    
    if (power == pw_strength)
    {
        P_GiveBody (player, 100);
        player->powers[power] = 1;
        return true;
    }
        
    if (player->powers[power])
        return false;        // already got it
                
    player->powers[power] = 1;
    return true;
}



//
// P_TouchSpecialThing
//
void
P_TouchSpecialThing
( mobj_t*        special,
  mobj_t*        toucher )
{
    player_t*        player;
    int                i;
    fixed_t        delta;
    int                sound;
                
    delta = special->z - toucher->z;

    if (delta > toucher->height
        || delta < -8*FRACUNIT)
    {
        // out of reach
        return;
    }
    
        
    sound = sfx_itemup;        
    player = toucher->player;

    // Dead thing touching.
    // Can happen with a sliding player corpse.
    if (toucher->health <= 0)
        return;

    // Identify by sprite.
    switch (special->sprite)
    {
        // armor
      case SPR_ARM1:
        if (!P_GiveArmor (player, 1))
            return;
        player->message = GOTARMOR;
        break;
                
      case SPR_ARM2:
        if (!P_GiveArmor (player, 2))
            return;
        player->message = GOTMEGA;
        break;
        
        // bonus items
      case SPR_BON1:
        player->health++;                // can go over 100%
        if (player->health > 200)
            player->health = 200;
        player->mo->health = player->health;
        player->message = GOTHTHBONUS;
        break;
        
      case SPR_BON2:
        player->armorpoints++;                // can go over 100%
        if (player->armorpoints > 200)
            player->armorpoints = 200;
        if (!player->armortype)
            player->armortype = 1;
        player->message = GOTARMBONUS;
        break;
        
      case SPR_SOUL:
        player->health += 100;
        if (player->health > 200)
            player->health = 200;
        player->mo->health = player->health;
        player->message = GOTSUPER;
        sound = sfx_getpow;
        break;
        
      case SPR_MEGA:
        if (gamemode != commercial)
            return;
        player->health = 200;
        player->mo->health = player->health;
        P_GiveArmor (player,2);
        player->message = GOTMSPHERE;
        sound = sfx_getpow;
        break;
        
        // cards
        // leave cards for everyone
      case SPR_BKEY:
        if (!player->cards[it_bluecard])
            player->message = GOTBLUECARD;
        P_GiveCard (player, it_bluecard);
        if (!netgame)
            break;
        return;
        
      case SPR_YKEY:
        if (!player->cards[it_yellowcard])
            player->message = GOTYELWCARD;
        P_GiveCard (player, it_yellowcard);
        if (!netgame)
            break;
        return;
        
      case SPR_RKEY:
        if (!player->cards[it_redcard])
            player->message = GOTREDCARD;
        P_GiveCard (player, it_redcard);
        if (!netgame)
            break;
        return;
        
      case SPR_BSKU:
        if (!player->cards[it_blueskull])
            player->message = GOTBLUESKUL;
        P_GiveCard (player, it_blueskull);
        if (!netgame)
            break;
        return;
        
      case SPR_YSKU:
        if (!player->cards[it_yellowskull])
            player->message = GOTYELWSKUL;
        P_GiveCard (player, it_yellowskull);
        if (!netgame)
            break;
        return;
        
      case SPR_RSKU:
        if (!player->cards[it_redskull])
            player->message = GOTREDSKULL;
        P_GiveCard (player, it_redskull);
        if (!netgame)
            break;
        return;
        
        // medikits, heals
      case SPR_STIM:
        if (!P_GiveBody (player, 10))
            return;
        player->message = GOTSTIM;
        break;
        
      case SPR_MEDI:
        if (!P_GiveBody (player, 25))
            return;

        if (player->health < 25)
            player->message = GOTMEDINEED;
        else
            player->message = GOTMEDIKIT;
        break;

        
        // power ups
      case SPR_PINV:
        if (!P_GivePower (player, pw_invulnerability))
            return;
        player->message = GOTINVUL;
        sound = sfx_getpow;
        break;
        
      case SPR_PSTR:
        if (!P_GivePower (player, pw_strength))
            return;
        player->message = GOTBERSERK;
        if (player->readyweapon != wp_fist)
            player->pendingweapon = wp_fist;
        sound = sfx_getpow;
        break;
        
      case SPR_PINS:
        if (!P_GivePower (player, pw_invisibility))
            return;
        player->message = GOTINVIS;
        sound = sfx_getpow;
        break;
        
      case SPR_SUIT:
        if (!P_GivePower (player, pw_ironfeet))
            return;
        player->message = GOTSUIT;
        sound = sfx_getpow;
        break;
        
      case SPR_PMAP:
        if (!P_GivePower (player, pw_allmap))
            return;
        player->message = GOTMAP;
        sound = sfx_getpow;
        break;
        
      case SPR_PVIS:
        if (!P_GivePower (player, pw_infrared))
            return;
        player->message = GOTVISOR;
        sound = sfx_getpow;
        break;
        
        // ammo
      case SPR_CLIP:
        if (special->flags & MF_DROPPED)
        {
            if (!P_GiveAmmo (player,am_clip,0))
                return;
        }
        else
        {
            if (!P_GiveAmmo (player,am_clip,1))
                return;
        }
        player->message = GOTCLIP;
        break;
        
      case SPR_AMMO:
        if (!P_GiveAmmo (player, am_clip,5))
            return;
        player->message = GOTCLIPBOX;
        break;
        
      case SPR_ROCK:
        if (!P_GiveAmmo (player, am_misl,1))
            return;
        player->message = GOTROCKET;
        break;
        
      case SPR_BROK:
        if (!P_GiveAmmo (player, am_misl,5))
            return;
        player->message = GOTROCKBOX;
        break;
        
      case SPR_CELL:
        if (!P_GiveAmmo (player, am_cell,1))
            return;
        player->message = GOTCELL;
        break;
        
      case SPR_CELP:
        if (!P_GiveAmmo (player, am_cell,5))
            return;
        player->message = GOTCELLBOX;
        break;
        
      case SPR_SHEL:
        if (!P_GiveAmmo (player, am_shell,1))
            return;
        player->message = GOTSHELLS;
        break;
        
      case SPR_SBOX:
        if (!P_GiveAmmo (player, am_shell,5))
            return;
        player->message = GOTSHELLBOX;
        break;
        
      case SPR_BPAK:
        if (!player->backpack)
        {
            for (i=0 ; i<NUMAMMO ; i++)
                player->maxammo[i] *= 2;
            player->backpack = true;
        }
        for (i=0 ; i<NUMAMMO ; i++)
            P_GiveAmmo (player, i, 1);
        player->message = GOTBACKPACK;
        break;
        
        // weapons
      case SPR_BFUG:
        if (!P_GiveWeapon (player, wp_bfg, false) )
            return;
        player->message = GOTBFG9000;
        sound = sfx_wpnup;        
        break;
        
      case SPR_MGUN:
        if (!P_GiveWeapon (player, wp_chaingun, special->flags&MF_DROPPED) )
            return;
        player->message = GOTCHAINGUN;
        sound = sfx_wpnup;        
        break;
        
      case SPR_CSAW:
        if (!P_GiveWeapon (player, wp_chainsaw, false) )
            return;
        player->message = GOTCHAINSAW;
        sound = sfx_wpnup;        
        break;
        
      case SPR_LAUN:
        if (!P_GiveWeapon (player, wp_missile, false) )
            return;
        player->message = GOTLAUNCHER;
        sound = sfx_wpnup;        
        break;
        
      case SPR_PLAS:
        if (!P_GiveWeapon (player, wp_plasma, false) )
            return;
        player->message = GOTPLASMA;
        sound = sfx_wpnup;        
        break;
        
      case SPR_SHOT:
        if (!P_GiveWeapon (player, wp_shotgun, special->flags&MF_DROPPED ) )
            return;
        player->message = GOTSHOTGUN;
        sound = sfx_wpnup;        
        break;
                
      case SPR_SGN2:
        if (!P_GiveWeapon (player, wp_supershotgun, special->flags&MF_DROPPED ) )
            return;
        player->message = GOTSHOTGUN2;
        sound = sfx_wpnup;        
        break;
                
      default:
        I_Error ("P_SpecialThing: Unknown gettable thing");
    }
        
    if (special->flags & MF_COUNTITEM)
        player->itemcount++;
    P_RemoveMobj (special);
    player->bonuscount += BONUSADD;
    if (player == &players[consoleplayer])
        S_StartSound (0, sound);
}


//
// KillMobj
//
void
P_KillMobj
( mobj_t*        source,
  mobj_t*        target )
{
    mobjtype_t        item;
    mobj_t*        mo;
        
    target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY);

    if (target->type != MT_SKULL)
        target->flags &= ~MF_NOGRAVITY;

    target->flags |= MF_CORPSE|MF_DROPOFF;
    target->height >>= 2;

    if (source && source->player)
    {
        // count for intermission
        if (target->flags & MF_COUNTKILL)
            source->player->killcount++;        

        if (target->player)
            source->player->frags[target->player-players]++;
    }
    else if (!netgame && (target->flags & MF_COUNTKILL) )
    {
        // count all monster deaths,
        // even those caused by other monsters
        players[0].killcount++;
    }
    
    if (target->player)
    {
        // count environment kills against you
        if (!source)        
            target->player->frags[target->player-players]++;
                        
        target->flags &= ~MF_SOLID;
        target->player->playerstate = PST_DEAD;
        P_DropWeapon (target->player);

        if (target->player == &players[consoleplayer]
            && automapactive)
        {
            // don't die in auto map,
            // switch view prior to dying
            AM_Stop ();
        }
        
    }

    if (target->health < -target->info->spawnhealth 
        && target->info->xdeathstate)
    {
        P_SetMobjState (target, target->info->xdeathstate);
    }
    else
        P_SetMobjState (target, target->info->deathstate);
    target->tics -= P_Random()&3;

    if (target->tics < 1)
        target->tics = 1;
                
    //        I_StartSound (&actor->r, actor->info->deathsound);


    // Drop stuff.
    // This determines the kind of object spawned
    // during the death frame of a thing.
    switch (target->type)
    {
      case MT_WOLFSS:
      case MT_POSSESSED:
        item = MT_CLIP;
        break;
        
      case MT_SHOTGUY:
        item = MT_SHOTGUN;
        break;
        
      case MT_CHAINGUY:
        item = MT_CHAINGUN;
        break;
        
      default:
        return;
    }

    mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item);
    mo->flags |= MF_DROPPED;        // special versions of items
}




//
// P_DamageMobj
// Damages both enemies and players
// "inflictor" is the thing that caused the damage
//  creature or missile, can be 0 (slime, etc)
// "source" is the thing to target after taking damage
//  creature or 0
// Source and inflictor are the same for melee attacks.
// Source can be 0 for slime, barrel explosions
// and other environmental stuff.
//
void
P_DamageMobj
( mobj_t*        target,
  mobj_t*        inflictor,
  mobj_t*        source,
  int                 damage )
{
    unsigned        ang;
    int                saved;
    player_t*        player;
    fixed_t        thrust;
    int                temp;
        
    if ( !(target->flags & MF_SHOOTABLE) )
        return;        // shouldn't happen...
                
    if (target->health <= 0)
        return;

    if ( target->flags & MF_SKULLFLY )
    {
        target->momx = target->momy = target->momz = 0;
    }
        
    player = target->player;
    if (player && gameskill == sk_baby)
        damage >>= 1;         // take half damage in trainer mode
                

    // Some close combat weapons should not
    // inflict thrust and push the victim out of reach,
    // thus kick away unless using the chainsaw.
    if (inflictor
        && !(target->flags & MF_NOCLIP)
        && (!source
            || !source->player
            || source->player->readyweapon != wp_chainsaw))
    {
        ang = R_PointToAngle2 ( inflictor->x,
                                inflictor->y,
                                target->x,
                                target->y);
                
        thrust = damage*(FRACUNIT>>3)*100/target->info->mass;

        // make fall forwards sometimes
        if ( damage < 40
             && damage > target->health
             && target->z - inflictor->z > 64*FRACUNIT
             && (P_Random ()&1) )
        {
            ang += ANG180;
            thrust *= 4;
        }
                
        ang >>= ANGLETOFINESHIFT;
        target->momx += FixedMul (thrust, finecosine[ang]);
        target->momy += FixedMul (thrust, finesine[ang]);
    }
    
    // player specific
    if (player)
    {
        // end of game hell hack
        if (target->subsector->sector->special == 11
            && damage >= target->health)
        {
            damage = target->health - 1;
        }
        

        // Below certain threshold,
        // ignore damage in GOD mode, or with INVUL power.
        if ( damage < 1000
             && ( (player->cheats&CF_GODMODE)
                  || player->powers[pw_invulnerability] ) )
        {
            return;
        }
        
        if (player->armortype)
        {
            if (player->armortype == 1)
                saved = damage/3;
            else
                saved = damage/2;
            
            if (player->armorpoints <= saved)
            {
                // armor is used up
                saved = player->armorpoints;
                player->armortype = 0;
            }
            player->armorpoints -= saved;
            damage -= saved;
        }
        player->health -= damage;         // mirror mobj health here for Dave
        if (player->health < 0)
            player->health = 0;
        
        player->attacker = source;
        player->damagecount += damage;        // add damage after armor / invuln

        if (player->damagecount > 100)
            player->damagecount = 100;        // teleport stomp does 10k points...
        
        temp = damage < 100 ? damage : 100;

        if (player == &players[consoleplayer])
            I_Tactile (40,10,40+temp*2);
    }
    
    // do the damage        
    target->health -= damage;        
    if (target->health <= 0)
    {
        P_KillMobj (source, target);
        return;
    }

    if ( (P_Random () < target->info->painchance)
         && !(target->flags&MF_SKULLFLY) )
    {
        target->flags |= MF_JUSTHIT;        // fight back!
        
        P_SetMobjState (target, target->info->painstate);
    }
                        
    target->reactiontime = 0;                // we're awake now...        

    if ( (!target->threshold || target->type == MT_VILE)
         && source && source != target
         && source->type != MT_VILE)
    {
        // if not intent on another player,
        // chase after this one
        target->target = source;
        target->threshold = BASETHRESHOLD;
        if (target->state == &states[target->info->spawnstate]
            && target->info->seestate != S_NULL)
            P_SetMobjState (target, target->info->seestate);
    }
                        
}

void T_FireFlicker(fireflicker_t* flick)
{
    int amount;

    if (--flick->count)
        return;

    amount = (P_Random() & 3) * 16;

    if (flick->sector->lightlevel - amount < flick->minlight)
        flick->sector->lightlevel = flick->minlight;
    else
        flick->sector->lightlevel = flick->maxlight - amount;

    flick->count = 4;
}


//
// P_SpawnFireFlicker
//
void P_SpawnFireFlicker(sector_t* sector)
{
    fireflicker_t* flick;

    // Note that we are resetting sector attributes.
    // Nothing special about it during gameplay.
    sector->special = 0;

    flick = Z_Malloc(sizeof(*flick), PU_LEVSPEC, 0);

    P_AddThinker(&flick->thinker);

    flick->thinker.function.acp1 = (actionf_p1)T_FireFlicker;
    flick->sector = sector;
    flick->maxlight = sector->lightlevel;
    flick->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel) + 16;
    flick->count = 4;
}


//
// BROKEN LIGHT FLASHING
//

//
// T_LightFlash
// Do flashing lights.
//
void T_LightFlash(lightflash_t* flash)
{
    if (--flash->count)
        return;

    if (flash->sector->lightlevel == flash->maxlight)
    {
        flash->sector->lightlevel = flash->minlight;
        flash->count = (P_Random() & flash->mintime) + 1;
    }
    else
    {
        flash->sector->lightlevel = flash->maxlight;
        flash->count = (P_Random() & flash->maxtime) + 1;
    }
}


//
// P_SpawnLightFlash
// After the map has been loaded, scan each sector
// for specials that spawn thinkers
//
void P_SpawnLightFlash(sector_t* sector)
{
    lightflash_t* flash;

    // nothing special about it during gameplay
    sector->special = 0;

    flash = Z_Malloc(sizeof(*flash), PU_LEVSPEC, 0);

    P_AddThinker(&flash->thinker);

    flash->thinker.function.acp1 = (actionf_p1)T_LightFlash;
    flash->sector = sector;
    flash->maxlight = sector->lightlevel;

    flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel);
    flash->maxtime = 64;
    flash->mintime = 7;
    flash->count = (P_Random() & flash->maxtime) + 1;
}


//
// STROBE LIGHT FLASHING
//

//
// T_StrobeFlash
//
void T_StrobeFlash(strobe_t* flash)
{
    if (--flash->count)
        return;

    if (flash->sector->lightlevel == flash->minlight)
    {
        flash->sector->lightlevel = flash->maxlight;
        flash->count = flash->brighttime;
    }
    else
    {
        flash->sector->lightlevel = flash->minlight;
        flash->count = flash->darktime;
    }
}


//
// P_SpawnStrobeFlash
// After the map has been loaded, scan each sector
// for specials that spawn thinkers
//
void P_SpawnStrobeFlash(sector_t* sector, int fastOrSlow, int inSync)
{
    strobe_t* flash;

    flash = Z_Malloc(sizeof(*flash), PU_LEVSPEC, 0);

    P_AddThinker(&flash->thinker);

    flash->sector = sector;
    flash->darktime = fastOrSlow;
    flash->brighttime = STROBEBRIGHT;
    flash->thinker.function.acp1 = (actionf_p1)T_StrobeFlash;
    flash->maxlight = sector->lightlevel;
    flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel);

    if (flash->minlight == flash->maxlight)
        flash->minlight = 0;

    // nothing special about it during gameplay
    sector->special = 0;

    if (!inSync)
        flash->count = (P_Random() & 7) + 1;
    else
        flash->count = 1;
}


//
// Start strobing lights (usually from a trigger)
//
void EV_StartLightStrobing(line_t* line)
{
    int secnum;
    sector_t* sec;

    secnum = -1;
    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];
        if (sec->specialdata)
            continue;

        P_SpawnStrobeFlash(sec, SLOWDARK, 0);
    }
}


//
// TURN LINE'S TAG LIGHTS OFF
//
void EV_TurnTagLightsOff(line_t* line)
{
    int i;
    int j;
    int min;
    sector_t* sector;
    sector_t* tsec;
    line_t* templine;

    sector = sectors;

    for (j = 0; j < numsectors; j++, sector++)
    {
        if (sector->tag == line->tag)
        {
            min = sector->lightlevel;
            for (i = 0; i < sector->linecount; i++)
            {
                templine = sector->lines[i];
                tsec = getNextSector(templine, sector);
                if (!tsec)
                    continue;
                if (tsec->lightlevel < min)
                    min = tsec->lightlevel;
            }
            sector->lightlevel = min;
        }
    }
}


//
// TURN LINE'S TAG LIGHTS ON
//
void EV_LightTurnOn(line_t* line, int bright)
{
    int i;
    int j;
    sector_t* sector;
    sector_t* temp;
    line_t* templine;

    sector = sectors;

    for (i = 0; i < numsectors; i++, sector++)
    {
        if (sector->tag == line->tag)
        {
            // bright = 0 means to search
            // for highest light level
            // surrounding sector
            if (!bright)
            {
                for (j = 0; j < sector->linecount; j++)
                {
                    templine = sector->lines[j];
                    temp = getNextSector(templine, sector);

                    if (!temp)
                        continue;

                    if (temp->lightlevel > bright)
                        bright = temp->lightlevel;
                }
            }
            sector->lightlevel = bright;
        }
    }
}


//
// Spawn glowing light
//
void T_Glow(glow_t* g)
{
    switch (g->direction)
    {
        case -1:
            // DOWN
            g->sector->lightlevel -= GLOWSPEED;
            if (g->sector->lightlevel <= g->minlight)
            {
                g->sector->lightlevel += GLOWSPEED;
                g->direction = 1;
            }
            break;

        case 1:
            // UP
            g->sector->lightlevel += GLOWSPEED;
            if (g->sector->lightlevel >= g->maxlight)
            {
                g->sector->lightlevel -= GLOWSPEED;
                g->direction = -1;
            }
            break;
    }
}


void P_SpawnGlowingLight(sector_t* sector)
{
    glow_t* g;

    g = Z_Malloc(sizeof(*g), PU_LEVSPEC, 0);

    P_AddThinker(&g->thinker);

    g->sector = sector;
    g->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel);
    g->maxlight = sector->lightlevel;
    g->thinker.function.acp1 = (actionf_p1)T_Glow;
    g->direction = -1;

    sector->special = 0;
}
#define MAXSPECIALCROSS 8


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".
doom_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

line_t* spechit[MAXSPECIALCROSS];
int numspechit;

mobj_t* linetarget; // who got hit (or 0)
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;
mobj_t* usething;
doom_boolean crushchange;
doom_boolean nofit;


// slopes to top and bottom of target
extern fixed_t topslope;
extern fixed_t bottomslope;


//
// TELEPORT MOVE
// 

//
// PIT_StompThing
//
doom_boolean PIT_StompThing(mobj_t* thing)
{
    fixed_t blockdist;

    if (!(thing->flags & MF_SHOOTABLE))
        return true;

    blockdist = thing->radius + tmthing->radius;

    if (doom_abs(thing->x - tmx) >= blockdist
        || doom_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
//
doom_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 = 0;

    // 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
//
doom_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
//
doom_boolean PIT_CheckThing(mobj_t* thing)
{
    fixed_t                blockdist;
    doom_boolean                solid;
    int                        damage;

    if (!(thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)))
        return true;

    blockdist = thing->radius + tmthing->radius;

    if (doom_abs(thing->x - tmx) >= blockdist
        || doom_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
//
doom_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 = 0;

    // 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.
//
doom_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((int)(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.
//
doom_boolean P_ThingHeightClip(mobj_t* thing)
{
    doom_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;

    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
//
doom_boolean PTR_SlideTraverse(intercept_t* in)
{
    line_t* li;

    if (!in->isaline)
        I_Error("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
//

//
// PTR_AimTraverse
// Sets linetaget and aimslope when a target is aimed at.
//
doom_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
//
doom_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 && li->backsector->ceilingpic == skyflatnum)
                return false;
        }

        // Spawn bullet puffs.
        P_SpawnPuff(x, y, z);

        // don't go any farther
        return false;
    }

    // shoot a thing
    th = in->d.thing;
    if (th == shootthing)
        return true; // can't shoot self

    if (!(th->flags & MF_SHOOTABLE))
        return true; // corpse or something

    // check angles to see if the thing can be aimed at
    dist = FixedMul(attackrange, in->frac);
    thingtopslope = FixedDiv(th->z + th->height - shootz, dist);

    if (thingtopslope < aimslope)
        return true; // shot over the thing

    thingbottomslope = FixedDiv(th->z - shootz, dist);

    if (thingbottomslope > aimslope)
        return true; // shot under the thing


    // hit thing
    // position a bit closer
    frac = in->frac - FixedDiv(10 * FRACUNIT, attackrange);

    x = trace.x + FixedMul(trace.dx, frac);
    y = trace.y + FixedMul(trace.dy, frac);
    z = shootz + FixedMul(aimslope, FixedMul(frac, attackrange));

    // Spawn bullet puffs or blod spots,
    // depending on target type.
    if (in->d.thing->flags & MF_NOBLOOD)
        P_SpawnPuff(x, y, z);
    else
        P_SpawnBlood(x, y, z, la_damage);

    if (la_damage)
        P_DamageMobj(th, shootthing, shootthing, la_damage);

    // don't go any farther
    return false;
}


//
// P_AimLineAttack
//
fixed_t P_AimLineAttack(mobj_t* t1, angle_t angle, fixed_t distance)
{
    fixed_t        x2;
    fixed_t        y2;

    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 = 0;

    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
//
doom_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;

    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".
//
doom_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 = doom_abs(thing->x - bombspot->x);
    dy = doom_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.
//

//
// PIT_ChangeSector
//
doom_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, 0, 0, 10);

        // spray blood in a random direction
        mo = P_SpawnMobj(thing->x,
                         thing->y,
                         thing->z + thing->height / 2, MT_BLOOD);

        mo->momx = (P_Random() - P_Random()) << 12;
        mo->momy = (P_Random() - P_Random()) << 12;
    }

    // keep checking (crush other things)        
    return true;
}


//
// P_ChangeSector
//
doom_boolean P_ChangeSector(sector_t* sector, doom_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;
}
fixed_t opentop;
fixed_t openbottom;
fixed_t openrange;
fixed_t lowfloor;
intercept_t intercepts[MAXINTERCEPTS];
intercept_t* intercept_p;
divline_t trace;
doom_boolean earlyout;
int ptflags;


//
// P_AproxDistance
// Gives an estimation of distance (not exact)
//
fixed_t P_AproxDistance(fixed_t dx, fixed_t dy)
{
    dx = doom_abs(dx);
    dy = doom_abs(dy);
    if (dx < dy)
        return dx + dy - (dx >> 1);
    return dx + dy - (dy >> 1);
}


//
// P_PointOnLineSide
// Returns 0 or 1
//
int P_PointOnLineSide(fixed_t x, fixed_t y, line_t* line)
{
    fixed_t dx;
    fixed_t dy;
    fixed_t left;
    fixed_t right;

    if (!line->dx)
    {
        if (x <= line->v1->x)
            return line->dy > 0;

        return line->dy < 0;
    }
    if (!line->dy)
    {
        if (y <= line->v1->y)
            return line->dx < 0;

        return line->dx > 0;
    }

    dx = (x - line->v1->x);
    dy = (y - line->v1->y);

    left = FixedMul(line->dy >> FRACBITS, dx);
    right = FixedMul(dy, line->dx >> FRACBITS);

    if (right < left)
        return 0; // front side
    return 1; // back side
}


//
// P_BoxOnLineSide
// Considers the line to be infinite
// Returns side 0 or 1, -1 if box crosses the line.
//
int P_BoxOnLineSide(fixed_t* tmbox, line_t* ld)
{
    int p1;
    int p2;

    switch (ld->slopetype)
    {
        case ST_HORIZONTAL:
            p1 = tmbox[BOXTOP] > ld->v1->y;
            p2 = tmbox[BOXBOTTOM] > ld->v1->y;
            if (ld->dx < 0)
            {
                p1 ^= 1;
                p2 ^= 1;
            }
            break;

        case ST_VERTICAL:
            p1 = tmbox[BOXRIGHT] < ld->v1->x;
            p2 = tmbox[BOXLEFT] < ld->v1->x;
            if (ld->dy < 0)
            {
                p1 ^= 1;
                p2 ^= 1;
            }
            break;

        case ST_POSITIVE:
            p1 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXTOP], ld);
            p2 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld);
            break;

        case ST_NEGATIVE:
            p1 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXTOP], ld);
            p2 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld);
            break;
    }

    if (p1 == p2)
        return p1;
    return -1;
}


//
// P_PointOnDivlineSide
// Returns 0 or 1.
//
int P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t* line)
{
    fixed_t dx;
    fixed_t dy;
    fixed_t left;
    fixed_t right;

    if (!line->dx)
    {
        if (x <= line->x)
            return line->dy > 0;

        return line->dy < 0;
    }
    if (!line->dy)
    {
        if (y <= line->y)
            return line->dx < 0;

        return line->dx > 0;
    }

    dx = (x - line->x);
    dy = (y - line->y);

    // try to quickly decide by looking at sign bits
    if ((line->dy ^ line->dx ^ dx ^ dy) & 0x80000000)
    {
        if ((line->dy ^ dx) & 0x80000000)
            return 1; // (left is negative)
        return 0;
    }

    left = FixedMul(line->dy >> 8, dx >> 8);
    right = FixedMul(dy >> 8, line->dx >> 8);

    if (right < left)
        return 0; // front side
    return 1; // back side
}


//
// P_MakeDivline
//
void P_MakeDivline(line_t* li, divline_t* dl)
{
    dl->x = li->v1->x;
    dl->y = li->v1->y;
    dl->dx = li->dx;
    dl->dy = li->dy;
}


//
// P_InterceptVector
// Returns the fractional intercept point
// along the first divline.
// This is only called by the addthings
// and addlines traversers.
//
fixed_t P_InterceptVector(divline_t* v2, divline_t* v1)
{
    fixed_t frac;
    fixed_t num;
    fixed_t den;

    den = FixedMul(v1->dy >> 8, v2->dx) - FixedMul(v1->dx >> 8, v2->dy);

    if (den == 0)
        return 0;
    //        I_Error ("P_InterceptVector: parallel");

    num =
        FixedMul((v1->x - v2->x) >> 8, v1->dy)
        + FixedMul((v2->y - v1->y) >> 8, v1->dx);

    frac = FixedDiv(num, den);

    return frac;
}


//
// P_LineOpening
// Sets opentop and openbottom to the window
// through a two sided line.
// OPTIMIZE: keep this precalculated
//
void P_LineOpening(line_t* linedef)
{
    sector_t* front;
    sector_t* back;

    if (linedef->sidenum[1] == -1)
    {
        // single sided line
        openrange = 0;
        return;
    }

    front = linedef->frontsector;
    back = linedef->backsector;

    if (front->ceilingheight < back->ceilingheight)
        opentop = front->ceilingheight;
    else
        opentop = back->ceilingheight;

    if (front->floorheight > back->floorheight)
    {
        openbottom = front->floorheight;
        lowfloor = back->floorheight;
    }
    else
    {
        openbottom = back->floorheight;
        lowfloor = front->floorheight;
    }

    openrange = opentop - openbottom;
}


//
// THING POSITION SETTING
//

//
// P_UnsetThingPosition
// Unlinks a thing from block map and sectors.
// On each position change, BLOCKMAP and other
// lookups maintaining lists ot things inside
// these structures need to be updated.
//
void P_UnsetThingPosition(mobj_t* thing)
{
    int blockx;
    int blocky;

    if (!(thing->flags & MF_NOSECTOR))
    {
        // inert things don't need to be in blockmap?
        // unlink from subsector
        if (thing->snext)
            thing->snext->sprev = thing->sprev;

        if (thing->sprev)
            thing->sprev->snext = thing->snext;
        else
            thing->subsector->sector->thinglist = thing->snext;
    }

    if (!(thing->flags & MF_NOBLOCKMAP))
    {
        // inert things don't need to be in blockmap
        // unlink from block map
        if (thing->bnext)
            thing->bnext->bprev = thing->bprev;

        if (thing->bprev)
            thing->bprev->bnext = thing->bnext;
        else
        {
            blockx = (thing->x - bmaporgx) >> MAPBLOCKSHIFT;
            blocky = (thing->y - bmaporgy) >> MAPBLOCKSHIFT;

            if (blockx >= 0 && blockx < bmapwidth
                && blocky >= 0 && blocky < bmapheight)
            {
                blocklinks[blocky * bmapwidth + blockx] = thing->bnext;
            }
        }
    }
}


//
// P_SetThingPosition
// Links a thing into both a block and a subsector
// based on it's x y.
// Sets thing->subsector properly
//
void P_SetThingPosition(mobj_t* thing)
{
    subsector_t* ss;
    sector_t* sec;
    int blockx;
    int blocky;
    mobj_t** link;


    // link into subsector
    ss = R_PointInSubsector(thing->x, thing->y);
    thing->subsector = ss;

    if (!(thing->flags & MF_NOSECTOR))
    {
        // invisible things don't go into the sector links
        sec = ss->sector;

        thing->sprev = 0;
        thing->snext = sec->thinglist;

        if (sec->thinglist)
            sec->thinglist->sprev = thing;

        sec->thinglist = thing;
    }

    // link into blockmap
    if (!(thing->flags & MF_NOBLOCKMAP))
    {
        // inert things don't need to be in blockmap                
        blockx = (thing->x - bmaporgx) >> MAPBLOCKSHIFT;
        blocky = (thing->y - bmaporgy) >> MAPBLOCKSHIFT;

        if (blockx >= 0
            && blockx < bmapwidth
            && blocky >= 0
            && blocky < bmapheight)
        {
            link = &blocklinks[blocky * bmapwidth + blockx];
            thing->bprev = 0;
            thing->bnext = *link;
            if (*link)
                (*link)->bprev = thing;

            *link = thing;
        }
        else
        {
            // thing is off the map
            thing->bnext = thing->bprev = 0;
        }
    }
}


//
// BLOCK MAP ITERATORS
// For each line/thing in the given mapblock,
// call the passed PIT_* function.
// If the function returns false,
// exit with false without checking anything else.
//

//
// P_BlockLinesIterator
// The validcount flags are used to avoid checking lines
// that are marked in multiple mapblocks,
// so increment validcount before the first call
// to P_BlockLinesIterator, then make one or more calls
// to it.
//
doom_boolean P_BlockLinesIterator(int x, int y, doom_boolean(*func)(line_t*))
{
    int offset;
    short* list;
    line_t* ld;

    if (x < 0
        || y < 0
        || x >= bmapwidth
        || y >= bmapheight)
    {
        return true;
    }

    offset = y * bmapwidth + x;

    offset = *(blockmap + offset);

    for (list = blockmaplump + offset; *list != -1; list++)
    {
        ld = &lines[*list];

        if (ld->validcount == validcount)
            continue;         // line has already been checked

        ld->validcount = validcount;

        if (!func(ld))
            return false;
    }
    return true;        // everything was checked
}


//
// P_BlockThingsIterator
//
doom_boolean P_BlockThingsIterator(int x, int y, doom_boolean(*func)(mobj_t*))
{
    mobj_t* mobj;

    if (x < 0
        || y < 0
        || x >= bmapwidth
        || y >= bmapheight)
    {
        return true;
    }


    for (mobj = blocklinks[y * bmapwidth + x];
         mobj;
         mobj = mobj->bnext)
    {
        if (!func(mobj))
            return false;
    }
    return true;
}


//
// INTERCEPT ROUTINES
//

//
// PIT_AddLineIntercepts.
// Looks for lines in the given block
// that intercept the given trace
// to add to the intercepts list.
//
// A line is crossed if its endpoints
// are on opposite sides of the trace.
// Returns true if earlyout and a solid line hit.
//
doom_boolean PIT_AddLineIntercepts(line_t* ld)
{
    int s1;
    int s2;
    fixed_t frac;
    divline_t dl;

    // avoid precision problems with two routines
    if (trace.dx > FRACUNIT * 16
        || trace.dy > FRACUNIT * 16
        || trace.dx < -FRACUNIT * 16
        || trace.dy < -FRACUNIT * 16)
    {
        s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace);
        s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace);
    }
    else
    {
        s1 = P_PointOnLineSide(trace.x, trace.y, ld);
        s2 = P_PointOnLineSide(trace.x + trace.dx, trace.y + trace.dy, ld);
    }

    if (s1 == s2)
        return true; // line isn't crossed

    // hit the line
    P_MakeDivline(ld, &dl);
    frac = P_InterceptVector(&trace, &dl);

    if (frac < 0)
        return true; // behind source

    // try to early out the check
    if (earlyout
        && frac < FRACUNIT
        && !ld->backsector)
    {
        return false; // stop checking
    }

    intercept_p->frac = frac;
    intercept_p->isaline = true;
    intercept_p->d.line = ld;
    intercept_p++;

    return true; // continue
}


//
// PIT_AddThingIntercepts
//
doom_boolean PIT_AddThingIntercepts(mobj_t* thing)
{
    fixed_t x1;
    fixed_t y1;
    fixed_t x2;
    fixed_t y2;

    int s1;
    int s2;

    doom_boolean tracepositive;

    divline_t dl;

    fixed_t frac;

    tracepositive = (trace.dx ^ trace.dy) > 0;

    // check a corner to corner crossection for hit
    if (tracepositive)
    {
        x1 = thing->x - thing->radius;
        y1 = thing->y + thing->radius;

        x2 = thing->x + thing->radius;
        y2 = thing->y - thing->radius;
    }
    else
    {
        x1 = thing->x - thing->radius;
        y1 = thing->y - thing->radius;

        x2 = thing->x + thing->radius;
        y2 = thing->y + thing->radius;
    }

    s1 = P_PointOnDivlineSide(x1, y1, &trace);
    s2 = P_PointOnDivlineSide(x2, y2, &trace);

    if (s1 == s2)
        return true; // line isn't crossed

    dl.x = x1;
    dl.y = y1;
    dl.dx = x2 - x1;
    dl.dy = y2 - y1;

    frac = P_InterceptVector(&trace, &dl);

    if (frac < 0)
        return true; // behind source

    intercept_p->frac = frac;
    intercept_p->isaline = false;
    intercept_p->d.thing = thing;
    intercept_p++;

    return true; // keep going
}


//
// P_TraverseIntercepts
// Returns true if the traverser function returns true
// for all lines.
// 
doom_boolean P_TraverseIntercepts(traverser_t func, fixed_t maxfrac)
{
    int count;
    fixed_t dist;
    intercept_t* scan;
    intercept_t* in;

    count = (int)(intercept_p - intercepts);

    in = 0;                        // shut up compiler warning

    while (count--)
    {
        dist = DOOM_MAXINT;
        for (scan = intercepts; scan < intercept_p; scan++)
        {
            if (scan->frac < dist)
            {
                dist = scan->frac;
                in = scan;
            }
        }

        if (dist > maxfrac)
            return true;        // checked everything in range                

#if 0  // UNUSED
        {
            // don't check these yet, there may be others inserted
            in = scan = intercepts;
            for (scan = intercepts; scan < intercept_p; scan++)
                if (scan->frac > maxfrac)
                    *in++ = *scan;
            intercept_p = in;
            return false;
        }
#endif

        if (!func(in))
            return false;        // don't bother going farther

        in->frac = DOOM_MAXINT;
    }

    return true;                // everything was traversed
}


//
// P_PathTraverse
// Traces a line from x1,y1 to x2,y2,
// calling the traverser function for each.
// Returns true if the traverser function returns true
// for all lines.
//
doom_boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, int flags, doom_boolean(*trav) (intercept_t*))
{
    fixed_t xt1;
    fixed_t yt1;
    fixed_t xt2;
    fixed_t yt2;

    fixed_t xstep;
    fixed_t ystep;

    fixed_t partial;

    fixed_t xintercept;
    fixed_t yintercept;

    int mapx;
    int mapy;

    int mapxstep;
    int mapystep;

    int count;

    earlyout = flags & PT_EARLYOUT;

    validcount++;
    intercept_p = intercepts;

    if (((x1 - bmaporgx) & (MAPBLOCKSIZE - 1)) == 0)
        x1 += FRACUNIT;        // don't side exactly on a line

    if (((y1 - bmaporgy) & (MAPBLOCKSIZE - 1)) == 0)
        y1 += FRACUNIT;        // don't side exactly on a line

    trace.x = x1;
    trace.y = y1;
    trace.dx = x2 - x1;
    trace.dy = y2 - y1;

    x1 -= bmaporgx;
    y1 -= bmaporgy;
    xt1 = x1 >> MAPBLOCKSHIFT;
    yt1 = y1 >> MAPBLOCKSHIFT;

    x2 -= bmaporgx;
    y2 -= bmaporgy;
    xt2 = x2 >> MAPBLOCKSHIFT;
    yt2 = y2 >> MAPBLOCKSHIFT;

    if (xt2 > xt1)
    {
        mapxstep = 1;
        partial = FRACUNIT - ((x1 >> MAPBTOFRAC) & (FRACUNIT - 1));
        ystep = FixedDiv(y2 - y1, doom_abs(x2 - x1));
    }
    else if (xt2 < xt1)
    {
        mapxstep = -1;
        partial = (x1 >> MAPBTOFRAC) & (FRACUNIT - 1);
        ystep = FixedDiv(y2 - y1, doom_abs(x2 - x1));
    }
    else
    {
        mapxstep = 0;
        partial = FRACUNIT;
        ystep = 256 * FRACUNIT;
    }

    yintercept = (y1 >> MAPBTOFRAC) + FixedMul(partial, ystep);


    if (yt2 > yt1)
    {
        mapystep = 1;
        partial = FRACUNIT - ((y1 >> MAPBTOFRAC) & (FRACUNIT - 1));
        xstep = FixedDiv(x2 - x1, doom_abs(y2 - y1));
    }
    else if (yt2 < yt1)
    {
        mapystep = -1;
        partial = (y1 >> MAPBTOFRAC) & (FRACUNIT - 1);
        xstep = FixedDiv(x2 - x1, doom_abs(y2 - y1));
    }
    else
    {
        mapystep = 0;
        partial = FRACUNIT;
        xstep = 256 * FRACUNIT;
    }
    xintercept = (x1 >> MAPBTOFRAC) + FixedMul(partial, xstep);

    // Step through map blocks.
    // Count is present to prevent a round off error
    // from skipping the break.
    mapx = xt1;
    mapy = yt1;

    for (count = 0; count < 64; count++)
    {
        if (flags & PT_ADDLINES)
        {
            if (!P_BlockLinesIterator(mapx, mapy, PIT_AddLineIntercepts))
                return false;        // early out
        }

        if (flags & PT_ADDTHINGS)
        {
            if (!P_BlockThingsIterator(mapx, mapy, PIT_AddThingIntercepts))
                return false;        // early out
        }

        if (mapx == xt2
            && mapy == yt2)
        {
            break;
        }

        if ((yintercept >> FRACBITS) == mapy)
        {
            yintercept += ystep;
            mapx += mapxstep;
        }
        else if ((xintercept >> FRACBITS) == mapx)
        {
            xintercept += xstep;
            mapy += mapystep;
        }
    }

    // go through the sorted list
    return P_TraverseIntercepts(trav, FRACUNIT);
}
#define STOPSPEED 0x1000
#define FRICTION 0xe800


//
// P_SetMobjState
// Returns true if the mobj is still present.
//
mapthing_t itemrespawnque[ITEMQUESIZE];
int itemrespawntime[ITEMQUESIZE];
int iquehead;
int iquetail;


extern fixed_t attackrange;


void G_PlayerReborn(int player);
void P_SpawnMapThing(mapthing_t* mthing);


doom_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.acp1)
            st->action.acp1(mobj);

        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  
//
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 &&
                    ceilingline->backsector &&
                    ceilingline->backsector->ceilingpic == skyflatnum)
                {
                    // 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

        // Note (id):
        //  somebody left this after the setting momz to 0,
        //  kinda useless there.
        if (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 ((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);
    mo->spawnpoint = mobj->spawnpoint;
    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(mobj_t* mobj)
{
    // momentum movement
    if (mobj->momx
        || mobj->momy
        || (mobj->flags & MF_SKULLFLY))
    {
        P_XYMovement(mobj);

        // FIXME: decent NOP/0/Nil function pointer please.
        if (mobj->thinker.function.acv == (actionf_v)(-1))
            return;                // mobj was removed
    }
    if ((mobj->z != mobj->floorz)
        || mobj->momz)
    {
        P_ZMovement(mobj);

        // FIXME: decent NOP/0/Nil function pointer please.
        if (mobj->thinker.function.acv == (actionf_v)(-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, 0);
    doom_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.acp1 = (actionf_p1)P_MobjThinker;

    P_AddThinker(&mobj->thinker);

    return mobj;
}


//
// P_RemoveMobj
//
void P_RemoveMobj(mobj_t* mobj)
{
    if ((mobj->flags & MF_SPECIAL)
        && !(mobj->flags & MF_DROPPED)
        && (mobj->type != MT_INV)
        && (mobj->type != MT_INS))
    {
        itemrespawnque[iquehead] = 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);
    mo->spawnpoint = *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 = 0;
    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])
        {
            doom_memcpy(deathmatch_p, mthing, sizeof(*mthing));
            deathmatch_p++;
        }
        return;
    }

    // check for players specially
    if (mthing->type <= 4)
    {
        // save spots for respawning in network games
        playerstarts[mthing->type - 1] = *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("Error: P_SpawnMapThing: Unknown type %i at (%i, %i)",
        //        mthing->type,
        //        mthing->x, mthing->y);
        
        
        doom_strcpy(error_buf, "Error: P_SpawnMapThing: Unknown type ");
        doom_concat(error_buf, doom_itoa(mthing->type, 10));
        doom_concat(error_buf, " at (");
        doom_concat(error_buf, doom_itoa(mthing->x, 10));
        doom_concat(error_buf, ", ");
        doom_concat(error_buf, doom_itoa(mthing->y, 10));
        doom_concat(error_buf, ")");
        I_Error(error_buf);
    }

    // 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);
    mobj->spawnpoint = *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
//
void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z)
{
    mobj_t* th;

    z += ((P_Random() - P_Random()) << 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_Random() - P_Random()) << 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_Random() - P_Random()) << 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);
}
plat_t* activeplats[MAXPLATS];


//
// Move a plat up and down
//
void T_PlatRaise(plat_t* plat)
{
    result_e res;

    switch (plat->status)
    {
        case up:
            res = T_MovePlane(plat->sector,
                              plat->speed,
                              plat->high,
                              plat->crush, 0, 1);

            if (plat->type == raiseAndChange
                || plat->type == raiseToNearestAndChange)
            {
                if (!(leveltime & 7))
                    S_StartSound((mobj_t*)&plat->sector->soundorg,
                                 sfx_stnmov);
            }

            if (res == crushed && (!plat->crush))
            {
                plat->count = plat->wait;
                plat->status = down;
                S_StartSound((mobj_t*)&plat->sector->soundorg,
                             sfx_pstart);
            }
            else
            {
                if (res == pastdest)
                {
                    plat->count = plat->wait;
                    plat->status = waiting;
                    S_StartSound((mobj_t*)&plat->sector->soundorg,
                                 sfx_pstop);

                    switch (plat->type)
                    {
                        case blazeDWUS:
                        case downWaitUpStay:
                            P_RemoveActivePlat(plat);
                            break;

                        case raiseAndChange:
                        case raiseToNearestAndChange:
                            P_RemoveActivePlat(plat);
                            break;

                        default:
                            break;
                    }
                }
            }
            break;

        case down:
            res = T_MovePlane(plat->sector, plat->speed, plat->low, false, 0, -1);

            if (res == pastdest)
            {
                plat->count = plat->wait;
                plat->status = waiting;
                S_StartSound((mobj_t*)&plat->sector->soundorg, sfx_pstop);
            }
            break;

        case waiting:
            if (!--plat->count)
            {
                if (plat->sector->floorheight == plat->low)
                    plat->status = up;
                else
                    plat->status = down;
                S_StartSound((mobj_t*)&plat->sector->soundorg, sfx_pstart);
            }

        case in_stasis:
            break;
    }
}


//
// Do Platforms
//  "amount" is only used for SOME platforms.
//
int EV_DoPlat(line_t* line, plattype_e type, int amount)
{
    plat_t* plat;
    int secnum;
    int rtn;
    sector_t* sec;

    secnum = -1;
    rtn = 0;

    // Activate all <type> plats that are in_stasis
    switch (type)
    {
        case perpetualRaise:
            P_ActivateInStasis(line->tag);
            break;
        default:
            break;
    }

    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        sec = &sectors[secnum];

        if (sec->specialdata)
            continue;

        // Find lowest & highest floors around sector
        rtn = 1;
        plat = Z_Malloc(sizeof(*plat), PU_LEVSPEC, 0);
        P_AddThinker(&plat->thinker);

        plat->type = type;
        plat->sector = sec;
        plat->sector->specialdata = plat;
        plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise;
        plat->crush = false;
        plat->tag = line->tag;

        switch (type)
        {
            case raiseToNearestAndChange:
                plat->speed = PLATSPEED / 2;
                sec->floorpic = sides[line->sidenum[0]].sector->floorpic;
                plat->high = P_FindNextHighestFloor(sec, sec->floorheight);
                plat->wait = 0;
                plat->status = up;
                // NO MORE DAMAGE, IF APPLICABLE
                sec->special = 0;

                S_StartSound((mobj_t*)&sec->soundorg, sfx_stnmov);
                break;

            case raiseAndChange:
                plat->speed = PLATSPEED / 2;
                sec->floorpic = sides[line->sidenum[0]].sector->floorpic;
                plat->high = sec->floorheight + amount * FRACUNIT;
                plat->wait = 0;
                plat->status = up;

                S_StartSound((mobj_t*)&sec->soundorg, sfx_stnmov);
                break;

            case downWaitUpStay:
                plat->speed = PLATSPEED * 4;
                plat->low = P_FindLowestFloorSurrounding(sec);

                if (plat->low > sec->floorheight)
                    plat->low = sec->floorheight;

                plat->high = sec->floorheight;
                plat->wait = 35 * PLATWAIT;
                plat->status = down;
                S_StartSound((mobj_t*)&sec->soundorg, sfx_pstart);
                break;

            case blazeDWUS:
                plat->speed = PLATSPEED * 8;
                plat->low = P_FindLowestFloorSurrounding(sec);

                if (plat->low > sec->floorheight)
                    plat->low = sec->floorheight;

                plat->high = sec->floorheight;
                plat->wait = 35 * PLATWAIT;
                plat->status = down;
                S_StartSound((mobj_t*)&sec->soundorg, sfx_pstart);
                break;

            case perpetualRaise:
                plat->speed = PLATSPEED;
                plat->low = P_FindLowestFloorSurrounding(sec);

                if (plat->low > sec->floorheight)
                    plat->low = sec->floorheight;

                plat->high = P_FindHighestFloorSurrounding(sec);

                if (plat->high < sec->floorheight)
                    plat->high = sec->floorheight;

                plat->wait = 35 * PLATWAIT;
                plat->status = P_Random() & 1;

                S_StartSound((mobj_t*)&sec->soundorg, sfx_pstart);
                break;
        }
        P_AddActivePlat(plat);
    }

    return rtn;
}

void P_ActivateInStasis(int tag)
{
    int i;

    for (i = 0; i < MAXPLATS; i++)
        if (activeplats[i]
            && (activeplats[i])->tag == tag
            && (activeplats[i])->status == in_stasis)
        {
            (activeplats[i])->status = (activeplats[i])->oldstatus;
            (activeplats[i])->thinker.function.acp1 = (actionf_p1)T_PlatRaise;
        }
}

void EV_StopPlat(line_t* line)
{
    int j;

    for (j = 0; j < MAXPLATS; j++)
        if (activeplats[j]
            && ((activeplats[j])->status != in_stasis)
            && ((activeplats[j])->tag == line->tag))
        {
            (activeplats[j])->oldstatus = (activeplats[j])->status;
            (activeplats[j])->status = in_stasis;
            (activeplats[j])->thinker.function.acv = (actionf_v)0;
        }
}

void P_AddActivePlat(plat_t* plat)
{
    int i;

    for (i = 0; i < MAXPLATS; i++)
        if (activeplats[i] == 0)
        {
            activeplats[i] = plat;
            return;
        }
    I_Error("Error: P_AddActivePlat: no more plats!");
}

void P_RemoveActivePlat(plat_t* plat)
{
    int i;
    for (i = 0; i < MAXPLATS; i++)
        if (plat == activeplats[i])
        {
            (activeplats[i])->sector->specialdata = 0;
            P_RemoveThinker(&(activeplats[i])->thinker);
            activeplats[i] = 0;

            return;
        }
    I_Error("Error: P_RemoveActivePlat: can't find plat!");
}
#define LOWERSPEED (FRACUNIT*6)
#define RAISESPEED (FRACUNIT*6)

#define WEAPONBOTTOM (128*FRACUNIT)
#define WEAPONTOP (32*FRACUNIT)

// plasma cells for a bfg attack
#define BFGCELLS 40     


fixed_t swingx;
fixed_t swingy;
fixed_t bulletslope;


//
// P_SetPsprite
//
void P_SetPsprite(player_t* player, int position, statenum_t stnum)
{
    pspdef_t* psp;
    state_t* state;

    psp = &player->psprites[position];

    do
    {
        if (!stnum)
        {
            // object removed itself
            psp->state = 0;
            break;
        }

        state = &states[stnum];
        psp->state = state;
        psp->tics = state->tics;        // could be 0

        if (state->misc1)
        {
            // coordinate set
            psp->sx = state->misc1 << FRACBITS;
            psp->sy = state->misc2 << FRACBITS;
        }

        // Call action routine.
        // Modified handling.
        if (state->action.acp2)
        {
            state->action.acp2(player, psp);
            if (!psp->state)
                break;
        }

        stnum = psp->state->nextstate;

    } while (!psp->tics);
    // an initial state of 0 could cycle through
}


//
// P_BringUpWeapon
// Starts bringing the pending weapon up
// from the bottom of the screen.
// Uses player
//
void P_BringUpWeapon(player_t* player)
{
    statenum_t newstate;

    if (player->pendingweapon == wp_nochange)
        player->pendingweapon = player->readyweapon;

    if (player->pendingweapon == wp_chainsaw)
        S_StartSound(player->mo, sfx_sawup);

    newstate = weaponinfo[player->pendingweapon].upstate;

    player->pendingweapon = wp_nochange;
    player->psprites[ps_weapon].sy = WEAPONBOTTOM;

    P_SetPsprite(player, ps_weapon, newstate);
}


//
// P_CheckAmmo
// Returns true if there is enough ammo to shoot.
// If not, selects the next weapon to use.
//
doom_boolean P_CheckAmmo(player_t* player)
{
    ammotype_t ammo;
    int count;

    ammo = weaponinfo[player->readyweapon].ammo;

    // Minimal amount for one shot varies.
    if (player->readyweapon == wp_bfg)
        count = BFGCELLS;
    else if (player->readyweapon == wp_supershotgun)
        count = 2;        // Double barrel.
    else
        count = 1;        // Regular.

    // Some do not need ammunition anyway.
    // Return if current ammunition sufficient.
    if (ammo == am_noammo || player->ammo[ammo] >= count)
        return true;

    // Out of ammo, pick a weapon to change to.
    // Preferences are set here.
    do
    {
        if (player->weaponowned[wp_plasma]
            && player->ammo[am_cell]
            && (gamemode != shareware))
        {
            player->pendingweapon = wp_plasma;
        }
        else if (player->weaponowned[wp_supershotgun]
                 && player->ammo[am_shell] > 2
                 && (gamemode == commercial))
        {
            player->pendingweapon = wp_supershotgun;
        }
        else if (player->weaponowned[wp_chaingun]
                 && player->ammo[am_clip])
        {
            player->pendingweapon = wp_chaingun;
        }
        else if (player->weaponowned[wp_shotgun]
                 && player->ammo[am_shell])
        {
            player->pendingweapon = wp_shotgun;
        }
        else if (player->ammo[am_clip])
        {
            player->pendingweapon = wp_pistol;
        }
        else if (player->weaponowned[wp_chainsaw])
        {
            player->pendingweapon = wp_chainsaw;
        }
        else if (player->weaponowned[wp_missile]
                 && player->ammo[am_misl])
        {
            player->pendingweapon = wp_missile;
        }
        else if (player->weaponowned[wp_bfg]
                 && player->ammo[am_cell] > 40
                 && (gamemode != shareware))
        {
            player->pendingweapon = wp_bfg;
        }
        else
        {
            // If everything fails.
            player->pendingweapon = wp_fist;
        }

    } while (player->pendingweapon == wp_nochange);

    // Now set appropriate weapon overlay.
    P_SetPsprite(player,
                 ps_weapon,
                 weaponinfo[player->readyweapon].downstate);

    return false;
}


//
// P_FireWeapon.
//
void P_FireWeapon(player_t* player)
{
    statenum_t newstate;

    if (!P_CheckAmmo(player))
        return;

    P_SetMobjState(player->mo, S_PLAY_ATK1);
    newstate = weaponinfo[player->readyweapon].atkstate;
    P_SetPsprite(player, ps_weapon, newstate);
    P_NoiseAlert(player->mo, player->mo);

    // [pd] Stop gun bobbing when shooting
    pspdef_t* psp;
    psp = &player->psprites[ps_weapon];
    psp->sx = FRACUNIT;
    psp->sy = WEAPONTOP;
}


//
// P_DropWeapon
// Player died, so put the weapon away.
//
void P_DropWeapon(player_t* player)
{
    P_SetPsprite(player,
                 ps_weapon,
                 weaponinfo[player->readyweapon].downstate);
}


//
// A_WeaponReady
// The player can fire the weapon
// or change to another weapon at this time.
// Follows after getting weapon up,
// or after previous attack/fire sequence.
//
void A_WeaponReady(player_t* player, pspdef_t* psp)
{
    statenum_t newstate;
    int angle;

    // get out of attack state
    if (player->mo->state == &states[S_PLAY_ATK1]
        || player->mo->state == &states[S_PLAY_ATK2])
    {
        P_SetMobjState(player->mo, S_PLAY);
    }

    if (player->readyweapon == wp_chainsaw
        && psp->state == &states[S_SAW])
    {
        S_StartSound(player->mo, sfx_sawidl);
    }

    // check for change
    //  if player is dead, put the weapon away
    if (player->pendingweapon != wp_nochange || !player->health)
    {
        // change weapon
        //  (pending weapon should allready be validated)
        newstate = weaponinfo[player->readyweapon].downstate;
        P_SetPsprite(player, ps_weapon, newstate);
        return;
    }

    // check for fire
    //  the missile launcher and bfg do not auto fire
    if (player->cmd.buttons & BT_ATTACK)
    {
        if (!player->attackdown
            || (player->readyweapon != wp_missile
                && player->readyweapon != wp_bfg))
        {
            player->attackdown = true;
            P_FireWeapon(player);
            return;
        }
    }
    else
        player->attackdown = false;

    // bob the weapon based on movement speed
    angle = (128 * leveltime) & FINEMASK;
    psp->sx = FRACUNIT + FixedMul(player->bob, finecosine[angle]);
    angle &= FINEANGLES / 2 - 1;
    psp->sy = WEAPONTOP + FixedMul(player->bob, finesine[angle]);
}


//
// A_ReFire
// The player can re-fire the weapon
// without lowering it entirely.
//
void A_ReFire(player_t* player, pspdef_t* psp)
{
    // check for fire
    //  (if a weaponchange is pending, let it go through instead)
    if ((player->cmd.buttons & BT_ATTACK)
        && player->pendingweapon == wp_nochange
        && player->health)
    {
        player->refire++;
        P_FireWeapon(player);
    }
    else
    {
        player->refire = 0;
        P_CheckAmmo(player);
    }
}


void A_CheckReload(player_t* player, pspdef_t* psp)
{
    P_CheckAmmo(player);
}


//
// A_Lower
// Lowers current weapon,
//  and changes weapon at bottom.
//
void A_Lower(player_t* player, pspdef_t* psp)
{
    psp->sy += LOWERSPEED;

    // Is already down.
    if (psp->sy < WEAPONBOTTOM)
        return;

    // Player is dead.
    if (player->playerstate == PST_DEAD)
    {
        psp->sy = WEAPONBOTTOM;

        // don't bring weapon back up
        return;
    }

    // The old weapon has been lowered off the screen,
    // so change the weapon and start raising it
    if (!player->health)
    {
        // Player is dead, so keep the weapon off screen.
        P_SetPsprite(player, ps_weapon, S_NULL);
        return;
    }

    player->readyweapon = player->pendingweapon;

    P_BringUpWeapon(player);
}


//
// A_Raise
//
void A_Raise(player_t* player, pspdef_t* psp)
{
    statenum_t newstate;

    psp->sy -= RAISESPEED;

    if (psp->sy > WEAPONTOP)
        return;

    psp->sy = WEAPONTOP;

    // The weapon has been raised all the way,
    //  so change to the ready state.
    newstate = weaponinfo[player->readyweapon].readystate;

    P_SetPsprite(player, ps_weapon, newstate);
}


//
// A_GunFlash
//
void A_GunFlash(player_t* player, pspdef_t* psp)
{
    P_SetMobjState(player->mo, S_PLAY_ATK2);
    P_SetPsprite(player, ps_flash, weaponinfo[player->readyweapon].flashstate);
}


//
// WEAPON ATTACKS
//

//
// A_Punch
//
void A_Punch(player_t* player, pspdef_t* psp)
{
    angle_t angle;
    int damage;
    int slope;

    damage = (P_Random() % 10 + 1) << 1;

    if (player->powers[pw_strength])
        damage *= 10;

    angle = player->mo->angle;
    angle += (P_Random() - P_Random()) << 18;
    slope = P_AimLineAttack(player->mo, angle, MELEERANGE);
    P_LineAttack(player->mo, angle, MELEERANGE, slope, damage);

    // turn to face target
    if (linetarget)
    {
        S_StartSound(player->mo, sfx_punch);
        player->mo->angle = R_PointToAngle2(player->mo->x,
                                            player->mo->y,
                                            linetarget->x,
                                            linetarget->y);
    }
}


//
// A_Saw
//
void A_Saw(player_t* player, pspdef_t* psp)
{
    angle_t angle;
    int damage;
    int slope;

    damage = 2 * (P_Random() % 10 + 1);
    angle = player->mo->angle;
    angle += (P_Random() - P_Random()) << 18;

    // use meleerange + 1 se the puff doesn't skip the flash
    slope = P_AimLineAttack(player->mo, angle, MELEERANGE + 1);
    P_LineAttack(player->mo, angle, MELEERANGE + 1, slope, damage);

    if (!linetarget)
    {
        S_StartSound(player->mo, sfx_sawful);
        return;
    }
    S_StartSound(player->mo, sfx_sawhit);

    // turn to face target
    angle = R_PointToAngle2(player->mo->x, player->mo->y,
                            linetarget->x, linetarget->y);
    if (angle - player->mo->angle > ANG180)
    {
        if (angle - player->mo->angle < -ANG90 / 20)
            player->mo->angle = angle + ANG90 / 21;
        else
            player->mo->angle -= ANG90 / 20;
    }
    else
    {
        if (angle - player->mo->angle > ANG90 / 20)
            player->mo->angle = angle - ANG90 / 21;
        else
            player->mo->angle += ANG90 / 20;
    }
    player->mo->flags |= MF_JUSTATTACKED;
}


//
// A_FireMissile
//
void A_FireMissile(player_t* player, pspdef_t* psp)
{
    player->ammo[weaponinfo[player->readyweapon].ammo]--;
    P_SpawnPlayerMissile(player->mo, MT_ROCKET);
}


//
// A_FireBFG
//
void A_FireBFG(player_t* player, pspdef_t* psp)
{
    player->ammo[weaponinfo[player->readyweapon].ammo] -= BFGCELLS;
    P_SpawnPlayerMissile(player->mo, MT_BFG);
}


//
// A_FirePlasma
//
void A_FirePlasma(player_t* player, pspdef_t* psp)
{
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite(player,
                 ps_flash,
                 weaponinfo[player->readyweapon].flashstate + (P_Random() & 1));

    P_SpawnPlayerMissile(player->mo, MT_PLASMA);
}


//
// P_BulletSlope
// Sets a slope so a near miss is at aproximately
// the height of the intended target
//
void P_BulletSlope(mobj_t* mo)
{
    angle_t an;

    // see which target is to be aimed at
    an = mo->angle;
    bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT);

    if (!linetarget)
    {
        an += 1 << 26;
        bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT);
        if (!linetarget)
        {
            an -= 2 << 26;
            bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT);
        }
    }
}


//
// P_GunShot
//
void P_GunShot(mobj_t* mo, doom_boolean accurate)
{
    angle_t angle;
    int damage;

    damage = 5 * (P_Random() % 3 + 1);
    angle = mo->angle;

    if (!accurate)
        angle += (P_Random() - P_Random()) << 18;

    P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage);
}


//
// A_FirePistol
//
void A_FirePistol(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_pistol);

    P_SetMobjState(player->mo, S_PLAY_ATK2);
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite(player,
                 ps_flash,
                 weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope(player->mo);
    P_GunShot(player->mo, !player->refire);
}


//
// A_FireShotgun
//
void A_FireShotgun(player_t* player, pspdef_t* psp)
{
    int i;

    S_StartSound(player->mo, sfx_shotgn);
    P_SetMobjState(player->mo, S_PLAY_ATK2);

    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite(player,
                 ps_flash,
                 weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope(player->mo);

    for (i = 0; i < 7; i++)
        P_GunShot(player->mo, false);
}


//
// A_FireShotgun2
//
void A_FireShotgun2(player_t* player, pspdef_t* psp)
{
    int i;
    angle_t angle;
    int damage;


    S_StartSound(player->mo, sfx_dshtgn);
    P_SetMobjState(player->mo, S_PLAY_ATK2);

    player->ammo[weaponinfo[player->readyweapon].ammo] -= 2;

    P_SetPsprite(player,
                 ps_flash,
                 weaponinfo[player->readyweapon].flashstate);

    P_BulletSlope(player->mo);

    for (i = 0; i < 20; i++)
    {
        damage = 5 * (P_Random() % 3 + 1);
        angle = player->mo->angle;
        angle += (P_Random() - P_Random()) << 19;
        P_LineAttack(player->mo,
                     angle,
                     MISSILERANGE,
                     bulletslope + ((P_Random() - P_Random()) << 5), damage);
    }
}


//
// A_FireCGun
//
void A_FireCGun(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_pistol);

    if (!player->ammo[weaponinfo[player->readyweapon].ammo])
        return;

    P_SetMobjState(player->mo, S_PLAY_ATK2);
    player->ammo[weaponinfo[player->readyweapon].ammo]--;

    P_SetPsprite(player,
                 ps_flash,
                 weaponinfo[player->readyweapon].flashstate
                 + psp->state
                 - &states[S_CHAIN1]);

    P_BulletSlope(player->mo);

    P_GunShot(player->mo, !player->refire);
}


//
// ?
//
void A_Light0(player_t* player, pspdef_t* psp)
{
    player->extralight = 0;
}

void A_Light1(player_t* player, pspdef_t* psp)
{
    player->extralight = 1;
}

void A_Light2(player_t* player, pspdef_t* psp)
{
    player->extralight = 2;
}


//
// A_BFGSpray
// Spawn a BFG explosion on every monster in view
//
void A_BFGSpray(mobj_t* mo)
{
    int i;
    int j;
    int damage;
    angle_t an;

    // offset angles from its attack angle
    for (i = 0; i < 40; i++)
    {
        an = mo->angle - ANG90 / 2 + ANG90 / 40 * i;

        // mo->target is the originator (player)
        //  of the missile
        P_AimLineAttack(mo->target, an, 16 * 64 * FRACUNIT);

        if (!linetarget)
            continue;

        P_SpawnMobj(linetarget->x,
                    linetarget->y,
                    linetarget->z + (linetarget->height >> 2),
                    MT_EXTRABFG);

        damage = 0;
        for (j = 0; j < 15; j++)
            damage += (P_Random() & 7) + 1;

        P_DamageMobj(linetarget, mo->target, mo->target, damage);
    }
}


//
// A_BFGsound
//
void A_BFGsound(player_t* player, pspdef_t* psp)
{
    S_StartSound(player->mo, sfx_bfg);
}


//
// P_SetupPsprites
// Called at start of level for each player.
//
void P_SetupPsprites(player_t* player)
{
    int i;

    // remove all psprites
    for (i = 0; i < NUMPSPRITES; i++)
        player->psprites[i].state = 0;

    // spawn the gun
    player->pendingweapon = player->readyweapon;
    P_BringUpWeapon(player);
}


//
// P_MovePsprites
// Called every tic by player thinking routine.
//
void P_MovePsprites(player_t* player)
{
    int i;
    pspdef_t* psp;
    state_t* state;

    psp = &player->psprites[0];
    for (i = 0; i < NUMPSPRITES; i++, psp++)
    {
        // a null state means not active
        if ((state = psp->state))
        {
            // drop tic count and possibly change state

            // a -1 tic count never changes
            if (psp->tics != -1)
            {
                psp->tics--;
                if (!psp->tics)
                    P_SetPsprite(player, i, psp->state->nextstate);
            }
        }
    }

    player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx;
    player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy;
}
#define PADSAVEP() save_p += (4 - ((long long)save_p & 3)) & 3


byte* save_p;


// TODO: [pd] We are loading/saving raw pointers. It will not work with saves from 32bits system. We need to rewrite those functions.
//
// P_ArchivePlayers
//
void P_ArchivePlayers(void)
{
    int i;
    int j;
    player_t* dest;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (!playeringame[i])
            continue;

        PADSAVEP();

        dest = (player_t*)save_p;
        doom_memcpy(dest, &players[i], sizeof(player_t));
        save_p += sizeof(player_t);
        for (j = 0; j < NUMPSPRITES; j++)
        {
            if (dest->psprites[j].state)
            {
                dest->psprites[j].state
                    = (state_t*)(dest->psprites[j].state - states);
            }
        }
    }
}


//
// P_UnArchivePlayers
//
void P_UnArchivePlayers(void)
{
    int i;
    int j;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (!playeringame[i])
            continue;

        PADSAVEP();

        doom_memcpy(&players[i], save_p, sizeof(player_t));
        save_p += sizeof(player_t);

        // will be set when unarc thinker
        players[i].mo = 0;
        players[i].message = 0;
        players[i].attacker = 0;

        for (j = 0; j < NUMPSPRITES; j++)
        {
            if (players[i].psprites[j].state)
            {
                players[i].psprites[j].state
                    = &states[(long long)players[i].psprites[j].state];
            }
        }
    }
}


//
// P_ArchiveWorld
//
void P_ArchiveWorld(void)
{
    int i;
    int j;
    sector_t* sec;
    line_t* li;
    side_t* si;
    short* put;

    put = (short*)save_p;

    // do sectors
    for (i = 0, sec = sectors; i < numsectors; i++, sec++)
    {
        *put++ = sec->floorheight >> FRACBITS;
        *put++ = sec->ceilingheight >> FRACBITS;
        *put++ = sec->floorpic;
        *put++ = sec->ceilingpic;
        *put++ = sec->lightlevel;
        *put++ = sec->special; // needed?
        *put++ = sec->tag; // needed?
    }


    // do lines
    for (i = 0, li = lines; i < numlines; i++, li++)
    {
        *put++ = li->flags;
        *put++ = li->special;
        *put++ = li->tag;
        for (j = 0; j < 2; j++)
        {
            if (li->sidenum[j] == -1)
                continue;

            si = &sides[li->sidenum[j]];

            *put++ = si->textureoffset >> FRACBITS;
            *put++ = si->rowoffset >> FRACBITS;
            *put++ = si->toptexture;
            *put++ = si->bottomtexture;
            *put++ = si->midtexture;
        }
    }

    save_p = (byte*)put;
}


//
// P_UnArchiveWorld
//
void P_UnArchiveWorld(void)
{
    int i;
    int j;
    sector_t* sec;
    line_t* li;
    side_t* si;
    short* get;

    get = (short*)save_p;

    // do sectors
    for (i = 0, sec = sectors; i < numsectors; i++, sec++)
    {
        sec->floorheight = *get++ << FRACBITS;
        sec->ceilingheight = *get++ << FRACBITS;
        sec->floorpic = *get++;
        sec->ceilingpic = *get++;
        sec->lightlevel = *get++;
        sec->special = *get++; // needed?
        sec->tag = *get++; // needed?
        sec->specialdata = 0;
        sec->soundtarget = 0;
    }

    // do lines
    for (i = 0, li = lines; i < numlines; i++, li++)
    {
        li->flags = *get++;
        li->special = *get++;
        li->tag = *get++;
        for (j = 0; j < 2; j++)
        {
            if (li->sidenum[j] == -1)
                continue;
            si = &sides[li->sidenum[j]];
            si->textureoffset = *get++ << FRACBITS;
            si->rowoffset = *get++ << FRACBITS;
            si->toptexture = *get++;
            si->bottomtexture = *get++;
            si->midtexture = *get++;
        }
    }

    save_p = (byte*)get;
}


//
// Thinkers
//
typedef enum
{
    tc_end,
    tc_mobj
} thinkerclass_t;


//
// P_ArchiveThinkers
//
void P_ArchiveThinkers(void)
{
    thinker_t* th;
    mobj_t* mobj;

    // save off the current thinkers
    for (th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if (th->function.acp1 == (actionf_p1)P_MobjThinker)
        {
            *save_p++ = tc_mobj;
            PADSAVEP();
            mobj = (mobj_t*)save_p;
            doom_memcpy(mobj, th, sizeof(*mobj));
            save_p += sizeof(*mobj);
            mobj->state = (state_t*)(mobj->state - states);

            if (mobj->player)
                mobj->player = (player_t*)((mobj->player - players) + 1);
            continue;
        }

        // I_Error ("P_ArchiveThinkers: Unknown thinker function");
    }

    // add a terminating marker
    *save_p++ = tc_end;
}


//
// P_UnArchiveThinkers
//
void P_UnArchiveThinkers(void)
{
    byte tclass;
    thinker_t* currentthinker;
    thinker_t* next;
    mobj_t* mobj;

    // remove all the current thinkers
    currentthinker = thinkercap.next;
    while (currentthinker != &thinkercap)
    {
        next = currentthinker->next;

        if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
            P_RemoveMobj((mobj_t*)currentthinker);
        else
            Z_Free(currentthinker);

        currentthinker = next;
    }
    P_InitThinkers();

    // read in saved thinkers
    while (1)
    {
        tclass = *save_p++;
        switch (tclass)
        {
            case tc_end:
                return;         // end of list

            case tc_mobj:
                PADSAVEP();
                mobj = Z_Malloc(sizeof(*mobj), PU_LEVEL, 0);
                doom_memcpy(mobj, save_p, sizeof(*mobj));
                save_p += sizeof(*mobj);
                mobj->state = &states[(long long)mobj->state];
                mobj->target = 0;
                if (mobj->player)
                {
                    mobj->player = &players[(long long)mobj->player - 1];
                    mobj->player->mo = mobj;
                }
                P_SetThingPosition(mobj);
                mobj->info = &mobjinfo[mobj->type];
                mobj->floorz = mobj->subsector->sector->floorheight;
                mobj->ceilingz = mobj->subsector->sector->ceilingheight;
                mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
                P_AddThinker(&mobj->thinker);
                break;

            default:
            {
                //I_Error("Error: Unknown tclass %i in savegame", tclass);
                
                doom_strcpy(error_buf, "Error: Unknown tclass ");
                doom_concat(error_buf, doom_itoa(tclass, 10));
                doom_concat(error_buf, " in savegame");
                I_Error(error_buf);
            }
        }
    }
}


//
// P_ArchiveSpecials
//
enum
{
    tc_ceiling,
    tc_door,
    tc_floor,
    tc_plat,
    tc_flash,
    tc_strobe,
    tc_glow,
    tc_endspecials
} specials_e;


//
// Things to handle:
//
// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list
// T_VerticalDoor, (vldoor_t: sector_t * swizzle),
// T_MoveFloor, (floormove_t: sector_t * swizzle),
// T_LightFlash, (lightflash_t: sector_t * swizzle),
// T_StrobeFlash, (strobe_t: sector_t *),
// T_Glow, (glow_t: sector_t *),
// T_PlatRaise, (plat_t: sector_t *), - active list
//
void P_ArchiveSpecials(void)
{
    thinker_t* th;
    ceiling_t* ceiling;
    vldoor_t* door;
    floormove_t* floor;
    plat_t* plat;
    lightflash_t* flash;
    strobe_t* strobe;
    glow_t* glow;
    int i;

    // save off the current thinkers
    for (th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if (th->function.acv == (actionf_v)0)
        {
            for (i = 0; i < MAXCEILINGS; i++)
                if (activeceilings[i] == (ceiling_t*)th)
                    break;

            if (i < MAXCEILINGS)
            {
                *save_p++ = tc_ceiling;
                PADSAVEP();
                ceiling = (ceiling_t*)save_p;
                doom_memcpy(ceiling, th, sizeof(*ceiling));
                save_p += sizeof(*ceiling);
                ceiling->sector = (sector_t*)(ceiling->sector - sectors);
            }
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
        {
            *save_p++ = tc_ceiling;
            PADSAVEP();
            ceiling = (ceiling_t*)save_p;
            doom_memcpy(ceiling, th, sizeof(*ceiling));
            save_p += sizeof(*ceiling);
            ceiling->sector = (sector_t*)(ceiling->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_VerticalDoor)
        {
            *save_p++ = tc_door;
            PADSAVEP();
            door = (vldoor_t*)save_p;
            doom_memcpy(door, th, sizeof(*door));
            save_p += sizeof(*door);
            door->sector = (sector_t*)(door->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_MoveFloor)
        {
            *save_p++ = tc_floor;
            PADSAVEP();
            floor = (floormove_t*)save_p;
            doom_memcpy(floor, th, sizeof(*floor));
            save_p += sizeof(*floor);
            floor->sector = (sector_t*)(floor->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_PlatRaise)
        {
            *save_p++ = tc_plat;
            PADSAVEP();
            plat = (plat_t*)save_p;
            doom_memcpy(plat, th, sizeof(*plat));
            save_p += sizeof(*plat);
            plat->sector = (sector_t*)(plat->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_LightFlash)
        {
            *save_p++ = tc_flash;
            PADSAVEP();
            flash = (lightflash_t*)save_p;
            doom_memcpy(flash, th, sizeof(*flash));
            save_p += sizeof(*flash);
            flash->sector = (sector_t*)(flash->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
        {
            *save_p++ = tc_strobe;
            PADSAVEP();
            strobe = (strobe_t*)save_p;
            doom_memcpy(strobe, th, sizeof(*strobe));
            save_p += sizeof(*strobe);
            strobe->sector = (sector_t*)(strobe->sector - sectors);
            continue;
        }

        if (th->function.acp1 == (actionf_p1)T_Glow)
        {
            *save_p++ = tc_glow;
            PADSAVEP();
            glow = (glow_t*)save_p;
            doom_memcpy(glow, th, sizeof(*glow));
            save_p += sizeof(*glow);
            glow->sector = (sector_t*)(glow->sector - sectors);
            continue;
        }
    }

    // add a terminating marker
    *save_p++ = tc_endspecials;
}


//
// P_UnArchiveSpecials
//
void P_UnArchiveSpecials(void)
{
    byte tclass;
    ceiling_t* ceiling;
    vldoor_t* door;
    floormove_t* floor;
    plat_t* plat;
    lightflash_t* flash;
    strobe_t* strobe;
    glow_t* glow;


    // read in saved thinkers
    while (1)
    {
        tclass = *save_p++;
        switch (tclass)
        {
            case tc_endspecials:
                return;        // end of list

            case tc_ceiling:
                PADSAVEP();
                ceiling = Z_Malloc(sizeof(*ceiling), PU_LEVEL, 0);
                doom_memcpy(ceiling, save_p, sizeof(*ceiling));
                save_p += sizeof(*ceiling);
                ceiling->sector = &sectors[(long long)ceiling->sector];
                ceiling->sector->specialdata = ceiling;

                if (ceiling->thinker.function.acp1)
                    ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling;

                P_AddThinker(&ceiling->thinker);
                P_AddActiveCeiling(ceiling);
                break;

            case tc_door:
                PADSAVEP();
                door = Z_Malloc(sizeof(*door), PU_LEVEL, 0);
                doom_memcpy(door, save_p, sizeof(*door));
                save_p += sizeof(*door);
                door->sector = &sectors[(long long)door->sector];
                door->sector->specialdata = door;
                door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor;
                P_AddThinker(&door->thinker);
                break;

            case tc_floor:
                PADSAVEP();
                floor = Z_Malloc(sizeof(*floor), PU_LEVEL, 0);
                doom_memcpy(floor, save_p, sizeof(*floor));
                save_p += sizeof(*floor);
                floor->sector = &sectors[(long long)floor->sector];
                floor->sector->specialdata = floor;
                floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
                P_AddThinker(&floor->thinker);
                break;

            case tc_plat:
                PADSAVEP();
                plat = Z_Malloc(sizeof(*plat), PU_LEVEL, 0);
                doom_memcpy(plat, save_p, sizeof(*plat));
                save_p += sizeof(*plat);
                plat->sector = &sectors[(long long)plat->sector];
                plat->sector->specialdata = plat;

                if (plat->thinker.function.acp1)
                    plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise;

                P_AddThinker(&plat->thinker);
                P_AddActivePlat(plat);
                break;

            case tc_flash:
                PADSAVEP();
                flash = Z_Malloc(sizeof(*flash), PU_LEVEL, 0);
                doom_memcpy(flash, save_p, sizeof(*flash));
                save_p += sizeof(*flash);
                flash->sector = &sectors[(long long)flash->sector];
                flash->thinker.function.acp1 = (actionf_p1)T_LightFlash;
                P_AddThinker(&flash->thinker);
                break;

            case tc_strobe:
                PADSAVEP();
                strobe = Z_Malloc(sizeof(*strobe), PU_LEVEL, 0);
                doom_memcpy(strobe, save_p, sizeof(*strobe));
                save_p += sizeof(*strobe);
                strobe->sector = &sectors[(long long)strobe->sector];
                strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash;
                P_AddThinker(&strobe->thinker);
                break;

            case tc_glow:
                PADSAVEP();
                glow = Z_Malloc(sizeof(*glow), PU_LEVEL, 0);
                doom_memcpy(glow, save_p, sizeof(*glow));
                save_p += sizeof(*glow);
                glow->sector = &sectors[(long long)glow->sector];
                glow->thinker.function.acp1 = (actionf_p1)T_Glow;
                P_AddThinker(&glow->thinker);
                break;

            default:
            {
                //I_Error("Error: P_UnarchiveSpecials:Unknown tclass %i "
                //        "in savegame", tclass);
                
                doom_strcpy(error_buf, "Error: P_UnarchiveSpecials:Unknown tclass ");
                doom_concat(error_buf, doom_itoa(tclass, 10));
                doom_concat(error_buf, " in savegame");
                I_Error(error_buf);
            }
        }
    }
}
#define MAX_DEATHMATCH_STARTS        10


//
// MAP related Lookup tables.
// Store VERTEXES, LINEDEFS, SIDEDEFS, etc.
//
int numvertexes;
vertex_t* vertexes;

int numsegs;
seg_t* segs;

int numsectors;
sector_t* sectors;

int numsubsectors;
subsector_t* subsectors;

int numnodes;
node_t* nodes;

int numlines;
line_t* lines;

int numsides;
side_t* sides;

// BLOCKMAP
// Created from axis aligned bounding box
// of the map, a rectangular array of
// blocks of size ...
// Used to speed up collision detection
// by spatial subdivision in 2D.
//
// Blockmap size.
int bmapwidth;
int bmapheight; // size in mapblocks
short* blockmap; // int for larger maps
// offsets in blockmap are from here
short* blockmaplump;
// origin of block map
fixed_t bmaporgx;
fixed_t bmaporgy;
// for thing chains
mobj_t** blocklinks;

// REJECT
// For fast sight rejection.
// Speeds up enemy AI by skipping detailed
//  LineOf Sight calculation.
// Without special effect, this could be
//  used as a PVS lookup as well.
//
byte* rejectmatrix;

mapthing_t deathmatchstarts[MAX_DEATHMATCH_STARTS];
mapthing_t* deathmatch_p;
mapthing_t playerstarts[MAXPLAYERS];


void P_SpawnMapThing(mapthing_t* mthing);


//
// P_LoadVertexes
//
void P_LoadVertexes(int lump)
{
    byte* data;
    int i;
    mapvertex_t* ml;
    vertex_t* li;

    // Determine number of lumps:
    //  total lump length / vertex record length.
    numvertexes = W_LumpLength(lump) / sizeof(mapvertex_t);

    // Allocate zone memory for buffer.
    vertexes = Z_Malloc(numvertexes * sizeof(vertex_t), PU_LEVEL, 0);

    // Load data into cache.
    data = W_CacheLumpNum(lump, PU_STATIC);

    ml = (mapvertex_t*)data;
    li = vertexes;

    // Copy and convert vertex coordinates,
    // internal representation as fixed.
    for (i = 0; i < numvertexes; i++, li++, ml++)
    {
        li->x = SHORT(ml->x) << FRACBITS;
        li->y = SHORT(ml->y) << FRACBITS;
    }

    // Free buffer memory.
    Z_Free(data);
}


//
// P_LoadSegs
//
void P_LoadSegs(int lump)
{
    byte* data;
    int i;
    mapseg_t* ml;
    seg_t* li;
    line_t* ldef;
    int linedef;
    int side;

    numsegs = W_LumpLength(lump) / sizeof(mapseg_t);
    segs = Z_Malloc(numsegs * sizeof(seg_t), PU_LEVEL, 0);
    doom_memset(segs, 0, numsegs * sizeof(seg_t));
    data = W_CacheLumpNum(lump, PU_STATIC);

    ml = (mapseg_t*)data;
    li = segs;
    for (i = 0; i < numsegs; i++, li++, ml++)
    {
        li->v1 = &vertexes[SHORT(ml->v1)];
        li->v2 = &vertexes[SHORT(ml->v2)];

        li->angle = (SHORT(ml->angle)) << 16;
        li->offset = (SHORT(ml->offset)) << 16;
        linedef = SHORT(ml->linedef);
        ldef = &lines[linedef];
        li->linedef = ldef;
        side = SHORT(ml->side);
        li->sidedef = &sides[ldef->sidenum[side]];
        li->frontsector = sides[ldef->sidenum[side]].sector;
        if (ldef->flags & ML_TWOSIDED)
            li->backsector = sides[ldef->sidenum[side ^ 1]].sector;
        else
            li->backsector = 0;
    }

    Z_Free(data);
}


//
// P_LoadSubsectors
//
void P_LoadSubsectors(int lump)
{
    byte* data;
    int i;
    mapsubsector_t* ms;
    subsector_t* ss;

    numsubsectors = W_LumpLength(lump) / sizeof(mapsubsector_t);
    subsectors = Z_Malloc(numsubsectors * sizeof(subsector_t), PU_LEVEL, 0);
    data = W_CacheLumpNum(lump, PU_STATIC);

    ms = (mapsubsector_t*)data;
    doom_memset(subsectors, 0, numsubsectors * sizeof(subsector_t));
    ss = subsectors;

    for (i = 0; i < numsubsectors; i++, ss++, ms++)
    {
        ss->numlines = SHORT(ms->numsegs);
        ss->firstline = SHORT(ms->firstseg);
    }

    Z_Free(data);
}


//
// P_LoadSectors
//
void P_LoadSectors(int lump)
{
    byte* data;
    int i;
    mapsector_t* ms;
    sector_t* ss;

    numsectors = W_LumpLength(lump) / sizeof(mapsector_t);
    sectors = Z_Malloc(numsectors * sizeof(sector_t), PU_LEVEL, 0);
    doom_memset(sectors, 0, numsectors * sizeof(sector_t));
    data = W_CacheLumpNum(lump, PU_STATIC);

    ms = (mapsector_t*)data;
    ss = sectors;
    for (i = 0; i < numsectors; i++, ss++, ms++)
    {
        ss->floorheight = SHORT(ms->floorheight) << FRACBITS;
        ss->ceilingheight = SHORT(ms->ceilingheight) << FRACBITS;
        ss->floorpic = R_FlatNumForName(ms->floorpic);
        ss->ceilingpic = R_FlatNumForName(ms->ceilingpic);
        ss->lightlevel = SHORT(ms->lightlevel);
        ss->special = SHORT(ms->special);
        ss->tag = SHORT(ms->tag);
        ss->thinglist = 0;
    }

    Z_Free(data);
}


//
// P_LoadNodes
//
void P_LoadNodes(int lump)
{
    byte* data;
    int i;
    int j;
    int k;
    mapnode_t* mn;
    node_t* no;

    numnodes = W_LumpLength(lump) / sizeof(mapnode_t);
    nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0);
    data = W_CacheLumpNum(lump, PU_STATIC);

    mn = (mapnode_t*)data;
    no = nodes;

    for (i = 0; i < numnodes; i++, no++, mn++)
    {
        no->x = SHORT(mn->x) << FRACBITS;
        no->y = SHORT(mn->y) << FRACBITS;
        no->dx = SHORT(mn->dx) << FRACBITS;
        no->dy = SHORT(mn->dy) << FRACBITS;
        for (j = 0; j < 2; j++)
        {
            no->children[j] = SHORT(mn->children[j]);
            for (k = 0; k < 4; k++)
                no->bbox[j][k] = SHORT(mn->bbox[j][k]) << FRACBITS;
        }
    }

    Z_Free(data);
}


//
// P_LoadThings
//
void P_LoadThings(int lump)
{
    byte* data;
    int i;
    mapthing_t* mt;
    int numthings;
    doom_boolean spawn;

    data = W_CacheLumpNum(lump, PU_STATIC);
    numthings = W_LumpLength(lump) / sizeof(mapthing_t);

    mt = (mapthing_t*)data;
    for (i = 0; i < numthings; i++, mt++)
    {
        spawn = true;

        // Do not spawn cool, new monsters if !commercial
        if (gamemode != commercial)
        {
            switch (mt->type)
            {
                case 68:        // Arachnotron
                case 64:        // Archvile
                case 88:        // Boss Brain
                case 89:        // Boss Shooter
                case 69:        // Hell Knight
                case 67:        // Mancubus
                case 71:        // Pain Elemental
                case 65:        // Former Human Commando
                case 66:        // Revenant
                case 84:        // Wolf SS
                    spawn = false;
                    break;
            }
        }
        if (spawn == false)
            break;

        // Do spawn all other stuff. 
        mt->x = SHORT(mt->x);
        mt->y = SHORT(mt->y);
        mt->angle = SHORT(mt->angle);
        mt->type = SHORT(mt->type);
        mt->options = SHORT(mt->options);

        P_SpawnMapThing(mt);
    }

    Z_Free(data);
}


//
// P_LoadLineDefs
// Also counts secret lines for intermissions.
//
void P_LoadLineDefs(int lump)
{
    byte* data;
    int i;
    maplinedef_t* mld;
    line_t* ld;
    vertex_t* v1;
    vertex_t* v2;

    numlines = W_LumpLength(lump) / sizeof(maplinedef_t);
    lines = Z_Malloc(numlines * sizeof(line_t), PU_LEVEL, 0);
    doom_memset(lines, 0, numlines * sizeof(line_t));
    data = W_CacheLumpNum(lump, PU_STATIC);

    mld = (maplinedef_t*)data;
    ld = lines;
    for (i = 0; i < numlines; i++, mld++, ld++)
    {
        ld->flags = SHORT(mld->flags);
        ld->special = SHORT(mld->special);
        ld->tag = SHORT(mld->tag);
        v1 = ld->v1 = &vertexes[SHORT(mld->v1)];
        v2 = ld->v2 = &vertexes[SHORT(mld->v2)];
        ld->dx = v2->x - v1->x;
        ld->dy = v2->y - v1->y;

        if (!ld->dx)
            ld->slopetype = ST_VERTICAL;
        else if (!ld->dy)
            ld->slopetype = ST_HORIZONTAL;
        else
        {
            if (FixedDiv(ld->dy, ld->dx) > 0)
                ld->slopetype = ST_POSITIVE;
            else
                ld->slopetype = ST_NEGATIVE;
        }

        if (v1->x < v2->x)
        {
            ld->bbox[BOXLEFT] = v1->x;
            ld->bbox[BOXRIGHT] = v2->x;
        }
        else
        {
            ld->bbox[BOXLEFT] = v2->x;
            ld->bbox[BOXRIGHT] = v1->x;
        }

        if (v1->y < v2->y)
        {
            ld->bbox[BOXBOTTOM] = v1->y;
            ld->bbox[BOXTOP] = v2->y;
        }
        else
        {
            ld->bbox[BOXBOTTOM] = v2->y;
            ld->bbox[BOXTOP] = v1->y;
        }

        ld->sidenum[0] = SHORT(mld->sidenum[0]);
        ld->sidenum[1] = SHORT(mld->sidenum[1]);

        if (ld->sidenum[0] != -1)
            ld->frontsector = sides[ld->sidenum[0]].sector;
        else
            ld->frontsector = 0;

        if (ld->sidenum[1] != -1)
            ld->backsector = sides[ld->sidenum[1]].sector;
        else
            ld->backsector = 0;
    }

    Z_Free(data);
}


//
// P_LoadSideDefs
//
void P_LoadSideDefs(int lump)
{
    byte* data;
    int i;
    mapsidedef_t* msd;
    side_t* sd;

    numsides = W_LumpLength(lump) / sizeof(mapsidedef_t);
    sides = Z_Malloc(numsides * sizeof(side_t), PU_LEVEL, 0);
    doom_memset(sides, 0, numsides * sizeof(side_t));
    data = W_CacheLumpNum(lump, PU_STATIC);

    msd = (mapsidedef_t*)data;
    sd = sides;
    for (i = 0; i < numsides; i++, msd++, sd++)
    {
        sd->textureoffset = SHORT(msd->textureoffset) << FRACBITS;
        sd->rowoffset = SHORT(msd->rowoffset) << FRACBITS;
        sd->toptexture = R_TextureNumForName(msd->toptexture);
        sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
        sd->midtexture = R_TextureNumForName(msd->midtexture);
        sd->sector = &sectors[SHORT(msd->sector)];
    }

    Z_Free(data);
}


//
// P_LoadBlockMap
//
void P_LoadBlockMap(int lump)
{
    int i;
    int count;

    blockmaplump = W_CacheLumpNum(lump, PU_LEVEL);
    blockmap = blockmaplump + 4;
    count = W_LumpLength(lump) / 2;

    for (i = 0; i < count; i++)
        blockmaplump[i] = SHORT(blockmaplump[i]);

    bmaporgx = blockmaplump[0] << FRACBITS;
    bmaporgy = blockmaplump[1] << FRACBITS;
    bmapwidth = blockmaplump[2];
    bmapheight = blockmaplump[3];

    // clear out mobj chains
    count = sizeof(*blocklinks) * bmapwidth * bmapheight;
    blocklinks = Z_Malloc(count, PU_LEVEL, 0);
    doom_memset(blocklinks, 0, count);
}


//
// P_GroupLines
// Builds sector line lists and subsector sector numbers.
// Finds block bounding boxes for sectors.
//
void P_GroupLines(void)
{
    line_t** linebuffer;
    int i;
    int j;
    int total;
    line_t* li;
    sector_t* sector;
    subsector_t* ss;
    seg_t* seg;
    fixed_t bbox[4];
    int block;

    // look up sector number for each subsector
    ss = subsectors;
    for (i = 0; i < numsubsectors; i++, ss++)
    {
        seg = &segs[ss->firstline];
        ss->sector = seg->sidedef->sector;
    }

    // count number of lines in each sector
    li = lines;
    total = 0;
    for (i = 0; i < numlines; i++, li++)
    {
        total++;
        li->frontsector->linecount++;

        if (li->backsector && li->backsector != li->frontsector)
        {
            li->backsector->linecount++;
            total++;
        }
    }

    // build line tables for each sector        
    linebuffer = Z_Malloc(total * sizeof(line_t*), PU_LEVEL, 0);
    sector = sectors;
    for (i = 0; i < numsectors; i++, sector++)
    {
        M_ClearBox(bbox);
        sector->lines = linebuffer;
        li = lines;
        for (j = 0; j < numlines; j++, li++)
        {
            if (li->frontsector == sector || li->backsector == sector)
            {
                *linebuffer++ = li;
                M_AddToBox(bbox, li->v1->x, li->v1->y);
                M_AddToBox(bbox, li->v2->x, li->v2->y);
            }
        }
        if (linebuffer - sector->lines != sector->linecount)
            I_Error("Error: P_GroupLines: miscounted");

        // set the degenmobj_t to the middle of the bounding box
        sector->soundorg.x = (bbox[BOXRIGHT] + bbox[BOXLEFT]) / 2;
        sector->soundorg.y = (bbox[BOXTOP] + bbox[BOXBOTTOM]) / 2;

        // adjust bounding box to map blocks
        block = (bbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT;
        block = block >= bmapheight ? bmapheight - 1 : block;
        sector->blockbox[BOXTOP] = block;

        block = (bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT;
        block = block < 0 ? 0 : block;
        sector->blockbox[BOXBOTTOM] = block;

        block = (bbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT;
        block = block >= bmapwidth ? bmapwidth - 1 : block;
        sector->blockbox[BOXRIGHT] = block;

        block = (bbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT;
        block = block < 0 ? 0 : block;
        sector->blockbox[BOXLEFT] = block;
    }
}


//
// P_SetupLevel
//
void P_SetupLevel(int episode, int map, int playermask, skill_t skill)
{
    int i;
    char lumpname[9];
    int lumpnum;

    totalkills = totalitems = totalsecret = wminfo.maxfrags = 0;
    wminfo.partime = 180;
    for (i = 0; i < MAXPLAYERS; i++)
    {
        players[i].killcount = players[i].secretcount
            = players[i].itemcount = 0;
    }

    // Initial height of PointOfView
    // will be set by player think.
    players[consoleplayer].viewz = 1;

    // Make sure all sounds are stopped before Z_FreeTags.
    S_Start();

    Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);

    // UNUSED W_Profile ();
    P_InitThinkers();

    // if working with a devlopment map, reload it
    W_Reload();

    // find map name
    if (gamemode == commercial)
    {
        if (map < 10)
        {
            //doom_sprintf(lumpname, "map0%i", map);
            doom_strcpy(lumpname, "map0");
            doom_concat(lumpname, doom_itoa(map, 10));
        }
        else
        {
            //doom_sprintf(lumpname, "map%i", map);
            doom_strcpy(lumpname, "map");
            doom_concat(lumpname, doom_itoa(map, 10));
        }
    }
    else
    {
        lumpname[0] = 'E';
        lumpname[1] = '0' + episode;
        lumpname[2] = 'M';
        lumpname[3] = '0' + map;
        lumpname[4] = 0;
    }

    lumpnum = W_GetNumForName(lumpname);

    leveltime = 0;

    // note: most of this ordering is important        
    P_LoadBlockMap(lumpnum + ML_BLOCKMAP);
    P_LoadVertexes(lumpnum + ML_VERTEXES);
    P_LoadSectors(lumpnum + ML_SECTORS);
    P_LoadSideDefs(lumpnum + ML_SIDEDEFS);

    P_LoadLineDefs(lumpnum + ML_LINEDEFS);
    P_LoadSubsectors(lumpnum + ML_SSECTORS);
    P_LoadNodes(lumpnum + ML_NODES);
    P_LoadSegs(lumpnum + ML_SEGS);

    rejectmatrix = W_CacheLumpNum(lumpnum + ML_REJECT, PU_LEVEL);
    P_GroupLines();

    bodyqueslot = 0;
    deathmatch_p = deathmatchstarts;
    P_LoadThings(lumpnum + ML_THINGS);

    // if deathmatch, randomly spawn the active players
    if (deathmatch)
    {
        for (i = 0; i < MAXPLAYERS; i++)
            if (playeringame[i])
            {
                players[i].mo = 0;
                G_DeathMatchSpawnPlayer(i);
            }
    }

    // clear special respawning que
    iquehead = iquetail = 0;

    // set up world state
    P_SpawnSpecials();

    // preload graphics
    if (precache)
        R_PrecacheLevel();
}


//
// P_Init
//
void P_Init(void)
{
    P_InitSwitchList();
    P_InitPicAnims();
    R_InitSprites(sprnames);
}
fixed_t sightzstart; // eye z of looker
fixed_t topslope;
fixed_t bottomslope; // slopes to top and bottom of target

divline_t strace; // from t1 to t2
fixed_t t2x;
fixed_t t2y;

int sightcounts[2];


//
// P_DivlineSide
// Returns side 0 (front), 1 (back), or 2 (on).
//
int P_DivlineSide(fixed_t x, fixed_t y, divline_t* node)
{
    fixed_t dx;
    fixed_t dy;
    fixed_t left;
    fixed_t right;

    if (!node->dx)
    {
        if (x == node->x)
            return 2;

        if (x <= node->x)
            return node->dy > 0;

        return node->dy < 0;
    }

    if (!node->dy)
    {
        if (x == node->y)
            return 2;

        if (y <= node->y)
            return node->dx < 0;

        return node->dx > 0;
    }

    dx = (x - node->x);
    dy = (y - node->y);

    left = (node->dy >> FRACBITS) * (dx >> FRACBITS);
    right = (dy >> FRACBITS) * (node->dx >> FRACBITS);

    if (right < left)
        return 0; // front side

    if (left == right)
        return 2;
    return 1; // back side
}


//
// P_InterceptVector2
// Returns the fractional intercept point
// along the first divline.
// This is only called by the addthings and addlines traversers.
//
fixed_t P_InterceptVector2(divline_t* v2, divline_t* v1)
{
    fixed_t frac;
    fixed_t num;
    fixed_t den;

    den = FixedMul(v1->dy >> 8, v2->dx) - FixedMul(v1->dx >> 8, v2->dy);

    if (den == 0)
        return 0;

    num = FixedMul((v1->x - v2->x) >> 8, v1->dy) +
        FixedMul((v2->y - v1->y) >> 8, v1->dx);
    frac = FixedDiv(num, den);

    return frac;
}


//
// P_CrossSubsector
// Returns true
//  if strace crosses the given subsector successfully.
//
doom_boolean P_CrossSubsector(int num)
{
    seg_t* seg;
    line_t* line;
    int s1;
    int s2;
    int count;
    subsector_t* sub;
    sector_t* front;
    sector_t* back;
    fixed_t opentop;
    fixed_t openbottom;
    divline_t divl;
    vertex_t* v1;
    vertex_t* v2;
    fixed_t frac;
    fixed_t slope;

#ifdef RANGECHECK
    if (num >= numsubsectors)
    {
        //I_Error("Error: P_CrossSubsector: ss %i with numss = %i",
        //        num,
        //        numsubsectors);
        
        doom_strcpy(error_buf, "Error: P_CrossSubsector: ss ");
        doom_concat(error_buf, doom_itoa(num, 10));
        doom_concat(error_buf, " with numss = ");
        doom_concat(error_buf, doom_itoa(numsubsectors, 10));
        I_Error(error_buf);
    }
#endif

    sub = &subsectors[num];

    // check lines
    count = sub->numlines;
    seg = &segs[sub->firstline];

    for (; count; seg++, count--)
    {
        line = seg->linedef;

        // allready checked other side?
        if (line->validcount == validcount)
            continue;

        line->validcount = validcount;

        v1 = line->v1;
        v2 = line->v2;
        s1 = P_DivlineSide(v1->x, v1->y, &strace);
        s2 = P_DivlineSide(v2->x, v2->y, &strace);

        // line isn't crossed?
        if (s1 == s2)
            continue;

        divl.x = v1->x;
        divl.y = v1->y;
        divl.dx = v2->x - v1->x;
        divl.dy = v2->y - v1->y;
        s1 = P_DivlineSide(strace.x, strace.y, &divl);
        s2 = P_DivlineSide(t2x, t2y, &divl);

        // line isn't crossed?
        if (s1 == s2)
            continue;

        // stop because it is not two sided anyway
        // might do this after updating validcount?
        if (!(line->flags & ML_TWOSIDED))
            return false;

        // crosses a two sided line
        front = seg->frontsector;
        back = seg->backsector;

        // no wall to block sight with?
        if (front->floorheight == back->floorheight
            && front->ceilingheight == back->ceilingheight)
            continue;

        // possible occluder
        // because of ceiling height differences
        if (front->ceilingheight < back->ceilingheight)
            opentop = front->ceilingheight;
        else
            opentop = back->ceilingheight;

        // because of ceiling height differences
        if (front->floorheight > back->floorheight)
            openbottom = front->floorheight;
        else
            openbottom = back->floorheight;

        // quick test for totally closed doors
        if (openbottom >= opentop)
            return false; // stop

        frac = P_InterceptVector2(&strace, &divl);

        if (front->floorheight != back->floorheight)
        {
            slope = FixedDiv(openbottom - sightzstart, frac);
            if (slope > bottomslope)
                bottomslope = slope;
        }

        if (front->ceilingheight != back->ceilingheight)
        {
            slope = FixedDiv(opentop - sightzstart, frac);
            if (slope < topslope)
                topslope = slope;
        }

        if (topslope <= bottomslope)
            return false; // stop                                
    }

    // passed the subsector ok
    return true;
}


//
// P_CrossBSPNode
// Returns true
//  if strace crosses the given node successfully.
//
doom_boolean P_CrossBSPNode(int bspnum)
{
    node_t* bsp;
    int side;

    if (bspnum & NF_SUBSECTOR)
    {
        if (bspnum == -1)
            return P_CrossSubsector(0);
        else
            return P_CrossSubsector(bspnum & (~NF_SUBSECTOR));
    }

    bsp = &nodes[bspnum];

    // decide which side the start point is on
    side = P_DivlineSide(strace.x, strace.y, (divline_t*)bsp);
    if (side == 2)
        side = 0; // an "on" should cross both sides

    // cross the starting side
    if (!P_CrossBSPNode(bsp->children[side]))
        return false;

    // the partition plane is crossed here
    if (side == P_DivlineSide(t2x, t2y, (divline_t*)bsp))
    {
        // the line doesn't touch the other side
        return true;
    }

    // cross the ending side                
    return P_CrossBSPNode(bsp->children[side ^ 1]);
}


//
// P_CheckSight
// Returns true
//  if a straight line between t1 and t2 is unobstructed.
// Uses REJECT.
//
doom_boolean P_CheckSight(mobj_t* t1, mobj_t* t2)
{
    int s1;
    int s2;
    int pnum;
    int bytenum;
    int bitnum;

    // First check for trivial rejection.

    // Determine subsector entries in REJECT table.
    s1 = (int)(t1->subsector->sector - sectors);
    s2 = (int)(t2->subsector->sector - sectors);
    pnum = s1 * numsectors + s2;
    bytenum = pnum >> 3;
    bitnum = 1 << (pnum & 7);

    // Check in REJECT table.
    if (rejectmatrix[bytenum] & bitnum)
    {
        sightcounts[0]++;

        // can't possibly be connected
        return false;
    }

    // An unobstructed LOS is possible.
    // Now look from eyes of t1 to any part of t2.
    sightcounts[1]++;

    validcount++;

    sightzstart = t1->z + t1->height - (t1->height >> 2);
    topslope = (t2->z + t2->height) - sightzstart;
    bottomslope = (t2->z) - sightzstart;

    strace.x = t1->x;
    strace.y = t1->y;
    t2x = t2->x;
    t2y = t2->y;
    strace.dx = t2->x - t1->x;
    strace.dy = t2->y - t1->y;

    // the head node is the last node output
    return P_CrossBSPNode(numnodes - 1);
}
#define MAXANIMS 32
#define MAXLINEANIMS 64

// 20 adjoining sectors max! [dsl] Useless comment is useless
#define MAX_ADJOINING_SECTORS 20


//
// Animating textures and planes
// There is another anim_t used in wi_stuff, unrelated.
//
typedef struct
{
    doom_boolean istexture;
    int picnum;
    int basepic;
    int numpics;
    int speed;
} anim_t;

//
// source animation definition
//
typedef struct
{
    doom_boolean istexture; // if false, it is a flat
    char endname[9];
    char startname[9];
    int speed;
} animdef_t;


doom_boolean levelTimer;
int levelTimeCount;
short numlinespecials;
line_t* linespeciallist[MAXLINEANIMS];


extern anim_t anims[MAXANIMS];
extern anim_t* lastanim;


//
// P_InitPicAnims
//

// Floor/ceiling animation sequences,
//  defined by first and last frame,
//  i.e. the flat (64x64 tile) name to
//  be used.
// The full animation sequence is given
//  using all the flats between the start
//  and end entry, in the order found in
//  the WAD file.
//
animdef_t animdefs[] =
{
    {false,       "NUKAGE3",      "NUKAGE1",      8},
    {false,       "FWATER4",      "FWATER1",      8},
    {false,       "SWATER4",      "SWATER1",      8},
    {false,       "LAVA4",        "LAVA1",        8},
    {false,       "BLOOD3",       "BLOOD1",       8},

    // DOOM II flat animations.
    {false,       "RROCK08",      "RROCK05",      8},
    {false,       "SLIME04",      "SLIME01",      8},
    {false,       "SLIME08",      "SLIME05",      8},
    {false,       "SLIME12",      "SLIME09",      8},

    {true,        "BLODGR4",      "BLODGR1",      8},
    {true,        "SLADRIP3",     "SLADRIP1",     8},

    {true,        "BLODRIP4",     "BLODRIP1",     8},
    {true,        "FIREWALL",     "FIREWALA",     8},
    {true,        "GSTFONT3",     "GSTFONT1",     8},
    {true,        "FIRELAVA",     "FIRELAV3",     8},
    {true,        "FIREMAG3",     "FIREMAG1",     8},
    {true,        "FIREBLU2",     "FIREBLU1",     8},
    {true,        "ROCKRED3",     "ROCKRED1",     8},

    {true,        "BFALL4",       "BFALL1",       8},
    {true,        "SFALL4",       "SFALL1",       8},
    {true,        "WFALL4",       "WFALL1",       8},
    {true,        "DBRAIN4",      "DBRAIN1",      8},

    {-1}
};

anim_t anims[MAXANIMS];
anim_t* lastanim;


//
//      Animating line specials
//

extern short numlinespecials;
extern line_t* linespeciallist[MAXLINEANIMS];


void P_InitPicAnims(void)
{
    int i;

    // Init animation
    lastanim = anims;
    for (i = 0; animdefs[i].istexture != (doom_boolean)-1; i++)
    {
        if (animdefs[i].istexture)
        {
            // different episode ?
            if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_TextureNumForName(animdefs[i].endname);
            lastanim->basepic = R_TextureNumForName(animdefs[i].startname);
        }
        else
        {
            if (W_CheckNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_FlatNumForName(animdefs[i].endname);
            lastanim->basepic = R_FlatNumForName(animdefs[i].startname);
        }

        lastanim->istexture = animdefs[i].istexture;
        lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;

        if (lastanim->numpics < 2)
        {
            //I_Error("Error: P_InitPicAnims: bad cycle from %s to %s",
            //        animdefs[i].startname,
            //        animdefs[i].endname);
            
            doom_strcpy(error_buf, "Error: P_InitPicAnims: bad cycle from ");
            doom_concat(error_buf, animdefs[i].startname);
            doom_concat(error_buf, " to ");
            doom_concat(error_buf, animdefs[i].endname);
            I_Error(error_buf);
        }

        lastanim->speed = animdefs[i].speed;
        lastanim++;
    }
}


//
// UTILITIES
//

//
// getSide()
// Will return a side_t*
//  given the number of the current sector,
//  the line number, and the side (0/1) that you want.
//
side_t* getSide(int currentSector, int line, int side)
{
    return &sides[(sectors[currentSector].lines[line])->sidenum[side]];
}


//
// getSector()
// Will return a sector_t*
//  given the number of the current sector,
//  the line number and the side (0/1) that you want.
//
sector_t* getSector(int currentSector, int line, int side)
{
    return sides[(sectors[currentSector].lines[line])->sidenum[side]].sector;
}


//
// twoSided()
// Given the sector number and the line number,
//  it will tell you whether the line is two-sided or not.
//
int twoSided(int sector, int line)
{
    return (sectors[sector].lines[line])->flags & ML_TWOSIDED;
}


//
// getNextSector()
// Return sector_t * of sector next to current.
// 0 if not two-sided line
//
sector_t* getNextSector(line_t* line, sector_t* sec)
{
    if (!(line->flags & ML_TWOSIDED))
        return 0;

    if (line->frontsector == sec)
        return line->backsector;

    return line->frontsector;
}


//
// P_FindLowestFloorSurrounding()
// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
fixed_t P_FindLowestFloorSurrounding(sector_t* sec)
{
    int i;
    line_t* check;
    sector_t* other;
    fixed_t floor = sec->floorheight;

    for (i = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check, sec);

        if (!other)
            continue;

        if (other->floorheight < floor)
            floor = other->floorheight;
    }

    return floor;
}


//
// P_FindHighestFloorSurrounding()
// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
fixed_t P_FindHighestFloorSurrounding(sector_t* sec)
{
    int i;
    line_t* check;
    sector_t* other;
    fixed_t floor = -500 * FRACUNIT;

    for (i = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check, sec);

        if (!other)
            continue;

        if (other->floorheight > floor)
            floor = other->floorheight;
    }

    return floor;
}


//
// P_FindNextHighestFloor
// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS
// Note: this should be doable w/o a fixed array.
fixed_t P_FindNextHighestFloor(sector_t* sec, int currentheight)
{
    int i;
    int h;
    int min;
    line_t* check;
    sector_t* other;
    fixed_t height = currentheight;

    fixed_t heightlist[MAX_ADJOINING_SECTORS];

    for (i = 0, h = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check, sec);

        if (!other)
            continue;

        if (other->floorheight > height)
            heightlist[h++] = other->floorheight;

        // Check for overflow. Exit.
        if (h >= MAX_ADJOINING_SECTORS)
        {
            doom_print(
                    "Sector with more than 20 adjoining sectors\n");
            break;
        }
    }

    // Find lowest height in list
    if (!h)
        return currentheight;

    min = heightlist[0];

    // Range checking? 
    for (i = 1; i < h; i++)
        if (heightlist[i] < min)
            min = heightlist[i];

    return min;
}


//
// FIND LOWEST CEILING IN THE SURROUNDING SECTORS
//
fixed_t P_FindLowestCeilingSurrounding(sector_t* sec)
{
    int i;
    line_t* check;
    sector_t* other;
    fixed_t height = DOOM_MAXINT;

    for (i = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check, sec);

        if (!other)
            continue;

        if (other->ceilingheight < height)
            height = other->ceilingheight;
    }

    return height;
}


//
// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS
//
fixed_t P_FindHighestCeilingSurrounding(sector_t* sec)
{
    int i;
    line_t* check;
    sector_t* other;
    fixed_t height = 0;

    for (i = 0; i < sec->linecount; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check, sec);

        if (!other)
            continue;

        if (other->ceilingheight > height)
            height = other->ceilingheight;
    }

    return height;
}


//
// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO
//
int P_FindSectorFromLineTag(line_t* line, int start)
{
    int i;

    for (i = start + 1; i < numsectors; i++)
        if (sectors[i].tag == line->tag)
            return i;

    return -1;
}


//
// Find minimum light from an adjacent sector
//
int P_FindMinSurroundingLight(sector_t* sector, int max)
{
    int i;
    int min;
    line_t* line;
    sector_t* check;

    min = max;
    for (i = 0; i < sector->linecount; i++)
    {
        line = sector->lines[i];
        check = getNextSector(line, sector);

        if (!check)
            continue;

        if (check->lightlevel < min)
            min = check->lightlevel;
    }

    return min;
}


//
// EVENTS
// Events are operations triggered by using, crossing,
// or shooting special lines, or by timed thinkers.
//

//
// P_CrossSpecialLine - TRIGGER
// Called every time a thing origin is about
//  to cross a line with a non 0 special.
//
void P_CrossSpecialLine(int linenum, int side, mobj_t* thing)
{
    line_t* line;
    int ok;

    line = &lines[linenum];

    //        Triggers that other things can activate
    if (!thing->player)
    {
        // Things that should NOT trigger specials...
        switch (thing->type)
        {
            case MT_ROCKET:
            case MT_PLASMA:
            case MT_BFG:
            case MT_TROOPSHOT:
            case MT_HEADSHOT:
            case MT_BRUISERSHOT:
                return;
                break;

            default: break;
        }

        ok = 0;
        switch (line->special)
        {
            case 39:        // TELEPORT TRIGGER
            case 97:        // TELEPORT RETRIGGER
            case 125:        // TELEPORT MONSTERONLY TRIGGER
            case 126:        // TELEPORT MONSTERONLY RETRIGGER
            case 4:        // RAISE DOOR
            case 10:        // PLAT DOWN-WAIT-UP-STAY TRIGGER
            case 88:        // PLAT DOWN-WAIT-UP-STAY RETRIGGER
                ok = 1;
                break;
        }
        if (!ok)
            return;
    }

    // Note: could use some const's here.
    switch (line->special)
    {
        // TRIGGERS.
        // All from here to RETRIGGERS.
        case 2:
            // Open Door
            EV_DoDoor(line, door_open);
            line->special = 0;
            break;

        case 3:
            // Close Door
            EV_DoDoor(line, door_close);
            line->special = 0;
            break;

        case 4:
            // Raise Door
            EV_DoDoor(line, door_normal);
            line->special = 0;
            break;

        case 5:
            // Raise Floor
            EV_DoFloor(line, raiseFloor);
            line->special = 0;
            break;

        case 6:
            // Fast Ceiling Crush & Raise
            EV_DoCeiling(line, fastCrushAndRaise);
            line->special = 0;
            break;

        case 8:
            // Build Stairs
            EV_BuildStairs(line, build8);
            line->special = 0;
            break;

        case 10:
            // PlatDownWaitUp
            EV_DoPlat(line, downWaitUpStay, 0);
            line->special = 0;
            break;

        case 12:
            // Light Turn On - brightest near
            EV_LightTurnOn(line, 0);
            line->special = 0;
            break;

        case 13:
            // Light Turn On 255
            EV_LightTurnOn(line, 255);
            line->special = 0;
            break;

        case 16:
            // Close Door 30
            EV_DoDoor(line, close30ThenOpen);
            line->special = 0;
            break;

        case 17:
            // Start Light Strobing
            EV_StartLightStrobing(line);
            line->special = 0;
            break;

        case 19:
            // Lower Floor
            EV_DoFloor(line, lowerFloor);
            line->special = 0;
            break;

        case 22:
            // Raise floor to nearest height and change texture
            EV_DoPlat(line, raiseToNearestAndChange, 0);
            line->special = 0;
            break;

        case 25:
            // Ceiling Crush and Raise
            EV_DoCeiling(line, crushAndRaise);
            line->special = 0;
            break;

        case 30:
            // Raise floor to shortest texture height
            //  on either side of lines.
            EV_DoFloor(line, raiseToTexture);
            line->special = 0;
            break;

        case 35:
            // Lights Very Dark
            EV_LightTurnOn(line, 35);
            line->special = 0;
            break;

        case 36:
            // Lower Floor (TURBO)
            EV_DoFloor(line, turboLower);
            line->special = 0;
            break;

        case 37:
            // LowerAndChange
            EV_DoFloor(line, lowerAndChange);
            line->special = 0;
            break;

        case 38:
            // Lower Floor To Lowest
            EV_DoFloor(line, lowerFloorToLowest);
            line->special = 0;
            break;

        case 39:
            // TELEPORT!
            EV_Teleport(line, side, thing);
            line->special = 0;
            break;

        case 40:
            // RaiseCeilingLowerFloor
            EV_DoCeiling(line, raiseToHighest);
            EV_DoFloor(line, lowerFloorToLowest);
            line->special = 0;
            break;

        case 44:
            // Ceiling Crush
            EV_DoCeiling(line, lowerAndCrush);
            line->special = 0;
            break;

        case 52:
            // EXIT!
            G_ExitLevel();
            break;

        case 53:
            // Perpetual Platform Raise
            EV_DoPlat(line, perpetualRaise, 0);
            line->special = 0;
            break;

        case 54:
            // Platform Stop
            EV_StopPlat(line);
            line->special = 0;
            break;

        case 56:
            // Raise Floor Crush
            EV_DoFloor(line, raiseFloorCrush);
            line->special = 0;
            break;

        case 57:
            // Ceiling Crush Stop
            EV_CeilingCrushStop(line);
            line->special = 0;
            break;

        case 58:
            // Raise Floor 24
            EV_DoFloor(line, raiseFloor24);
            line->special = 0;
            break;

        case 59:
            // Raise Floor 24 And Change
            EV_DoFloor(line, raiseFloor24AndChange);
            line->special = 0;
            break;

        case 104:
            // Turn lights off in sector(tag)
            EV_TurnTagLightsOff(line);
            line->special = 0;
            break;

        case 108:
            // Blazing Door Raise (faster than TURBO!)
            EV_DoDoor(line, blazeRaise);
            line->special = 0;
            break;

        case 109:
            // Blazing Door Open (faster than TURBO!)
            EV_DoDoor(line, blazeOpen);
            line->special = 0;
            break;

        case 100:
            // Build Stairs Turbo 16
            EV_BuildStairs(line, turbo16);
            line->special = 0;
            break;

        case 110:
            // Blazing Door Close (faster than TURBO!)
            EV_DoDoor(line, blazeClose);
            line->special = 0;
            break;

        case 119:
            // Raise floor to nearest surr. floor
            EV_DoFloor(line, raiseFloorToNearest);
            line->special = 0;
            break;

        case 121:
            // Blazing PlatDownWaitUpStay
            EV_DoPlat(line, blazeDWUS, 0);
            line->special = 0;
            break;

        case 124:
            // Secret EXIT
            G_SecretExitLevel();
            break;

        case 125:
            // TELEPORT MonsterONLY
            if (!thing->player)
            {
                EV_Teleport(line, side, thing);
                line->special = 0;
            }
            break;

        case 130:
            // Raise Floor Turbo
            EV_DoFloor(line, raiseFloorTurbo);
            line->special = 0;
            break;

        case 141:
            // Silent Ceiling Crush & Raise
            EV_DoCeiling(line, silentCrushAndRaise);
            line->special = 0;
            break;

            // RETRIGGERS.  All from here till end.
        case 72:
            // Ceiling Crush
            EV_DoCeiling(line, lowerAndCrush);
            break;

        case 73:
            // Ceiling Crush and Raise
            EV_DoCeiling(line, crushAndRaise);
            break;

        case 74:
            // Ceiling Crush Stop
            EV_CeilingCrushStop(line);
            break;

        case 75:
            // Close Door
            EV_DoDoor(line, door_close);
            break;

        case 76:
            // Close Door 30
            EV_DoDoor(line, close30ThenOpen);
            break;

        case 77:
            // Fast Ceiling Crush & Raise
            EV_DoCeiling(line, fastCrushAndRaise);
            break;

        case 79:
            // Lights Very Dark
            EV_LightTurnOn(line, 35);
            break;

        case 80:
            // Light Turn On - brightest near
            EV_LightTurnOn(line, 0);
            break;

        case 81:
            // Light Turn On 255
            EV_LightTurnOn(line, 255);
            break;

        case 82:
            // Lower Floor To Lowest
            EV_DoFloor(line, lowerFloorToLowest);
            break;

        case 83:
            // Lower Floor
            EV_DoFloor(line, lowerFloor);
            break;

        case 84:
            // LowerAndChange
            EV_DoFloor(line, lowerAndChange);
            break;

        case 86:
            // Open Door
            EV_DoDoor(line, door_open);
            break;

        case 87:
            // Perpetual Platform Raise
            EV_DoPlat(line, perpetualRaise, 0);
            break;

        case 88:
            // PlatDownWaitUp
            EV_DoPlat(line, downWaitUpStay, 0);
            break;

        case 89:
            // Platform Stop
            EV_StopPlat(line);
            break;

        case 90:
            // Raise Door
            EV_DoDoor(line, door_normal);
            break;

        case 91:
            // Raise Floor
            EV_DoFloor(line, raiseFloor);
            break;

        case 92:
            // Raise Floor 24
            EV_DoFloor(line, raiseFloor24);
            break;

        case 93:
            // Raise Floor 24 And Change
            EV_DoFloor(line, raiseFloor24AndChange);
            break;

        case 94:
            // Raise Floor Crush
            EV_DoFloor(line, raiseFloorCrush);
            break;

        case 95:
            // Raise floor to nearest height
            // and change texture.
            EV_DoPlat(line, raiseToNearestAndChange, 0);
            break;

        case 96:
            // Raise floor to shortest texture height
            // on either side of lines.
            EV_DoFloor(line, raiseToTexture);
            break;

        case 97:
            // TELEPORT!
            EV_Teleport(line, side, thing);
            break;

        case 98:
            // Lower Floor (TURBO)
            EV_DoFloor(line, turboLower);
            break;

        case 105:
            // Blazing Door Raise (faster than TURBO!)
            EV_DoDoor(line, blazeRaise);
            break;

        case 106:
            // Blazing Door Open (faster than TURBO!)
            EV_DoDoor(line, blazeOpen);
            break;

        case 107:
            // Blazing Door Close (faster than TURBO!)
            EV_DoDoor(line, blazeClose);
            break;

        case 120:
            // Blazing PlatDownWaitUpStay.
            EV_DoPlat(line, blazeDWUS, 0);
            break;

        case 126:
            // TELEPORT MonsterONLY.
            if (!thing->player)
                EV_Teleport(line, side, thing);
            break;

        case 128:
            // Raise To Nearest Floor
            EV_DoFloor(line, raiseFloorToNearest);
            break;

        case 129:
            // Raise Floor Turbo
            EV_DoFloor(line, raiseFloorTurbo);
            break;
    }
}


//
// P_ShootSpecialLine - IMPACT SPECIALS
// Called when a thing shoots a special line.
//
void P_ShootSpecialLine(mobj_t* thing, line_t* line)
{
    int ok;

    // Impacts that other things can activate.
    if (!thing->player)
    {
        ok = 0;
        switch (line->special)
        {
            case 46:
                // OPEN DOOR IMPACT
                ok = 1;
                break;
        }
        if (!ok)
            return;
    }

    switch (line->special)
    {
        case 24:
            // RAISE FLOOR
            EV_DoFloor(line, raiseFloor);
            P_ChangeSwitchTexture(line, 0);
            break;

        case 46:
            // OPEN DOOR
            EV_DoDoor(line, door_open);
            P_ChangeSwitchTexture(line, 1);
            break;

        case 47:
            // RAISE FLOOR NEAR AND CHANGE
            EV_DoPlat(line, raiseToNearestAndChange, 0);
            P_ChangeSwitchTexture(line, 0);
            break;
    }
}


//
// P_PlayerInSpecialSector
// Called every tic frame
//  that the player origin is in a special sector
//
void P_PlayerInSpecialSector(player_t* player)
{
    sector_t* sector;

    sector = player->mo->subsector->sector;

    // Falling, not all the way down yet?
    if (player->mo->z != sector->floorheight)
        return;

    // Has hitten ground.
    switch (sector->special)
    {
        case 5:
            // HELLSLIME DAMAGE
            if (!player->powers[pw_ironfeet])
                if (!(leveltime & 0x1f))
                    P_DamageMobj(player->mo, 0, 0, 10);
            break;

        case 7:
            // NUKAGE DAMAGE
            if (!player->powers[pw_ironfeet])
                if (!(leveltime & 0x1f))
                    P_DamageMobj(player->mo, 0, 0, 5);
            break;

        case 16:
            // SUPER HELLSLIME DAMAGE
        case 4:
            // STROBE HURT
            if (!player->powers[pw_ironfeet]
                || (P_Random() < 5))
            {
                if (!(leveltime & 0x1f))
                    P_DamageMobj(player->mo, 0, 0, 20);
            }
            break;

        case 9:
            // SECRET SECTOR
            player->secretcount++;
            player->message = "A secret is revealed!";
            S_StartSound(0, sfx_getpow);
            sector->special = 0;
            break;

        case 11:
            // EXIT SUPER DAMAGE! (for E1M8 finale)
            player->cheats &= ~CF_GODMODE;

            if (!(leveltime & 0x1f))
                P_DamageMobj(player->mo, 0, 0, 20);

            if (player->health <= 10)
                G_ExitLevel();
            break;

        default:
        {
            //I_Error("Error: P_PlayerInSpecialSector: "
            //        "unknown special %i",
            //        sector->special);
            
            
            doom_strcpy(error_buf, "Error: P_PlayerInSpecialSector: unknown special ");
            doom_concat(error_buf, doom_itoa(sector->special, 10));
            I_Error(error_buf);
            break;
        }
    };
}


//
// P_UpdateSpecials
// Animate planes, scroll walls, etc.
//
void P_UpdateSpecials(void)
{
    anim_t* anim;
    int pic;
    int i;
    line_t* line;

    // LEVEL TIMER
    if (levelTimer == true)
    {
        levelTimeCount--;
        if (!levelTimeCount)
            G_ExitLevel();
    }

    // ANIMATE FLATS AND TEXTURES GLOBALLY
    for (anim = anims; anim < lastanim; anim++)
    {
        for (i = anim->basepic; i < anim->basepic + anim->numpics; i++)
        {
            pic = anim->basepic + ((leveltime / anim->speed + i) % anim->numpics);
            if (anim->istexture)
                texturetranslation[i] = pic;
            else
                flattranslation[i] = pic;
        }
    }

    // ANIMATE LINE SPECIALS
    for (i = 0; i < numlinespecials; i++)
    {
        line = linespeciallist[i];
        switch (line->special)
        {
            case 48:
                // EFFECT FIRSTCOL SCROLL +
                sides[line->sidenum[0]].textureoffset += FRACUNIT;
                break;
        }
    }

    // DO BUTTONS
    for (i = 0; i < MAXBUTTONS; i++)
        if (buttonlist[i].btimer)
        {
            buttonlist[i].btimer--;
            if (!buttonlist[i].btimer)
            {
                switch (buttonlist[i].where)
                {
                    case top:
                        sides[buttonlist[i].line->sidenum[0]].toptexture =
                            buttonlist[i].btexture;
                        break;

                    case middle:
                        sides[buttonlist[i].line->sidenum[0]].midtexture =
                            buttonlist[i].btexture;
                        break;

                    case bottom:
                        sides[buttonlist[i].line->sidenum[0]].bottomtexture =
                            buttonlist[i].btexture;
                        break;
                }
                S_StartSound((mobj_t*)&buttonlist[i].soundorg, sfx_swtchn);
                doom_memset(&buttonlist[i], 0, sizeof(button_t));
            }
        }
}


//
// Special Stuff that can not be categorized
//
int EV_DoDonut(line_t* line)
{
    sector_t* s1;
    sector_t* s2;
    sector_t* s3;
    int secnum;
    int rtn;
    int i;
    floormove_t* floor;

    secnum = -1;
    rtn = 0;
    while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
    {
        s1 = &sectors[secnum];

        // ALREADY MOVING?  IF SO, KEEP GOING...
        if (s1->specialdata)
            continue;

        rtn = 1;
        s2 = getNextSector(s1->lines[0], s1);
        for (i = 0; i < s2->linecount; i++)
        {
            if ((!(s2->lines[i]->flags & ML_TWOSIDED)) ||
                (s2->lines[i]->backsector == s1))
                continue;
            s3 = s2->lines[i]->backsector;

            //        Spawn rising slime
            floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker(&floor->thinker);
            s2->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
            floor->type = donutRaise;
            floor->crush = false;
            floor->direction = 1;
            floor->sector = s2;
            floor->speed = FLOORSPEED / 2;
            floor->texture = s3->floorpic;
            floor->newspecial = 0;
            floor->floordestheight = s3->floorheight;

            //        Spawn lowering donut-hole
            floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker(&floor->thinker);
            s1->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor;
            floor->type = lowerFloor;
            floor->crush = false;
            floor->direction = -1;
            floor->sector = s1;
            floor->speed = FLOORSPEED / 2;
            floor->floordestheight = s3->floorheight;
            break;
        }
    }

    return rtn;
}


//
// SPECIAL SPAWNING
//

//
// P_SpawnSpecials
// After the map has been loaded, scan for specials
//  that spawn thinkers
//

// Parses command line parameters.
void P_SpawnSpecials(void)
{
    sector_t* sector;
    int i;
    int episode;

    episode = 1;
    if (W_CheckNumForName("texture2") >= 0)
        episode = 2;


    // See if -TIMER needs to be used.
    levelTimer = false;

    i = M_CheckParm("-avg");
    if (i && deathmatch)
    {
        levelTimer = true;
        levelTimeCount = 20 * 60 * 35;
    }

    i = M_CheckParm("-timer");
    if (i && deathmatch)
    {
        int time;
        time = doom_atoi(myargv[i + 1]) * 60 * 35;
        levelTimer = true;
        levelTimeCount = time;
    }

    //        Init special SECTORs.
    sector = sectors;
    for (i = 0; i < numsectors; i++, sector++)
    {
        if (!sector->special)
            continue;

        switch (sector->special)
        {
            case 1:
                // FLICKERING LIGHTS
                P_SpawnLightFlash(sector);
                break;

            case 2:
                // STROBE FAST
                P_SpawnStrobeFlash(sector, FASTDARK, 0);
                break;

            case 3:
                // STROBE SLOW
                P_SpawnStrobeFlash(sector, SLOWDARK, 0);
                break;

            case 4:
                // STROBE FAST/DEATH SLIME
                P_SpawnStrobeFlash(sector, FASTDARK, 0);
                sector->special = 4;
                break;

            case 8:
                // GLOWING LIGHT
                P_SpawnGlowingLight(sector);
                break;
            case 9:
                // SECRET SECTOR
                totalsecret++;
                break;

            case 10:
                // DOOR CLOSE IN 30 SECONDS
                P_SpawnDoorCloseIn30(sector);
                break;

            case 12:
                // SYNC STROBE SLOW
                P_SpawnStrobeFlash(sector, SLOWDARK, 1);
                break;

            case 13:
                // SYNC STROBE FAST
                P_SpawnStrobeFlash(sector, FASTDARK, 1);
                break;

            case 14:
                // DOOR RAISE IN 5 MINUTES
                P_SpawnDoorRaiseIn5Mins(sector, i);
                break;

            case 17:
                P_SpawnFireFlicker(sector);
                break;
        }
    }


    // Init line EFFECTs
    numlinespecials = 0;
    for (i = 0; i < numlines; i++)
    {
        switch (lines[i].special)
        {
            case 48:
                // EFFECT FIRSTCOL SCROLL+
                linespeciallist[numlinespecials] = &lines[i];
                numlinespecials++;
                break;
        }
    }


    // Init other misc stuff
    for (i = 0; i < MAXCEILINGS; i++)
        activeceilings[i] = 0;

    for (i = 0; i < MAXPLATS; i++)
        activeplats[i] = 0;

    for (i = 0; i < MAXBUTTONS; i++)
        doom_memset(&buttonlist[i], 0, sizeof(button_t));
}
switchlist_t alphSwitchList[] =
{
    // Doom shareware episode 1 switches
    {"SW1BRCOM",    "SW2BRCOM", 1},
    {"SW1BRN1",     "SW2BRN1",  1},
    {"SW1BRN2",     "SW2BRN2",  1},
    {"SW1BRNGN",    "SW2BRNGN", 1},
    {"SW1BROWN",    "SW2BROWN", 1},
    {"SW1COMM",     "SW2COMM",  1},
    {"SW1COMP",     "SW2COMP",  1},
    {"SW1DIRT",     "SW2DIRT",  1},
    {"SW1EXIT",     "SW2EXIT",  1},
    {"SW1GRAY",     "SW2GRAY",  1},
    {"SW1GRAY1",    "SW2GRAY1", 1},
    {"SW1METAL",    "SW2METAL", 1},
    {"SW1PIPE",     "SW2PIPE",  1},
    {"SW1SLAD",     "SW2SLAD",  1},
    {"SW1STARG",    "SW2STARG", 1},
    {"SW1STON1",    "SW2STON1", 1},
    {"SW1STON2",    "SW2STON2", 1},
    {"SW1STONE",    "SW2STONE", 1},
    {"SW1STRTN",    "SW2STRTN", 1},

    // Doom registered episodes 2&3 switches
    {"SW1BLUE",     "SW2BLUE",  2},
    {"SW1CMT",      "SW2CMT",   2},
    {"SW1GARG",     "SW2GARG",  2},
    {"SW1GSTON",    "SW2GSTON", 2},
    {"SW1HOT",      "SW2HOT",   2},
    {"SW1LION",     "SW2LION",  2},
    {"SW1SATYR",    "SW2SATYR", 2},
    {"SW1SKIN",     "SW2SKIN",  2},
    {"SW1VINE",     "SW2VINE",  2},
    {"SW1WOOD",     "SW2WOOD",  2},

    // Doom II switches
    {"SW1PANEL",    "SW2PANEL", 3},
    {"SW1ROCK",     "SW2ROCK",  3},
    {"SW1MET2",     "SW2MET2",  3},
    {"SW1WDMET",    "SW2WDMET", 3},
    {"SW1BRIK",     "SW2BRIK",  3},
    {"SW1MOD1",     "SW2MOD1",  3},
    {"SW1ZIM",      "SW2ZIM",   3},
    {"SW1STON6",    "SW2STON6", 3},
    {"SW1TEK",      "SW2TEK",   3},
    {"SW1MARB",     "SW2MARB",  3},
    {"SW1SKULL",    "SW2SKULL", 3},

    {"\0",          "\0",       0}
};

int switchlist[MAXSWITCHES * 2];
int numswitches;
button_t buttonlist[MAXBUTTONS];


//
// P_InitSwitchList
// Only called at game initialization.
//
void P_InitSwitchList(void)
{
    int i;
    int index;
    int episode;

    episode = 1;

    if (gamemode == registered)
        episode = 2;
    else
        if (gamemode == commercial)
            episode = 3;

    for (index = 0, i = 0; i < MAXSWITCHES; i++)
    {
        if (!alphSwitchList[i].episode)
        {
            numswitches = index / 2;
            switchlist[index] = -1;
            break;
        }

        if (alphSwitchList[i].episode <= episode)
        {
            switchlist[index++] = R_TextureNumForName(alphSwitchList[i].name1);
            switchlist[index++] = R_TextureNumForName(alphSwitchList[i].name2);
        }
    }
}


//
// Start a button counting down till it turns off.
//
void P_StartButton(line_t* line, bwhere_e w, int texture, int time)
{
    int i;

    // See if button is already pressed
    for (i = 0; i < MAXBUTTONS; i++)
    {
        if (buttonlist[i].btimer
            && buttonlist[i].line == line)
        {

            return;
        }
    }

    for (i = 0; i < MAXBUTTONS; i++)
    {
        if (!buttonlist[i].btimer)
        {
            buttonlist[i].line = line;
            buttonlist[i].where = w;
            buttonlist[i].btexture = texture;
            buttonlist[i].btimer = time;
            buttonlist[i].soundorg = (mobj_t*)&line->frontsector->soundorg;
            return;
        }
    }

    I_Error("Error: P_StartButton: no button slots left!");
}


//
// Function that changes wall texture.
// Tell it if switch is ok to use again (1=yes, it's a button).
//
void P_ChangeSwitchTexture(line_t* line, int useAgain)
{
    int     texTop;
    int     texMid;
    int     texBot;
    int     i;
    int     sound;

    if (!useAgain)
        line->special = 0;

    texTop = sides[line->sidenum[0]].toptexture;
    texMid = sides[line->sidenum[0]].midtexture;
    texBot = sides[line->sidenum[0]].bottomtexture;

    sound = sfx_swtchn;

    // EXIT SWITCH?
    if (line->special == 11)
        sound = sfx_swtchx;

    for (i = 0; i < numswitches * 2; i++)
    {
        if (switchlist[i] == texTop)
        {
            S_StartSound(buttonlist->soundorg, sound);
            sides[line->sidenum[0]].toptexture = switchlist[i ^ 1];

            if (useAgain)
                P_StartButton(line, top, switchlist[i], BUTTONTIME);

            return;
        }
        else
        {
            if (switchlist[i] == texMid)
            {
                S_StartSound(buttonlist->soundorg, sound);
                sides[line->sidenum[0]].midtexture = switchlist[i ^ 1];

                if (useAgain)
                    P_StartButton(line, middle, switchlist[i], BUTTONTIME);

                return;
            }
            else
            {
                if (switchlist[i] == texBot)
                {
                    S_StartSound(buttonlist->soundorg, sound);
                    sides[line->sidenum[0]].bottomtexture = switchlist[i ^ 1];

                    if (useAgain)
                        P_StartButton(line, bottom, switchlist[i], BUTTONTIME);

                    return;
                }
            }
        }
    }
}


//
// P_UseSpecialLine
// Called when a thing uses a special line.
// Only the front sides of lines are usable.
//
doom_boolean P_UseSpecialLine(mobj_t* thing, line_t* line, int side)
{
    // Err...
    // Use the back sides of VERY SPECIAL lines...
    if (side)
    {
        switch (line->special)
        {
            case 124:
                // Sliding door open&close
                // UNUSED?
                break;

            default:
                return false;
                break;
        }
    }


    // Switches that other things can activate.
    if (!thing->player)
    {
        // never open secret doors
        if (line->flags & ML_SECRET)
            return false;

        switch (line->special)
        {
            case 1:         // MANUAL DOOR RAISE
            case 32:        // MANUAL BLUE
            case 33:        // MANUAL RED
            case 34:        // MANUAL YELLOW
                break;

            default:
                return false;
                break;
        }
    }


    // do something  
    switch (line->special)
    {
        // MANUALS
        case 1:                // Vertical Door
        case 26:                // Blue Door/Locked
        case 27:                // Yellow Door /Locked
        case 28:                // Red Door /Locked

        case 31:                // Manual door open
        case 32:                // Blue locked door open
        case 33:                // Red locked door open
        case 34:                // Yellow locked door open

        case 117:                // Blazing door raise
        case 118:                // Blazing door open
            EV_VerticalDoor(line, thing);
            break;

            //UNUSED - Door Slide Open&Close
            // case 124:
            // EV_SlidingDoor (line, thing);
            // break;

            // SWITCHES
        case 7:
            // Build Stairs
            if (EV_BuildStairs(line, build8))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 9:
            // Change Donut
            if (EV_DoDonut(line))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 11:
            // Exit level
            P_ChangeSwitchTexture(line, 0);
            G_ExitLevel();
            break;

        case 14:
            // Raise Floor 32 and change texture
            if (EV_DoPlat(line, raiseAndChange, 32))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 15:
            // Raise Floor 24 and change texture
            if (EV_DoPlat(line, raiseAndChange, 24))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 18:
            // Raise Floor to next highest floor
            if (EV_DoFloor(line, raiseFloorToNearest))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 20:
            // Raise Plat next highest floor and change texture
            if (EV_DoPlat(line, raiseToNearestAndChange, 0))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 21:
            // PlatDownWaitUpStay
            if (EV_DoPlat(line, downWaitUpStay, 0))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 23:
            // Lower Floor to Lowest
            if (EV_DoFloor(line, lowerFloorToLowest))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 29:
            // Raise Door
            if (EV_DoDoor(line, door_normal))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 41:
            // Lower Ceiling to Floor
            if (EV_DoCeiling(line, lowerToFloor))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 71:
            // Turbo Lower Floor
            if (EV_DoFloor(line, turboLower))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 49:
            // Ceiling Crush And Raise
            if (EV_DoCeiling(line, crushAndRaise))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 50:
            // Close Door
            if (EV_DoDoor(line, door_close))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 51:
            // Secret EXIT
            P_ChangeSwitchTexture(line, 0);
            G_SecretExitLevel();
            break;

        case 55:
            // Raise Floor Crush
            if (EV_DoFloor(line, raiseFloorCrush))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 101:
            // Raise Floor
            if (EV_DoFloor(line, raiseFloor))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 102:
            // Lower Floor to Surrounding floor height
            if (EV_DoFloor(line, lowerFloor))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 103:
            // Open Door
            if (EV_DoDoor(line, door_open))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 111:
            // Blazing Door Raise (faster than TURBO!)
            if (EV_DoDoor(line, blazeRaise))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 112:
            // Blazing Door Open (faster than TURBO!)
            if (EV_DoDoor(line, blazeOpen))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 113:
            // Blazing Door Close (faster than TURBO!)
            if (EV_DoDoor(line, blazeClose))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 122:
            // Blazing PlatDownWaitUpStay
            if (EV_DoPlat(line, blazeDWUS, 0))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 127:
            // Build Stairs Turbo 16
            if (EV_BuildStairs(line, turbo16))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 131:
            // Raise Floor Turbo
            if (EV_DoFloor(line, raiseFloorTurbo))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 133:
            // BlzOpenDoor BLUE
        case 135:
            // BlzOpenDoor RED
        case 137:
            // BlzOpenDoor YELLOW
            if (EV_DoLockedDoor(line, blazeOpen, thing))
                P_ChangeSwitchTexture(line, 0);
            break;

        case 140:
            // Raise Floor 512
            if (EV_DoFloor(line, raiseFloor512))
                P_ChangeSwitchTexture(line, 0);
            break;

            // BUTTONS
        case 42:
            // Close Door
            if (EV_DoDoor(line, door_close))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 43:
            // Lower Ceiling to Floor
            if (EV_DoCeiling(line, lowerToFloor))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 45:
            // Lower Floor to Surrounding floor height
            if (EV_DoFloor(line, lowerFloor))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 60:
            // Lower Floor to Lowest
            if (EV_DoFloor(line, lowerFloorToLowest))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 61:
            // Open Door
            if (EV_DoDoor(line, door_open))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 62:
            // PlatDownWaitUpStay
            if (EV_DoPlat(line, downWaitUpStay, 1))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 63:
            // Raise Door
            if (EV_DoDoor(line, door_normal))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 64:
            // Raise Floor to ceiling
            if (EV_DoFloor(line, raiseFloor))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 66:
            // Raise Floor 24 and change texture
            if (EV_DoPlat(line, raiseAndChange, 24))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 67:
            // Raise Floor 32 and change texture
            if (EV_DoPlat(line, raiseAndChange, 32))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 65:
            // Raise Floor Crush
            if (EV_DoFloor(line, raiseFloorCrush))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 68:
            // Raise Plat to next highest floor and change texture
            if (EV_DoPlat(line, raiseToNearestAndChange, 0))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 69:
            // Raise Floor to next highest floor
            if (EV_DoFloor(line, raiseFloorToNearest))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 70:
            // Turbo Lower Floor
            if (EV_DoFloor(line, turboLower))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 114:
            // Blazing Door Raise (faster than TURBO!)
            if (EV_DoDoor(line, blazeRaise))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 115:
            // Blazing Door Open (faster than TURBO!)
            if (EV_DoDoor(line, blazeOpen))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 116:
            // Blazing Door Close (faster than TURBO!)
            if (EV_DoDoor(line, blazeClose))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 123:
            // Blazing PlatDownWaitUpStay
            if (EV_DoPlat(line, blazeDWUS, 0))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 132:
            // Raise Floor Turbo
            if (EV_DoFloor(line, raiseFloorTurbo))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 99:
            // BlzOpenDoor BLUE
        case 134:
            // BlzOpenDoor RED
        case 136:
            // BlzOpenDoor YELLOW
            if (EV_DoLockedDoor(line, blazeOpen, thing))
                P_ChangeSwitchTexture(line, 1);
            break;

        case 138:
            // Light Turn On
            EV_LightTurnOn(line, 255);
            P_ChangeSwitchTexture(line, 1);
            break;

        case 139:
            // Light Turn Off
            EV_LightTurnOn(line, 35);
            P_ChangeSwitchTexture(line, 1);
            break;
    }

    return true;
}
int EV_Teleport(line_t* line, int side, mobj_t* thing)
{
    int i;
    int tag;
    mobj_t* m;
    mobj_t* fog;
    unsigned an;
    thinker_t* thinker;
    sector_t* sector;
    fixed_t oldx;
    fixed_t oldy;
    fixed_t oldz;

    // don't teleport missiles
    if (thing->flags & MF_MISSILE)
        return 0;

    // Don't teleport if hit back of line,
    //  so you can get out of teleporter.
    if (side == 1)
        return 0;

    tag = line->tag;
    for (i = 0; i < numsectors; i++)
    {
        if (sectors[i].tag == tag)
        {
            thinker = thinkercap.next;
            for (thinker = thinkercap.next;
                 thinker != &thinkercap;
                 thinker = thinker->next)
            {
                // not a mobj
                if (thinker->function.acp1 != (actionf_p1)P_MobjThinker)
                    continue;

                m = (mobj_t*)thinker;

                // not a teleportman
                if (m->type != MT_TELEPORTMAN)
                    continue;

                sector = m->subsector->sector;
                // wrong sector
                if (sector - sectors != i)
                    continue;

                oldx = thing->x;
                oldy = thing->y;
                oldz = thing->z;

                if (!P_TeleportMove(thing, m->x, m->y))
                    return 0;

                thing->z = thing->floorz;  //fixme: not needed?
                if (thing->player)
                    thing->player->viewz = thing->z + thing->player->viewheight;

                // spawn teleport fog at source and destination
                fog = P_SpawnMobj(oldx, oldy, oldz, MT_TFOG);
                S_StartSound(fog, sfx_telept);
                an = m->angle >> ANGLETOFINESHIFT;
                fog = P_SpawnMobj(m->x + 20 * finecosine[an], m->y + 20 * finesine[an]
                                  , thing->z, MT_TFOG);

                // emit sound, where?
                S_StartSound(fog, sfx_telept);

                // don't move for a bit
                if (thing->player)
                    thing->reactiontime = 18;

                thing->angle = m->angle;
                thing->momx = thing->momy = thing->momz = 0;
                return 1;
            }
        }
    }

    return 0;
}
int leveltime;

//
// THINKERS
// All thinkers should be allocated by Z_Malloc
// so they can be operated on uniformly.
// The actual structures will vary in size,
// but the first element must be thinker_t.
//

// Both the head and tail of the thinker list.
thinker_t thinkercap;


//
// P_InitThinkers
//
void P_InitThinkers(void)
{
    thinkercap.prev = thinkercap.next = &thinkercap;
}


//
// P_AddThinker
// Adds a new thinker at the end of the list.
//
void P_AddThinker(thinker_t* thinker)
{
    thinkercap.prev->next = thinker;
    thinker->next = &thinkercap;
    thinker->prev = thinkercap.prev;
    thinkercap.prev = thinker;
}


//
// P_RemoveThinker
// Deallocation is lazy -- it will not actually be freed
// until its thinking turn comes up.
//
void P_RemoveThinker(thinker_t* thinker)
{
    // FIXME: NOP.
    thinker->function.acv = (actionf_v)(-1);
}


//
// P_RunThinkers
//
void P_RunThinkers(void)
{
    thinker_t* currentthinker;

    currentthinker = thinkercap.next;
    while (currentthinker != &thinkercap)
    {
        if (currentthinker->function.acv == (actionf_v)(-1))
        {
            // time to remove it
            currentthinker->next->prev = currentthinker->prev;
            currentthinker->prev->next = currentthinker->next;
            Z_Free(currentthinker);
        }
        else
        {
            if (currentthinker->function.acp1)
                currentthinker->function.acp1(currentthinker);
        }
        currentthinker = currentthinker->next;
    }
}


//
// P_Ticker
//
void P_Ticker(void)
{
    int i;

    // run the tic
    if (paused)
        return;

    // pause if in menu and at least one tic has been run
    if (!netgame
        && menuactive
        && !demoplayback
        && players[consoleplayer].viewz != 1)
    {
        return;
    }

    for (i = 0; i < MAXPLAYERS; i++)
        if (playeringame[i])
            P_PlayerThink(&players[i]);

    P_RunThinkers();
    P_UpdateSpecials();
    P_RespawnSpecials();

    // for par times
    leveltime++;
}
#define INVERSECOLORMAP 32

// 16 pixels of bob
#define MAXBOB 0x100000        

#define ANG5 (ANG90/18)


//
// Movement.
//

doom_boolean onground;


//
// P_Thrust
// Moves the given origin along a given angle.
//
void P_Thrust(player_t* player, angle_t angle, fixed_t move)
{
    angle >>= ANGLETOFINESHIFT;

    player->mo->momx += FixedMul(move, finecosine[angle]);
    player->mo->momy += FixedMul(move, finesine[angle]);
}


//
// P_CalcHeight
// Calculate the walking / running height adjustment
//
void P_CalcHeight(player_t* player)
{
    int angle;
    fixed_t bob;

    // Regular movement bobbing
    // (needs to be calculated for gun swing
    // even if not on ground)
    // OPTIMIZE: tablify angle
    // Note: a LUT allows for effects
    //  like a ramp with low health.
    player->bob =
        FixedMul(player->mo->momx, player->mo->momx)
        + FixedMul(player->mo->momy, player->mo->momy);

    player->bob >>= 2;

    if (player->bob > MAXBOB)
        player->bob = MAXBOB;

    if ((player->cheats & CF_NOMOMENTUM) || !onground)
    {
        player->viewz = player->mo->z + VIEWHEIGHT;

        if (player->viewz > player->mo->ceilingz - 4 * FRACUNIT)
            player->viewz = player->mo->ceilingz - 4 * FRACUNIT;

        player->viewz = player->mo->z + player->viewheight;
        return;
    }

    angle = (FINEANGLES / 20 * leveltime) & FINEMASK;
    bob = FixedMul(player->bob / 2, finesine[angle]);


    // move viewheight
    if (player->playerstate == PST_LIVE)
    {
        player->viewheight += player->deltaviewheight;

        if (player->viewheight > VIEWHEIGHT)
        {
            player->viewheight = VIEWHEIGHT;
            player->deltaviewheight = 0;
        }

        if (player->viewheight < VIEWHEIGHT / 2)
        {
            player->viewheight = VIEWHEIGHT / 2;
            if (player->deltaviewheight <= 0)
                player->deltaviewheight = 1;
        }

        if (player->deltaviewheight)
        {
            player->deltaviewheight += FRACUNIT / 4;
            if (!player->deltaviewheight)
                player->deltaviewheight = 1;
        }
    }
    player->viewz = player->mo->z + player->viewheight + bob;

    if (player->viewz > player->mo->ceilingz - 4 * FRACUNIT)
        player->viewz = player->mo->ceilingz - 4 * FRACUNIT;
}


//
// P_MovePlayer
//
void P_MovePlayer(player_t* player)
{
    ticcmd_t* cmd;

    cmd = &player->cmd;

    player->mo->angle += (cmd->angleturn << 16);

    // Do not let the player control movement
    //  if not onground.
    onground = (player->mo->z <= player->mo->floorz);

    if (cmd->forwardmove && onground)
        P_Thrust(player, player->mo->angle, cmd->forwardmove * 2048);

    if (cmd->sidemove && onground)
        P_Thrust(player, player->mo->angle - ANG90, cmd->sidemove * 2048);

    if ((cmd->forwardmove || cmd->sidemove)
        && player->mo->state == &states[S_PLAY])
    {
        P_SetMobjState(player->mo, S_PLAY_RUN1);
    }
}


//
// P_DeathThink
// Fall on your face when dying.
// Decrease POV height to floor height.
//
void P_DeathThink(player_t* player)
{
    angle_t angle;
    angle_t delta;

    P_MovePsprites(player);

    // fall to the ground
    if (player->viewheight > 6 * FRACUNIT)
        player->viewheight -= FRACUNIT;

    if (player->viewheight < 6 * FRACUNIT)
        player->viewheight = 6 * FRACUNIT;

    player->deltaviewheight = 0;
    onground = (player->mo->z <= player->mo->floorz);
    P_CalcHeight(player);

    if (player->attacker && player->attacker != player->mo)
    {
        angle = R_PointToAngle2(player->mo->x,
                                player->mo->y,
                                player->attacker->x,
                                player->attacker->y);

        delta = angle - player->mo->angle;

        if (delta < ANG5 || delta >(unsigned) - ANG5)
        {
            // Looking at killer,
            //  so fade damage flash down.
            player->mo->angle = angle;

            if (player->damagecount)
                player->damagecount--;
        }
        else if (delta < ANG180)
            player->mo->angle += ANG5;
        else
            player->mo->angle -= ANG5;
    }
    else if (player->damagecount)
        player->damagecount--;


    if (player->cmd.buttons & BT_USE)
        player->playerstate = PST_REBORN;
}


//
// P_PlayerThink
//
void P_PlayerThink(player_t* player)
{
    ticcmd_t* cmd;
    weapontype_t newweapon;

    // fixme: do this in the cheat code
    if (player->cheats & CF_NOCLIP)
        player->mo->flags |= MF_NOCLIP;
    else
        player->mo->flags &= ~MF_NOCLIP;

    // chain saw run forward
    cmd = &player->cmd;
    if (player->mo->flags & MF_JUSTATTACKED)
    {
        cmd->angleturn = 0;
        cmd->forwardmove = 0xc800 / 512;
        cmd->sidemove = 0;
        player->mo->flags &= ~MF_JUSTATTACKED;
    }


    if (player->playerstate == PST_DEAD)
    {
        P_DeathThink(player);
        return;
    }

    // Move around.
    // Reactiontime is used to prevent movement
    //  for a bit after a teleport.
    if (player->mo->reactiontime)
        player->mo->reactiontime--;
    else
        P_MovePlayer(player);

    P_CalcHeight(player);

    if (player->mo->subsector->sector->special)
        P_PlayerInSpecialSector(player);

    // Check for weapon change.

    // A special event has no other buttons.
    if (cmd->buttons & BT_SPECIAL)
        cmd->buttons = 0;

    if (cmd->buttons & BT_CHANGE)
    {
        // The actual changing of the weapon is done
        //  when the weapon psprite can do it
        //  (read: not in the middle of an attack).
        newweapon = (cmd->buttons & BT_WEAPONMASK) >> BT_WEAPONSHIFT;

        if (newweapon == wp_fist
            && player->weaponowned[wp_chainsaw]
            && !(player->readyweapon == wp_chainsaw
                 && player->powers[pw_strength]))
        {
            newweapon = wp_chainsaw;
        }

        if ((gamemode == commercial)
            && newweapon == wp_shotgun
            && player->weaponowned[wp_supershotgun]
            && player->readyweapon != wp_supershotgun)
        {
            newweapon = wp_supershotgun;
        }


        if (player->weaponowned[newweapon]
            && newweapon != player->readyweapon)
        {
            // Do not go to plasma or BFG in shareware,
            //  even if cheated.
            if ((newweapon != wp_plasma
                 && newweapon != wp_bfg)
                || (gamemode != shareware))
            {
                player->pendingweapon = newweapon;
            }
        }
    }

    // check for use
    if (cmd->buttons & BT_USE)
    {
        if (!player->usedown)
        {
            P_UseLines(player);
            player->usedown = true;
        }
    }
    else
        player->usedown = false;

    // cycle psprites
    P_MovePsprites(player);

    // Counters, time dependend power ups.

    // Strength counts up to diminish fade.
    if (player->powers[pw_strength])
        player->powers[pw_strength]++;

    if (player->powers[pw_invulnerability])
        player->powers[pw_invulnerability]--;

    if (player->powers[pw_invisibility])
        if (!--player->powers[pw_invisibility])
            player->mo->flags &= ~MF_SHADOW;

    if (player->powers[pw_infrared])
        player->powers[pw_infrared]--;

    if (player->powers[pw_ironfeet])
        player->powers[pw_ironfeet]--;

    if (player->damagecount)
        player->damagecount--;

    if (player->bonuscount)
        player->bonuscount--;


    // Handling colormaps.
    if (player->powers[pw_invulnerability])
    {
        if (player->powers[pw_invulnerability] > 4 * 32
            || (player->powers[pw_invulnerability] & 8))
            player->fixedcolormap = INVERSECOLORMAP;
        else
            player->fixedcolormap = 0;
    }
    else if (player->powers[pw_infrared])
    {
        if (player->powers[pw_infrared] > 4 * 32
            || (player->powers[pw_infrared] & 8))
        {
            // almost full bright
            player->fixedcolormap = 1;
        }
        else
            player->fixedcolormap = 0;
    }
    else
        player->fixedcolormap = 0;
}
#define MAXSEGS 32


//
// ClipWallSegment
// Clips the given range of columns
// and includes it in the new clip list.
//
typedef struct
{
    int first;
    int last;
} cliprange_t;


seg_t* curline;
side_t* sidedef;
line_t* linedef;
sector_t* frontsector;
sector_t* backsector;

drawseg_t drawsegs[MAXDRAWSEGS];
drawseg_t* ds_p;

// newend is one past the last valid seg
cliprange_t* newend;
cliprange_t solidsegs[MAXSEGS];

int checkcoord[12][4] =
{
    {3,0,2,1},
    {3,0,2,0},
    {3,1,2,0},
    {0},
    {2,0,2,1},
    {0,0,0,0},
    {3,1,3,0},
    {0},
    {2,0,3,1},
    {2,1,3,1},
    {2,1,3,0}
};


void R_StoreWallRange(int start, int stop);


//
// R_ClearDrawSegs
//
void R_ClearDrawSegs(void)
{
    ds_p = drawsegs;
}


//
// R_ClipSolidWallSegment
// Does handle solid walls,
//  e.g. single sided LineDefs (middle texture)
//  that entirely block the view.
// 
void R_ClipSolidWallSegment(int first, int last)
{
    cliprange_t* next;
    cliprange_t* start;

    // Find the first range that touches the range
    //  (adjacent pixels are touching).
    start = solidsegs;
    while (start->last < first - 1)
        start++;

    if (first < start->first)
    {
        if (last < start->first - 1)
        {
            // Post is entirely visible (above start),
            //  so insert a new clippost.
            R_StoreWallRange(first, last);
            next = newend;
            newend++;

            while (next != start)
            {
                *next = *(next - 1);
                next--;
            }
            next->first = first;
            next->last = last;
            return;
        }

        // There is a fragment above *start.
        R_StoreWallRange(first, start->first - 1);
        // Now adjust the clip size.
        start->first = first;
    }

    // Bottom contained in start?
    if (last <= start->last)
        return;

    next = start;
    while (last >= (next + 1)->first - 1)
    {
        // There is a fragment between two posts.
        R_StoreWallRange(next->last + 1, (next + 1)->first - 1);
        next++;

        if (last <= next->last)
        {
            // Bottom is contained in next.
            // Adjust the clip size.
            start->last = next->last;
            goto crunch;
        }
    }

    // There is a fragment after *next.
    R_StoreWallRange(next->last + 1, last);
    // Adjust the clip size.
    start->last = last;

    // Remove start+1 to next from the clip list,
    // because start now covers their area.
crunch:
    if (next == start)
    {
        // Post just extended past the bottom of one post.
        return;
    }


    while (next++ != newend)
    {
        // Remove a post.
        *++start = *next;
    }

    newend = start + 1;
}


//
// R_ClipPassWallSegment
// Clips the given range of columns,
//  but does not includes it in the clip list.
// Does handle windows,
//  e.g. LineDefs with upper and lower texture.
//
void R_ClipPassWallSegment(int first, int last)
{
    cliprange_t* start;

    // Find the first range that touches the range
    //  (adjacent pixels are touching).
    start = solidsegs;
    while (start->last < first - 1)
        start++;

    if (first < start->first)
    {
        if (last < start->first - 1)
        {
            // Post is entirely visible (above start).
            R_StoreWallRange(first, last);
            return;
        }

        // There is a fragment above *start.
        R_StoreWallRange(first, start->first - 1);
    }

    // Bottom contained in start?
    if (last <= start->last)
        return;

    while (last >= (start + 1)->first - 1)
    {
        // There is a fragment between two posts.
        R_StoreWallRange(start->last + 1, (start + 1)->first - 1);
        start++;

        if (last <= start->last)
            return;
    }

    // There is a fragment after *next.
    R_StoreWallRange(start->last + 1, last);
}


//
// R_ClearClipSegs
//
void R_ClearClipSegs(void)
{
    solidsegs[0].first = -0x7fffffff;
    solidsegs[0].last = -1;
    solidsegs[1].first = viewwidth;
    solidsegs[1].last = 0x7fffffff;
    newend = solidsegs + 2;
}


//
// R_AddLine
// Clips the given segment
// and adds any visible pieces to the line list.
//
void R_AddLine(seg_t* line)
{
    int x1;
    int x2;
    angle_t angle1;
    angle_t angle2;
    angle_t span;
    angle_t tspan;

    curline = line;

    // OPTIMIZE: quickly reject orthogonal back sides.
    angle1 = R_PointToAngle(line->v1->x, line->v1->y);
    angle2 = R_PointToAngle(line->v2->x, line->v2->y);

    // Clip to view edges.
    // OPTIMIZE: make constant out of 2*clipangle (FIELDOFVIEW).
    span = angle1 - angle2;

    // Back side? I.e. backface culling?
    if (span >= ANG180)
        return;

    // Global angle needed by segcalc.
    rw_angle1 = angle1;
    angle1 -= viewangle;
    angle2 -= viewangle;

    tspan = angle1 + clipangle;
    if (tspan > 2 * clipangle)
    {
        tspan -= 2 * clipangle;

        // Totally off the left edge?
        if (tspan >= span)
            return;

        angle1 = clipangle;
    }
    tspan = clipangle - angle2;
    if (tspan > 2 * clipangle)
    {
        tspan -= 2 * clipangle;

        // Totally off the left edge?
        if (tspan >= span)
            return;
#pragma warning(push)
#pragma warning(disable : 4146)
        angle2 = -clipangle;
#pragma warning(pop)
    }

    // The seg is in the view range,
    // but not necessarily visible.
    angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT;
    angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT;
    x1 = viewangletox[angle1];
    x2 = viewangletox[angle2];

    // Does not cross a pixel?
    if (x1 == x2)
        return;

    backsector = line->backsector;

    // Single sided line?
    if (!backsector)
        goto clipsolid;

    // Closed door.
    if (backsector->ceilingheight <= frontsector->floorheight
        || backsector->floorheight >= frontsector->ceilingheight)
        goto clipsolid;

    // Window.
    if (backsector->ceilingheight != frontsector->ceilingheight
        || backsector->floorheight != frontsector->floorheight)
        goto clippass;

    // Reject empty lines used for triggers
    //  and special events.
    // Identical floor and ceiling on both sides,
    // identical light levels on both sides,
    // and no middle texture.
    if (backsector->ceilingpic == frontsector->ceilingpic
        && backsector->floorpic == frontsector->floorpic
        && backsector->lightlevel == frontsector->lightlevel
        && curline->sidedef->midtexture == 0)
    {
        return;
    }


clippass:
    R_ClipPassWallSegment(x1, x2 - 1);
    return;

clipsolid:
    R_ClipSolidWallSegment(x1, x2 - 1);
}


//
// R_CheckBBox
// Checks BSP node/subtree bounding box.
// Returns true
//  if some part of the bbox might be visible.
//
doom_boolean R_CheckBBox(fixed_t* bspcoord)
{
    int boxx;
    int boxy;
    int boxpos;

    fixed_t x1;
    fixed_t y1;
    fixed_t x2;
    fixed_t y2;

    angle_t angle1;
    angle_t angle2;
    angle_t span;
    angle_t tspan;

    cliprange_t* start;

    int sx1;
    int sx2;

    // Find the corners of the box
    // that define the edges from current viewpoint.
    if (viewx <= bspcoord[BOXLEFT])
        boxx = 0;
    else if (viewx < bspcoord[BOXRIGHT])
        boxx = 1;
    else
        boxx = 2;

    if (viewy >= bspcoord[BOXTOP])
        boxy = 0;
    else if (viewy > bspcoord[BOXBOTTOM])
        boxy = 1;
    else
        boxy = 2;

    boxpos = (boxy << 2) + boxx;
    if (boxpos == 5)
        return true;

    x1 = bspcoord[checkcoord[boxpos][0]];
    y1 = bspcoord[checkcoord[boxpos][1]];
    x2 = bspcoord[checkcoord[boxpos][2]];
    y2 = bspcoord[checkcoord[boxpos][3]];

    // check clip list for an open space
    angle1 = R_PointToAngle(x1, y1) - viewangle;
    angle2 = R_PointToAngle(x2, y2) - viewangle;

    span = angle1 - angle2;

    // Sitting on a line?
    if (span >= ANG180)
        return true;

    tspan = angle1 + clipangle;

    if (tspan > 2 * clipangle)
    {
        tspan -= 2 * clipangle;

        // Totally off the left edge?
        if (tspan >= span)
            return false;

        angle1 = clipangle;
    }
    tspan = clipangle - angle2;
    if (tspan > 2 * clipangle)
    {
        tspan -= 2 * clipangle;

        // Totally off the left edge?
        if (tspan >= span)
            return false;

#pragma warning(push)
#pragma warning(disable : 4146)
        angle2 = -clipangle;
#pragma warning(pop)
    }


    // Find the first clippost
    //  that touches the source post
    //  (adjacent pixels are touching).
    angle1 = (angle1 + ANG90) >> ANGLETOFINESHIFT;
    angle2 = (angle2 + ANG90) >> ANGLETOFINESHIFT;
    sx1 = viewangletox[angle1];
    sx2 = viewangletox[angle2];

    // Does not cross a pixel.
    if (sx1 == sx2)
        return false;
    sx2--;

    start = solidsegs;
    while (start->last < sx2)
        start++;

    if (sx1 >= start->first
        && sx2 <= start->last)
    {
        // The clippost contains the new span.
        return false;
    }

    return true;
}


//
// R_Subsector
// Determine floor/ceiling planes.
// Add sprites of things in sector.
// Draw one or more line segments.
//
void R_Subsector(int num)
{
    int count;
    seg_t* line;
    subsector_t* sub;

#ifdef RANGECHECK
    if (num >= numsubsectors)
    {
        //I_Error("Error: R_Subsector: ss %i with numss = %i",
        //        num,
        //        numsubsectors);

        
        doom_strcpy(error_buf, "Error: R_Subsector: ss ");
        doom_concat(error_buf, doom_itoa(num, 10));
        doom_concat(error_buf, " with numss = ");
        doom_concat(error_buf, doom_itoa(numsubsectors, 10));
        I_Error(error_buf);
    }
#endif

    sscount++;
    sub = &subsectors[num];
    frontsector = sub->sector;
    count = sub->numlines;
    line = &segs[sub->firstline];

    if (frontsector->floorheight < viewz)
    {
        floorplane = R_FindPlane(frontsector->floorheight,
                                 frontsector->floorpic,
                                 frontsector->lightlevel);
    }
    else
        floorplane = 0;

    if (frontsector->ceilingheight > viewz
        || frontsector->ceilingpic == skyflatnum)
    {
        ceilingplane = R_FindPlane(frontsector->ceilingheight,
                                   frontsector->ceilingpic,
                                   frontsector->lightlevel);
    }
    else
        ceilingplane = 0;

    R_AddSprites(frontsector);

    while (count--)
    {
        R_AddLine(line);
        line++;
    }
}


//
// RenderBSPNode
// Renders all subsectors below a given node,
//  traversing subtree recursively.
// Just call with BSP root.
void R_RenderBSPNode(int bspnum)
{
    node_t* bsp;
    int side;

    // Found a subsector?
    if (bspnum & NF_SUBSECTOR)
    {
        if (bspnum == -1)
            R_Subsector(0);
        else
            R_Subsector(bspnum & (~NF_SUBSECTOR));
        return;
    }

    bsp = &nodes[bspnum];

    // Decide which side the view point is on.
    side = R_PointOnSide(viewx, viewy, bsp);

    // Recursively divide front space.
    R_RenderBSPNode(bsp->children[side]);

    // Possibly divide back space.
    if (R_CheckBBox(bsp->bbox[side ^ 1]))
        R_RenderBSPNode(bsp->children[side ^ 1]);
}
#if defined(DOOM_WIN32)

// #define strncasecmp strnicmp
#elif defined(DOOM_APPLE)

#else
#ifdef LINUX
//#include  <alloca.h>
#endif
#endif
//#include "r_data.h"


//
// Graphics.
// DOOM graphics for walls and sprites
// is stored in vertical runs of opaque pixels (posts).
// A column is composed of zero or more posts,
// a patch or sprite is composed of zero or more columns.
//


//
// Texture definition.
// Each texture is composed of one or more patches,
// with patches being lumps stored in the WAD.
// The lumps are referenced by number, and patched
// into the rectangular texture space using origin
// and possibly other attributes.
//
typedef struct
{
    short originx;
    short originy;
    short patch;
    short stepdir;
    short colormap;
} mappatch_t;


//
// Texture definition.
// A DOOM wall texture is a list of patches
// which are to be combined in a predefined order.
//
typedef struct
{
    char name[8];
    doom_boolean masked;
    short width;
    short height;
    //void **columndirectory; // OBSOLETE
    int columndirectory; // [pd] If it's not used, at least make sure it's the right size! Pointers are 8 bytes in x64
    short patchcount;
    mappatch_t patches[1];
} maptexture_t;


// A single patch from a texture definition,
// basically a rectangular area within
// the texture rectangle.
typedef struct
{
    // Block origin (allways UL),
    // which has allready accounted
    // for the internal origin of the patch.
    int originx;
    int originy;
    int patch;
} texpatch_t;


// A maptexturedef_t describes a rectangular texture,
// which is composed of one or more mappatch_t structures
// that arrange graphic patches.
typedef struct
{
    // Keep name for switch changing, etc.
    char name[8];
    short width;
    short height;

    // All the patches[patchcount]
    //  are drawn back to front into the cached texture.
    short patchcount;
    texpatch_t patches[1];
} texture_t;


int firstflat;
int lastflat;
int numflats;

int firstpatch;
int lastpatch;
int numpatches;

int firstspritelump;
int lastspritelump;
int numspritelumps;

int numtextures;
texture_t** textures;

int* texturewidthmask;
// needed for texture pegging
fixed_t* textureheight;
int* texturecompositesize;
short** texturecolumnlump;
unsigned short** texturecolumnofs;
byte** texturecomposite;

// for global animation
int* flattranslation;
int* texturetranslation;

// needed for pre rendering
fixed_t* spritewidth;
fixed_t* spriteoffset;
fixed_t* spritetopoffset;

lighttable_t* colormaps;

int flatmemory;
int texturememory;
int spritememory;


//
// MAPTEXTURE_T CACHING
// When a texture is first needed,
//  it counts the number of composite columns
//  required in the texture and allocates space
//  for a column directory and any new columns.
// The directory will simply point inside other patches
//  if there is only one patch in a given column,
//  but any columns with multiple patches
//  will have new column_ts generated.
//


//
// R_DrawColumnInCache
// Clip and draw a column
//  from a patch into a cached post.
//
void R_DrawColumnInCache(column_t* patch, byte* cache, int originy, int cacheheight)
{
    int count;
    int position;
    byte* source;
    byte* dest;

    dest = (byte*)cache + 3;

    while (patch->topdelta != 0xff)
    {
        source = (byte*)patch + 3;
        count = patch->length;
        position = originy + patch->topdelta;

        if (position < 0)
        {
            count += position;
            position = 0;
        }

        if (position + count > cacheheight)
            count = cacheheight - position;

        if (count > 0)
            doom_memcpy(cache + position, source, count);

        patch = (column_t*)((byte*)patch + patch->length + 4);
    }
}


//
// R_GenerateComposite
// Using the texture definition,
//  the composite texture is created from the patches,
//  and each column is cached.
//
void R_GenerateComposite(int texnum)
{
    byte* block;
    texture_t* texture;
    texpatch_t* patch;
    patch_t* realpatch;
    int x;
    int x1;
    int x2;
    int i;
    column_t* patchcol;
    short* collump;
    unsigned short* colofs;

    texture = textures[texnum];

    block = Z_Malloc(texturecompositesize[texnum],
                     PU_STATIC,
                     &texturecomposite[texnum]);

    collump = texturecolumnlump[texnum];
    colofs = texturecolumnofs[texnum];

    // Composite the columns together.
    patch = texture->patches;

    for (i = 0, patch = texture->patches;
         i < texture->patchcount;
         i++, patch++)
    {
        realpatch = W_CacheLumpNum(patch->patch, PU_CACHE);
        x1 = patch->originx;
        x2 = x1 + SHORT(realpatch->width);

        if (x1 < 0)
            x = 0;
        else
            x = x1;

        if (x2 > texture->width)
            x2 = texture->width;

        for (; x < x2; x++)
        {
            // Column does not have multiple patches?
            if (collump[x] >= 0)
                continue;

            patchcol = (column_t*)((byte*)realpatch
                                   + LONG(realpatch->columnofs[x - x1]));
            R_DrawColumnInCache(patchcol,
                                block + colofs[x],
                                patch->originy,
                                texture->height);
        }
    }

    // Now that the texture has been built in column cache,
    //  it is purgable from zone memory.
    Z_ChangeTag(block, PU_CACHE);
}


//
// R_GenerateLookup
//
void R_GenerateLookup(int texnum)
{
    texture_t* texture;
    byte* patchcount; // patchcount[texture->width]
    texpatch_t* patch;
    patch_t* realpatch;
    int x;
    int x1;
    int x2;
    int i;
    short* collump;
    unsigned short* colofs;

    texture = textures[texnum];

    // Composited texture not created yet.
    texturecomposite[texnum] = 0;

    texturecompositesize[texnum] = 0;
    collump = texturecolumnlump[texnum];
    colofs = texturecolumnofs[texnum];

    // Now count the number of columns
    //  that are covered by more than one patch.
    // Fill in the lump / offset, so columns
    //  with only a single patch are all done.
    patchcount = (byte*)doom_malloc(texture->width);
    doom_memset(patchcount, 0, texture->width);
    patch = texture->patches;

    for (i = 0, patch = texture->patches;
         i < texture->patchcount;
         i++, patch++)
    {
        realpatch = W_CacheLumpNum(patch->patch, PU_CACHE);
        x1 = patch->originx;
        x2 = x1 + SHORT(realpatch->width);

        if (x1 < 0)
            x = 0;
        else
            x = x1;

        if (x2 > texture->width)
            x2 = texture->width;
        for (; x < x2; x++)
        {
            patchcount[x]++;
            collump[x] = patch->patch;
            colofs[x] = LONG(realpatch->columnofs[x - x1]) + 3;
        }
    }

    for (x = 0; x < texture->width; x++)
    {
        if (!patchcount[x])
        {
            //doom_print("R_GenerateLookup: column without a patch (%s)\n",
            //    texture->name);
            doom_print("R_GenerateLookup: column without a patch (");
            doom_print(texture->name);
            doom_print(")\n");
            return;
        }
        // I_Error ("R_GenerateLookup: column without a patch");

        if (patchcount[x] > 1)
        {
            // Use the cached block.
            collump[x] = -1;
            colofs[x] = texturecompositesize[texnum];

            if (texturecompositesize[texnum] > 0x10000 - texture->height)
            {
                //I_Error("Error: R_GenerateLookup: texture %i is >64k",
                //        texnum);
                
                doom_strcpy(error_buf, "Error: R_GenerateLookup: texture ");
                doom_concat(error_buf, doom_itoa(texnum, 10));
                doom_concat(error_buf, " is >64k");
                I_Error(error_buf);
            }

            texturecompositesize[texnum] += texture->height;
        }
    }

    doom_free(patchcount);
}


//
// R_GetColumn
//
byte* R_GetColumn(int tex, int col)
{
    int lump;
    int ofs;

    col &= texturewidthmask[tex];
    lump = texturecolumnlump[tex][col];
    ofs = texturecolumnofs[tex][col];

    if (lump > 0)
        return (byte*)W_CacheLumpNum(lump, PU_CACHE) + ofs;

    if (!texturecomposite[tex])
        R_GenerateComposite(tex);

    return texturecomposite[tex] + ofs;
}


//
// R_InitTextures
// Initializes the texture list
//  with the textures from the world map.
//
void R_InitTextures(void)
{
    maptexture_t* mtexture;
    texture_t* texture;
    mappatch_t* mpatch;
    texpatch_t* patch;

    int i;
    int j;

    int* maptex;
    int* maptex2;
    int* maptex1;

    char name[9];
    char* names;
    char* name_p;

    int* patchlookup;

    int totalwidth;
    int nummappatches;
    int offset;
    int maxoff;
    int maxoff2;
    int numtextures1;
    int numtextures2;

    int* directory;

    int temp1;
    int temp2;
    int temp3;

    // Load the patch names from pnames.lmp.
    name[8] = 0;
    names = W_CacheLumpName("PNAMES", PU_STATIC);
    nummappatches = LONG(*((int*)names));
    name_p = names + 4;
    patchlookup = doom_malloc(nummappatches * sizeof(*patchlookup));

    for (i = 0; i < nummappatches; i++)
    {
        doom_strncpy(name, name_p + i * 8, 8);
        patchlookup[i] = W_CheckNumForName(name);
    }
    Z_Free(names);

    // Load the map texture definitions from textures.lmp.
    // The data is contained in one or two lumps,
    //  TEXTURE1 for shareware, plus TEXTURE2 for commercial.
    maptex = maptex1 = W_CacheLumpName("TEXTURE1", PU_STATIC);
    numtextures1 = LONG(*maptex);
    maxoff = W_LumpLength(W_GetNumForName("TEXTURE1"));
    directory = maptex + 1;

    if (W_CheckNumForName("TEXTURE2") != -1)
    {
        maptex2 = W_CacheLumpName("TEXTURE2", PU_STATIC);
        numtextures2 = LONG(*maptex2);
        maxoff2 = W_LumpLength(W_GetNumForName("TEXTURE2"));
    }
    else
    {
        maptex2 = 0;
        numtextures2 = 0;
        maxoff2 = 0;
    }
    numtextures = numtextures1 + numtextures2;

    textures = Z_Malloc(numtextures * sizeof(texture_t*), PU_STATIC, 0);
    texturecolumnlump = Z_Malloc(numtextures * sizeof(short*), PU_STATIC, 0);
    texturecolumnofs = Z_Malloc(numtextures * sizeof(unsigned short*), PU_STATIC, 0);
    texturecomposite = Z_Malloc(numtextures * sizeof(byte*), PU_STATIC, 0);
    texturecompositesize = Z_Malloc(numtextures * sizeof(int), PU_STATIC, 0);
    texturewidthmask = Z_Malloc(numtextures * sizeof(int), PU_STATIC, 0);
    textureheight = Z_Malloc(numtextures * sizeof(fixed_t), PU_STATIC, 0);

    totalwidth = 0;

    // Really complex printing shit...
    temp1 = W_GetNumForName("S_START");  // P_???????
    temp2 = W_GetNumForName("S_END") - 1;
    temp3 = ((temp2 - temp1 + 63) / 64) + ((numtextures + 63) / 64);
    doom_print("[");
    for (i = 0; i < temp3; i++)
        doom_print(" ");
    doom_print("         ]");
    for (i = 0; i < temp3; i++)
        doom_print("\x8");
    doom_print("\x8\x8\x8\x8\x8\x8\x8\x8\x8\x8");

    for (i = 0; i < numtextures; i++, directory++)
    {
        if (!(i & 63))
            doom_print(".");

        if (i == numtextures1)
        {
            // Start looking in second texture file.
            maptex = maptex2;
            maxoff = maxoff2;
            directory = maptex + 1;
        }

        offset = LONG(*directory);

        if (offset > maxoff)
            I_Error("Error: R_InitTextures: bad texture directory");

        mtexture = (maptexture_t*)((byte*)maptex + offset);

        texture = textures[i] =
            Z_Malloc(sizeof(texture_t)
                     + sizeof(texpatch_t) * (SHORT(mtexture->patchcount) - 1),
                     PU_STATIC, 0);

        texture->width = SHORT(mtexture->width);
        texture->height = SHORT(mtexture->height);
        texture->patchcount = SHORT(mtexture->patchcount);

        doom_memcpy(texture->name, mtexture->name, sizeof(texture->name));
        mpatch = &mtexture->patches[0];
        patch = &texture->patches[0];

        for (j = 0; j < texture->patchcount; j++, mpatch++, patch++)
        {
            patch->originx = SHORT(mpatch->originx);
            patch->originy = SHORT(mpatch->originy);
            patch->patch = patchlookup[SHORT(mpatch->patch)];
            if (patch->patch == -1)
            {
                //I_Error("Error: R_InitTextures: Missing patch in texture %s",
                //        texture->name);
                
                doom_strcpy(error_buf, "Error: R_InitTextures: Missing patch in texture ");
                doom_concat(error_buf, texture->name);
                I_Error(error_buf);
            }
        }
        texturecolumnlump[i] = Z_Malloc(texture->width * sizeof(short), PU_STATIC, 0);
        texturecolumnofs[i] = Z_Malloc(texture->width * sizeof(unsigned short), PU_STATIC, 0);

        j = 1;
        while (j * 2 <= texture->width)
            j <<= 1;

        texturewidthmask[i] = j - 1;
        textureheight[i] = texture->height << FRACBITS;

        totalwidth += texture->width;
    }

    Z_Free(maptex1);
    if (maptex2)
        Z_Free(maptex2);

    // Precalculate whatever possible.        
    for (i = 0; i < numtextures; i++)
        R_GenerateLookup(i);

    // Create translation table for global animation.
    texturetranslation = Z_Malloc((numtextures + 1) * sizeof(int), PU_STATIC, 0);

    for (i = 0; i < numtextures; i++)
        texturetranslation[i] = i;

    doom_free(patchlookup);
}


//
// R_InitFlats
//
void R_InitFlats(void)
{
    int i;

    firstflat = W_GetNumForName("F_START") + 1;
    lastflat = W_GetNumForName("F_END") - 1;
    numflats = lastflat - firstflat + 1;

    // Create translation table for global animation.
    flattranslation = Z_Malloc((numflats + 1) * sizeof(int), PU_STATIC, 0);

    for (i = 0; i < numflats; i++)
        flattranslation[i] = i;
}


//
// R_InitSpriteLumps
// Finds the width and hoffset of all sprites in the wad,
//  so the sprite does not need to be cached completely
//  just for having the header info ready during rendering.
//
void R_InitSpriteLumps(void)
{
    int i;
    patch_t* patch;

    firstspritelump = W_GetNumForName("S_START") + 1;
    lastspritelump = W_GetNumForName("S_END") - 1;

    numspritelumps = lastspritelump - firstspritelump + 1;
    spritewidth = Z_Malloc(numspritelumps * sizeof(fixed_t), PU_STATIC, 0);
    spriteoffset = Z_Malloc(numspritelumps * sizeof(fixed_t), PU_STATIC, 0);
    spritetopoffset = Z_Malloc(numspritelumps * sizeof(fixed_t), PU_STATIC, 0);

    for (i = 0; i < numspritelumps; i++)
    {
        if (!(i & 63))
            doom_print(".");

        patch = W_CacheLumpNum(firstspritelump + i, PU_CACHE);
        spritewidth[i] = SHORT(patch->width) << FRACBITS;
        spriteoffset[i] = SHORT(patch->leftoffset) << FRACBITS;
        spritetopoffset[i] = SHORT(patch->topoffset) << FRACBITS;
    }
}


//
// R_InitColormaps
//
void R_InitColormaps(void)
{
    int lump, length;

    // Load in the light tables, 
    //  256 byte align tables.
    lump = W_GetNumForName("COLORMAP");
    length = W_LumpLength(lump) + 255;
    colormaps = Z_Malloc(length, PU_STATIC, 0);
    colormaps = (byte*)(((unsigned long long)colormaps + 255) & ~0xff);
    W_ReadLump(lump, colormaps);
}


//
// R_InitData
// Locates all the lumps
//  that will be used by all views
// Must be called after W_Init.
//
void R_InitData(void)
{
    R_InitTextures();
    doom_print("\nInitTextures");
    R_InitFlats();
    doom_print("\nInitFlats");
    R_InitSpriteLumps();
    doom_print("\nInitSprites");
    R_InitColormaps();
    doom_print("\nInitColormaps");
}


//
// R_FlatNumForName
// Retrieval, get a flat number for a flat name.
//
int R_FlatNumForName(char* name)
{
    int i;
    char namet[9];

    i = W_CheckNumForName(name);

    if (i == -1)
    {
        namet[8] = 0;
        doom_memcpy(namet, name, 8);
        //I_Error("Error: R_FlatNumForName: %s not found", namet);
        
        doom_strcpy(error_buf, "Error: R_FlatNumForName: ");
        doom_concat(error_buf, namet);
        doom_concat(error_buf, " not found");
        I_Error(error_buf);
    }
    return i - firstflat;
}


//
// R_CheckTextureNumForName
// Check whether texture is available.
// Filter out NoTexture indicator.
//
int R_CheckTextureNumForName(char* name)
{
    int i;

    // "NoTexture" marker.
    if (name[0] == '-')
        return 0;

    for (i = 0; i < numtextures; i++)
        if (!doom_strncasecmp(textures[i]->name, name, 8))
            return i;

    return -1;
}


//
// R_TextureNumForName
// Calls R_CheckTextureNumForName,
//  aborts with error message.
//
int R_TextureNumForName(char* name)
{
    int i;

    i = R_CheckTextureNumForName(name);

    if (i == -1)
    {
        //I_Error("Error: R_TextureNumForName: %s not found",
        //        name);
        
        doom_strcpy(error_buf, "Error: R_TextureNumForName: ");
        doom_concat(error_buf, name);
        doom_concat(error_buf, " not found");
        I_Error(error_buf);
    }
    return i;
}


//
// R_PrecacheLevel
// Preloads all relevant graphics for the level.
//
void R_PrecacheLevel(void)
{
    char* flatpresent;
    char* texturepresent;
    char* spritepresent;

    int i;
    int j;
    int k;
    int lump;

    texture_t* texture;
    thinker_t* th;
    spriteframe_t* sf;

    if (demoplayback)
        return;

    // Precache flats.
    flatpresent = doom_malloc(numflats);
    doom_memset(flatpresent, 0, numflats);

    for (i = 0; i < numsectors; i++)
    {
        flatpresent[sectors[i].floorpic] = 1;
        flatpresent[sectors[i].ceilingpic] = 1;
    }

    flatmemory = 0;

    for (i = 0; i < numflats; i++)
    {
        if (flatpresent[i])
        {
            lump = firstflat + i;
            flatmemory += lumpinfo[lump].size;
            W_CacheLumpNum(lump, PU_CACHE);
        }
    }

    // Precache textures.
    texturepresent = doom_malloc(numtextures);
    doom_memset(texturepresent, 0, numtextures);

    for (i = 0; i < numsides; i++)
    {
        texturepresent[sides[i].toptexture] = 1;
        texturepresent[sides[i].midtexture] = 1;
        texturepresent[sides[i].bottomtexture] = 1;
    }

    // Sky texture is always present.
    // Note that F_SKY1 is the name used to
    //  indicate a sky floor/ceiling as a flat,
    //  while the sky texture is stored like
    //  a wall texture, with an episode dependend
    //  name.
    texturepresent[skytexture] = 1;

    texturememory = 0;
    for (i = 0; i < numtextures; i++)
    {
        if (!texturepresent[i])
            continue;

        texture = textures[i];

        for (j = 0; j < texture->patchcount; j++)
        {
            lump = texture->patches[j].patch;
            texturememory += lumpinfo[lump].size;
            W_CacheLumpNum(lump, PU_CACHE);
        }
    }

    // Precache sprites.
    spritepresent = doom_malloc(numsprites);
    doom_memset(spritepresent, 0, numsprites);

    for (th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if (th->function.acp1 == (actionf_p1)P_MobjThinker)
            spritepresent[((mobj_t*)th)->sprite] = 1;
    }

    spritememory = 0;
    for (i = 0; i < numsprites; i++)
    {
        if (!spritepresent[i])
            continue;

        for (j = 0; j < sprites[i].numframes; j++)
        {
            sf = &sprites[i].spriteframes[j];
            for (k = 0; k < 8; k++)
            {
                lump = firstspritelump + sf->lump[k];
                spritememory += lumpinfo[lump].size;
                W_CacheLumpNum(lump, PU_CACHE);
            }
        }
    }

    doom_free(texturepresent);
    doom_free(flatpresent);
    doom_free(spritepresent);
}
#define MAXWIDTH 1120
#define MAXHEIGHT 832

// status bar height at bottom of screen
#define SBARHEIGHT 32

#define FUZZTABLE 50 
#define FUZZOFF (SCREENWIDTH)


//
// All drawing to the view buffer is accomplished in this file.
// The other refresh files only know about ccordinates,
//  not the architecture of the frame buffer.
// Conveniently, the frame buffer is a linear one,
//  and we need only the base address,
//  and the total size == width*height*depth/8.,
//

byte* viewimage;
int viewwidth;
int scaledviewwidth;
int viewheight;
int viewwindowx;
int viewwindowy;
byte* ylookup[MAXHEIGHT];
int columnofs[MAXWIDTH];

// Color tables for different players,
//  translate a limited part to another
//  (color ramps used for  suit colors).
//
byte translations[3][256];

//
// R_DrawColumn
// Source is the top of the column to scale.
//
lighttable_t* dc_colormap;
int dc_x;
int dc_yl;
int dc_yh;
fixed_t dc_iscale;
fixed_t dc_texturemid;

// first pixel in a column (possibly virtual) 
byte* dc_source;

// just for profiling 
int dccount;

int fuzzoffset[FUZZTABLE] =
{
    FUZZOFF,-FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,
    FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,
    FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,
    FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,
    FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,
    FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,
    FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF
};

int fuzzpos = 0;

byte* dc_translation;
byte* translationtables;


//
// A column is a vertical slice/span from a wall texture that,
// given the DOOM style restrictions on the view orientation,
// will always have constant z depth.
// Thus a special case loop for very fast rendering can
// be used. It has also been used with Wolfenstein 3D.
// 
void R_DrawColumn(void)
{
    int count;
    byte* dest;
    fixed_t frac;
    fixed_t fracstep;

    count = dc_yh - dc_yl;

    // Zero length, column does not exceed a pixel.
    if (count < 0)
        return;

#ifdef RANGECHECK 
    if ((unsigned)dc_x >= SCREENWIDTH
        || dc_yl < 0
        || dc_yh >= SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x);
        
        doom_strcpy(error_buf, "Error: R_DrawColumn: ");
        doom_concat(error_buf, doom_itoa(dc_yl, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(dc_yh, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(dc_x, 10));
        I_Error(error_buf);
    }
#endif 

    // Framebuffer destination address.
    // Use ylookup LUT to avoid multiply with ScreenWidth.
    // Use columnofs LUT for subwindows? 
    dest = ylookup[dc_yl] + columnofs[dc_x];

    // Determine scaling,
    //  which is the only mapping to be done.
    fracstep = dc_iscale;
    frac = dc_texturemid + (dc_yl - centery) * fracstep;

    // Inner loop that does the actual texture mapping,
    //  e.g. a DDA-lile scaling.
    // This is as fast as it gets.
    do
    {
        // Re-map color indices from wall texture column
        //  using a lighting/special effects LUT.
        *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]];

        dest += SCREENWIDTH;
        frac += fracstep;

    } while (count--);
}


void R_DrawColumnLow(void)
{
    int count;
    byte* dest;
    byte* dest2;
    fixed_t frac;
    fixed_t fracstep;

    count = dc_yh - dc_yl;

    // Zero length.
    if (count < 0)
        return;

#ifdef RANGECHECK 
    if ((unsigned)dc_x >= SCREENWIDTH
        || dc_yl < 0
        || dc_yh >= SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x);
        
        doom_strcpy(error_buf, "Error: R_DrawColumn: ");
        doom_concat(error_buf, doom_itoa(dc_yl, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(dc_yh, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(dc_x, 10));
        I_Error(error_buf);
    }
#endif 
    // Blocky mode, need to multiply by 2.
    dc_x <<= 1;

    dest = ylookup[dc_yl] + columnofs[dc_x];
    dest2 = ylookup[dc_yl] + columnofs[dc_x + 1];

    fracstep = dc_iscale;
    frac = dc_texturemid + (dc_yl - centery) * fracstep;

    do
    {
        // Hack. Does not work corretly.
        *dest2 = *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]];
        dest += SCREENWIDTH;
        dest2 += SCREENWIDTH;
        frac += fracstep;

    } while (count--);
}


//
// Spectre/Invisibility.
//

//
// Framebuffer postprocessing.
// Creates a fuzzy image by copying pixels
//  from adjacent ones to left and right.
// Used with an all black colormap, this
//  could create the SHADOW effect,
//  i.e. spectres and invisible players.
//
void R_DrawFuzzColumn(void)
{
    int count;
    byte* dest;
    fixed_t frac;
    fixed_t fracstep;

    // Adjust borders. Low... 
    if (!dc_yl)
        dc_yl = 1;

    // .. and high.
    if (dc_yh == viewheight - 1)
        dc_yh = viewheight - 2;

    count = dc_yh - dc_yl;

    // Zero length.
    if (count < 0)
        return;


#ifdef RANGECHECK 
    if ((unsigned)dc_x >= SCREENWIDTH
        || dc_yl < 0 || dc_yh >= SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawFuzzColumn: %i to %i at %i",
        //        dc_yl, dc_yh, dc_x);
        
        doom_strcpy(error_buf, "Error: R_DrawFuzzColumn: ");
        doom_concat(error_buf, doom_itoa(dc_yl, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(dc_yh, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(dc_x, 10));
        I_Error(error_buf);
    }
#endif

    // Does not work with blocky mode.
    dest = ylookup[dc_yl] + columnofs[dc_x];

    // Looks familiar.
    fracstep = dc_iscale;
    frac = dc_texturemid + (dc_yl - centery) * fracstep;

    // Looks like an attempt at dithering,
    //  using the colormap #6 (of 0-31, a bit
    //  brighter than average).
    do
    {
        // Lookup framebuffer, and retrieve
        //  a pixel that is either one column
        //  left or right of the current one.
        // Add index from colormap to index.
        *dest = colormaps[6 * 256 + dest[fuzzoffset[fuzzpos]]];

        // Clamp table lookup index.
        if (++fuzzpos == FUZZTABLE)
            fuzzpos = 0;

        dest += SCREENWIDTH;

        frac += fracstep;
    } while (count--);
}


//
// R_DrawTranslatedColumn
// Used to draw player sprites
//  with the green colorramp mapped to others.
// Could be used with different translation
//  tables, e.g. the lighter colored version
//  of the BaronOfHell, the HellKnight, uses
//  identical sprites, kinda brightened up.
//
void R_DrawTranslatedColumn(void)
{
    int count;
    byte* dest;
    fixed_t frac;
    fixed_t fracstep;

    count = dc_yh - dc_yl;
    if (count < 0)
        return;

#ifdef RANGECHECK 
    if ((unsigned)dc_x >= SCREENWIDTH
        || dc_yl < 0
        || dc_yh >= SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawColumn: %i to %i at %i",
        //        dc_yl, dc_yh, dc_x);
        
        doom_strcpy(error_buf, "Error: R_DrawColumn: ");
        doom_concat(error_buf, doom_itoa(dc_yl, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(dc_yh, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(dc_x, 10));
        I_Error(error_buf);
    }
#endif 

    // FIXME. As above.
    dest = ylookup[dc_yl] + columnofs[dc_x];

    // Looks familiar.
    fracstep = dc_iscale;
    frac = dc_texturemid + (dc_yl - centery) * fracstep;

    // Here we do an additional index re-mapping.
    do
    {
        // Translation tables are used
        //  to map certain colorramps to other ones,
        //  used with PLAY sprites.
        // Thus the "green" ramp of the player 0 sprite
        //  is mapped to gray, red, black/indigo. 
        *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]];
        dest += SCREENWIDTH;

        frac += fracstep;
    } while (count--);
}


//
// R_InitTranslationTables
// Creates the translation tables to map
// the green color ramp to gray, brown, red.
// Assumes a given structure of the PLAYPAL.
// Could be read from a lump instead.
//
void R_InitTranslationTables(void)
{
    int i;

    translationtables = Z_Malloc(256 * 3 + 255, PU_STATIC, 0);
    translationtables = (byte*)(((unsigned long long)translationtables + 255) & ~255);

    // translate just the 16 green colors
    for (i = 0; i < 256; i++)
    {
        if (i >= 0x70 && i <= 0x7f)
        {
            // map green ramp to gray, brown, red
            translationtables[i] = 0x60 + (i & 0xf);
            translationtables[i + 256] = 0x40 + (i & 0xf);
            translationtables[i + 512] = 0x20 + (i & 0xf);
        }
        else
        {
            // Keep all other colors as is.
            translationtables[i] = translationtables[i + 256]
                = translationtables[i + 512] = i;
        }
    }
}


//
// R_DrawSpan 
// With DOOM style restrictions on view orientation,
// the floors and ceilings consist of horizontal slices
// or spans with constant z depth.
// However, rotation around the world z axis is possible,
// thus this mapping, while simpler and faster than
// perspective correct texture mapping, has to traverse
// the texture at an angle in all but a few cases.
// In consequence, flats are not stored by column (like walls),
// and the inner loop has to step in texture space u and v.
//
int ds_y;
int ds_x1;
int ds_x2;

lighttable_t* ds_colormap;

fixed_t ds_xfrac;
fixed_t ds_yfrac;
fixed_t ds_xstep;
fixed_t ds_ystep;

// start of a 64*64 tile image 
byte* ds_source;

// just for profiling
int dscount;


//
// Draws the actual span.
void R_DrawSpan(void)
{
    fixed_t xfrac;
    fixed_t yfrac;
    byte* dest;
    int count;
    int spot;

#ifdef RANGECHECK 
    if (ds_x2 < ds_x1
        || ds_x1<0
        || ds_x2 >= SCREENWIDTH
        || (unsigned)ds_y>SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawSpan: %i to %i at %i",
        //        ds_x1, ds_x2, ds_y);
        
        doom_strcpy(error_buf, "Error: R_DrawSpan: ");
        doom_concat(error_buf, doom_itoa(ds_x1, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(ds_x2, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(ds_y, 10));
        I_Error(error_buf);
    }
#endif 

    xfrac = ds_xfrac;
    yfrac = ds_yfrac;

    dest = ylookup[ds_y] + columnofs[ds_x1];

    // We do not check for zero spans here?
    count = ds_x2 - ds_x1;

    do
    {
        // Current texture index in u,v.
        spot = ((yfrac >> (16 - 6)) & (63 * 64)) + ((xfrac >> 16) & 63);

        // Lookup pixel from flat texture tile,
        //  re-index using light/colormap.
        *dest++ = ds_colormap[ds_source[spot]];

        // Next step in u,v.
        xfrac += ds_xstep;
        yfrac += ds_ystep;

    } while (count--);
}


//
// Again..
//
void R_DrawSpanLow(void)
{
    fixed_t xfrac;
    fixed_t yfrac;
    byte* dest;
    int count;
    int spot;

#ifdef RANGECHECK 
    if (ds_x2 < ds_x1
        || ds_x1<0
        || ds_x2 >= SCREENWIDTH
        || (unsigned)ds_y>SCREENHEIGHT)
    {
        //I_Error("Error: R_DrawSpan: %i to %i at %i",
        //        ds_x1, ds_x2, ds_y);
        
        
        doom_strcpy(error_buf, "Error: R_DrawSpan: ");
        doom_concat(error_buf, doom_itoa(ds_x1, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(ds_x2, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(ds_y, 10));
        I_Error(error_buf);
    }
#endif 

    xfrac = ds_xfrac;
    yfrac = ds_yfrac;

    // Blocky mode, need to multiply by 2.
    ds_x1 <<= 1;
    ds_x2 <<= 1;

    dest = ylookup[ds_y] + columnofs[ds_x1];

    count = ds_x2 - ds_x1;
    do
    {
        spot = ((yfrac >> (16 - 6)) & (63 * 64)) + ((xfrac >> 16) & 63);
        // Lowres/blocky mode does it twice,
        //  while scale is adjusted appropriately.
        *dest++ = ds_colormap[ds_source[spot]];
        *dest++ = ds_colormap[ds_source[spot]];

        xfrac += ds_xstep;
        yfrac += ds_ystep;

    } while (count--);
}


//
// R_InitBuffer 
// Creats lookup tables that avoid
//  multiplies and other hazzles
//  for getting the framebuffer address
//  of a pixel to draw.
//
void R_InitBuffer(int width, int height)
{
    int i;

    // Handle resize,
    //  e.g. smaller view windows
    //  with border and/or status bar.
    viewwindowx = (SCREENWIDTH - width) >> 1;

    // Column offset. For windows.
    for (i = 0; i < width; i++)
        columnofs[i] = viewwindowx + i;

    // Samw with base row offset.
    if (width == SCREENWIDTH)
        viewwindowy = 0;
    else
        viewwindowy = (SCREENHEIGHT - SBARHEIGHT - height) >> 1;

    // Preclaculate all row offsets.
    for (i = 0; i < height; i++)
        ylookup[i] = screens[0] + (i + viewwindowy) * SCREENWIDTH;
}


//
// R_FillBackScreen
// Fills the back screen with a pattern
//  for variable screen sizes
// Also draws a beveled edge.
//
void R_FillBackScreen(void)
{
    byte* src;
    byte* dest;
    int x;
    int y;
    patch_t* patch;

    // DOOM border patch.
    char name1[] = "FLOOR7_2";

    // DOOM II border patch.
    char name2[] = "GRNROCK";

    char* name;

    if (scaledviewwidth == 320)
        return;

    if (gamemode == commercial)
        name = name2;
    else
        name = name1;

    src = W_CacheLumpName(name, PU_CACHE);
    dest = screens[1];

    for (y = 0; y < SCREENHEIGHT - SBARHEIGHT; y++)
    {
        for (x = 0; x < SCREENWIDTH / 64; x++)
        {
            doom_memcpy(dest, src + ((y & 63) << 6), 64);
            dest += 64;
        }

        if (SCREENWIDTH & 63)
        {
            doom_memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63);
            dest += (SCREENWIDTH & 63);
        }
    }

    patch = W_CacheLumpName("brdr_t", PU_CACHE);

    for (x = 0; x < scaledviewwidth; x += 8)
        V_DrawPatch(viewwindowx + x, viewwindowy - 8, 1, patch);
    patch = W_CacheLumpName("brdr_b", PU_CACHE);

    for (x = 0; x < scaledviewwidth; x += 8)
        V_DrawPatch(viewwindowx + x, viewwindowy + viewheight, 1, patch);
    patch = W_CacheLumpName("brdr_l", PU_CACHE);

    for (y = 0; y < viewheight; y += 8)
        V_DrawPatch(viewwindowx - 8, viewwindowy + y, 1, patch);
    patch = W_CacheLumpName("brdr_r", PU_CACHE);

    for (y = 0; y < viewheight; y += 8)
        V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy + y, 1, patch);


    // Draw beveled edge. 
    V_DrawPatch(viewwindowx - 8,
                viewwindowy - 8,
                1,
                W_CacheLumpName("brdr_tl", PU_CACHE));

    V_DrawPatch(viewwindowx + scaledviewwidth,
                viewwindowy - 8,
                1,
                W_CacheLumpName("brdr_tr", PU_CACHE));

    V_DrawPatch(viewwindowx - 8,
                viewwindowy + viewheight,
                1,
                W_CacheLumpName("brdr_bl", PU_CACHE));

    V_DrawPatch(viewwindowx + scaledviewwidth,
                viewwindowy + viewheight,
                1,
                W_CacheLumpName("brdr_br", PU_CACHE));
}


//
// Copy a screen buffer.
//
void R_VideoErase(unsigned ofs, int count)
{
    // LFB copy.
    // This might not be a good idea if memcpy
    //  is not optiomal, e.g. byte by byte on
    //  a 32bit CPU, as GNU GCC/Linux libc did
    //  at one point.
    doom_memcpy(screens[0] + ofs, screens[1] + ofs, count);
}


//
// R_DrawViewBorder
// Draws the border around the view
//  for different size windows?
//
void V_MarkRect(int x, int y, int width, int height);

void R_DrawViewBorder(void)
{
    int top;
    int side;
    int ofs;
    int i;

    if (scaledviewwidth == SCREENWIDTH)
        return;

    top = ((SCREENHEIGHT - SBARHEIGHT) - viewheight) / 2;
    side = (SCREENWIDTH - scaledviewwidth) / 2;

    // copy top and one line of left side 
    R_VideoErase(0, top * SCREENWIDTH + side);

    // copy one line of right side and bottom 
    ofs = (viewheight + top) * SCREENWIDTH - side;
    R_VideoErase(ofs, top * SCREENWIDTH + side);

    // copy sides using wraparound 
    ofs = top * SCREENWIDTH + SCREENWIDTH - side;
    side <<= 1;

    for (i = 1; i < viewheight; i++)
    {
        R_VideoErase(ofs, side);
        ofs += SCREENWIDTH;
    }

    // ? 
    V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT - SBARHEIGHT);
}
#define FIELDOFVIEW 2048 // Fineangles in the SCREENWIDTH wide window.
#define DISTMAP 2


int viewangleoffset;

// increment every time a check is made
int validcount = 1;

lighttable_t* fixedcolormap;

int centerx;
int centery;

fixed_t centerxfrac;
fixed_t centeryfrac;
fixed_t projection;

// just for profiling purposes
int framecount;

int sscount;
int linecount;
int loopcount;

fixed_t viewx;
fixed_t viewy;
fixed_t viewz;

angle_t viewangle;

fixed_t viewcos;
fixed_t viewsin;

player_t* viewplayer;

// 0 = high, 1 = low
int detailshift;

//
// precalculated math tables
//
angle_t clipangle;

// The viewangletox[viewangle + FINEANGLES/4] lookup
// maps the visible view angles to screen X coordinates,
// flattening the arc to a flat projection plane.
// There will be many angles mapped to the same X. 
int viewangletox[FINEANGLES / 2];

// The xtoviewangleangle[] table maps a screen pixel
// to the lowest viewangle that maps back to x ranges
// from clipangle to -clipangle.
angle_t xtoviewangle[SCREENWIDTH + 1];

fixed_t* finecosine = &finesine[FINEANGLES / 4];

lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE];
lighttable_t* scalelightfixed[MAXLIGHTSCALE];
lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ];

// bumped light from gun blasts
int extralight;

doom_boolean setsizeneeded;
int setblocks;
int setdetail;


extern lighttable_t** walllights;
extern int detailLevel;
extern int screenblocks;


void (*colfunc) (void);
void (*basecolfunc) (void);
void (*fuzzcolfunc) (void);
void (*transcolfunc) (void);
void (*spanfunc) (void);


//
// R_AddPointToBox
// Expand a given bbox
// so that it encloses a given point.
//
void R_AddPointToBox(int x, int y, fixed_t* box)
{
    if (x < box[BOXLEFT])
        box[BOXLEFT] = x;
    if (x > box[BOXRIGHT])
        box[BOXRIGHT] = x;
    if (y < box[BOXBOTTOM])
        box[BOXBOTTOM] = y;
    if (y > box[BOXTOP])
        box[BOXTOP] = y;
}


//
// R_PointOnSide
// Traverse BSP (sub) tree,
//  check point against partition plane.
// Returns side 0 (front) or 1 (back).
//
int R_PointOnSide(fixed_t x, fixed_t y, node_t* node)
{
    fixed_t dx;
    fixed_t dy;
    fixed_t left;
    fixed_t right;

    if (!node->dx)
    {
        if (x <= node->x)
            return node->dy > 0;

        return node->dy < 0;
    }
    if (!node->dy)
    {
        if (y <= node->y)
            return node->dx < 0;

        return node->dx > 0;
    }

    dx = (x - node->x);
    dy = (y - node->y);

    // Try to quickly decide by looking at sign bits.
    if ((node->dy ^ node->dx ^ dx ^ dy) & 0x80000000)
    {
        if ((node->dy ^ dx) & 0x80000000)
        {
            // (left is negative)
            return 1;
        }
        return 0;
    }

    left = FixedMul(node->dy >> FRACBITS, dx);
    right = FixedMul(dy, node->dx >> FRACBITS);

    if (right < left)
    {
        // front side
        return 0;
    }
    // back side
    return 1;
}


int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t* line)
{
    fixed_t lx;
    fixed_t ly;
    fixed_t ldx;
    fixed_t ldy;
    fixed_t dx;
    fixed_t dy;
    fixed_t left;
    fixed_t right;

    lx = line->v1->x;
    ly = line->v1->y;

    ldx = line->v2->x - lx;
    ldy = line->v2->y - ly;

    if (!ldx)
    {
        if (x <= lx)
            return ldy > 0;

        return ldy < 0;
    }
    if (!ldy)
    {
        if (y <= ly)
            return ldx < 0;

        return ldx > 0;
    }

    dx = (x - lx);
    dy = (y - ly);

    // Try to quickly decide by looking at sign bits.
    if ((ldy ^ ldx ^ dx ^ dy) & 0x80000000)
    {
        if ((ldy ^ dx) & 0x80000000)
        {
            // (left is negative)
            return 1;
        }
        return 0;
    }

    left = FixedMul(ldy >> FRACBITS, dx);
    right = FixedMul(dy, ldx >> FRACBITS);

    if (right < left)
    {
        // front side
        return 0;
    }
    // back side
    return 1;
}


//
// R_PointToAngle
// To get a global angle from cartesian coordinates,
//  the coordinates are flipped until they are in
//  the first octant of the coordinate system, then
//  the y (<=x) is scaled and divided by x to get a
//  tangent (slope) value which is looked up in the
//  tantoangle[] table.

angle_t R_PointToAngle(fixed_t x, fixed_t y)
{
    x -= viewx;
    y -= viewy;

    if ((!x) && (!y))
        return 0;

    if (x >= 0)
    {
        // x >=0
        if (y >= 0)
        {
            // y>= 0

            if (x > y)
            {
                // octant 0
                return tantoangle[SlopeDiv(y, x)];
            }
            else
            {
                // octant 1
                return ANG90 - 1 - tantoangle[SlopeDiv(x, y)];
            }
        }
        else
        {
            // y<0
            y = -y;

            if (x > y)
            {
                // octant 8
#pragma warning(push)
#pragma warning(disable : 4146)
                return -tantoangle[SlopeDiv(y, x)];
#pragma warning(pop)
            }
            else
            {
                // octant 7
                return ANG270 + tantoangle[SlopeDiv(x, y)];
            }
        }
    }
    else
    {
        // x<0
        x = -x;

        if (y >= 0)
        {
            // y>= 0
            if (x > y)
            {
                // octant 3
                return ANG180 - 1 - tantoangle[SlopeDiv(y, x)];
            }
            else
            {
                // octant 2
                return ANG90 + tantoangle[SlopeDiv(x, y)];
            }
        }
        else
        {
            // y<0
            y = -y;

            if (x > y)
            {
                // octant 4
                return ANG180 + tantoangle[SlopeDiv(y, x)];
            }
            else
            {
                // octant 5
                return ANG270 - 1 - tantoangle[SlopeDiv(x, y)];
            }
        }
    }
    return 0;
}


angle_t R_PointToAngle2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2)
{
    viewx = x1;
    viewy = y1;

    return R_PointToAngle(x2, y2);
}


fixed_t R_PointToDist(fixed_t x, fixed_t y)
{
    int     angle;
    fixed_t dx;
    fixed_t dy;
    fixed_t temp;
    fixed_t dist;

    dx = doom_abs(x - viewx);
    dy = doom_abs(y - viewy);

    if (dy > dx)
    {
        temp = dx;
        dx = dy;
        dy = temp;
    }

    angle = (tantoangle[FixedDiv(dy, dx) >> DBITS] + ANG90) >> ANGLETOFINESHIFT;

    // use as cosine
    dist = FixedDiv(dx, finesine[angle]);

    return dist;
}


//
// R_InitPointToAngle
//
void R_InitPointToAngle(void)
{
}


//
// R_ScaleFromGlobalAngle
// Returns the texture mapping scale
//  for the current line (horizontal span)
//  at the given angle.
// rw_distance must be calculated first.
//
fixed_t R_ScaleFromGlobalAngle(angle_t visangle)
{
    fixed_t scale;
    int anglea;
    int angleb;
    int sinea;
    int sineb;
    fixed_t num;
    int den;

    anglea = ANG90 + (visangle - viewangle);
    angleb = ANG90 + (visangle - rw_normalangle);

    // both sines are allways positive
    sinea = finesine[anglea >> ANGLETOFINESHIFT];
    sineb = finesine[angleb >> ANGLETOFINESHIFT];
    num = FixedMul(projection, sineb) << detailshift;
    den = FixedMul(rw_distance, sinea);

    if (den > num >> 16)
    {
        scale = FixedDiv(num, den);

        if (scale > 64 * FRACUNIT)
            scale = 64 * FRACUNIT;
        else if (scale < 256)
            scale = 256;
    }
    else
        scale = 64 * FRACUNIT;

    return scale;
}


//
// R_InitTables
//
void R_InitTables(void)
{
}


//
// R_InitTextureMapping
//
void R_InitTextureMapping(void)
{
    int     i;
    int     x;
    int     t;
    fixed_t focallength;

    // Use tangent table to generate viewangletox:
    // viewangletox will give the next greatest x
    // after the view angle.
    //
    // Calc focallength
    // so FIELDOFVIEW angles covers SCREENWIDTH.
    focallength = FixedDiv(centerxfrac,
                           finetangent[FINEANGLES / 4 + FIELDOFVIEW / 2]);

    for (i = 0; i < FINEANGLES / 2; i++)
    {
        if (finetangent[i] > FRACUNIT * 2)
            t = -1;
        else if (finetangent[i] < -FRACUNIT * 2)
            t = viewwidth + 1;
        else
        {
            t = FixedMul(finetangent[i], focallength);
            t = (centerxfrac - t + FRACUNIT - 1) >> FRACBITS;

            if (t < -1)
                t = -1;
            else if (t > viewwidth + 1)
                t = viewwidth + 1;
        }
        viewangletox[i] = t;
    }

    // Scan viewangletox[] to generate xtoviewangle[]:
    // xtoviewangle will give the smallest view angle
    // that maps to x.        
    for (x = 0; x <= viewwidth; x++)
    {
        i = 0;
        while (viewangletox[i] > x)
            i++;
        xtoviewangle[x] = (i << ANGLETOFINESHIFT) - ANG90;
    }

    // Take out the fencepost cases from viewangletox.
    for (i = 0; i < FINEANGLES / 2; i++)
    {
        t = FixedMul(finetangent[i], focallength);
        t = centerx - t;

        if (viewangletox[i] == -1)
            viewangletox[i] = 0;
        else if (viewangletox[i] == viewwidth + 1)
            viewangletox[i] = viewwidth;
    }

    clipangle = xtoviewangle[0];
}


//
// R_InitLightTables
// Only inits the zlight table,
// because the scalelight table changes with view size.
//

void R_InitLightTables(void)
{
    int i;
    int j;
    int level;
    int startmap;
    int scale;

    // Calculate the light levels to use
    //  for each level / distance combination.
    for (i = 0; i < LIGHTLEVELS; i++)
    {
        startmap = ((LIGHTLEVELS - 1 - i) * 2) * NUMCOLORMAPS / LIGHTLEVELS;
        for (j = 0; j < MAXLIGHTZ; j++)
        {
            scale = FixedDiv((SCREENWIDTH / 2 * FRACUNIT), (j + 1) << LIGHTZSHIFT);
            scale >>= LIGHTSCALESHIFT;
            level = startmap - scale / DISTMAP;

            if (level < 0)
                level = 0;

            if (level >= NUMCOLORMAPS)
                level = NUMCOLORMAPS - 1;

            zlight[i][j] = colormaps + level * 256;
        }
    }
}


//
// R_SetViewSize
// Do not really change anything here,
//  because it might be in the middle of a refresh.
// The change will take effect next refresh.
//

void R_SetViewSize(int blocks, int detail)
{
    setsizeneeded = true;
    setblocks = blocks;
    setdetail = detail;
}


//
// R_ExecuteSetViewSize
//
void R_ExecuteSetViewSize(void)
{
    fixed_t cosadj;
    fixed_t dy;
    int     i;
    int     j;
    int     level;
    int     startmap;

    setsizeneeded = false;

    if (setblocks == 11)
    {
        scaledviewwidth = SCREENWIDTH;
        viewheight = SCREENHEIGHT;
    }
    else
    {
        scaledviewwidth = setblocks * 32;
        viewheight = (setblocks * 168 / 10) & ~7;
    }

    detailshift = setdetail;
    viewwidth = scaledviewwidth >> detailshift;

    centery = viewheight / 2;
    centerx = viewwidth / 2;
    centerxfrac = centerx << FRACBITS;
    centeryfrac = centery << FRACBITS;
    projection = centerxfrac;

    if (!detailshift)
    {
        colfunc = basecolfunc = R_DrawColumn;
        fuzzcolfunc = R_DrawFuzzColumn;
        transcolfunc = R_DrawTranslatedColumn;
        spanfunc = R_DrawSpan;
    }
    else
    {
        colfunc = basecolfunc = R_DrawColumnLow;
        fuzzcolfunc = R_DrawFuzzColumn;
        transcolfunc = R_DrawTranslatedColumn;
        spanfunc = R_DrawSpanLow;
    }

    R_InitBuffer(scaledviewwidth, viewheight);

    R_InitTextureMapping();

    // psprite scales
    pspritescale = FRACUNIT * viewwidth / SCREENWIDTH;
    pspriteiscale = FRACUNIT * SCREENWIDTH / viewwidth;

    // thing clipping
    for (i = 0; i < viewwidth; i++)
        screenheightarray[i] = viewheight;

    // planes
    for (i = 0; i < viewheight; i++)
    {
        dy = ((i - viewheight / 2) << FRACBITS) + FRACUNIT / 2;
        dy = doom_abs(dy);
        yslope[i] = FixedDiv((viewwidth << detailshift) / 2 * FRACUNIT, dy);
    }

    for (i = 0; i < viewwidth; i++)
    {
        cosadj = doom_abs(finecosine[xtoviewangle[i] >> ANGLETOFINESHIFT]);
        distscale[i] = FixedDiv(FRACUNIT, cosadj);
    }

    // Calculate the light levels to use
    //  for each level / scale combination.
    for (i = 0; i < LIGHTLEVELS; i++)
    {
        startmap = ((LIGHTLEVELS - 1 - i) * 2) * NUMCOLORMAPS / LIGHTLEVELS;
        for (j = 0; j < MAXLIGHTSCALE; j++)
        {
            level = startmap - j * SCREENWIDTH / (viewwidth << detailshift) / DISTMAP;

            if (level < 0)
                level = 0;

            if (level >= NUMCOLORMAPS)
                level = NUMCOLORMAPS - 1;

            scalelight[i][j] = colormaps + level * 256;
        }
    }
}


//
// R_Init
//
void R_Init(void)
{
    R_InitData();
    doom_print("\nR_InitData");
    R_InitPointToAngle();
    doom_print("\nR_InitPointToAngle");
    R_InitTables();
    // viewwidth / viewheight / detailLevel are set by the defaults
    doom_print("\nR_InitTables");

    R_SetViewSize(screenblocks, detailLevel);
    R_InitPlanes();
    doom_print("\nR_InitPlanes");
    R_InitLightTables();
    doom_print("\nR_InitLightTables");
    R_InitSkyMap();
    doom_print("\nR_InitSkyMap");
    R_InitTranslationTables();
    doom_print("\nR_InitTranslationsTables");

    framecount = 0;
}


//
// R_PointInSubsector
//
subsector_t* R_PointInSubsector(fixed_t x, fixed_t y)
{
    node_t* node;
    int     side;
    int     nodenum;

    // single subsector is a special case
    if (!numnodes)
        return subsectors;

    nodenum = numnodes - 1;

    while (!(nodenum & NF_SUBSECTOR))
    {
        node = &nodes[nodenum];
        side = R_PointOnSide(x, y, node);
        nodenum = node->children[side];
    }

    return &subsectors[nodenum & ~NF_SUBSECTOR];
}


//
// R_SetupFrame
//
void R_SetupFrame(player_t* player)
{
    int i;

    viewplayer = player;
    viewx = player->mo->x;
    viewy = player->mo->y;
    viewangle = player->mo->angle + viewangleoffset;
    extralight = player->extralight;

    viewz = player->viewz;

    viewsin = finesine[viewangle >> ANGLETOFINESHIFT];
    viewcos = finecosine[viewangle >> ANGLETOFINESHIFT];

    sscount = 0;

    if (player->fixedcolormap)
    {
        fixedcolormap =
            colormaps
            + player->fixedcolormap * 256 * sizeof(lighttable_t);

        walllights = scalelightfixed;

        for (i = 0; i < MAXLIGHTSCALE; i++)
            scalelightfixed[i] = fixedcolormap;
    }
    else
        fixedcolormap = 0;

    framecount++;
    validcount++;
}


//
// R_RenderView
//
void R_RenderPlayerView(player_t* player)
{
    R_SetupFrame(player);

    // Clear buffers.
    R_ClearClipSegs();
    R_ClearDrawSegs();
    R_ClearPlanes();
    R_ClearSprites();

    // check for new console commands.
    NetUpdate();

    // The head node is the last node output.
    R_RenderBSPNode(numnodes - 1);

    // Check for new console commands.
    NetUpdate();

    R_DrawPlanes();

    // Check for new console commands.
    NetUpdate();

    R_DrawMasked();

    // Check for new console commands.
    NetUpdate();
}
#define MAXVISPLANES        128
#define MAXOPENINGS        SCREENWIDTH*64


planefunction_t floorfunc;
planefunction_t ceilingfunc;

//
// opening
//

// Here comes the obnoxious "visplane".
visplane_t visplanes[MAXVISPLANES];
visplane_t* lastvisplane;
visplane_t* floorplane;
visplane_t* ceilingplane;

// ?
short openings[MAXOPENINGS];
short* lastopening;

//
// Clip values are the solid pixel bounding the range.
//  floorclip starts out SCREENHEIGHT
//  ceilingclip starts out -1
//
short floorclip[SCREENWIDTH];
short ceilingclip[SCREENWIDTH];

//
// spanstart holds the start of a plane span
// initialized to 0 at start
//
int spanstart[SCREENHEIGHT];
int spanstop[SCREENHEIGHT];

//
// texture mapping
//
lighttable_t** planezlight;
fixed_t planeheight;

fixed_t yslope[SCREENHEIGHT];
fixed_t distscale[SCREENWIDTH];
fixed_t basexscale;
fixed_t baseyscale;

fixed_t cachedheight[SCREENHEIGHT];
fixed_t cacheddistance[SCREENHEIGHT];
fixed_t cachedxstep[SCREENHEIGHT];
fixed_t cachedystep[SCREENHEIGHT];


//
// R_InitPlanes
// Only at game startup.
//
void R_InitPlanes(void)
{
    // Doh!
}


//
// R_MapPlane
//
// Uses global vars:
//  planeheight
//  ds_source
//  basexscale
//  baseyscale
//  viewx
//  viewy
//
// BASIC PRIMITIVE
//
void R_MapPlane(int y, int x1, int x2)
{
    angle_t        angle;
    fixed_t        distance;
    fixed_t        length;
    unsigned        index;

#ifdef RANGECHECK
    if (x2 < x1
        || x1<0
        || x2 >= viewwidth
        || (unsigned)y>(unsigned)viewheight)
    {
        //I_Error("Error: R_MapPlane: %i, %i at %i", x1, x2, y);
        
        doom_strcpy(error_buf, "Error: R_MapPlane: ");
        doom_concat(error_buf, doom_itoa(x1, 10));
        doom_concat(error_buf, ", ");
        doom_concat(error_buf, doom_itoa(x2, 10));
        doom_concat(error_buf, " at ");
        doom_concat(error_buf, doom_itoa(y, 10));
        I_Error(error_buf);
    }
#endif

    if (planeheight != cachedheight[y])
    {
        cachedheight[y] = planeheight;
        distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
        ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale);
        ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale);
    }
    else
    {
        distance = cacheddistance[y];
        ds_xstep = cachedxstep[y];
        ds_ystep = cachedystep[y];
    }

    length = FixedMul(distance, distscale[x1]);
    angle = (viewangle + xtoviewangle[x1]) >> ANGLETOFINESHIFT;
    ds_xfrac = viewx + FixedMul(finecosine[angle], length);
    ds_yfrac = -viewy - FixedMul(finesine[angle], length);

    if (fixedcolormap)
        ds_colormap = fixedcolormap;
    else
    {
        index = distance >> LIGHTZSHIFT;

        if (index >= MAXLIGHTZ)
            index = MAXLIGHTZ - 1;

        ds_colormap = planezlight[index];
    }

    ds_y = y;
    ds_x1 = x1;
    ds_x2 = x2;

    // high or low detail
    spanfunc();
}


//
// R_ClearPlanes
// At begining of frame.
//
void R_ClearPlanes(void)
{
    int i;
    angle_t angle;

    // opening / clipping determination
    for (i = 0; i < viewwidth; i++)
    {
        floorclip[i] = viewheight;
        ceilingclip[i] = -1;
    }

    lastvisplane = visplanes;
    lastopening = openings;

    // texture calculation
    doom_memset(cachedheight, 0, sizeof(cachedheight));

    // left to right mapping
    angle = (viewangle - ANG90) >> ANGLETOFINESHIFT;

    // scale will be unit scale at SCREENWIDTH/2 distance
    basexscale = FixedDiv(finecosine[angle], centerxfrac);
    baseyscale = -FixedDiv(finesine[angle], centerxfrac);
}


//
// R_FindPlane
//
visplane_t* R_FindPlane(fixed_t height, int picnum, int lightlevel)
{
    visplane_t* check;

    if (picnum == skyflatnum)
    {
        height = 0; // all skys map together
        lightlevel = 0;
    }

    for (check = visplanes; check < lastvisplane; check++)
    {
        if (height == check->height
            && picnum == check->picnum
            && lightlevel == check->lightlevel)
        {
            break;
        }
    }

    if (check < lastvisplane)
        return check;

    if (lastvisplane - visplanes == MAXVISPLANES)
        I_Error("Error: R_FindPlane: no more visplanes");

    lastvisplane++;

    check->height = height;
    check->picnum = picnum;
    check->lightlevel = lightlevel;
    check->minx = SCREENWIDTH;
    check->maxx = -1;

    doom_memset(check->top, 0xff, sizeof(check->top));

    return check;
}


//
// R_CheckPlane
//
visplane_t* R_CheckPlane(visplane_t* pl, int start, int stop)
{
    int intrl;
    int intrh;
    int unionl;
    int unionh;
    int x;

    if (start < pl->minx)
    {
        intrl = pl->minx;
        unionl = start;
    }
    else
    {
        unionl = pl->minx;
        intrl = start;
    }

    if (stop > pl->maxx)
    {
        intrh = pl->maxx;
        unionh = stop;
    }
    else
    {
        unionh = pl->maxx;
        intrh = stop;
    }

    for (x = intrl; x <= intrh; x++)
        if (pl->top[x] != 0xff)
            break;

    if (x > intrh)
    {
        pl->minx = unionl;
        pl->maxx = unionh;

        // use the same one
        return pl;
    }

    // make a new visplane
    lastvisplane->height = pl->height;
    lastvisplane->picnum = pl->picnum;
    lastvisplane->lightlevel = pl->lightlevel;

    pl = lastvisplane++;
    pl->minx = start;
    pl->maxx = stop;

    doom_memset(pl->top, 0xff, sizeof(pl->top));

    return pl;
}


//
// R_MakeSpans
//
void R_MakeSpans(int x, int t1, int b1, int t2, int b2)
{
    while (t1 < t2 && t1 <= b1)
    {
        R_MapPlane(t1, spanstart[t1], x - 1);
        t1++;
    }
    while (b1 > b2 && b1 >= t1)
    {
        R_MapPlane(b1, spanstart[b1], x - 1);
        b1--;
    }

    while (t2 < t1 && t2 <= b2)
    {
        spanstart[t2] = x;
        t2++;
    }
    while (b2 > b1 && b2 >= t2)
    {
        spanstart[b2] = x;
        b2--;
    }
}


//
// R_DrawPlanes
// At the end of each frame.
//
void R_DrawPlanes(void)
{
    visplane_t* pl;
    int light;
    int x;
    int stop;
    int angle;

#ifdef RANGECHECK
    if (ds_p - drawsegs > MAXDRAWSEGS)
    {
        //I_Error("Error: R_DrawPlanes: drawsegs overflow (%i)",
        //        ds_p - drawsegs);
        
        doom_strcpy(error_buf, "Error: R_DrawPlanes: drawsegs overflow (");
        doom_concat(error_buf, doom_itoa((int)(ds_p - drawsegs), 10));
        doom_concat(error_buf, ")");
        I_Error(error_buf);
    }

    if (lastvisplane - visplanes > MAXVISPLANES)
    {
        //I_Error("Error: R_DrawPlanes: visplane overflow (%i)",
        //        lastvisplane - visplanes);
        
        doom_strcpy(error_buf, "Error: R_DrawPlanes: visplane overflow (");
        doom_concat(error_buf, doom_itoa((int)(lastvisplane - visplanes), 10));
        doom_concat(error_buf, ")");
        I_Error(error_buf);
    }

    if (lastopening - openings > MAXOPENINGS)
    {
        //I_Error("Error: R_DrawPlanes: opening overflow (%i)",
        //        lastopening - openings);
        
        doom_strcpy(error_buf, "Error: R_DrawPlanes: opening overflow (");
        doom_concat(error_buf, doom_itoa((int)(lastopening - openings), 10));
        doom_concat(error_buf, ")");
        I_Error(error_buf);
    }
#endif

    for (pl = visplanes; pl < lastvisplane; pl++)
    {
        if (pl->minx > pl->maxx)
            continue;


        // sky flat
        if (pl->picnum == skyflatnum)
        {
            dc_iscale = pspriteiscale >> detailshift;

            // Sky is allways drawn full bright,
            //  i.e. colormaps[0] is used.
            // Because of this hack, sky is not affected
            //  by INVUL inverse mapping.
            dc_colormap = colormaps;
            dc_texturemid = skytexturemid;
            for (x = pl->minx; x <= pl->maxx; x++)
            {
                dc_yl = pl->top[x];
                dc_yh = pl->bottom[x];

                if (dc_yl <= dc_yh)
                {
                    angle = (viewangle + xtoviewangle[x]) >> ANGLETOSKYSHIFT;
                    dc_x = x;
                    dc_source = R_GetColumn(skytexture, angle);
                    colfunc();
                }
            }
            continue;
        }

        // regular flat
        ds_source = W_CacheLumpNum(firstflat +
                                   flattranslation[pl->picnum],
                                   PU_STATIC);

        planeheight = doom_abs(pl->height - viewz);
        light = (pl->lightlevel >> LIGHTSEGSHIFT) + extralight;

        if (light >= LIGHTLEVELS)
            light = LIGHTLEVELS - 1;

        if (light < 0)
            light = 0;

        planezlight = zlight[light];

        pl->top[pl->maxx + 1] = 0xff;
        pl->top[pl->minx - 1] = 0xff;

        stop = pl->maxx + 1;

        for (x = pl->minx; x <= stop; x++)
        {
            R_MakeSpans(x, pl->top[x - 1],
                        pl->bottom[x - 1],
                        pl->top[x],
                        pl->bottom[x]);
        }

        Z_ChangeTag(ds_source, PU_CACHE);
    }
}
#define HEIGHTBITS 12
#define HEIGHTUNIT (1<<HEIGHTBITS)


// OPTIMIZE: closed two sided lines as single sided

// True if any of the segs textures might be visible.
doom_boolean segtextured;

// False if the back side is the same plane.
doom_boolean markfloor;
doom_boolean markceiling;

doom_boolean maskedtexture;
int toptexture;
int bottomtexture;
int midtexture;

angle_t rw_normalangle;
// angle to line origin
int rw_angle1;

//
// regular wall
//
int rw_x;
int rw_stopx;
angle_t rw_centerangle;
fixed_t rw_offset;
fixed_t rw_distance;
fixed_t rw_scale;
fixed_t rw_scalestep;
fixed_t rw_midtexturemid;
fixed_t rw_toptexturemid;
fixed_t rw_bottomtexturemid;

int worldtop;
int worldbottom;
int worldhigh;
int worldlow;

fixed_t pixhigh;
fixed_t pixlow;
fixed_t pixhighstep;
fixed_t pixlowstep;

fixed_t topfrac;
fixed_t topstep;

fixed_t bottomfrac;
fixed_t bottomstep;

lighttable_t** walllights;

short* maskedtexturecol;


//
// R_RenderMaskedSegRange
//
void R_RenderMaskedSegRange(drawseg_t* ds, int x1, int x2)
{
    unsigned index;
    column_t* col;
    int lightnum;
    int texnum;

    // Calculate light table.
    // Use different light tables
    //   for horizontal / vertical / diagonal. Diagonal?
    // OPTIMIZE: get rid of LIGHTSEGSHIFT globally
    curline = ds->curline;
    frontsector = curline->frontsector;
    backsector = curline->backsector;
    texnum = texturetranslation[curline->sidedef->midtexture];

    lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight;

    if (curline->v1->y == curline->v2->y)
        lightnum--;
    else if (curline->v1->x == curline->v2->x)
        lightnum++;

    if (lightnum < 0)
        walllights = scalelight[0];
    else if (lightnum >= LIGHTLEVELS)
        walllights = scalelight[LIGHTLEVELS - 1];
    else
        walllights = scalelight[lightnum];

    maskedtexturecol = ds->maskedtexturecol;

    rw_scalestep = ds->scalestep;
    spryscale = ds->scale1 + (x1 - ds->x1) * rw_scalestep;
    mfloorclip = ds->sprbottomclip;
    mceilingclip = ds->sprtopclip;

    // find positioning
    if (curline->linedef->flags & ML_DONTPEGBOTTOM)
    {
        dc_texturemid = frontsector->floorheight > backsector->floorheight
            ? frontsector->floorheight : backsector->floorheight;
        dc_texturemid = dc_texturemid + textureheight[texnum] - viewz;
    }
    else
    {
        dc_texturemid = frontsector->ceilingheight < backsector->ceilingheight
            ? frontsector->ceilingheight : backsector->ceilingheight;
        dc_texturemid = dc_texturemid - viewz;
    }
    dc_texturemid += curline->sidedef->rowoffset;

    if (fixedcolormap)
        dc_colormap = fixedcolormap;

    // draw the columns
    for (dc_x = x1; dc_x <= x2; dc_x++)
    {
        // calculate lighting
        if (maskedtexturecol[dc_x] != DOOM_MAXSHORT)
        {
            if (!fixedcolormap)
            {
                index = spryscale >> LIGHTSCALESHIFT;

                if (index >= MAXLIGHTSCALE)
                    index = MAXLIGHTSCALE - 1;

                dc_colormap = walllights[index];
            }

            sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
            dc_iscale = 0xffffffffu / (unsigned)spryscale;

            // draw the texture
            col = (column_t*)(
                (byte*)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);

            R_DrawMaskedColumn(col);
            maskedtexturecol[dc_x] = DOOM_MAXSHORT;
        }
        spryscale += rw_scalestep;
    }
}


//
// R_RenderSegLoop
// Draws zero, one, or two textures (and possibly a masked
//  texture) for walls.
// Can draw or mark the starting pixel of floor and ceiling
//  textures.
// CALLED: CORE LOOPING ROUTINE.
//
void R_RenderSegLoop(void)
{
    angle_t angle;
    unsigned index;
    int yl;
    int yh;
    int mid;
    fixed_t texturecolumn;
    int top;
    int bottom;

    for (; rw_x < rw_stopx; rw_x++)
    {
        // mark floor / ceiling areas
        yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS;

        // no space above wall?
        if (yl < ceilingclip[rw_x] + 1)
            yl = ceilingclip[rw_x] + 1;

        if (markceiling)
        {
            top = ceilingclip[rw_x] + 1;
            bottom = yl - 1;

            if (bottom >= floorclip[rw_x])
                bottom = floorclip[rw_x] - 1;

            if (top <= bottom)
            {
                ceilingplane->top[rw_x] = top;
                ceilingplane->bottom[rw_x] = bottom;
            }
        }

        yh = bottomfrac >> HEIGHTBITS;

        if (yh >= floorclip[rw_x])
            yh = floorclip[rw_x] - 1;

        if (markfloor)
        {
            top = yh + 1;
            bottom = floorclip[rw_x] - 1;
            if (top <= ceilingclip[rw_x])
                top = ceilingclip[rw_x] + 1;
            if (top <= bottom)
            {
                floorplane->top[rw_x] = top;
                floorplane->bottom[rw_x] = bottom;
            }
        }

        // texturecolumn and lighting are independent of wall tiers
        if (segtextured)
        {
            // calculate texture offset
            angle = (rw_centerangle + xtoviewangle[rw_x]) >> ANGLETOFINESHIFT;
            texturecolumn = rw_offset - FixedMul(finetangent[angle], rw_distance);
            texturecolumn >>= FRACBITS;
            // calculate lighting
            index = rw_scale >> LIGHTSCALESHIFT;

            if (index >= MAXLIGHTSCALE)
                index = MAXLIGHTSCALE - 1;

            dc_colormap = walllights[index];
            dc_x = rw_x;
            dc_iscale = 0xffffffffu / (unsigned)rw_scale;
        }

        // draw the wall tiers
        if (midtexture)
        {
            // single sided line
            dc_yl = yl;
            dc_yh = yh;
            dc_texturemid = rw_midtexturemid;
            dc_source = R_GetColumn(midtexture, texturecolumn);
            colfunc();
            ceilingclip[rw_x] = viewheight;
            floorclip[rw_x] = -1;
        }
        else
        {
            // two sided line
            if (toptexture)
            {
                // top wall
                mid = pixhigh >> HEIGHTBITS;
                pixhigh += pixhighstep;

                if (mid >= floorclip[rw_x])
                    mid = floorclip[rw_x] - 1;

                if (mid >= yl)
                {
                    dc_yl = yl;
                    dc_yh = mid;
                    dc_texturemid = rw_toptexturemid;
                    dc_source = R_GetColumn(toptexture, texturecolumn);
                    colfunc();
                    ceilingclip[rw_x] = mid;
                }
                else
                    ceilingclip[rw_x] = yl - 1;
            }
            else
            {
                // no top wall
                if (markceiling)
                    ceilingclip[rw_x] = yl - 1;
            }

            if (bottomtexture)
            {
                // bottom wall
                mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS;
                pixlow += pixlowstep;

                // no space above wall?
                if (mid <= ceilingclip[rw_x])
                    mid = ceilingclip[rw_x] + 1;

                if (mid <= yh)
                {
                    dc_yl = mid;
                    dc_yh = yh;
                    dc_texturemid = rw_bottomtexturemid;
                    dc_source = R_GetColumn(bottomtexture,
                                            texturecolumn);
                    colfunc();
                    floorclip[rw_x] = mid;
                }
                else
                    floorclip[rw_x] = yh + 1;
            }
            else
            {
                // no bottom wall
                if (markfloor)
                    floorclip[rw_x] = yh + 1;
            }

            if (maskedtexture)
            {
                // save texturecol
                //  for backdrawing of masked mid texture
                maskedtexturecol[rw_x] = texturecolumn;
            }
        }

        rw_scale += rw_scalestep;
        topfrac += topstep;
        bottomfrac += bottomstep;
    }
}


//
// R_StoreWallRange
// A wall segment will be drawn
//  between start and stop pixels (inclusive).
//
void R_StoreWallRange(int start, int stop)
{
    fixed_t hyp;
    fixed_t sineval;
    angle_t distangle, offsetangle;
    fixed_t vtop;
    int lightnum;

    // don't overflow and crash
    if (ds_p == &drawsegs[MAXDRAWSEGS])
        return;

#ifdef RANGECHECK
    if (start >= viewwidth || start > stop)
    {
        //I_Error("Error: Bad R_RenderWallRange: %i to %i", start, stop);
        
        doom_strcpy(error_buf, "Error: Bad R_RenderWallRange: ");
        doom_concat(error_buf, doom_itoa(start, 10));
        doom_concat(error_buf, " to ");
        doom_concat(error_buf, doom_itoa(stop, 10));
        I_Error(error_buf);
    }
#endif

    sidedef = curline->sidedef;
    linedef = curline->linedef;

    // mark the segment as visible for auto map
    linedef->flags |= ML_MAPPED;

    // calculate rw_distance for scale calculation
    rw_normalangle = curline->angle + ANG90;
    offsetangle = (angle_t)doom_abs((int)rw_normalangle - (int)rw_angle1);

    if (offsetangle > ANG90)
        offsetangle = ANG90;

    distangle = ANG90 - offsetangle;
    hyp = R_PointToDist(curline->v1->x, curline->v1->y);
    sineval = finesine[distangle >> ANGLETOFINESHIFT];
    rw_distance = FixedMul(hyp, sineval);


    ds_p->x1 = rw_x = start;
    ds_p->x2 = stop;
    ds_p->curline = curline;
    rw_stopx = stop + 1;

    // calculate scale at both ends and step
    ds_p->scale1 = rw_scale =
        R_ScaleFromGlobalAngle(viewangle + xtoviewangle[start]);

    if (stop > start)
    {
        ds_p->scale2 = R_ScaleFromGlobalAngle(viewangle + xtoviewangle[stop]);
        ds_p->scalestep = rw_scalestep =
            (ds_p->scale2 - rw_scale) / (stop - start);
    }
    else
    {
        // UNUSED: try to fix the stretched line bug
#if 0
        if (rw_distance < FRACUNIT / 2)
        {
            fixed_t                trx, try;
            fixed_t                gxt, gyt;

            trx = curline->v1->x - viewx;
            try = curline->v1->y - viewy;

            gxt = FixedMul(trx, viewcos);
            gyt = -FixedMul(try, viewsin);
            ds_p->scale1 = FixedDiv(projection, gxt - gyt) << detailshift;
        }
#endif
        ds_p->scale2 = ds_p->scale1;
    }

    // calculate texture boundaries
    //  and decide if floor / ceiling marks are needed
    worldtop = frontsector->ceilingheight - viewz;
    worldbottom = frontsector->floorheight - viewz;

    midtexture = toptexture = bottomtexture = maskedtexture = 0;
    ds_p->maskedtexturecol = 0;

    if (!backsector)
    {
        // single sided line
        midtexture = texturetranslation[sidedef->midtexture];
        // a single sided line is terminal, so it must mark ends
        markfloor = markceiling = true;
        if (linedef->flags & ML_DONTPEGBOTTOM)
        {
            vtop = frontsector->floorheight +
                textureheight[sidedef->midtexture];
            // bottom of texture at bottom
            rw_midtexturemid = vtop - viewz;
        }
        else
        {
            // top of texture at top
            rw_midtexturemid = worldtop;
        }
        rw_midtexturemid += sidedef->rowoffset;

        ds_p->silhouette = SIL_BOTH;
        ds_p->sprtopclip = screenheightarray;
        ds_p->sprbottomclip = negonearray;
        ds_p->bsilheight = DOOM_MAXINT;
        ds_p->tsilheight = DOOM_MININT;
    }
    else
    {
        // two sided line
        ds_p->sprtopclip = ds_p->sprbottomclip = 0;
        ds_p->silhouette = 0;

        if (frontsector->floorheight > backsector->floorheight)
        {
            ds_p->silhouette = SIL_BOTTOM;
            ds_p->bsilheight = frontsector->floorheight;
        }
        else if (backsector->floorheight > viewz)
        {
            ds_p->silhouette = SIL_BOTTOM;
            ds_p->bsilheight = DOOM_MAXINT;
            // ds_p->sprbottomclip = negonearray;
        }

        if (frontsector->ceilingheight < backsector->ceilingheight)
        {
            ds_p->silhouette |= SIL_TOP;
            ds_p->tsilheight = frontsector->ceilingheight;
        }
        else if (backsector->ceilingheight < viewz)
        {
            ds_p->silhouette |= SIL_TOP;
            ds_p->tsilheight = DOOM_MININT;
            // ds_p->sprtopclip = screenheightarray;
        }

        if (backsector->ceilingheight <= frontsector->floorheight)
        {
            ds_p->sprbottomclip = negonearray;
            ds_p->bsilheight = DOOM_MAXINT;
            ds_p->silhouette |= SIL_BOTTOM;
        }

        if (backsector->floorheight >= frontsector->ceilingheight)
        {
            ds_p->sprtopclip = screenheightarray;
            ds_p->tsilheight = DOOM_MININT;
            ds_p->silhouette |= SIL_TOP;
        }

        worldhigh = backsector->ceilingheight - viewz;
        worldlow = backsector->floorheight - viewz;

        // hack to allow height changes in outdoor areas
        if (frontsector->ceilingpic == skyflatnum
            && backsector->ceilingpic == skyflatnum)
        {
            worldtop = worldhigh;
        }

        if (worldlow != worldbottom
            || backsector->floorpic != frontsector->floorpic
            || backsector->lightlevel != frontsector->lightlevel)
        {
            markfloor = true;
        }
        else
        {
            // same plane on both sides
            markfloor = false;
        }

        if (worldhigh != worldtop
            || backsector->ceilingpic != frontsector->ceilingpic
            || backsector->lightlevel != frontsector->lightlevel)
        {
            markceiling = true;
        }
        else
        {
            // same plane on both sides
            markceiling = false;
        }

        if (backsector->ceilingheight <= frontsector->floorheight
            || backsector->floorheight >= frontsector->ceilingheight)
        {
            // closed door
            markceiling = markfloor = true;
        }

        if (worldhigh < worldtop)
        {
            // top texture
            toptexture = texturetranslation[sidedef->toptexture];
            if (linedef->flags & ML_DONTPEGTOP)
            {
                // top of texture at top
                rw_toptexturemid = worldtop;
            }
            else
            {
                vtop =
                    backsector->ceilingheight
                    + textureheight[sidedef->toptexture];

                // bottom of texture
                rw_toptexturemid = vtop - viewz;
            }
        }
        if (worldlow > worldbottom)
        {
            // bottom texture
            bottomtexture = texturetranslation[sidedef->bottomtexture];

            if (linedef->flags & ML_DONTPEGBOTTOM)
            {
                // bottom of texture at bottom
                // top of texture at top
                rw_bottomtexturemid = worldtop;
            }
            else        // top of texture at top
                rw_bottomtexturemid = worldlow;
        }
        rw_toptexturemid += sidedef->rowoffset;
        rw_bottomtexturemid += sidedef->rowoffset;

        // allocate space for masked texture tables
        if (sidedef->midtexture)
        {
            // masked midtexture
            maskedtexture = true;
            ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x;
            lastopening += rw_stopx - rw_x;
        }
    }

    // calculate rw_offset (only needed for textured lines)
    segtextured = midtexture | toptexture | bottomtexture | maskedtexture;

    if (segtextured)
    {
        offsetangle = rw_normalangle - rw_angle1;

        if (offsetangle > ANG180)
#pragma warning(push)
#pragma warning(disable : 4146)
            offsetangle = -offsetangle;
#pragma warning(pop)

        if (offsetangle > ANG90)
            offsetangle = ANG90;

        sineval = finesine[offsetangle >> ANGLETOFINESHIFT];
        rw_offset = FixedMul(hyp, sineval);

        if (rw_normalangle - rw_angle1 < ANG180)
            rw_offset = -rw_offset;

        rw_offset += sidedef->textureoffset + curline->offset;
        rw_centerangle = ANG90 + viewangle - rw_normalangle;

        // calculate light table
        //  use different light tables
        //  for horizontal / vertical / diagonal
        // OPTIMIZE: get rid of LIGHTSEGSHIFT globally
        if (!fixedcolormap)
        {
            lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT) + extralight;

            if (curline->v1->y == curline->v2->y)
                lightnum--;
            else if (curline->v1->x == curline->v2->x)
                lightnum++;

            if (lightnum < 0)
                walllights = scalelight[0];
            else if (lightnum >= LIGHTLEVELS)
                walllights = scalelight[LIGHTLEVELS - 1];
            else
                walllights = scalelight[lightnum];
        }
    }

    // if a floor / ceiling plane is on the wrong side
    //  of the view plane, it is definitely invisible
    //  and doesn't need to be marked.

    if (frontsector->floorheight >= viewz)
    {
        // above view plane
        markfloor = false;
    }

    if (frontsector->ceilingheight <= viewz
        && frontsector->ceilingpic != skyflatnum)
    {
        // below view plane
        markceiling = false;
    }


    // calculate incremental stepping values for texture edges
    worldtop >>= 4;
    worldbottom >>= 4;

    topstep = -FixedMul(rw_scalestep, worldtop);
    topfrac = (centeryfrac >> 4) - FixedMul(worldtop, rw_scale);

    bottomstep = -FixedMul(rw_scalestep, worldbottom);
    bottomfrac = (centeryfrac >> 4) - FixedMul(worldbottom, rw_scale);

    if (backsector)
    {
        worldhigh >>= 4;
        worldlow >>= 4;

        if (worldhigh < worldtop)
        {
            pixhigh = (centeryfrac >> 4) - FixedMul(worldhigh, rw_scale);
            pixhighstep = -FixedMul(rw_scalestep, worldhigh);
        }

        if (worldlow > worldbottom)
        {
            pixlow = (centeryfrac >> 4) - FixedMul(worldlow, rw_scale);
            pixlowstep = -FixedMul(rw_scalestep, worldlow);
        }
    }

    // render it
    if (markceiling)
        ceilingplane = R_CheckPlane(ceilingplane, rw_x, rw_stopx - 1);

    if (markfloor)
        floorplane = R_CheckPlane(floorplane, rw_x, rw_stopx - 1);

    R_RenderSegLoop();

    // save sprite clipping info
    if (((ds_p->silhouette & SIL_TOP) || maskedtexture)
        && !ds_p->sprtopclip)
    {
        doom_memcpy(lastopening, ceilingclip + start, 2 * (rw_stopx - start));
        ds_p->sprtopclip = lastopening - start;
        lastopening += rw_stopx - start;
    }

    if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture)
        && !ds_p->sprbottomclip)
    {
        doom_memcpy(lastopening, floorclip + start, 2 * (rw_stopx - start));
        ds_p->sprbottomclip = lastopening - start;
        lastopening += rw_stopx - start;
    }

    if (maskedtexture && !(ds_p->silhouette & SIL_TOP))
    {
        ds_p->silhouette |= SIL_TOP;
        ds_p->tsilheight = DOOM_MININT;
    }
    if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM))
    {
        ds_p->silhouette |= SIL_BOTTOM;
        ds_p->bsilheight = DOOM_MAXINT;
    }
    ds_p++;
}
int skyflatnum;
int skytexture;
int skytexturemid;


//
// R_InitSkyMap
// Called whenever the view size changes.
//
void R_InitSkyMap(void)
{
    skytexturemid = 100 * FRACUNIT;
}
#define MINZ                                (FRACUNIT*4)
#define BASEYCENTER                        100


typedef struct
{
    int x1;
    int x2;

    int column;
    int topclip;
    int bottomclip;
} maskdraw_t;


//
// Sprite rotation 0 is facing the viewer,
//  rotation 1 is one angle turn CLOCKWISE around the axis.
// This is not the same as the angle,
//  which increases counter clockwise (protractor).
// There was a lot of stuff grabbed wrong, so I changed it...
//
fixed_t pspritescale;
fixed_t pspriteiscale;

lighttable_t** spritelights;

// constant arrays
//  used for psprite clipping and initializing clipping
short negonearray[SCREENWIDTH];
short screenheightarray[SCREENWIDTH];

// variables used to look up
//  and range check thing_t sprites patches
spritedef_t* sprites;
int numsprites;

spriteframe_t sprtemp[29];
int maxframe;
char* spritename;
vissprite_t vissprites[MAXVISSPRITES];
vissprite_t* vissprite_p;
int newvissprite;
vissprite_t vsprsortedhead;


//
// INITIALIZATION FUNCTIONS
//

//
// R_InstallSpriteLump
// Local function for R_InitSprites.
//
void R_InstallSpriteLump(int lump, unsigned frame, unsigned rotation, doom_boolean flipped)
{
    int r;

    if (frame >= 29 || rotation > 8)
    {
        //I_Error("Error: R_InstallSpriteLump: "
        //        "Bad frame characters in lump %i", lump);
        doom_strcpy(error_buf, "Error: R_InstallSpriteLump: Bad frame characters in lump ");
        doom_concat(error_buf, doom_itoa(lump, 10));
        I_Error(error_buf);
    }

    if ((int)frame > maxframe)
        maxframe = frame;

    if (rotation == 0)
    {
        // the lump should be used for all rotations
        if (sprtemp[frame].rotate == false)
        {
            //I_Error("Error: R_InitSprites: Sprite %s frame %c has "
            //        "multip rot=0 lump", spritename, 'A' + frame);
            doom_strcpy(error_buf, "Error: R_InitSprites: Sprite ");
            doom_concat(error_buf, spritename);
            doom_concat(error_buf, " frame ");
            doom_concat(error_buf, doom_ctoa('A' + frame));
            doom_concat(error_buf, " has multip rot=0 lump");
            I_Error(error_buf);
        }

        if (sprtemp[frame].rotate == true)
        {
            //I_Error("Error: R_InitSprites: Sprite %s frame %c has rotations "
            //        "and a rot=0 lump", spritename, 'A' + frame);
            doom_strcpy(error_buf, "Error: R_InitSprites: Sprite ");
            doom_concat(error_buf, spritename);
            doom_concat(error_buf, " frame ");
            doom_concat(error_buf, doom_ctoa('A' + frame));
            doom_concat(error_buf, " has rotations ");
            I_Error(error_buf);
        }

        sprtemp[frame].rotate = false;
        for (r = 0; r < 8; r++)
        {
            sprtemp[frame].lump[r] = lump - firstspritelump;
            sprtemp[frame].flip[r] = (byte)flipped;
        }
        return;
    }

    // the lump is only used for one rotation
    if (sprtemp[frame].rotate == false)
    {
        //I_Error("Error: R_InitSprites: Sprite %s frame %c has rotations "
        //        "and a rot=0 lump", spritename, 'A' + frame);
        doom_strcpy(error_buf, "Error: R_InitSprites: Sprite ");
        doom_concat(error_buf, spritename);
        doom_concat(error_buf, " frame ");
        doom_concat(error_buf, doom_ctoa('A' + frame));
        doom_concat(error_buf, " has rotations ");
        I_Error(error_buf);
    }

    sprtemp[frame].rotate = true;

    // make 0 based
    rotation--;
    if (sprtemp[frame].lump[rotation] != -1)
    {
        //I_Error("Error: R_InitSprites: Sprite %s : %c : %c "
        //        "has two lumps mapped to it",
        //        spritename, 'A' + frame, '1' + rotation);
        doom_strcpy(error_buf, "Error: R_InitSprites: Sprite ");
        doom_concat(error_buf, spritename);
        doom_concat(error_buf, " : ");
        doom_concat(error_buf, doom_ctoa('A' + frame));
        doom_concat(error_buf, " : ");
        doom_concat(error_buf, doom_ctoa('1' + rotation));
        doom_concat(error_buf, " ");
        I_Error(error_buf);
    }

    sprtemp[frame].lump[rotation] = lump - firstspritelump;
    sprtemp[frame].flip[rotation] = (byte)flipped;
}


//
// R_InitSpriteDefs
// Pass a null terminated list of sprite names
//  (4 chars exactly) to be used.
// Builds the sprite rotation matrixes to account
//  for horizontally flipped sprites.
// Will report an error if the lumps are inconsistant. 
// Only called at startup.
//
// Sprite lump names are 4 characters for the actor,
//  a letter for the frame, and a number for the rotation.
// A sprite that is flippable will have an additional
//  letter/number appended.
// The rotation character can be 0 to signify no rotations.
//
void R_InitSpriteDefs(char** namelist)
{
    char** check;
    int                i;
    int                l;
    int                intname;
    int                frame;
    int                rotation;
    int                start;
    int                end;
    int                patched;

    // count the number of sprite names
    check = namelist;
    while (*check != 0)
        check++;

    numsprites = (int)(check - namelist);

    if (!numsprites)
        return;

    sprites = Z_Malloc(numsprites * sizeof(*sprites), PU_STATIC, 0);

    start = firstspritelump - 1;
    end = lastspritelump + 1;

    // scan all the lump names for each of the names,
    //  noting the highest frame letter.
    // Just compare 4 characters as ints
    for (i = 0; i < numsprites; i++)
    {
        spritename = namelist[i];
        doom_memset(sprtemp, -1, sizeof(sprtemp));

        maxframe = -1;
        intname = *(int*)namelist[i];

        // scan the lumps,
        //  filling in the frames for whatever is found
        for (l = start + 1; l < end; l++)
        {
            if (*(int*)lumpinfo[l].name == intname)
            {
                frame = lumpinfo[l].name[4] - 'A';
                rotation = lumpinfo[l].name[5] - '0';

                if (modifiedgame)
                    patched = W_GetNumForName(lumpinfo[l].name);
                else
                    patched = l;

                R_InstallSpriteLump(patched, frame, rotation, false);

                if (lumpinfo[l].name[6])
                {
                    frame = lumpinfo[l].name[6] - 'A';
                    rotation = lumpinfo[l].name[7] - '0';
                    R_InstallSpriteLump(l, frame, rotation, true);
                }
            }
        }

        // check the frames that were found for completeness
        if (maxframe == -1)
        {
            sprites[i].numframes = 0;
            continue;
        }

        maxframe++;

        for (frame = 0; frame < maxframe; frame++)
        {
            switch ((int)sprtemp[frame].rotate)
            {
                case -1:
                {
                    // no rotations were found for that frame at all
                    //I_Error("Error: R_InitSprites: No patches found "
                    //        "for %s frame %c", namelist[i], frame + 'A');
                    doom_strcpy(error_buf, "Error: R_InitSprites: No patches found for ");
                    doom_concat(error_buf, namelist[i]);
                    doom_concat(error_buf, " frame ");
                    doom_concat(error_buf, doom_ctoa(frame + 'A'));
                    I_Error(error_buf);
                    break;
                }

                case 0:
                    // only the first rotation is needed
                    break;

                case 1:
                    // must have all 8 frames
                    for (rotation = 0; rotation < 8; rotation++)
                        if (sprtemp[frame].lump[rotation] == -1)
                        {
                            //I_Error("Error: R_InitSprites: Sprite %s frame %c "
                            //        "is missing rotations",
                            //        namelist[i], frame + 'A');
                            doom_strcpy(error_buf, "Error: R_InitSprites: Sprite ");
                            doom_concat(error_buf, namelist[i]);
                            doom_concat(error_buf, " frame ");
                            doom_concat(error_buf, doom_ctoa(frame + 'A'));
                            doom_concat(error_buf, " is missing rotations");
                            I_Error(error_buf);
                        }
                    break;
            }
        }

        // allocate space for the frames present and copy sprtemp to it
        sprites[i].numframes = maxframe;
        sprites[i].spriteframes =
            Z_Malloc(maxframe * sizeof(spriteframe_t), PU_STATIC, 0);
        doom_memcpy(sprites[i].spriteframes, sprtemp, maxframe * sizeof(spriteframe_t));
    }
}


//
// GAME FUNCTIONS
//

//
// R_InitSprites
// Called at program start.
//
void R_InitSprites(char** namelist)
{
    int i;

    for (i = 0; i < SCREENWIDTH; i++)
    {
        negonearray[i] = -1;
    }

    R_InitSpriteDefs(namelist);
}


//
// R_ClearSprites
// Called at frame start.
//
void R_ClearSprites(void)
{
    vissprite_p = vissprites;
}


//
// R_NewVisSprite
//
vissprite_t overflowsprite;

vissprite_t* R_NewVisSprite(void)
{
    if (vissprite_p == &vissprites[MAXVISSPRITES])
        return &overflowsprite;

    vissprite_p++;
    return vissprite_p - 1;
}


//
// R_DrawMaskedColumn
// Used for sprites and masked mid textures.
// Masked means: partly transparent, i.e. stored
//  in posts/runs of opaque pixels.
//
short* mfloorclip;
short* mceilingclip;

fixed_t spryscale;
fixed_t sprtopscreen;

void R_DrawMaskedColumn(column_t* column)
{
    int topscreen;
    int bottomscreen;
    fixed_t basetexturemid;

    basetexturemid = dc_texturemid;

    for (; column->topdelta != 0xff; )
    {
        // calculate unclipped screen coordinates
        //  for post
        topscreen = sprtopscreen + spryscale * column->topdelta;
        bottomscreen = topscreen + spryscale * column->length;

        dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS;
        dc_yh = (bottomscreen - 1) >> FRACBITS;

        if (dc_yh >= mfloorclip[dc_x])
            dc_yh = mfloorclip[dc_x] - 1;
        if (dc_yl <= mceilingclip[dc_x])
            dc_yl = mceilingclip[dc_x] + 1;

        if (dc_yl <= dc_yh)
        {
            dc_source = (byte*)column + 3;
            dc_texturemid = basetexturemid - (column->topdelta << FRACBITS);
            // dc_source = (byte *)column + 3 - column->topdelta;

            // Drawn by either R_DrawColumn
            //  or (SHADOW) R_DrawFuzzColumn.
            colfunc();
        }
        column = (column_t*)((byte*)column + column->length + 4);
    }

    dc_texturemid = basetexturemid;
}


//
// R_DrawVisSprite
//  mfloorclip and mceilingclip should also be set.
//
void R_DrawVisSprite(vissprite_t* vis, int x1, int x2)
{
    column_t* column;
    int texturecolumn;
    fixed_t frac;
    patch_t* patch;

    patch = W_CacheLumpNum(vis->patch + firstspritelump, PU_CACHE);

    dc_colormap = vis->colormap;

    if (!dc_colormap)
    {
        // 0 colormap = shadow draw
        colfunc = fuzzcolfunc;
    }
    else if (vis->mobjflags & MF_TRANSLATION)
    {
        colfunc = R_DrawTranslatedColumn;
        dc_translation = translationtables - 256 +
            ((vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT - 8));
    }

    dc_iscale = doom_abs(vis->xiscale) >> detailshift;
    dc_texturemid = vis->texturemid;
    frac = vis->startfrac;
    spryscale = vis->scale;
    sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);

    for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale)
    {
        texturecolumn = frac >> FRACBITS;
#ifdef RANGECHECK
        if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
            I_Error("Error: R_DrawSpriteRange: bad texturecolumn");
#endif
        column = (column_t*)((byte*)patch +
                             LONG(patch->columnofs[texturecolumn]));
        R_DrawMaskedColumn(column);
    }

    colfunc = basecolfunc;
}


//
// R_ProjectSprite
// Generates a vissprite for a thing
//  if it might be visible.
//
void R_ProjectSprite(mobj_t* thing)
{
    fixed_t tr_x;
    fixed_t tr_y;

    fixed_t gxt;
    fixed_t gyt;

    fixed_t tx;
    fixed_t tz;

    fixed_t xscale;

    int x1;
    int x2;

    spritedef_t* sprdef;
    spriteframe_t* sprframe;
    int lump;

    unsigned rot;
    doom_boolean flip;

    int index;

    vissprite_t* vis;

    angle_t ang;
    fixed_t iscale;

    // transform the origin point
    tr_x = thing->x - viewx;
    tr_y = thing->y - viewy;

    gxt = FixedMul(tr_x, viewcos);
    gyt = -FixedMul(tr_y, viewsin);

    tz = gxt - gyt;

    // thing is behind view plane?
    if (tz < MINZ)
        return;

    xscale = FixedDiv(projection, tz);

    gxt = -FixedMul(tr_x, viewsin);
    gyt = FixedMul(tr_y, viewcos);
    tx = -(gyt + gxt);

    // too far off the side?
    if (doom_abs(tx) > (tz << 2))
        return;

    // decide which patch to use for sprite relative to player
#ifdef RANGECHECK
    if ((unsigned)thing->sprite >= (unsigned)numsprites)
    {
        //I_Error("Error: R_ProjectSprite: invalid sprite number %i ",
        //        thing->sprite);
        
        doom_strcpy(error_buf, "Error: R_ProjectSprite: invalid sprite number ");
        doom_concat(error_buf, doom_itoa(thing->sprite, 10));
        doom_concat(error_buf, " ");
        I_Error(error_buf);
    }
#endif
    sprdef = &sprites[thing->sprite];
#ifdef RANGECHECK
    if ((thing->frame & FF_FRAMEMASK) >= sprdef->numframes)
    {
        //I_Error("Error: R_ProjectSprite: invalid sprite frame %i : %i ",
        //        thing->sprite, thing->frame);
        
        doom_strcpy(error_buf, "Error: R_ProjectSprite: invalid sprite frame ");
        doom_concat(error_buf, doom_itoa(thing->sprite, 10));
        doom_concat(error_buf, " : ");
        doom_concat(error_buf, doom_itoa(thing->frame, 10));
        doom_concat(error_buf, " ");
        I_Error(error_buf);
    }
#endif
    sprframe = &sprdef->spriteframes[thing->frame & FF_FRAMEMASK];

    if (sprframe->rotate)
    {
        // choose a different rotation based on player view
        ang = R_PointToAngle(thing->x, thing->y);
        rot = (ang - thing->angle + (unsigned)(ANG45 / 2) * 9) >> 29;
        lump = sprframe->lump[rot];
        flip = (doom_boolean)sprframe->flip[rot];
    }
    else
    {
        // use single rotation for all views
        lump = sprframe->lump[0];
        flip = (doom_boolean)sprframe->flip[0];
    }

    // calculate edges of the shape
    tx -= spriteoffset[lump];
    x1 = (centerxfrac + FixedMul(tx, xscale)) >> FRACBITS;

    // off the right side?
    if (x1 > viewwidth)
        return;

    tx += spritewidth[lump];
    x2 = ((centerxfrac + FixedMul(tx, xscale)) >> FRACBITS) - 1;

    // off the left side
    if (x2 < 0)
        return;

    // store information in a vissprite
    vis = R_NewVisSprite();
    vis->mobjflags = thing->flags;
    vis->scale = xscale << detailshift;
    vis->gx = thing->x;
    vis->gy = thing->y;
    vis->gz = thing->z;
    vis->gzt = thing->z + spritetopoffset[lump];
    vis->texturemid = vis->gzt - viewz;
    vis->x1 = x1 < 0 ? 0 : x1;
    vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2;
    iscale = FixedDiv(FRACUNIT, xscale);

    if (flip)
    {
        vis->startfrac = spritewidth[lump] - 1;
        vis->xiscale = -iscale;
    }
    else
    {
        vis->startfrac = 0;
        vis->xiscale = iscale;
    }

    if (vis->x1 > x1)
        vis->startfrac += vis->xiscale * (vis->x1 - x1);
    vis->patch = lump;

    // get light level
    if (thing->flags & MF_SHADOW)
    {
        // shadow draw
        vis->colormap = 0;
    }
    else if (fixedcolormap)
    {
        // fixed map
        vis->colormap = fixedcolormap;
    }
    else if (thing->frame & FF_FULLBRIGHT)
    {
        // full bright
        vis->colormap = colormaps;
    }

    else
    {
        // diminished light
        index = xscale >> (LIGHTSCALESHIFT - detailshift);

        if (index >= MAXLIGHTSCALE)
            index = MAXLIGHTSCALE - 1;

        vis->colormap = spritelights[index];
    }
}


//
// R_AddSprites
// During BSP traversal, this adds sprites by sector.
//
void R_AddSprites(sector_t* sec)
{
    mobj_t* thing;
    int lightnum;

    // BSP is traversed by subsector.
    // A sector might have been split into several
    //  subsectors during BSP building.
    // Thus we check whether its already added.
    if (sec->validcount == validcount)
        return;

    // Well, now it will be done.
    sec->validcount = validcount;

    lightnum = (sec->lightlevel >> LIGHTSEGSHIFT) + extralight;

    if (lightnum < 0)
        spritelights = scalelight[0];
    else if (lightnum >= LIGHTLEVELS)
        spritelights = scalelight[LIGHTLEVELS - 1];
    else
        spritelights = scalelight[lightnum];

    // Handle all things in sector.
    for (thing = sec->thinglist; thing; thing = thing->snext)
        R_ProjectSprite(thing);
}


//
// R_DrawPSprite
//
void R_DrawPSprite(pspdef_t* psp)
{
    fixed_t tx;
    int x1;
    int x2;
    spritedef_t* sprdef;
    spriteframe_t* sprframe;
    int lump;
    doom_boolean flip;
    vissprite_t* vis;
    vissprite_t avis;

    // decide which patch to use
#ifdef RANGECHECK
    if ((unsigned)psp->state->sprite >= (unsigned)numsprites)
    {
        //I_Error("Error: R_ProjectSprite: invalid sprite number %i ",
        //        psp->state->sprite);
        doom_strcpy(error_buf, "Error: R_ProjectSprite: invalid sprite number ");
        doom_concat(error_buf, doom_itoa(psp->state->sprite, 10));
        doom_concat(error_buf, " ");
        I_Error(error_buf);
    }
#endif
    sprdef = &sprites[psp->state->sprite];
#ifdef RANGECHECK
    if ((psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes)
    {
        //I_Error("Error: R_ProjectSprite: invalid sprite frame %i : %i ",
        //        psp->state->sprite, psp->state->frame);
        doom_strcpy(error_buf, "Error: R_ProjectSprite: invalid sprite frame ");
        doom_concat(error_buf, doom_itoa(psp->state->sprite, 10));
        doom_concat(error_buf, " : ");
        doom_concat(error_buf, doom_itoa(psp->state->frame, 10));
        doom_concat(error_buf, " ");
        I_Error(error_buf);
    }
#endif
    sprframe = &sprdef->spriteframes[psp->state->frame & FF_FRAMEMASK];

    lump = sprframe->lump[0];
    flip = (doom_boolean)sprframe->flip[0];

    // calculate edges of the shape
    tx = psp->sx - 160 * FRACUNIT;

    tx -= spriteoffset[lump];
    x1 = (centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS;

    // off the right side
    if (x1 > viewwidth)
        return;

    tx += spritewidth[lump];
    x2 = ((centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS) - 1;

    // off the left side
    if (x2 < 0)
        return;

    // store information in a vissprite
    vis = &avis;
    vis->mobjflags = 0;
    vis->texturemid = (BASEYCENTER << FRACBITS) + FRACUNIT / 2 - (psp->sy - spritetopoffset[lump]);
    vis->x1 = x1 < 0 ? 0 : x1;
    vis->x2 = x2 >= viewwidth ? viewwidth - 1 : x2;
    vis->scale = pspritescale << detailshift;

    if (flip)
    {
        vis->xiscale = -pspriteiscale;
        vis->startfrac = spritewidth[lump] - 1;
    }
    else
    {
        vis->xiscale = pspriteiscale;
        vis->startfrac = 0;
    }

    if (vis->x1 > x1)
        vis->startfrac += vis->xiscale * (vis->x1 - x1);

    vis->patch = lump;

    if (viewplayer->powers[pw_invisibility] > 4 * 32
        || viewplayer->powers[pw_invisibility] & 8)
    {
        // shadow draw
        vis->colormap = 0;
    }
    else if (fixedcolormap)
    {
        // fixed color
        vis->colormap = fixedcolormap;
    }
    else if (psp->state->frame & FF_FULLBRIGHT)
    {
        // full bright
        vis->colormap = colormaps;
    }
    else
    {
        // local light
        vis->colormap = spritelights[MAXLIGHTSCALE - 1];
    }

    R_DrawVisSprite(vis, vis->x1, vis->x2);
}


//
// R_DrawPlayerSprites
//
void R_DrawPlayerSprites(void)
{
    int i;
    int lightnum;
    pspdef_t* psp;

    // get light level
    lightnum =
        (viewplayer->mo->subsector->sector->lightlevel >> LIGHTSEGSHIFT)
        + extralight;

    if (lightnum < 0)
        spritelights = scalelight[0];
    else if (lightnum >= LIGHTLEVELS)
        spritelights = scalelight[LIGHTLEVELS - 1];
    else
        spritelights = scalelight[lightnum];

    // clip to screen bounds
    mfloorclip = screenheightarray;
    mceilingclip = negonearray;

    // add all active psprites
    for (i = 0, psp = viewplayer->psprites;
         i < NUMPSPRITES;
         i++, psp++)
    {
        if (psp->state)
            R_DrawPSprite(psp);
    }
}


//
// R_SortVisSprites
//
void R_SortVisSprites(void)
{
    int i;
    int count;
    vissprite_t* ds;
    vissprite_t* best;
    vissprite_t unsorted;
    fixed_t bestscale;

    count = (int)(vissprite_p - vissprites);

    unsorted.next = unsorted.prev = &unsorted;

    if (!count)
        return;

    for (ds = vissprites; ds < vissprite_p; ds++)
    {
        ds->next = ds + 1;
        ds->prev = ds - 1;
    }

    vissprites[0].prev = &unsorted;
    unsorted.next = &vissprites[0];
    (vissprite_p - 1)->next = &unsorted;
    unsorted.prev = vissprite_p - 1;

    // pull the vissprites out by scale
    //best = 0;                // shut up the compiler warning
    vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead;
    for (i = 0; i < count; i++)
    {
        bestscale = DOOM_MAXINT;
        for (ds = unsorted.next; ds != &unsorted; ds = ds->next)
        {
            if (ds->scale < bestscale)
            {
                bestscale = ds->scale;
                best = ds;
            }
        }
        best->next->prev = best->prev;
        best->prev->next = best->next;
        best->next = &vsprsortedhead;
        best->prev = vsprsortedhead.prev;
        vsprsortedhead.prev->next = best;
        vsprsortedhead.prev = best;
    }
}


//
// R_DrawSprite
//
void R_DrawSprite(vissprite_t* spr)
{
    drawseg_t* ds;
    short clipbot[SCREENWIDTH];
    short cliptop[SCREENWIDTH];
    int x;
    int r1;
    int r2;
    fixed_t scale;
    fixed_t lowscale;
    int silhouette;

    for (x = spr->x1; x <= spr->x2; x++)
        clipbot[x] = cliptop[x] = -2;

    // Scan drawsegs from end to start for obscuring segs.
    // The first drawseg that has a greater scale
    //  is the clip seg.
    for (ds = ds_p - 1; ds >= drawsegs; ds--)
    {
        // determine if the drawseg obscures the sprite
        if (ds->x1 > spr->x2
            || ds->x2 < spr->x1
            || (!ds->silhouette
                && !ds->maskedtexturecol))
        {
            // does not cover sprite
            continue;
        }

        r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
        r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;

        if (ds->scale1 > ds->scale2)
        {
            lowscale = ds->scale2;
            scale = ds->scale1;
        }
        else
        {
            lowscale = ds->scale1;
            scale = ds->scale2;
        }

        if (scale < spr->scale
            || (lowscale < spr->scale
                && !R_PointOnSegSide(spr->gx, spr->gy, ds->curline)))
        {
            // masked mid texture?
            if (ds->maskedtexturecol)
                R_RenderMaskedSegRange(ds, r1, r2);
            // seg is behind sprite
            continue;
        }


        // clip this piece of the sprite
        silhouette = ds->silhouette;

        if (spr->gz >= ds->bsilheight)
            silhouette &= ~SIL_BOTTOM;

        if (spr->gzt <= ds->tsilheight)
            silhouette &= ~SIL_TOP;

        if (silhouette == 1)
        {
            // bottom sil
            for (x = r1; x <= r2; x++)
                if (clipbot[x] == -2)
                    clipbot[x] = ds->sprbottomclip[x];
        }
        else if (silhouette == 2)
        {
            // top sil
            for (x = r1; x <= r2; x++)
                if (cliptop[x] == -2)
                    cliptop[x] = ds->sprtopclip[x];
        }
        else if (silhouette == 3)
        {
            // both
            for (x = r1; x <= r2; x++)
            {
                if (clipbot[x] == -2)
                    clipbot[x] = ds->sprbottomclip[x];
                if (cliptop[x] == -2)
                    cliptop[x] = ds->sprtopclip[x];
            }
        }
    }

    // all clipping has been performed, so draw the sprite

    // check for unclipped columns
    for (x = spr->x1; x <= spr->x2; x++)
    {
        if (clipbot[x] == -2)
            clipbot[x] = viewheight;

        if (cliptop[x] == -2)
            cliptop[x] = -1;
    }

    mfloorclip = clipbot;
    mceilingclip = cliptop;
    R_DrawVisSprite(spr, spr->x1, spr->x2);
}


//
// R_DrawMasked
//
void R_DrawMasked(void)
{
    vissprite_t* spr;
    drawseg_t* ds;

    R_SortVisSprites();

    if (vissprite_p > vissprites)
    {
        // draw all vissprites back to front
        for (spr = vsprsortedhead.next;
             spr != &vsprsortedhead;
             spr = spr->next)
        {

            R_DrawSprite(spr);
        }
    }

    // render any remaining masked mid textures
    for (ds = ds_p - 1; ds >= drawsegs; ds--)
        if (ds->maskedtexturecol)
            R_RenderMaskedSegRange(ds, ds->x1, ds->x2);

    // draw the psprites on top of everything
    //  but does not draw on side views
    if (!viewangleoffset)
        R_DrawPlayerSprites();
}
#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_s_sound;

// whether songs are mus_paused
static doom_boolean mus_paused;

// music currently being played
static musicinfo_t* mus_playing_s_sound = 0;

static int nextcleanup;


// 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;

// following is set
//  by the defaults code in M_misc:
// number of channels available
int numChannels;


//
// Prototypes
//
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;

    //doom_print("S_Init: default sfx volume %d\n", sfxVolume);
    doom_print("S_Init: default sfx volume ");
    doom_print(doom_itoa(sfxVolume, 10));
    doom_print("\n");

    // 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_s_sound =
        (channel_t*)Z_Malloc(numChannels * sizeof(channel_t), PU_STATIC, 0);

    // Free all channels for use
    for (i = 0; i < numChannels; i++)
        channels_s_sound[i].sfxinfo = 0;

    // no sounds are playing, and they are not mus_paused
    mus_paused = 0;

    // Note that sounds have not been cached (yet).
    for (i = 1; i < NUMSFX; i++)
        S_sfx[i].lumpnum = S_sfx[i].usefulness = -1;
}


//
// 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_s_sound[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);

    nextcleanup = 15;
}


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("Error: Bad sfx #: %d", sfx_id);
        doom_strcpy(error_buf, "Error: Bad sfx #: ");
        doom_concat(error_buf, doom_itoa(sfx_id, 10));
        I_Error(error_buf);
    }

    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 != players[consoleplayer].mo)
    {
        rc = S_AdjustSoundParams(players[consoleplayer].mo,
                                 origin,
                                 &volume,
                                 &sep,
                                 &pitch);

        if (origin->x == players[consoleplayer].mo->x
            && origin->y == players[consoleplayer].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;

    //
    // This is supposed to handle the loading/caching.
    // For some odd reason, the caching is done nearly
    //  each time the sound is needed?
    //

    // get lumpnum if necessary
    if (sfx->lumpnum < 0)
        sfx->lumpnum = I_GetSfxLumpNum(sfx);

#ifndef SNDSRV
    // cache data if necessary
    if (!sfx->data)
    {
        doom_print(
                "S_StartSoundAtVolume: 16bit and not pre-cached - wtf?\n");

        // DOS remains, 8bit handling
        //sfx->data = (void *) W_CacheLumpNum(sfx->lumpnum, PU_MUSIC);
        // fprintf( stderr,
        //             "S_StartSoundAtVolume: loading %d (lump %d) : 0x%x\n",
        //       sfx_id, sfx->lumpnum, (int)sfx->data );

    }
#endif

    // increase the usefulness
    if (sfx->usefulness++ < 0)
        sfx->usefulness = 1;

    // Assigns the handle to one of the channels in the
    //  mix/output buffer.
    channels_s_sound[cnum].handle = I_StartSound(sfx_id,
                                         /*sfx->data,*/
                                         volume,
                                         sep,
                                         pitch,
                                         priority);
}


void S_StartSound(void* origin, int sfx_id)
{
    S_StartSoundAtVolume(origin, sfx_id, snd_SfxVolume);
}


void S_StopSound(void* origin)
{
    int cnum;

    for (cnum = 0; cnum < numChannels; cnum++)
    {
        if (channels_s_sound[cnum].sfxinfo && channels_s_sound[cnum].origin == origin)
        {
            S_StopChannel(cnum);
            break;
        }
    }
}


//
// Stop and resume music, during game PAUSE.
//
void S_PauseSound(void)
{
    if (mus_playing_s_sound && !mus_paused)
    {
        I_PauseSong(mus_playing_s_sound->handle);
        mus_paused = true;
    }
}


void S_ResumeSound(void)
{
    if (mus_playing_s_sound && mus_paused)
    {
        I_ResumeSong(mus_playing_s_sound->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_s_sound[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);
            }
        }
    }
}


void S_SetMusicVolume(int volume)
{
    if (volume < 0 || volume > 127)
    {
        //I_Error("Error: Attempt to set music volume at %d",
        //        volume);
        doom_strcpy(error_buf, "Error: Attempt to set music volume at ");
        doom_concat(error_buf, doom_itoa(volume, 10));
        I_Error(error_buf);
    }

    I_SetMusicVolume(127);
    I_SetMusicVolume(volume);
    snd_MusicVolume = volume;
}


void S_SetSfxVolume(int volume)
{
    if (volume < 0 || volume > 127)
    {
        //I_Error("Error: Attempt to set sfx volume at %d", volume);
        doom_strcpy(error_buf, "Error: Attempt to set sfx volume at ");
        doom_concat(error_buf, doom_itoa(volume, 10));
        I_Error(error_buf);
    }

    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))
    {
        //I_Error("Error: Bad music number %d", musicnum);
        doom_strcpy(error_buf, "Error: Bad music number ");
        doom_concat(error_buf, doom_itoa(musicnum, 10));
        I_Error(error_buf);
    }
    else
        music = &S_music[musicnum];

    if (mus_playing_s_sound == music)
        return;

    // shutdown old music
    S_StopMusic();

    // get lumpnum if neccessary
    if (!music->lumpnum)
    {
        //doom_sprintf(namebuf, "d_%s", music->name);
        doom_strcpy(namebuf, "d_");
        doom_concat(namebuf, music->name);
        music->lumpnum = W_GetNumForName(namebuf);
    }

    // load & register it
    music->data = (void*)W_CacheLumpNum(music->lumpnum, PU_MUSIC);
    music->handle = I_RegisterSong(music->data);

    // play it
    I_PlaySong(music->handle, looping);

    mus_playing_s_sound = music;
}


void S_StopMusic(void)
{
    if (mus_playing_s_sound)
    {
        if (mus_paused)
            I_ResumeSong(mus_playing_s_sound->handle);

        I_StopSong(mus_playing_s_sound->handle);
        I_UnRegisterSong(mus_playing_s_sound->handle);
        Z_ChangeTag(mus_playing_s_sound->data, PU_CACHE);

        mus_playing_s_sound->data = 0;
        mus_playing_s_sound = 0;
    }
}


void S_StopChannel(int cnum)
{
    int i;
    channel_t* c = &channels_s_sound[cnum];

    if (c->sfxinfo)
    {
        // stop the sound playing
        if (I_SoundIsPlaying(c->handle))
        {
#ifdef SAWDEBUG
            if (c->sfxinfo == &S_sfx[sfx_sawful])
                doom_print("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_s_sound[i].sfxinfo)
            {
                break;
            }
        }

        // degrade usefulness of sound data
        c->sfxinfo->usefulness--;

        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 = doom_abs(listener->x - source->x);
    ady = doom_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_s_sound[cnum].sfxinfo)
            break;
        else if (origin && channels_s_sound[cnum].origin == origin)
        {
            S_StopChannel(cnum);
            break;
        }
    }

    // None available
    if (cnum == numChannels)
    {
        // Look for lower priority
        for (cnum = 0; cnum < numChannels; cnum++)
            if (channels_s_sound[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_s_sound[cnum];

    // channel is decided to be cnum.
    c->sfxinfo = sfxinfo;
    c->origin = origin;

    return cnum;
}
musicinfo_t S_music[] =
{
    { 0 },
    { "e1m1", 0 },
    { "e1m2", 0 },
    { "e1m3", 0 },
    { "e1m4", 0 },
    { "e1m5", 0 },
    { "e1m6", 0 },
    { "e1m7", 0 },
    { "e1m8", 0 },
    { "e1m9", 0 },
    { "e2m1", 0 },
    { "e2m2", 0 },
    { "e2m3", 0 },
    { "e2m4", 0 },
    { "e2m5", 0 },
    { "e2m6", 0 },
    { "e2m7", 0 },
    { "e2m8", 0 },
    { "e2m9", 0 },
    { "e3m1", 0 },
    { "e3m2", 0 },
    { "e3m3", 0 },
    { "e3m4", 0 },
    { "e3m5", 0 },
    { "e3m6", 0 },
    { "e3m7", 0 },
    { "e3m8", 0 },
    { "e3m9", 0 },
    { "inter", 0 },
    { "intro", 0 },
    { "bunny", 0 },
    { "victor", 0 },
    { "introa", 0 },
    { "runnin", 0 },
    { "stalks", 0 },
    { "countd", 0 },
    { "betwee", 0 },
    { "doom", 0 },
    { "the_da", 0 },
    { "shawn", 0 },
    { "ddtblu", 0 },
    { "in_cit", 0 },
    { "dead", 0 },
    { "stlks2", 0 },
    { "theda2", 0 },
    { "doom2", 0 },
    { "ddtbl2", 0 },
    { "runni2", 0 },
    { "dead2", 0 },
    { "stlks3", 0 },
    { "romero", 0 },
    { "shawn2", 0 },
    { "messag", 0 },
    { "count2", 0 },
    { "ddtbl3", 0 },
    { "ampie", 0 },
    { "theda3", 0 },
    { "adrian", 0 },
    { "messg2", 0 },
    { "romer2", 0 },
    { "tense", 0 },
    { "shawn3", 0 },
    { "openin", 0 },
    { "evil", 0 },
    { "ultima", 0 },
    { "read_m", 0 },
    { "dm2ttl", 0 },
    { "dm2int", 0 }
};


//
// Information about all the sfx
//
sfxinfo_t S_sfx[] =
{
    // S_sfx[0] needs to be a dummy for odd reasons.
    { "none", false,  0, 0, -1, -1, 0 },
    { "pistol", false, 64, 0, -1, -1, 0 },
    { "shotgn", false, 64, 0, -1, -1, 0 },
    { "sgcock", false, 64, 0, -1, -1, 0 },
    { "dshtgn", false, 64, 0, -1, -1, 0 },
    { "dbopn", false, 64, 0, -1, -1, 0 },
    { "dbcls", false, 64, 0, -1, -1, 0 },
    { "dbload", false, 64, 0, -1, -1, 0 },
    { "plasma", false, 64, 0, -1, -1, 0 },
    { "bfg", false, 64, 0, -1, -1, 0 },
    { "sawup", false, 64, 0, -1, -1, 0 },
    { "sawidl", false, 118, 0, -1, -1, 0 },
    { "sawful", false, 64, 0, -1, -1, 0 },
    { "sawhit", false, 64, 0, -1, -1, 0 },
    { "rlaunc", false, 64, 0, -1, -1, 0 },
    { "rxplod", false, 70, 0, -1, -1, 0 },
    { "firsht", false, 70, 0, -1, -1, 0 },
    { "firxpl", false, 70, 0, -1, -1, 0 },
    { "pstart", false, 100, 0, -1, -1, 0 },
    { "pstop", false, 100, 0, -1, -1, 0 },
    { "doropn", false, 100, 0, -1, -1, 0 },
    { "dorcls", false, 100, 0, -1, -1, 0 },
    { "stnmov", false, 119, 0, -1, -1, 0 },
    { "swtchn", false, 78, 0, -1, -1, 0 },
    { "swtchx", false, 78, 0, -1, -1, 0 },
    { "plpain", false, 96, 0, -1, -1, 0 },
    { "dmpain", false, 96, 0, -1, -1, 0 },
    { "popain", false, 96, 0, -1, -1, 0 },
    { "vipain", false, 96, 0, -1, -1, 0 },
    { "mnpain", false, 96, 0, -1, -1, 0 },
    { "pepain", false, 96, 0, -1, -1, 0 },
    { "slop", false, 78, 0, -1, -1, 0 },
    { "itemup", true, 78, 0, -1, -1, 0 },
    { "wpnup", true, 78, 0, -1, -1, 0 },
    { "oof", false, 96, 0, -1, -1, 0 },
    { "telept", false, 32, 0, -1, -1, 0 },
    { "posit1", true, 98, 0, -1, -1, 0 },
    { "posit2", true, 98, 0, -1, -1, 0 },
    { "posit3", true, 98, 0, -1, -1, 0 },
    { "bgsit1", true, 98, 0, -1, -1, 0 },
    { "bgsit2", true, 98, 0, -1, -1, 0 },
    { "sgtsit", true, 98, 0, -1, -1, 0 },
    { "cacsit", true, 98, 0, -1, -1, 0 },
    { "brssit", true, 94, 0, -1, -1, 0 },
    { "cybsit", true, 92, 0, -1, -1, 0 },
    { "spisit", true, 90, 0, -1, -1, 0 },
    { "bspsit", true, 90, 0, -1, -1, 0 },
    { "kntsit", true, 90, 0, -1, -1, 0 },
    { "vilsit", true, 90, 0, -1, -1, 0 },
    { "mansit", true, 90, 0, -1, -1, 0 },
    { "pesit", true, 90, 0, -1, -1, 0 },
    { "sklatk", false, 70, 0, -1, -1, 0 },
    { "sgtatk", false, 70, 0, -1, -1, 0 },
    { "skepch", false, 70, 0, -1, -1, 0 },
    { "vilatk", false, 70, 0, -1, -1, 0 },
    { "claw", false, 70, 0, -1, -1, 0 },
    { "skeswg", false, 70, 0, -1, -1, 0 },
    { "pldeth", false, 32, 0, -1, -1, 0 },
    { "pdiehi", false, 32, 0, -1, -1, 0 },
    { "podth1", false, 70, 0, -1, -1, 0 },
    { "podth2", false, 70, 0, -1, -1, 0 },
    { "podth3", false, 70, 0, -1, -1, 0 },
    { "bgdth1", false, 70, 0, -1, -1, 0 },
    { "bgdth2", false, 70, 0, -1, -1, 0 },
    { "sgtdth", false, 70, 0, -1, -1, 0 },
    { "cacdth", false, 70, 0, -1, -1, 0 },
    { "skldth", false, 70, 0, -1, -1, 0 },
    { "brsdth", false, 32, 0, -1, -1, 0 },
    { "cybdth", false, 32, 0, -1, -1, 0 },
    { "spidth", false, 32, 0, -1, -1, 0 },
    { "bspdth", false, 32, 0, -1, -1, 0 },
    { "vildth", false, 32, 0, -1, -1, 0 },
    { "kntdth", false, 32, 0, -1, -1, 0 },
    { "pedth", false, 32, 0, -1, -1, 0 },
    { "skedth", false, 32, 0, -1, -1, 0 },
    { "posact", true, 120, 0, -1, -1, 0 },
    { "bgact", true, 120, 0, -1, -1, 0 },
    { "dmact", true, 120, 0, -1, -1, 0 },
    { "bspact", true, 100, 0, -1, -1, 0 },
    { "bspwlk", true, 100, 0, -1, -1, 0 },
    { "vilact", true, 100, 0, -1, -1, 0 },
    { "noway", false, 78, 0, -1, -1, 0 },
    { "barexp", false, 60, 0, -1, -1, 0 },
    { "punch", false, 64, 0, -1, -1, 0 },
    { "hoof", false, 70, 0, -1, -1, 0 },
    { "metal", false, 70, 0, -1, -1, 0 },
    { "chgun", false, 64, &S_sfx[sfx_pistol], 150, 0, 0 },
    { "tink", false, 60, 0, -1, -1, 0 },
    { "bdopn", false, 100, 0, -1, -1, 0 },
    { "bdcls", false, 100, 0, -1, -1, 0 },
    { "itmbk", false, 100, 0, -1, -1, 0 },
    { "flame", false, 32, 0, -1, -1, 0 },
    { "flamst", false, 32, 0, -1, -1, 0 },
    { "getpow", false, 60, 0, -1, -1, 0 },
    { "bospit", false, 70, 0, -1, -1, 0 },
    { "boscub", false, 70, 0, -1, -1, 0 },
    { "bossit", false, 70, 0, -1, -1, 0 },
    { "bospn", false, 70, 0, -1, -1, 0 },
    { "bosdth", false, 70, 0, -1, -1, 0 },
    { "manatk", false, 70, 0, -1, -1, 0 },
    { "mandth", false, 70, 0, -1, -1, 0 },
    { "sssit", false, 70, 0, -1, -1, 0 },
    { "ssdth", false, 70, 0, -1, -1, 0 },
    { "keenpn", false, 70, 0, -1, -1, 0 },
    { "keendt", false, 70, 0, -1, -1, 0 },
    { "skeact", false, 70, 0, -1, -1, 0 },
    { "skesit", false, 70, 0, -1, -1, 0 },
    { "skeatk", false, 70, 0, -1, -1, 0 },
    { "radio", false, 60, 0, -1, -1, 0 }
};
extern doom_boolean                automapactive;


//
// Hack display negative frags.
//  Loads and store the stminus lump.
//
patch_t* sttminus;


void STlib_init(void)
{
    sttminus = (patch_t*)W_CacheLumpName("STTMINUS", PU_STATIC);
}

// ?
void STlib_initNum(st_number_t* n, int x, int y, patch_t** pl, int* num, doom_boolean* on, int width)
{
    n->x = x;
    n->y = y;
    n->oldnum = 0;
    n->width = width;
    n->num = num;
    n->on = on;
    n->p = pl;
}

// 
// A fairly efficient way to draw a number
//  based on differences from the old number.
// Note: worth the trouble?
//
void STlib_drawNum(st_number_t* n, doom_boolean refresh)
{
    int numdigits = n->width;
    int num = *n->num;

    int w = SHORT(n->p[0]->width);
    int h = SHORT(n->p[0]->height);
    int x = n->x;

    int neg;

    n->oldnum = *n->num;

    neg = num < 0;

    if (neg)
    {
        if (numdigits == 2 && num < -9)
            num = -9;
        else if (numdigits == 3 && num < -99)
            num = -99;

        num = -num;
    }

    // clear the area
    x = n->x - numdigits * w;

    if (n->y - ST_Y < 0)
        I_Error("Error: drawNum: n->y - ST_Y < 0");

    V_CopyRect(x, n->y - ST_Y, STLIB_BG, w * numdigits, h, x, n->y, STLIB_FG);

    // if non-number, do not draw it
    if (num == 1994)
        return;

    x = n->x;

    // in the special case of 0, you draw 0
    if (!num)
        V_DrawPatch(x - w, n->y, STLIB_FG, n->p[0]);

    // draw the new number
    while (num && numdigits--)
    {
        x -= w;
        V_DrawPatch(x, n->y, STLIB_FG, n->p[num % 10]);
        num /= 10;
    }

    // draw a minus sign if necessary
    if (neg)
        V_DrawPatch(x - 8, n->y, STLIB_FG, sttminus);
}

//
void STlib_updateNum(st_number_t* n, doom_boolean refresh)
{
    if (*n->on) STlib_drawNum(n, refresh);
}

//
void STlib_initPercent(st_percent_t* p, int x, int y, patch_t** pl, int* num, doom_boolean* on, patch_t* percent)
{
    STlib_initNum(&p->n, x, y, pl, num, on, 3);
    p->p = percent;
}

void STlib_updatePercent(st_percent_t* per, int refresh)
{
    if (refresh && *per->n.on)
        V_DrawPatch(per->n.x, per->n.y, STLIB_FG, per->p);

    STlib_updateNum(&per->n, refresh);
}

void STlib_initMultIcon(st_multicon_t* i, int x, int y, patch_t** il, int* inum, doom_boolean* on)
{
    i->x = x;
    i->y = y;
    i->oldinum = -1;
    i->inum = inum;
    i->on = on;
    i->p = il;
}

void STlib_updateMultIcon(st_multicon_t* mi, doom_boolean refresh)
{
    int w;
    int h;
    int x;
    int y;

    if (*mi->on
        && (mi->oldinum != *mi->inum || refresh)
        && (*mi->inum != -1))
    {
        if (mi->oldinum != -1)
        {
            x = mi->x - SHORT(mi->p[mi->oldinum]->leftoffset);
            y = mi->y - SHORT(mi->p[mi->oldinum]->topoffset);
            w = SHORT(mi->p[mi->oldinum]->width);
            h = SHORT(mi->p[mi->oldinum]->height);

            if (y - ST_Y < 0)
                I_Error("Error: updateMultIcon: y - ST_Y < 0");

            V_CopyRect(x, y - ST_Y, STLIB_BG, w, h, x, y, STLIB_FG);
        }
        V_DrawPatch(mi->x, mi->y, STLIB_FG, mi->p[*mi->inum]);
        mi->oldinum = *mi->inum;
    }
}

void STlib_initBinIcon(st_binicon_t* b, int x, int y, patch_t* i, doom_boolean* val, doom_boolean* on)
{
    b->x = x;
    b->y = y;
    b->oldval = 0;
    b->val = val;
    b->on = on;
    b->p = i;
}

void STlib_updateBinIcon(st_binicon_t* bi, doom_boolean refresh)
{
    int x;
    int y;
    int w;
    int h;

    if (*bi->on && (bi->oldval != *bi->val || refresh))
    {
        x = bi->x - SHORT(bi->p->leftoffset);
        y = bi->y - SHORT(bi->p->topoffset);
        w = SHORT(bi->p->width);
        h = SHORT(bi->p->height);

        if (y - ST_Y < 0)
            I_Error("Error: updateBinIcon: y - ST_Y < 0");

        if (*bi->val)
            V_DrawPatch(bi->x, bi->y, STLIB_FG, bi->p);
        else
            V_CopyRect(x, y - ST_Y, STLIB_BG, w, h, x, y, STLIB_FG);

        bi->oldval = *bi->val;
    }
}
#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
#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 (doom_strlen(mapnames[(gameepisode - 1) * 9 + (gamemap - 1)]))

#define ST_MAPTITLEX (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH)

#define ST_MAPTITLEY 0
#define ST_MAPHEIGHT 1


static player_t* plyr; // main player in game
static doom_boolean st_firsttime; // ST_Start() has just been called
static int veryfirsttime = 1; // used to execute ST_Init() only once
static int lu_palette; // lump number for PLAYPAL
static unsigned int st_clock; // used for timing
static int st_msgcounter = 0; // used for making messages go away
static st_chatstateenum_t st_chatstate; // used when in chat
static st_stateenum_t st_gamestate; // whether in automap or first-person
static doom_boolean st_statusbaron; // whether left-side main status bar is active
static doom_boolean st_chat; // whether status bar chat is active
static doom_boolean st_oldchat; // value of st_chat before message popped up
static doom_boolean st_cursoron; // whether chat window has the cursor on
static doom_boolean st_notdeathmatch; // !deathmatch
static doom_boolean st_armson; // !deathmatch && st_statusbaron
static doom_boolean st_fragson; // !deathmatch
static patch_t* sbar; // main bar left
static patch_t* tallnum[10]; // 0-9, tall numbers
static patch_t* tallpercent; // tall % sign
static patch_t* shortnum[10]; // 0-9, short, yellow (,different!) numbers
static patch_t* keys[NUMCARDS]; // 3 key-cards, 3 skulls
static patch_t* faces[ST_NUMFACES]; // face status patches
static patch_t* faceback; // face background
static patch_t* armsbg; // main bar right
static patch_t* arms[6][2]; // weapon ownership patches
static st_number_t w_ready; // ready-weapon widget
static st_number_t w_frags; // in deathmatch only, summary of frags stats
static st_percent_t w_health; // health widget
static st_binicon_t w_armsbg; // arms background
static st_multicon_t w_arms[6]; // weapon ownership widgets
static st_multicon_t w_faces; // face status widget
static st_multicon_t w_keyboxes[3]; // keycard widgets
static st_percent_t w_armor; // armor widget
static st_number_t w_ammo[4]; // ammo widgets
static st_number_t w_maxammo[4]; // max ammo widgets
static int st_fragscount; // number of frags so far in deathmatch
static int st_oldhealth = -1; // used to use appopriately pained face
static doom_boolean oldweaponsowned[NUMWEAPONS]; // used for evil grin
static int st_facecount = 0; // count until face changes
static int st_faceindex = 0; // current face index, used by w_faces
static int keyboxes[3]; // holds key-type for each key box on bar
static int st_randomnumber; // a random number per tick
static int st_palette = 0;
static doom_boolean st_stopped = true;


// 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[];
extern int doom_flags;


void ST_Stop(void);


//
// STATUS BAR CODE
//

void ST_refreshBackground(void)
{
    if (st_statusbaron)
    {
        V_DrawPatch(ST_X, 0, STLIB_BG, sbar);

        if (netgame)
            V_DrawPatch(ST_FX, 0, STLIB_BG, faceback);

        V_CopyRect(ST_X, 0, STLIB_BG, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y, STLIB_FG);
    }
}


// Respond to keyboard input events,
//  intercept cheats.
doom_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:
                //        doom_print("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 = mus_runnin + (buf[0] - '0') * 10 + buf[1] - '0' - 1;

                    if (((buf[0] - '0') * 10 + buf[1] - '0') > 35)
                        plyr->message = STSTR_NOMUS;
                    else
                        S_ChangeMusic(musnum, 1);
                }
                else
                {
                    musnum = mus_e1m1 + (buf[0] - '1') * 9 + (buf[1] - '1');

                    if (((buf[0] - '1') * 9 + buf[1] - '1') > 31)
                        plyr->message = STSTR_NOMUS;
                    else
                        S_ChangeMusic(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];
                //doom_sprintf(buf, "ang=0x%x;x,y=(0x%x,0x%x)",
                //        players[consoleplayer].mo->angle,
                //        players[consoleplayer].mo->x,
                //        players[consoleplayer].mo->y);
                doom_strcpy(buf, "ang=0x");
                doom_concat(buf, doom_itoa(players[consoleplayer].mo->angle, 16));
                doom_concat(buf, ";x,y=(0x");
                doom_concat(buf, doom_itoa(players[consoleplayer].mo->x, 16));
                doom_concat(buf, ",0x");
                doom_concat(buf, doom_itoa(players[consoleplayer].mo->y, 16));
                doom_concat(buf, ")");
                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 = 0;
                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;
    doom_boolean    doevilgrin;

    if (priority < 10)
    {
        // dead
        if (!plyr->health)
        {
            priority = 9;
            st_faceindex = ST_DEADFACE;
            st_facecount = 1;
        }
    }

    if (priority < 9)
    {
        if (plyr->bonuscount)
        {
            // picking up bonus
            doevilgrin = false;

            for (i = 0; i < NUMWEAPONS; i++)
            {
                if (oldweaponsowned[i] != plyr->weaponowned[i])
                {
                    doevilgrin = true;
                    oldweaponsowned[i] = plyr->weaponowned[i];
                }
            }
            if (doevilgrin)
            {
                // evil grin if just picked up weapon
                priority = 8;
                st_facecount = ST_EVILGRINCOUNT;
                st_faceindex = ST_calcPainOffset() + ST_EVILGRINOFFSET;
            }
        }

    }

    if (priority < 8)
    {
        if (plyr->damagecount
            && plyr->attacker
            && plyr->attacker != plyr->mo)
        {
            // being attacked
            priority = 7;

            if (plyr->health - st_oldhealth > ST_MUCHPAIN)
            {
                st_facecount = ST_TURNCOUNT;
                st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET;
            }
            else
            {
                badguyangle = R_PointToAngle2(plyr->mo->x,
                                              plyr->mo->y,
                                              plyr->attacker->x,
                                              plyr->attacker->y);

                if (badguyangle > plyr->mo->angle)
                {
                    // whether right or left
                    diffang = badguyangle - plyr->mo->angle;
                    i = diffang > ANG180;
                }
                else
                {
                    // whether left or right
                    diffang = plyr->mo->angle - badguyangle;
                    i = diffang <= ANG180;
                } // confusing, aint it?


                st_facecount = ST_TURNCOUNT;
                st_faceindex = ST_calcPainOffset();

                if (diffang < ANG45)
                {
                    // head-on    
                    st_faceindex += ST_RAMPAGEOFFSET;
                }
                else if (i)
                {
                    // turn face right
                    st_faceindex += ST_TURNOFFSET;
                }
                else
                {
                    // turn face left
                    st_faceindex += ST_TURNOFFSET + 1;
                }
            }
        }
    }

    if (priority < 7)
    {
        // getting hurt because of your own damn stupidity
        if (plyr->damagecount)
        {
            if (plyr->health - st_oldhealth > ST_MUCHPAIN)
            {
                priority = 7;
                st_facecount = ST_TURNCOUNT;
                st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET;
            }
            else
            {
                priority = 6;
                st_facecount = ST_TURNCOUNT;
                st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET;
            }

        }

    }

    if (priority < 6)
    {
        // rapid firing
        if (plyr->attackdown)
        {
            if (lastattackdown == -1)
                lastattackdown = ST_RAMPAGEDELAY;
            else if (!--lastattackdown)
            {
                priority = 5;
                st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET;
                st_facecount = 1;
                lastattackdown = 1;
            }
        }
        else
            lastattackdown = -1;

    }

    if (priority < 5)
    {
        // invulnerability
        if ((plyr->cheats & CF_GODMODE)
            || plyr->powers[pw_invulnerability])
        {
            priority = 4;

            st_faceindex = ST_GODFACE;
            st_facecount = 1;
        }
    }

    // look left or look right if the facecount has timed out
    if (!st_facecount)
    {
        st_faceindex = ST_calcPainOffset() + (st_randomnumber % 3);
        st_facecount = ST_STRAIGHTFACECOUNT;
        priority = 0;
    }

    st_facecount--;
}


void ST_updateWidgets(void)
{
    static int  largeammo = 1994; // means "n/a"
    int         i;

    if (weaponinfo[plyr->readyweapon].ammo == am_noammo)
        w_ready.num = &largeammo;
    else
        w_ready.num = &plyr->ammo[weaponinfo[plyr->readyweapon].ammo];

    w_ready.data = plyr->readyweapon;

    // 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;
}


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(doom_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(doom_boolean fullscreen, doom_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 (doom_flags & DOOM_FLAG_MENU_DARKEN_BG)
    {
        ST_doRefresh();
    }
    else
    {
        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++)
    {
        //doom_sprintf(namebuf, "STTNUM%d", i);
        doom_strcpy(namebuf, "STTNUM");
        doom_concat(namebuf, doom_itoa(i, 10));
        tallnum[i] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC);

        //doom_sprintf(namebuf, "STYSNUM%d", i);
        doom_strcpy(namebuf, "STYSNUM");
        doom_concat(namebuf, doom_itoa(i, 10));
        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++)
    {
        //doom_sprintf(namebuf, "STKEYS%d", i);
        doom_strcpy(namebuf, "STKEYS");
        doom_concat(namebuf, doom_itoa(i, 10));
        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++)
    {
        //doom_sprintf(namebuf, "STGNUM%d", i + 2);
        doom_strcpy(namebuf, "STGNUM");
        doom_concat(namebuf, doom_itoa(i + 2, 10));

        // gray #
        arms[i][0] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC);

        // yellow #
        arms[i][1] = shortnum[i + 2];
    }

    // face backgrounds for different color players
    //doom_sprintf(namebuf, "STFB%d", consoleplayer);
    doom_strcpy(namebuf, "STFB");
    doom_concat(namebuf, doom_itoa(consoleplayer, 10));
    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++)
        {
            //doom_sprintf(namebuf, "STFST%d%d", i, j);
            doom_strcpy(namebuf, "STFST");
            doom_concat(namebuf, doom_itoa(i, 10));
            doom_concat(namebuf, doom_itoa(j, 10));
            faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
        }
        //doom_sprintf(namebuf, "STFTR%d0", i);        // turn right
        doom_strcpy(namebuf, "STFTR");
        doom_concat(namebuf, doom_itoa(i, 10));
        doom_concat(namebuf, "0");
        faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
        //doom_sprintf(namebuf, "STFTL%d0", i);        // turn left
        doom_strcpy(namebuf, "STFTL");
        doom_concat(namebuf, doom_itoa(i, 10));
        doom_concat(namebuf, "0");
        faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
        //doom_sprintf(namebuf, "STFOUCH%d", i);        // ouch!
        doom_strcpy(namebuf, "STFOUCH");
        doom_concat(namebuf, doom_itoa(i, 10));
        faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
        //doom_sprintf(namebuf, "STFEVL%d", i);        // evil grin ;)
        doom_strcpy(namebuf, "STFEVL");
        doom_concat(namebuf, doom_itoa(i, 10));
        faces[facenum++] = W_CacheLumpName(namebuf, PU_STATIC);
        //doom_sprintf(namebuf, "STFKILL%d", i);        // pissed off
        doom_strcpy(namebuf, "STFKILL");
        doom_concat(namebuf, doom_itoa(i, 10));
        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);
}

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);
}
int
SlopeDiv
( unsigned        num,
  unsigned        den)
{
    unsigned         ans;
    
    if (den < 512)
        return SLOPERANGE;

    ans = (num<<3)/(den>>8);

    return ans <= SLOPERANGE ? ans : SLOPERANGE;
}




int finetangent[4096] =
{
    -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683,
    -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368,
    -5178251,-4882318,-4618375,-4381502,-4167737,-3973855,-3797206,-3635590,
    -3487165,-3350381,-3223918,-3106651,-2997613,-2895966,-2800983,-2712030,
    -2628549,-2550052,-2476104,-2406322,-2340362,-2277919,-2218719,-2162516,
    -2109087,-2058233,-2009771,-1963536,-1919378,-1877161,-1836758,-1798063,
    -1760956,-1725348,-1691149,-1658278,-1626658,-1596220,-1566898,-1538632,
    -1511367,-1485049,-1459630,-1435065,-1411312,-1388330,-1366084,-1344537,
    -1323658,-1303416,-1283783,-1264730,-1246234,-1228269,-1210813,-1193846,
    -1177345,-1161294,-1145673,-1130465,-1115654,-1101225,-1087164,-1073455,
    -1060087,-1047046,-1034322,-1021901,-1009774,-997931,-986361,-975054,
    -964003,-953199,-942633,-932298,-922186,-912289,-902602,-893117,
    -883829,-874730,-865817,-857081,-848520,-840127,-831898,-823827,
    -815910,-808143,-800521,-793041,-785699,-778490,-771411,-764460,
    -757631,-750922,-744331,-737853,-731486,-725227,-719074,-713023,
    -707072,-701219,-695462,-689797,-684223,-678737,-673338,-668024,
    -662792,-657640,-652568,-647572,-642651,-637803,-633028,-628323,
    -623686,-619117,-614613,-610174,-605798,-601483,-597229,-593033,
    -588896,-584815,-580789,-576818,-572901,-569035,-565221,-561456,
    -557741,-554074,-550455,-546881,-543354,-539870,-536431,-533034,
    -529680,-526366,-523094,-519861,-516667,-513512,-510394,-507313,
    -504269,-501261,-498287,-495348,-492443,-489571,-486732,-483925,
    -481150,-478406,-475692,-473009,-470355,-467730,-465133,-462565,
    -460024,-457511,-455024,-452564,-450129,-447720,-445337,-442978,
    -440643,-438332,-436045,-433781,-431540,-429321,-427125,-424951,
    -422798,-420666,-418555,-416465,-414395,-412344,-410314,-408303,
    -406311,-404338,-402384,-400448,-398530,-396630,-394747,-392882,
    -391034,-389202,-387387,-385589,-383807,-382040,-380290,-378555,
    -376835,-375130,-373440,-371765,-370105,-368459,-366826,-365208,
    -363604,-362013,-360436,-358872,-357321,-355783,-354257,-352744,
    -351244,-349756,-348280,-346816,-345364,-343924,-342495,-341078,
    -339671,-338276,-336892,-335519,-334157,-332805,-331464,-330133,
    -328812,-327502,-326201,-324910,-323629,-322358,-321097,-319844,
    -318601,-317368,-316143,-314928,-313721,-312524,-311335,-310154,
    -308983,-307819,-306664,-305517,-304379,-303248,-302126,-301011,
    -299904,-298805,-297714,-296630,-295554,-294485,-293423,-292369,
    -291322,-290282,-289249,-288223,-287204,-286192,-285186,-284188,
    -283195,-282210,-281231,-280258,-279292,-278332,-277378,-276430,
    -275489,-274553,-273624,-272700,-271782,-270871,-269965,-269064,
    -268169,-267280,-266397,-265519,-264646,-263779,-262917,-262060,
    -261209,-260363,-259522,-258686,-257855,-257029,-256208,-255392,
    -254581,-253774,-252973,-252176,-251384,-250596,-249813,-249035,
    -248261,-247492,-246727,-245966,-245210,-244458,-243711,-242967,
    -242228,-241493,-240763,-240036,-239314,-238595,-237881,-237170,
    -236463,-235761,-235062,-234367,-233676,-232988,-232304,-231624,
    -230948,-230275,-229606,-228941,-228279,-227621,-226966,-226314,
    -225666,-225022,-224381,-223743,-223108,-222477,-221849,-221225,
    -220603,-219985,-219370,-218758,-218149,-217544,-216941,-216341,
    -215745,-215151,-214561,-213973,-213389,-212807,-212228,-211652,
    -211079,-210509,-209941,-209376,-208815,-208255,-207699,-207145,
    -206594,-206045,-205500,-204956,-204416,-203878,-203342,-202809,
    -202279,-201751,-201226,-200703,-200182,-199664,-199149,-198636,
    -198125,-197616,-197110,-196606,-196105,-195606,-195109,-194614,
    -194122,-193631,-193143,-192658,-192174,-191693,-191213,-190736,
    -190261,-189789,-189318,-188849,-188382,-187918,-187455,-186995,
    -186536,-186080,-185625,-185173,-184722,-184274,-183827,-183382,
    -182939,-182498,-182059,-181622,-181186,-180753,-180321,-179891,
    -179463,-179037,-178612,-178190,-177769,-177349,-176932,-176516,
    -176102,-175690,-175279,-174870,-174463,-174057,-173653,-173251,
    -172850,-172451,-172053,-171657,-171263,-170870,-170479,-170089,
    -169701,-169315,-168930,-168546,-168164,-167784,-167405,-167027,
    -166651,-166277,-165904,-165532,-165162,-164793,-164426,-164060,
    -163695,-163332,-162970,-162610,-162251,-161893,-161537,-161182,
    -160828,-160476,-160125,-159775,-159427,-159079,-158734,-158389,
    -158046,-157704,-157363,-157024,-156686,-156349,-156013,-155678,
    -155345,-155013,-154682,-154352,-154024,-153697,-153370,-153045,
    -152722,-152399,-152077,-151757,-151438,-151120,-150803,-150487,
    -150172,-149859,-149546,-149235,-148924,-148615,-148307,-148000,
    -147693,-147388,-147084,-146782,-146480,-146179,-145879,-145580,
    -145282,-144986,-144690,-144395,-144101,-143808,-143517,-143226,
    -142936,-142647,-142359,-142072,-141786,-141501,-141217,-140934,
    -140651,-140370,-140090,-139810,-139532,-139254,-138977,-138701,
    -138426,-138152,-137879,-137607,-137335,-137065,-136795,-136526,
    -136258,-135991,-135725,-135459,-135195,-134931,-134668,-134406,
    -134145,-133884,-133625,-133366,-133108,-132851,-132594,-132339,
    -132084,-131830,-131576,-131324,-131072,-130821,-130571,-130322,
    -130073,-129825,-129578,-129332,-129086,-128841,-128597,-128353,
    -128111,-127869,-127627,-127387,-127147,-126908,-126669,-126432,
    -126195,-125959,-125723,-125488,-125254,-125020,-124787,-124555,
    -124324,-124093,-123863,-123633,-123404,-123176,-122949,-122722,
    -122496,-122270,-122045,-121821,-121597,-121374,-121152,-120930,
    -120709,-120489,-120269,-120050,-119831,-119613,-119396,-119179,
    -118963,-118747,-118532,-118318,-118104,-117891,-117678,-117466,
    -117254,-117044,-116833,-116623,-116414,-116206,-115998,-115790,
    -115583,-115377,-115171,-114966,-114761,-114557,-114354,-114151,
    -113948,-113746,-113545,-113344,-113143,-112944,-112744,-112546,
    -112347,-112150,-111952,-111756,-111560,-111364,-111169,-110974,
    -110780,-110586,-110393,-110200,-110008,-109817,-109626,-109435,
    -109245,-109055,-108866,-108677,-108489,-108301,-108114,-107927,
    -107741,-107555,-107369,-107184,-107000,-106816,-106632,-106449,
    -106266,-106084,-105902,-105721,-105540,-105360,-105180,-105000,
    -104821,-104643,-104465,-104287,-104109,-103933,-103756,-103580,
    -103404,-103229,-103054,-102880,-102706,-102533,-102360,-102187,
    -102015,-101843,-101671,-101500,-101330,-101159,-100990,-100820,
    -100651,-100482,-100314,-100146,-99979,-99812,-99645,-99479,
    -99313,-99148,-98982,-98818,-98653,-98489,-98326,-98163,
    -98000,-97837,-97675,-97513,-97352,-97191,-97030,-96870,
    -96710,-96551,-96391,-96233,-96074,-95916,-95758,-95601,
    -95444,-95287,-95131,-94975,-94819,-94664,-94509,-94354,
    -94200,-94046,-93892,-93739,-93586,-93434,-93281,-93129,
    -92978,-92826,-92675,-92525,-92375,-92225,-92075,-91926,
    -91777,-91628,-91480,-91332,-91184,-91036,-90889,-90742,
    -90596,-90450,-90304,-90158,-90013,-89868,-89724,-89579,
    -89435,-89292,-89148,-89005,-88862,-88720,-88577,-88435,
    -88294,-88152,-88011,-87871,-87730,-87590,-87450,-87310,
    -87171,-87032,-86893,-86755,-86616,-86479,-86341,-86204,
    -86066,-85930,-85793,-85657,-85521,-85385,-85250,-85114,
    -84980,-84845,-84710,-84576,-84443,-84309,-84176,-84043,
    -83910,-83777,-83645,-83513,-83381,-83250,-83118,-82987,
    -82857,-82726,-82596,-82466,-82336,-82207,-82078,-81949,
    -81820,-81691,-81563,-81435,-81307,-81180,-81053,-80925,
    -80799,-80672,-80546,-80420,-80294,-80168,-80043,-79918,
    -79793,-79668,-79544,-79420,-79296,-79172,-79048,-78925,
    -78802,-78679,-78557,-78434,-78312,-78190,-78068,-77947,
    -77826,-77705,-77584,-77463,-77343,-77223,-77103,-76983,
    -76864,-76744,-76625,-76506,-76388,-76269,-76151,-76033,
    -75915,-75797,-75680,-75563,-75446,-75329,-75213,-75096,
    -74980,-74864,-74748,-74633,-74517,-74402,-74287,-74172,
    -74058,-73944,-73829,-73715,-73602,-73488,-73375,-73262,
    -73149,-73036,-72923,-72811,-72699,-72587,-72475,-72363,
    -72252,-72140,-72029,-71918,-71808,-71697,-71587,-71477,
    -71367,-71257,-71147,-71038,-70929,-70820,-70711,-70602,
    -70494,-70385,-70277,-70169,-70061,-69954,-69846,-69739,
    -69632,-69525,-69418,-69312,-69205,-69099,-68993,-68887,
    -68781,-68676,-68570,-68465,-68360,-68255,-68151,-68046,
    -67942,-67837,-67733,-67629,-67526,-67422,-67319,-67216,
    -67113,-67010,-66907,-66804,-66702,-66600,-66498,-66396,
    -66294,-66192,-66091,-65989,-65888,-65787,-65686,-65586,
    -65485,-65385,-65285,-65185,-65085,-64985,-64885,-64786,
    -64687,-64587,-64488,-64389,-64291,-64192,-64094,-63996,
    -63897,-63799,-63702,-63604,-63506,-63409,-63312,-63215,
    -63118,-63021,-62924,-62828,-62731,-62635,-62539,-62443,
    -62347,-62251,-62156,-62060,-61965,-61870,-61775,-61680,
    -61585,-61491,-61396,-61302,-61208,-61114,-61020,-60926,
    -60833,-60739,-60646,-60552,-60459,-60366,-60273,-60181,
    -60088,-59996,-59903,-59811,-59719,-59627,-59535,-59444,
    -59352,-59261,-59169,-59078,-58987,-58896,-58805,-58715,
    -58624,-58534,-58443,-58353,-58263,-58173,-58083,-57994,
    -57904,-57815,-57725,-57636,-57547,-57458,-57369,-57281,
    -57192,-57104,-57015,-56927,-56839,-56751,-56663,-56575,
    -56487,-56400,-56312,-56225,-56138,-56051,-55964,-55877,
    -55790,-55704,-55617,-55531,-55444,-55358,-55272,-55186,
    -55100,-55015,-54929,-54843,-54758,-54673,-54587,-54502,
    -54417,-54333,-54248,-54163,-54079,-53994,-53910,-53826,
    -53741,-53657,-53574,-53490,-53406,-53322,-53239,-53156,
    -53072,-52989,-52906,-52823,-52740,-52657,-52575,-52492,
    -52410,-52327,-52245,-52163,-52081,-51999,-51917,-51835,
    -51754,-51672,-51591,-51509,-51428,-51347,-51266,-51185,
    -51104,-51023,-50942,-50862,-50781,-50701,-50621,-50540,
    -50460,-50380,-50300,-50221,-50141,-50061,-49982,-49902,
    -49823,-49744,-49664,-49585,-49506,-49427,-49349,-49270,
    -49191,-49113,-49034,-48956,-48878,-48799,-48721,-48643,
    -48565,-48488,-48410,-48332,-48255,-48177,-48100,-48022,
    -47945,-47868,-47791,-47714,-47637,-47560,-47484,-47407,
    -47331,-47254,-47178,-47102,-47025,-46949,-46873,-46797,
    -46721,-46646,-46570,-46494,-46419,-46343,-46268,-46193,
    -46118,-46042,-45967,-45892,-45818,-45743,-45668,-45593,
    -45519,-45444,-45370,-45296,-45221,-45147,-45073,-44999,
    -44925,-44851,-44778,-44704,-44630,-44557,-44483,-44410,
    -44337,-44263,-44190,-44117,-44044,-43971,-43898,-43826,
    -43753,-43680,-43608,-43535,-43463,-43390,-43318,-43246,
    -43174,-43102,-43030,-42958,-42886,-42814,-42743,-42671,
    -42600,-42528,-42457,-42385,-42314,-42243,-42172,-42101,
    -42030,-41959,-41888,-41817,-41747,-41676,-41605,-41535,
    -41465,-41394,-41324,-41254,-41184,-41113,-41043,-40973,
    -40904,-40834,-40764,-40694,-40625,-40555,-40486,-40416,
    -40347,-40278,-40208,-40139,-40070,-40001,-39932,-39863,
    -39794,-39726,-39657,-39588,-39520,-39451,-39383,-39314,
    -39246,-39178,-39110,-39042,-38973,-38905,-38837,-38770,
    -38702,-38634,-38566,-38499,-38431,-38364,-38296,-38229,
    -38161,-38094,-38027,-37960,-37893,-37826,-37759,-37692,
    -37625,-37558,-37491,-37425,-37358,-37291,-37225,-37158,
    -37092,-37026,-36959,-36893,-36827,-36761,-36695,-36629,
    -36563,-36497,-36431,-36365,-36300,-36234,-36168,-36103,
    -36037,-35972,-35907,-35841,-35776,-35711,-35646,-35580,
    -35515,-35450,-35385,-35321,-35256,-35191,-35126,-35062,
    -34997,-34932,-34868,-34803,-34739,-34675,-34610,-34546,
    -34482,-34418,-34354,-34289,-34225,-34162,-34098,-34034,
    -33970,-33906,-33843,-33779,-33715,-33652,-33588,-33525,
    -33461,-33398,-33335,-33272,-33208,-33145,-33082,-33019,
    -32956,-32893,-32830,-32767,-32705,-32642,-32579,-32516,
    -32454,-32391,-32329,-32266,-32204,-32141,-32079,-32017,
    -31955,-31892,-31830,-31768,-31706,-31644,-31582,-31520,
    -31458,-31396,-31335,-31273,-31211,-31150,-31088,-31026,
    -30965,-30904,-30842,-30781,-30719,-30658,-30597,-30536,
    -30474,-30413,-30352,-30291,-30230,-30169,-30108,-30048,
    -29987,-29926,-29865,-29805,-29744,-29683,-29623,-29562,
    -29502,-29441,-29381,-29321,-29260,-29200,-29140,-29080,
    -29020,-28959,-28899,-28839,-28779,-28719,-28660,-28600,
    -28540,-28480,-28420,-28361,-28301,-28241,-28182,-28122,
    -28063,-28003,-27944,-27884,-27825,-27766,-27707,-27647,
    -27588,-27529,-27470,-27411,-27352,-27293,-27234,-27175,
    -27116,-27057,-26998,-26940,-26881,-26822,-26763,-26705,
    -26646,-26588,-26529,-26471,-26412,-26354,-26295,-26237,
    -26179,-26120,-26062,-26004,-25946,-25888,-25830,-25772,
    -25714,-25656,-25598,-25540,-25482,-25424,-25366,-25308,
    -25251,-25193,-25135,-25078,-25020,-24962,-24905,-24847,
    -24790,-24732,-24675,-24618,-24560,-24503,-24446,-24389,
    -24331,-24274,-24217,-24160,-24103,-24046,-23989,-23932,
    -23875,-23818,-23761,-23704,-23647,-23591,-23534,-23477,
    -23420,-23364,-23307,-23250,-23194,-23137,-23081,-23024,
    -22968,-22911,-22855,-22799,-22742,-22686,-22630,-22573,
    -22517,-22461,-22405,-22349,-22293,-22237,-22181,-22125,
    -22069,-22013,-21957,-21901,-21845,-21789,-21733,-21678,
    -21622,-21566,-21510,-21455,-21399,-21343,-21288,-21232,
    -21177,-21121,-21066,-21010,-20955,-20900,-20844,-20789,
    -20734,-20678,-20623,-20568,-20513,-20457,-20402,-20347,
    -20292,-20237,-20182,-20127,-20072,-20017,-19962,-19907,
    -19852,-19797,-19742,-19688,-19633,-19578,-19523,-19469,
    -19414,-19359,-19305,-19250,-19195,-19141,-19086,-19032,
    -18977,-18923,-18868,-18814,-18760,-18705,-18651,-18597,
    -18542,-18488,-18434,-18380,-18325,-18271,-18217,-18163,
    -18109,-18055,-18001,-17946,-17892,-17838,-17784,-17731,
    -17677,-17623,-17569,-17515,-17461,-17407,-17353,-17300,
    -17246,-17192,-17138,-17085,-17031,-16977,-16924,-16870,
    -16817,-16763,-16710,-16656,-16603,-16549,-16496,-16442,
    -16389,-16335,-16282,-16229,-16175,-16122,-16069,-16015,
    -15962,-15909,-15856,-15802,-15749,-15696,-15643,-15590,
    -15537,-15484,-15431,-15378,-15325,-15272,-15219,-15166,
    -15113,-15060,-15007,-14954,-14901,-14848,-14795,-14743,
    -14690,-14637,-14584,-14531,-14479,-14426,-14373,-14321,
    -14268,-14215,-14163,-14110,-14057,-14005,-13952,-13900,
    -13847,-13795,-13742,-13690,-13637,-13585,-13533,-13480,
    -13428,-13375,-13323,-13271,-13218,-13166,-13114,-13062,
    -13009,-12957,-12905,-12853,-12800,-12748,-12696,-12644,
    -12592,-12540,-12488,-12436,-12383,-12331,-12279,-12227,
    -12175,-12123,-12071,-12019,-11967,-11916,-11864,-11812,
    -11760,-11708,-11656,-11604,-11552,-11501,-11449,-11397,
    -11345,-11293,-11242,-11190,-11138,-11086,-11035,-10983,
    -10931,-10880,-10828,-10777,-10725,-10673,-10622,-10570,
    -10519,-10467,-10415,-10364,-10312,-10261,-10209,-10158,
    -10106,-10055,-10004,-9952,-9901,-9849,-9798,-9747,
    -9695,-9644,-9592,-9541,-9490,-9438,-9387,-9336,
    -9285,-9233,-9182,-9131,-9080,-9028,-8977,-8926,
    -8875,-8824,-8772,-8721,-8670,-8619,-8568,-8517,
    -8466,-8414,-8363,-8312,-8261,-8210,-8159,-8108,
    -8057,-8006,-7955,-7904,-7853,-7802,-7751,-7700,
    -7649,-7598,-7547,-7496,-7445,-7395,-7344,-7293,
    -7242,-7191,-7140,-7089,-7038,-6988,-6937,-6886,
    -6835,-6784,-6733,-6683,-6632,-6581,-6530,-6480,
    -6429,-6378,-6327,-6277,-6226,-6175,-6124,-6074,
    -6023,-5972,-5922,-5871,-5820,-5770,-5719,-5668,
    -5618,-5567,-5517,-5466,-5415,-5365,-5314,-5264,
    -5213,-5162,-5112,-5061,-5011,-4960,-4910,-4859,
    -4808,-4758,-4707,-4657,-4606,-4556,-4505,-4455,
    -4404,-4354,-4303,-4253,-4202,-4152,-4101,-4051,
    -4001,-3950,-3900,-3849,-3799,-3748,-3698,-3648,
    -3597,-3547,-3496,-3446,-3395,-3345,-3295,-3244,
    -3194,-3144,-3093,-3043,-2992,-2942,-2892,-2841,
    -2791,-2741,-2690,-2640,-2590,-2539,-2489,-2439,
    -2388,-2338,-2288,-2237,-2187,-2137,-2086,-2036,
    -1986,-1935,-1885,-1835,-1784,-1734,-1684,-1633,
    -1583,-1533,-1483,-1432,-1382,-1332,-1281,-1231,
    -1181,-1131,-1080,-1030,-980,-929,-879,-829,
    -779,-728,-678,-628,-578,-527,-477,-427,
    -376,-326,-276,-226,-175,-125,-75,-25,
    25,75,125,175,226,276,326,376,
    427,477,527,578,628,678,728,779,
    829,879,929,980,1030,1080,1131,1181,
    1231,1281,1332,1382,1432,1483,1533,1583,
    1633,1684,1734,1784,1835,1885,1935,1986,
    2036,2086,2137,2187,2237,2288,2338,2388,
    2439,2489,2539,2590,2640,2690,2741,2791,
    2841,2892,2942,2992,3043,3093,3144,3194,
    3244,3295,3345,3395,3446,3496,3547,3597,
    3648,3698,3748,3799,3849,3900,3950,4001,
    4051,4101,4152,4202,4253,4303,4354,4404,
    4455,4505,4556,4606,4657,4707,4758,4808,
    4859,4910,4960,5011,5061,5112,5162,5213,
    5264,5314,5365,5415,5466,5517,5567,5618,
    5668,5719,5770,5820,5871,5922,5972,6023,
    6074,6124,6175,6226,6277,6327,6378,6429,
    6480,6530,6581,6632,6683,6733,6784,6835,
    6886,6937,6988,7038,7089,7140,7191,7242,
    7293,7344,7395,7445,7496,7547,7598,7649,
    7700,7751,7802,7853,7904,7955,8006,8057,
    8108,8159,8210,8261,8312,8363,8414,8466,
    8517,8568,8619,8670,8721,8772,8824,8875,
    8926,8977,9028,9080,9131,9182,9233,9285,
    9336,9387,9438,9490,9541,9592,9644,9695,
    9747,9798,9849,9901,9952,10004,10055,10106,
    10158,10209,10261,10312,10364,10415,10467,10519,
    10570,10622,10673,10725,10777,10828,10880,10931,
    10983,11035,11086,11138,11190,11242,11293,11345,
    11397,11449,11501,11552,11604,11656,11708,11760,
    11812,11864,11916,11967,12019,12071,12123,12175,
    12227,12279,12331,12383,12436,12488,12540,12592,
    12644,12696,12748,12800,12853,12905,12957,13009,
    13062,13114,13166,13218,13271,13323,13375,13428,
    13480,13533,13585,13637,13690,13742,13795,13847,
    13900,13952,14005,14057,14110,14163,14215,14268,
    14321,14373,14426,14479,14531,14584,14637,14690,
    14743,14795,14848,14901,14954,15007,15060,15113,
    15166,15219,15272,15325,15378,15431,15484,15537,
    15590,15643,15696,15749,15802,15856,15909,15962,
    16015,16069,16122,16175,16229,16282,16335,16389,
    16442,16496,16549,16603,16656,16710,16763,16817,
    16870,16924,16977,17031,17085,17138,17192,17246,
    17300,17353,17407,17461,17515,17569,17623,17677,
    17731,17784,17838,17892,17946,18001,18055,18109,
    18163,18217,18271,18325,18380,18434,18488,18542,
    18597,18651,18705,18760,18814,18868,18923,18977,
    19032,19086,19141,19195,19250,19305,19359,19414,
    19469,19523,19578,19633,19688,19742,19797,19852,
    19907,19962,20017,20072,20127,20182,20237,20292,
    20347,20402,20457,20513,20568,20623,20678,20734,
    20789,20844,20900,20955,21010,21066,21121,21177,
    21232,21288,21343,21399,21455,21510,21566,21622,
    21678,21733,21789,21845,21901,21957,22013,22069,
    22125,22181,22237,22293,22349,22405,22461,22517,
    22573,22630,22686,22742,22799,22855,22911,22968,
    23024,23081,23137,23194,23250,23307,23364,23420,
    23477,23534,23591,23647,23704,23761,23818,23875,
    23932,23989,24046,24103,24160,24217,24274,24331,
    24389,24446,24503,24560,24618,24675,24732,24790,
    24847,24905,24962,25020,25078,25135,25193,25251,
    25308,25366,25424,25482,25540,25598,25656,25714,
    25772,25830,25888,25946,26004,26062,26120,26179,
    26237,26295,26354,26412,26471,26529,26588,26646,
    26705,26763,26822,26881,26940,26998,27057,27116,
    27175,27234,27293,27352,27411,27470,27529,27588,
    27647,27707,27766,27825,27884,27944,28003,28063,
    28122,28182,28241,28301,28361,28420,28480,28540,
    28600,28660,28719,28779,28839,28899,28959,29020,
    29080,29140,29200,29260,29321,29381,29441,29502,
    29562,29623,29683,29744,29805,29865,29926,29987,
    30048,30108,30169,30230,30291,30352,30413,30474,
    30536,30597,30658,30719,30781,30842,30904,30965,
    31026,31088,31150,31211,31273,31335,31396,31458,
    31520,31582,31644,31706,31768,31830,31892,31955,
    32017,32079,32141,32204,32266,32329,32391,32454,
    32516,32579,32642,32705,32767,32830,32893,32956,
    33019,33082,33145,33208,33272,33335,33398,33461,
    33525,33588,33652,33715,33779,33843,33906,33970,
    34034,34098,34162,34225,34289,34354,34418,34482,
    34546,34610,34675,34739,34803,34868,34932,34997,
    35062,35126,35191,35256,35321,35385,35450,35515,
    35580,35646,35711,35776,35841,35907,35972,36037,
    36103,36168,36234,36300,36365,36431,36497,36563,
    36629,36695,36761,36827,36893,36959,37026,37092,
    37158,37225,37291,37358,37425,37491,37558,37625,
    37692,37759,37826,37893,37960,38027,38094,38161,
    38229,38296,38364,38431,38499,38566,38634,38702,
    38770,38837,38905,38973,39042,39110,39178,39246,
    39314,39383,39451,39520,39588,39657,39726,39794,
    39863,39932,40001,40070,40139,40208,40278,40347,
    40416,40486,40555,40625,40694,40764,40834,40904,
    40973,41043,41113,41184,41254,41324,41394,41465,
    41535,41605,41676,41747,41817,41888,41959,42030,
    42101,42172,42243,42314,42385,42457,42528,42600,
    42671,42743,42814,42886,42958,43030,43102,43174,
    43246,43318,43390,43463,43535,43608,43680,43753,
    43826,43898,43971,44044,44117,44190,44263,44337,
    44410,44483,44557,44630,44704,44778,44851,44925,
    44999,45073,45147,45221,45296,45370,45444,45519,
    45593,45668,45743,45818,45892,45967,46042,46118,
    46193,46268,46343,46419,46494,46570,46646,46721,
    46797,46873,46949,47025,47102,47178,47254,47331,
    47407,47484,47560,47637,47714,47791,47868,47945,
    48022,48100,48177,48255,48332,48410,48488,48565,
    48643,48721,48799,48878,48956,49034,49113,49191,
    49270,49349,49427,49506,49585,49664,49744,49823,
    49902,49982,50061,50141,50221,50300,50380,50460,
    50540,50621,50701,50781,50862,50942,51023,51104,
    51185,51266,51347,51428,51509,51591,51672,51754,
    51835,51917,51999,52081,52163,52245,52327,52410,
    52492,52575,52657,52740,52823,52906,52989,53072,
    53156,53239,53322,53406,53490,53574,53657,53741,
    53826,53910,53994,54079,54163,54248,54333,54417,
    54502,54587,54673,54758,54843,54929,55015,55100,
    55186,55272,55358,55444,55531,55617,55704,55790,
    55877,55964,56051,56138,56225,56312,56400,56487,
    56575,56663,56751,56839,56927,57015,57104,57192,
    57281,57369,57458,57547,57636,57725,57815,57904,
    57994,58083,58173,58263,58353,58443,58534,58624,
    58715,58805,58896,58987,59078,59169,59261,59352,
    59444,59535,59627,59719,59811,59903,59996,60088,
    60181,60273,60366,60459,60552,60646,60739,60833,
    60926,61020,61114,61208,61302,61396,61491,61585,
    61680,61775,61870,61965,62060,62156,62251,62347,
    62443,62539,62635,62731,62828,62924,63021,63118,
    63215,63312,63409,63506,63604,63702,63799,63897,
    63996,64094,64192,64291,64389,64488,64587,64687,
    64786,64885,64985,65085,65185,65285,65385,65485,
    65586,65686,65787,65888,65989,66091,66192,66294,
    66396,66498,66600,66702,66804,66907,67010,67113,
    67216,67319,67422,67526,67629,67733,67837,67942,
    68046,68151,68255,68360,68465,68570,68676,68781,
    68887,68993,69099,69205,69312,69418,69525,69632,
    69739,69846,69954,70061,70169,70277,70385,70494,
    70602,70711,70820,70929,71038,71147,71257,71367,
    71477,71587,71697,71808,71918,72029,72140,72252,
    72363,72475,72587,72699,72811,72923,73036,73149,
    73262,73375,73488,73602,73715,73829,73944,74058,
    74172,74287,74402,74517,74633,74748,74864,74980,
    75096,75213,75329,75446,75563,75680,75797,75915,
    76033,76151,76269,76388,76506,76625,76744,76864,
    76983,77103,77223,77343,77463,77584,77705,77826,
    77947,78068,78190,78312,78434,78557,78679,78802,
    78925,79048,79172,79296,79420,79544,79668,79793,
    79918,80043,80168,80294,80420,80546,80672,80799,
    80925,81053,81180,81307,81435,81563,81691,81820,
    81949,82078,82207,82336,82466,82596,82726,82857,
    82987,83118,83250,83381,83513,83645,83777,83910,
    84043,84176,84309,84443,84576,84710,84845,84980,
    85114,85250,85385,85521,85657,85793,85930,86066,
    86204,86341,86479,86616,86755,86893,87032,87171,
    87310,87450,87590,87730,87871,88011,88152,88294,
    88435,88577,88720,88862,89005,89148,89292,89435,
    89579,89724,89868,90013,90158,90304,90450,90596,
    90742,90889,91036,91184,91332,91480,91628,91777,
    91926,92075,92225,92375,92525,92675,92826,92978,
    93129,93281,93434,93586,93739,93892,94046,94200,
    94354,94509,94664,94819,94975,95131,95287,95444,
    95601,95758,95916,96074,96233,96391,96551,96710,
    96870,97030,97191,97352,97513,97675,97837,98000,
    98163,98326,98489,98653,98818,98982,99148,99313,
    99479,99645,99812,99979,100146,100314,100482,100651,
    100820,100990,101159,101330,101500,101671,101843,102015,
    102187,102360,102533,102706,102880,103054,103229,103404,
    103580,103756,103933,104109,104287,104465,104643,104821,
    105000,105180,105360,105540,105721,105902,106084,106266,
    106449,106632,106816,107000,107184,107369,107555,107741,
    107927,108114,108301,108489,108677,108866,109055,109245,
    109435,109626,109817,110008,110200,110393,110586,110780,
    110974,111169,111364,111560,111756,111952,112150,112347,
    112546,112744,112944,113143,113344,113545,113746,113948,
    114151,114354,114557,114761,114966,115171,115377,115583,
    115790,115998,116206,116414,116623,116833,117044,117254,
    117466,117678,117891,118104,118318,118532,118747,118963,
    119179,119396,119613,119831,120050,120269,120489,120709,
    120930,121152,121374,121597,121821,122045,122270,122496,
    122722,122949,123176,123404,123633,123863,124093,124324,
    124555,124787,125020,125254,125488,125723,125959,126195,
    126432,126669,126908,127147,127387,127627,127869,128111,
    128353,128597,128841,129086,129332,129578,129825,130073,
    130322,130571,130821,131072,131324,131576,131830,132084,
    132339,132594,132851,133108,133366,133625,133884,134145,
    134406,134668,134931,135195,135459,135725,135991,136258,
    136526,136795,137065,137335,137607,137879,138152,138426,
    138701,138977,139254,139532,139810,140090,140370,140651,
    140934,141217,141501,141786,142072,142359,142647,142936,
    143226,143517,143808,144101,144395,144690,144986,145282,
    145580,145879,146179,146480,146782,147084,147388,147693,
    148000,148307,148615,148924,149235,149546,149859,150172,
    150487,150803,151120,151438,151757,152077,152399,152722,
    153045,153370,153697,154024,154352,154682,155013,155345,
    155678,156013,156349,156686,157024,157363,157704,158046,
    158389,158734,159079,159427,159775,160125,160476,160828,
    161182,161537,161893,162251,162610,162970,163332,163695,
    164060,164426,164793,165162,165532,165904,166277,166651,
    167027,167405,167784,168164,168546,168930,169315,169701,
    170089,170479,170870,171263,171657,172053,172451,172850,
    173251,173653,174057,174463,174870,175279,175690,176102,
    176516,176932,177349,177769,178190,178612,179037,179463,
    179891,180321,180753,181186,181622,182059,182498,182939,
    183382,183827,184274,184722,185173,185625,186080,186536,
    186995,187455,187918,188382,188849,189318,189789,190261,
    190736,191213,191693,192174,192658,193143,193631,194122,
    194614,195109,195606,196105,196606,197110,197616,198125,
    198636,199149,199664,200182,200703,201226,201751,202279,
    202809,203342,203878,204416,204956,205500,206045,206594,
    207145,207699,208255,208815,209376,209941,210509,211079,
    211652,212228,212807,213389,213973,214561,215151,215745,
    216341,216941,217544,218149,218758,219370,219985,220603,
    221225,221849,222477,223108,223743,224381,225022,225666,
    226314,226966,227621,228279,228941,229606,230275,230948,
    231624,232304,232988,233676,234367,235062,235761,236463,
    237170,237881,238595,239314,240036,240763,241493,242228,
    242967,243711,244458,245210,245966,246727,247492,248261,
    249035,249813,250596,251384,252176,252973,253774,254581,
    255392,256208,257029,257855,258686,259522,260363,261209,
    262060,262917,263779,264646,265519,266397,267280,268169,
    269064,269965,270871,271782,272700,273624,274553,275489,
    276430,277378,278332,279292,280258,281231,282210,283195,
    284188,285186,286192,287204,288223,289249,290282,291322,
    292369,293423,294485,295554,296630,297714,298805,299904,
    301011,302126,303248,304379,305517,306664,307819,308983,
    310154,311335,312524,313721,314928,316143,317368,318601,
    319844,321097,322358,323629,324910,326201,327502,328812,
    330133,331464,332805,334157,335519,336892,338276,339671,
    341078,342495,343924,345364,346816,348280,349756,351244,
    352744,354257,355783,357321,358872,360436,362013,363604,
    365208,366826,368459,370105,371765,373440,375130,376835,
    378555,380290,382040,383807,385589,387387,389202,391034,
    392882,394747,396630,398530,400448,402384,404338,406311,
    408303,410314,412344,414395,416465,418555,420666,422798,
    424951,427125,429321,431540,433781,436045,438332,440643,
    442978,445337,447720,450129,452564,455024,457511,460024,
    462565,465133,467730,470355,473009,475692,478406,481150,
    483925,486732,489571,492443,495348,498287,501261,504269,
    507313,510394,513512,516667,519861,523094,526366,529680,
    533034,536431,539870,543354,546881,550455,554074,557741,
    561456,565221,569035,572901,576818,580789,584815,588896,
    593033,597229,601483,605798,610174,614613,619117,623686,
    628323,633028,637803,642651,647572,652568,657640,662792,
    668024,673338,678737,684223,689797,695462,701219,707072,
    713023,719074,725227,731486,737853,744331,750922,757631,
    764460,771411,778490,785699,793041,800521,808143,815910,
    823827,831898,840127,848520,857081,865817,874730,883829,
    893117,902602,912289,922186,932298,942633,953199,964003,
    975054,986361,997931,1009774,1021901,1034322,1047046,1060087,
    1073455,1087164,1101225,1115654,1130465,1145673,1161294,1177345,
    1193846,1210813,1228269,1246234,1264730,1283783,1303416,1323658,
    1344537,1366084,1388330,1411312,1435065,1459630,1485049,1511367,
    1538632,1566898,1596220,1626658,1658278,1691149,1725348,1760956,
    1798063,1836758,1877161,1919378,1963536,2009771,2058233,2109087,
    2162516,2218719,2277919,2340362,2406322,2476104,2550052,2628549,
    2712030,2800983,2895966,2997613,3106651,3223918,3350381,3487165,
    3635590,3797206,3973855,4167737,4381502,4618375,4882318,5178251,
    5512368,5892567,6329090,6835455,7429880,8137527,8994149,10052327,
    11392683,13145455,15535599,18988036,24413316,34178904,56965752,170910304
};


int finesine[10240] =
{
    25,75,125,175,226,276,326,376,
    427,477,527,578,628,678,728,779,
    829,879,929,980,1030,1080,1130,1181,
    1231,1281,1331,1382,1432,1482,1532,1583,
    1633,1683,1733,1784,1834,1884,1934,1985,
    2035,2085,2135,2186,2236,2286,2336,2387,
    2437,2487,2537,2587,2638,2688,2738,2788,
    2839,2889,2939,2989,3039,3090,3140,3190,
    3240,3291,3341,3391,3441,3491,3541,3592,
    3642,3692,3742,3792,3843,3893,3943,3993,
    4043,4093,4144,4194,4244,4294,4344,4394,
    4445,4495,4545,4595,4645,4695,4745,4796,
    4846,4896,4946,4996,5046,5096,5146,5197,
    5247,5297,5347,5397,5447,5497,5547,5597,
    5647,5697,5748,5798,5848,5898,5948,5998,
    6048,6098,6148,6198,6248,6298,6348,6398,
    6448,6498,6548,6598,6648,6698,6748,6798,
    6848,6898,6948,6998,7048,7098,7148,7198,
    7248,7298,7348,7398,7448,7498,7548,7598,
    7648,7697,7747,7797,7847,7897,7947,7997,
    8047,8097,8147,8196,8246,8296,8346,8396,
    8446,8496,8545,8595,8645,8695,8745,8794,
    8844,8894,8944,8994,9043,9093,9143,9193,
    9243,9292,9342,9392,9442,9491,9541,9591,
    9640,9690,9740,9790,9839,9889,9939,9988,
    10038,10088,10137,10187,10237,10286,10336,10386,
    10435,10485,10534,10584,10634,10683,10733,10782,
    10832,10882,10931,10981,11030,11080,11129,11179,
    11228,11278,11327,11377,11426,11476,11525,11575,
    11624,11674,11723,11773,11822,11872,11921,11970,
    12020,12069,12119,12168,12218,12267,12316,12366,
    12415,12464,12514,12563,12612,12662,12711,12760,
    12810,12859,12908,12957,13007,13056,13105,13154,
    13204,13253,13302,13351,13401,13450,13499,13548,
    13597,13647,13696,13745,13794,13843,13892,13941,
    13990,14040,14089,14138,14187,14236,14285,14334,
    14383,14432,14481,14530,14579,14628,14677,14726,
    14775,14824,14873,14922,14971,15020,15069,15118,
    15167,15215,15264,15313,15362,15411,15460,15509,
    15557,15606,15655,15704,15753,15802,15850,15899,
    15948,15997,16045,16094,16143,16191,16240,16289,
    16338,16386,16435,16484,16532,16581,16629,16678,
    16727,16775,16824,16872,16921,16970,17018,17067,
    17115,17164,17212,17261,17309,17358,17406,17455,
    17503,17551,17600,17648,17697,17745,17793,17842,
    17890,17939,17987,18035,18084,18132,18180,18228,
    18277,18325,18373,18421,18470,18518,18566,18614,
    18663,18711,18759,18807,18855,18903,18951,19000,
    19048,19096,19144,19192,19240,19288,19336,19384,
    19432,19480,19528,19576,19624,19672,19720,19768,
    19816,19864,19912,19959,20007,20055,20103,20151,
    20199,20246,20294,20342,20390,20438,20485,20533,
    20581,20629,20676,20724,20772,20819,20867,20915,
    20962,21010,21057,21105,21153,21200,21248,21295,
    21343,21390,21438,21485,21533,21580,21628,21675,
    21723,21770,21817,21865,21912,21960,22007,22054,
    22102,22149,22196,22243,22291,22338,22385,22433,
    22480,22527,22574,22621,22668,22716,22763,22810,
    22857,22904,22951,22998,23045,23092,23139,23186,
    23233,23280,23327,23374,23421,23468,23515,23562,
    23609,23656,23703,23750,23796,23843,23890,23937,
    23984,24030,24077,24124,24171,24217,24264,24311,
    24357,24404,24451,24497,24544,24591,24637,24684,
    24730,24777,24823,24870,24916,24963,25009,25056,
    25102,25149,25195,25241,25288,25334,25381,25427,
    25473,25520,25566,25612,25658,25705,25751,25797,
    25843,25889,25936,25982,26028,26074,26120,26166,
    26212,26258,26304,26350,26396,26442,26488,26534,
    26580,26626,26672,26718,26764,26810,26856,26902,
    26947,26993,27039,27085,27131,27176,27222,27268,
    27313,27359,27405,27450,27496,27542,27587,27633,
    27678,27724,27770,27815,27861,27906,27952,27997,
    28042,28088,28133,28179,28224,28269,28315,28360,
    28405,28451,28496,28541,28586,28632,28677,28722,
    28767,28812,28858,28903,28948,28993,29038,29083,
    29128,29173,29218,29263,29308,29353,29398,29443,
    29488,29533,29577,29622,29667,29712,29757,29801,
    29846,29891,29936,29980,30025,30070,30114,30159,
    30204,30248,30293,30337,30382,30426,30471,30515,
    30560,30604,30649,30693,30738,30782,30826,30871,
    30915,30959,31004,31048,31092,31136,31181,31225,
    31269,31313,31357,31402,31446,31490,31534,31578,
    31622,31666,31710,31754,31798,31842,31886,31930,
    31974,32017,32061,32105,32149,32193,32236,32280,
    32324,32368,32411,32455,32499,32542,32586,32630,
    32673,32717,32760,32804,32847,32891,32934,32978,
    33021,33065,33108,33151,33195,33238,33281,33325,
    33368,33411,33454,33498,33541,33584,33627,33670,
    33713,33756,33799,33843,33886,33929,33972,34015,
    34057,34100,34143,34186,34229,34272,34315,34358,
    34400,34443,34486,34529,34571,34614,34657,34699,
    34742,34785,34827,34870,34912,34955,34997,35040,
    35082,35125,35167,35210,35252,35294,35337,35379,
    35421,35464,35506,35548,35590,35633,35675,35717,
    35759,35801,35843,35885,35927,35969,36011,36053,
    36095,36137,36179,36221,36263,36305,36347,36388,
    36430,36472,36514,36555,36597,36639,36681,36722,
    36764,36805,36847,36889,36930,36972,37013,37055,
    37096,37137,37179,37220,37262,37303,37344,37386,
    37427,37468,37509,37551,37592,37633,37674,37715,
    37756,37797,37838,37879,37920,37961,38002,38043,
    38084,38125,38166,38207,38248,38288,38329,38370,
    38411,38451,38492,38533,38573,38614,38655,38695,
    38736,38776,38817,38857,38898,38938,38979,39019,
    39059,39100,39140,39180,39221,39261,39301,39341,
    39382,39422,39462,39502,39542,39582,39622,39662,
    39702,39742,39782,39822,39862,39902,39942,39982,
    40021,40061,40101,40141,40180,40220,40260,40300,
    40339,40379,40418,40458,40497,40537,40576,40616,
    40655,40695,40734,40773,40813,40852,40891,40931,
    40970,41009,41048,41087,41127,41166,41205,41244,
    41283,41322,41361,41400,41439,41478,41517,41556,
    41595,41633,41672,41711,41750,41788,41827,41866,
    41904,41943,41982,42020,42059,42097,42136,42174,
    42213,42251,42290,42328,42366,42405,42443,42481,
    42520,42558,42596,42634,42672,42711,42749,42787,
    42825,42863,42901,42939,42977,43015,43053,43091,
    43128,43166,43204,43242,43280,43317,43355,43393,
    43430,43468,43506,43543,43581,43618,43656,43693,
    43731,43768,43806,43843,43880,43918,43955,43992,
    44029,44067,44104,44141,44178,44215,44252,44289,
    44326,44363,44400,44437,44474,44511,44548,44585,
    44622,44659,44695,44732,44769,44806,44842,44879,
    44915,44952,44989,45025,45062,45098,45135,45171,
    45207,45244,45280,45316,45353,45389,45425,45462,
    45498,45534,45570,45606,45642,45678,45714,45750,
    45786,45822,45858,45894,45930,45966,46002,46037,
    46073,46109,46145,46180,46216,46252,46287,46323,
    46358,46394,46429,46465,46500,46536,46571,46606,
    46642,46677,46712,46747,46783,46818,46853,46888,
    46923,46958,46993,47028,47063,47098,47133,47168,
    47203,47238,47273,47308,47342,47377,47412,47446,
    47481,47516,47550,47585,47619,47654,47688,47723,
    47757,47792,47826,47860,47895,47929,47963,47998,
    48032,48066,48100,48134,48168,48202,48237,48271,
    48305,48338,48372,48406,48440,48474,48508,48542,
    48575,48609,48643,48676,48710,48744,48777,48811,
    48844,48878,48911,48945,48978,49012,49045,49078,
    49112,49145,49178,49211,49244,49278,49311,49344,
    49377,49410,49443,49476,49509,49542,49575,49608,
    49640,49673,49706,49739,49771,49804,49837,49869,
    49902,49935,49967,50000,50032,50065,50097,50129,
    50162,50194,50226,50259,50291,50323,50355,50387,
    50420,50452,50484,50516,50548,50580,50612,50644,
    50675,50707,50739,50771,50803,50834,50866,50898,
    50929,50961,50993,51024,51056,51087,51119,51150,
    51182,51213,51244,51276,51307,51338,51369,51401,
    51432,51463,51494,51525,51556,51587,51618,51649,
    51680,51711,51742,51773,51803,51834,51865,51896,
    51926,51957,51988,52018,52049,52079,52110,52140,
    52171,52201,52231,52262,52292,52322,52353,52383,
    52413,52443,52473,52503,52534,52564,52594,52624,
    52653,52683,52713,52743,52773,52803,52832,52862,
    52892,52922,52951,52981,53010,53040,53069,53099,
    53128,53158,53187,53216,53246,53275,53304,53334,
    53363,53392,53421,53450,53479,53508,53537,53566,
    53595,53624,53653,53682,53711,53739,53768,53797,
    53826,53854,53883,53911,53940,53969,53997,54026,
    54054,54082,54111,54139,54167,54196,54224,54252,
    54280,54308,54337,54365,54393,54421,54449,54477,
    54505,54533,54560,54588,54616,54644,54672,54699,
    54727,54755,54782,54810,54837,54865,54892,54920,
    54947,54974,55002,55029,55056,55084,55111,55138,
    55165,55192,55219,55246,55274,55300,55327,55354,
    55381,55408,55435,55462,55489,55515,55542,55569,
    55595,55622,55648,55675,55701,55728,55754,55781,
    55807,55833,55860,55886,55912,55938,55965,55991,
    56017,56043,56069,56095,56121,56147,56173,56199,
    56225,56250,56276,56302,56328,56353,56379,56404,
    56430,56456,56481,56507,56532,56557,56583,56608,
    56633,56659,56684,56709,56734,56760,56785,56810,
    56835,56860,56885,56910,56935,56959,56984,57009,
    57034,57059,57083,57108,57133,57157,57182,57206,
    57231,57255,57280,57304,57329,57353,57377,57402,
    57426,57450,57474,57498,57522,57546,57570,57594,
    57618,57642,57666,57690,57714,57738,57762,57785,
    57809,57833,57856,57880,57903,57927,57950,57974,
    57997,58021,58044,58067,58091,58114,58137,58160,
    58183,58207,58230,58253,58276,58299,58322,58345,
    58367,58390,58413,58436,58459,58481,58504,58527,
    58549,58572,58594,58617,58639,58662,58684,58706,
    58729,58751,58773,58795,58818,58840,58862,58884,
    58906,58928,58950,58972,58994,59016,59038,59059,
    59081,59103,59125,59146,59168,59190,59211,59233,
    59254,59276,59297,59318,59340,59361,59382,59404,
    59425,59446,59467,59488,59509,59530,59551,59572,
    59593,59614,59635,59656,59677,59697,59718,59739,
    59759,59780,59801,59821,59842,59862,59883,59903,
    59923,59944,59964,59984,60004,60025,60045,60065,
    60085,60105,60125,60145,60165,60185,60205,60225,
    60244,60264,60284,60304,60323,60343,60363,60382,
    60402,60421,60441,60460,60479,60499,60518,60537,
    60556,60576,60595,60614,60633,60652,60671,60690,
    60709,60728,60747,60766,60785,60803,60822,60841,
    60859,60878,60897,60915,60934,60952,60971,60989,
    61007,61026,61044,61062,61081,61099,61117,61135,
    61153,61171,61189,61207,61225,61243,61261,61279,
    61297,61314,61332,61350,61367,61385,61403,61420,
    61438,61455,61473,61490,61507,61525,61542,61559,
    61577,61594,61611,61628,61645,61662,61679,61696,
    61713,61730,61747,61764,61780,61797,61814,61831,
    61847,61864,61880,61897,61913,61930,61946,61963,
    61979,61995,62012,62028,62044,62060,62076,62092,
    62108,62125,62141,62156,62172,62188,62204,62220,
    62236,62251,62267,62283,62298,62314,62329,62345,
    62360,62376,62391,62407,62422,62437,62453,62468,
    62483,62498,62513,62528,62543,62558,62573,62588,
    62603,62618,62633,62648,62662,62677,62692,62706,
    62721,62735,62750,62764,62779,62793,62808,62822,
    62836,62850,62865,62879,62893,62907,62921,62935,
    62949,62963,62977,62991,63005,63019,63032,63046,
    63060,63074,63087,63101,63114,63128,63141,63155,
    63168,63182,63195,63208,63221,63235,63248,63261,
    63274,63287,63300,63313,63326,63339,63352,63365,
    63378,63390,63403,63416,63429,63441,63454,63466,
    63479,63491,63504,63516,63528,63541,63553,63565,
    63578,63590,63602,63614,63626,63638,63650,63662,
    63674,63686,63698,63709,63721,63733,63745,63756,
    63768,63779,63791,63803,63814,63825,63837,63848,
    63859,63871,63882,63893,63904,63915,63927,63938,
    63949,63960,63971,63981,63992,64003,64014,64025,
    64035,64046,64057,64067,64078,64088,64099,64109,
    64120,64130,64140,64151,64161,64171,64181,64192,
    64202,64212,64222,64232,64242,64252,64261,64271,
    64281,64291,64301,64310,64320,64330,64339,64349,
    64358,64368,64377,64387,64396,64405,64414,64424,
    64433,64442,64451,64460,64469,64478,64487,64496,
    64505,64514,64523,64532,64540,64549,64558,64566,
    64575,64584,64592,64601,64609,64617,64626,64634,
    64642,64651,64659,64667,64675,64683,64691,64699,
    64707,64715,64723,64731,64739,64747,64754,64762,
    64770,64777,64785,64793,64800,64808,64815,64822,
    64830,64837,64844,64852,64859,64866,64873,64880,
    64887,64895,64902,64908,64915,64922,64929,64936,
    64943,64949,64956,64963,64969,64976,64982,64989,
    64995,65002,65008,65015,65021,65027,65033,65040,
    65046,65052,65058,65064,65070,65076,65082,65088,
    65094,65099,65105,65111,65117,65122,65128,65133,
    65139,65144,65150,65155,65161,65166,65171,65177,
    65182,65187,65192,65197,65202,65207,65212,65217,
    65222,65227,65232,65237,65242,65246,65251,65256,
    65260,65265,65270,65274,65279,65283,65287,65292,
    65296,65300,65305,65309,65313,65317,65321,65325,
    65329,65333,65337,65341,65345,65349,65352,65356,
    65360,65363,65367,65371,65374,65378,65381,65385,
    65388,65391,65395,65398,65401,65404,65408,65411,
    65414,65417,65420,65423,65426,65429,65431,65434,
    65437,65440,65442,65445,65448,65450,65453,65455,
    65458,65460,65463,65465,65467,65470,65472,65474,
    65476,65478,65480,65482,65484,65486,65488,65490,
    65492,65494,65496,65497,65499,65501,65502,65504,
    65505,65507,65508,65510,65511,65513,65514,65515,
    65516,65518,65519,65520,65521,65522,65523,65524,
    65525,65526,65527,65527,65528,65529,65530,65530,
    65531,65531,65532,65532,65533,65533,65534,65534,
    65534,65535,65535,65535,65535,65535,65535,65535,
    65535,65535,65535,65535,65535,65535,65535,65534,
    65534,65534,65533,65533,65532,65532,65531,65531,
    65530,65530,65529,65528,65527,65527,65526,65525,
    65524,65523,65522,65521,65520,65519,65518,65516,
    65515,65514,65513,65511,65510,65508,65507,65505,
    65504,65502,65501,65499,65497,65496,65494,65492,
    65490,65488,65486,65484,65482,65480,65478,65476,
    65474,65472,65470,65467,65465,65463,65460,65458,
    65455,65453,65450,65448,65445,65442,65440,65437,
    65434,65431,65429,65426,65423,65420,65417,65414,
    65411,65408,65404,65401,65398,65395,65391,65388,
    65385,65381,65378,65374,65371,65367,65363,65360,
    65356,65352,65349,65345,65341,65337,65333,65329,
    65325,65321,65317,65313,65309,65305,65300,65296,
    65292,65287,65283,65279,65274,65270,65265,65260,
    65256,65251,65246,65242,65237,65232,65227,65222,
    65217,65212,65207,65202,65197,65192,65187,65182,
    65177,65171,65166,65161,65155,65150,65144,65139,
    65133,65128,65122,65117,65111,65105,65099,65094,
    65088,65082,65076,65070,65064,65058,65052,65046,
    65040,65033,65027,65021,65015,65008,65002,64995,
    64989,64982,64976,64969,64963,64956,64949,64943,
    64936,64929,64922,64915,64908,64902,64895,64887,
    64880,64873,64866,64859,64852,64844,64837,64830,
    64822,64815,64808,64800,64793,64785,64777,64770,
    64762,64754,64747,64739,64731,64723,64715,64707,
    64699,64691,64683,64675,64667,64659,64651,64642,
    64634,64626,64617,64609,64600,64592,64584,64575,
    64566,64558,64549,64540,64532,64523,64514,64505,
    64496,64487,64478,64469,64460,64451,64442,64433,
    64424,64414,64405,64396,64387,64377,64368,64358,
    64349,64339,64330,64320,64310,64301,64291,64281,
    64271,64261,64252,64242,64232,64222,64212,64202,
    64192,64181,64171,64161,64151,64140,64130,64120,
    64109,64099,64088,64078,64067,64057,64046,64035,
    64025,64014,64003,63992,63981,63971,63960,63949,
    63938,63927,63915,63904,63893,63882,63871,63859,
    63848,63837,63825,63814,63803,63791,63779,63768,
    63756,63745,63733,63721,63709,63698,63686,63674,
    63662,63650,63638,63626,63614,63602,63590,63578,
    63565,63553,63541,63528,63516,63504,63491,63479,
    63466,63454,63441,63429,63416,63403,63390,63378,
    63365,63352,63339,63326,63313,63300,63287,63274,
    63261,63248,63235,63221,63208,63195,63182,63168,
    63155,63141,63128,63114,63101,63087,63074,63060,
    63046,63032,63019,63005,62991,62977,62963,62949,
    62935,62921,62907,62893,62879,62865,62850,62836,
    62822,62808,62793,62779,62764,62750,62735,62721,
    62706,62692,62677,62662,62648,62633,62618,62603,
    62588,62573,62558,62543,62528,62513,62498,62483,
    62468,62453,62437,62422,62407,62391,62376,62360,
    62345,62329,62314,62298,62283,62267,62251,62236,
    62220,62204,62188,62172,62156,62141,62125,62108,
    62092,62076,62060,62044,62028,62012,61995,61979,
    61963,61946,61930,61913,61897,61880,61864,61847,
    61831,61814,61797,61780,61764,61747,61730,61713,
    61696,61679,61662,61645,61628,61611,61594,61577,
    61559,61542,61525,61507,61490,61473,61455,61438,
    61420,61403,61385,61367,61350,61332,61314,61297,
    61279,61261,61243,61225,61207,61189,61171,61153,
    61135,61117,61099,61081,61062,61044,61026,61007,
    60989,60971,60952,60934,60915,60897,60878,60859,
    60841,60822,60803,60785,60766,60747,60728,60709,
    60690,60671,60652,60633,60614,60595,60576,60556,
    60537,60518,60499,60479,60460,60441,60421,60402,
    60382,60363,60343,60323,60304,60284,60264,60244,
    60225,60205,60185,60165,60145,60125,60105,60085,
    60065,60045,60025,60004,59984,59964,59944,59923,
    59903,59883,59862,59842,59821,59801,59780,59759,
    59739,59718,59697,59677,59656,59635,59614,59593,
    59572,59551,59530,59509,59488,59467,59446,59425,
    59404,59382,59361,59340,59318,59297,59276,59254,
    59233,59211,59190,59168,59146,59125,59103,59081,
    59059,59038,59016,58994,58972,58950,58928,58906,
    58884,58862,58840,58818,58795,58773,58751,58729,
    58706,58684,58662,58639,58617,58594,58572,58549,
    58527,58504,58481,58459,58436,58413,58390,58367,
    58345,58322,58299,58276,58253,58230,58207,58183,
    58160,58137,58114,58091,58067,58044,58021,57997,
    57974,57950,57927,57903,57880,57856,57833,57809,
    57785,57762,57738,57714,57690,57666,57642,57618,
    57594,57570,57546,57522,57498,57474,57450,57426,
    57402,57377,57353,57329,57304,57280,57255,57231,
    57206,57182,57157,57133,57108,57083,57059,57034,
    57009,56984,56959,56935,56910,56885,56860,56835,
    56810,56785,56760,56734,56709,56684,56659,56633,
    56608,56583,56557,56532,56507,56481,56456,56430,
    56404,56379,56353,56328,56302,56276,56250,56225,
    56199,56173,56147,56121,56095,56069,56043,56017,
    55991,55965,55938,55912,55886,55860,55833,55807,
    55781,55754,55728,55701,55675,55648,55622,55595,
    55569,55542,55515,55489,55462,55435,55408,55381,
    55354,55327,55300,55274,55246,55219,55192,55165,
    55138,55111,55084,55056,55029,55002,54974,54947,
    54920,54892,54865,54837,54810,54782,54755,54727,
    54699,54672,54644,54616,54588,54560,54533,54505,
    54477,54449,54421,54393,54365,54337,54308,54280,
    54252,54224,54196,54167,54139,54111,54082,54054,
    54026,53997,53969,53940,53911,53883,53854,53826,
    53797,53768,53739,53711,53682,53653,53624,53595,
    53566,53537,53508,53479,53450,53421,53392,53363,
    53334,53304,53275,53246,53216,53187,53158,53128,
    53099,53069,53040,53010,52981,52951,52922,52892,
    52862,52832,52803,52773,52743,52713,52683,52653,
    52624,52594,52564,52534,52503,52473,52443,52413,
    52383,52353,52322,52292,52262,52231,52201,52171,
    52140,52110,52079,52049,52018,51988,51957,51926,
    51896,51865,51834,51803,51773,51742,51711,51680,
    51649,51618,51587,51556,51525,51494,51463,51432,
    51401,51369,51338,51307,51276,51244,51213,51182,
    51150,51119,51087,51056,51024,50993,50961,50929,
    50898,50866,50834,50803,50771,50739,50707,50675,
    50644,50612,50580,50548,50516,50484,50452,50420,
    50387,50355,50323,50291,50259,50226,50194,50162,
    50129,50097,50065,50032,50000,49967,49935,49902,
    49869,49837,49804,49771,49739,49706,49673,49640,
    49608,49575,49542,49509,49476,49443,49410,49377,
    49344,49311,49278,49244,49211,49178,49145,49112,
    49078,49045,49012,48978,48945,48911,48878,48844,
    48811,48777,48744,48710,48676,48643,48609,48575,
    48542,48508,48474,48440,48406,48372,48338,48304,
    48271,48237,48202,48168,48134,48100,48066,48032,
    47998,47963,47929,47895,47860,47826,47792,47757,
    47723,47688,47654,47619,47585,47550,47516,47481,
    47446,47412,47377,47342,47308,47273,47238,47203,
    47168,47133,47098,47063,47028,46993,46958,46923,
    46888,46853,46818,46783,46747,46712,46677,46642,
    46606,46571,46536,46500,46465,46429,46394,46358,
    46323,46287,46252,46216,46180,46145,46109,46073,
    46037,46002,45966,45930,45894,45858,45822,45786,
    45750,45714,45678,45642,45606,45570,45534,45498,
    45462,45425,45389,45353,45316,45280,45244,45207,
    45171,45135,45098,45062,45025,44989,44952,44915,
    44879,44842,44806,44769,44732,44695,44659,44622,
    44585,44548,44511,44474,44437,44400,44363,44326,
    44289,44252,44215,44178,44141,44104,44067,44029,
    43992,43955,43918,43880,43843,43806,43768,43731,
    43693,43656,43618,43581,43543,43506,43468,43430,
    43393,43355,43317,43280,43242,43204,43166,43128,
    43091,43053,43015,42977,42939,42901,42863,42825,
    42787,42749,42711,42672,42634,42596,42558,42520,
    42481,42443,42405,42366,42328,42290,42251,42213,
    42174,42136,42097,42059,42020,41982,41943,41904,
    41866,41827,41788,41750,41711,41672,41633,41595,
    41556,41517,41478,41439,41400,41361,41322,41283,
    41244,41205,41166,41127,41088,41048,41009,40970,
    40931,40891,40852,40813,40773,40734,40695,40655,
    40616,40576,40537,40497,40458,40418,40379,40339,
    40300,40260,40220,40180,40141,40101,40061,40021,
    39982,39942,39902,39862,39822,39782,39742,39702,
    39662,39622,39582,39542,39502,39462,39422,39382,
    39341,39301,39261,39221,39180,39140,39100,39059,
    39019,38979,38938,38898,38857,38817,38776,38736,
    38695,38655,38614,38573,38533,38492,38451,38411,
    38370,38329,38288,38248,38207,38166,38125,38084,
    38043,38002,37961,37920,37879,37838,37797,37756,
    37715,37674,37633,37592,37551,37509,37468,37427,
    37386,37344,37303,37262,37220,37179,37137,37096,
    37055,37013,36972,36930,36889,36847,36805,36764,
    36722,36681,36639,36597,36556,36514,36472,36430,
    36388,36347,36305,36263,36221,36179,36137,36095,
    36053,36011,35969,35927,35885,35843,35801,35759,
    35717,35675,35633,35590,35548,35506,35464,35421,
    35379,35337,35294,35252,35210,35167,35125,35082,
    35040,34997,34955,34912,34870,34827,34785,34742,
    34699,34657,34614,34571,34529,34486,34443,34400,
    34358,34315,34272,34229,34186,34143,34100,34057,
    34015,33972,33929,33886,33843,33799,33756,33713,
    33670,33627,33584,33541,33498,33454,33411,33368,
    33325,33281,33238,33195,33151,33108,33065,33021,
    32978,32934,32891,32847,32804,32760,32717,32673,
    32630,32586,32542,32499,32455,32411,32368,32324,
    32280,32236,32193,32149,32105,32061,32017,31974,
    31930,31886,31842,31798,31754,31710,31666,31622,
    31578,31534,31490,31446,31402,31357,31313,31269,
    31225,31181,31136,31092,31048,31004,30959,30915,
    30871,30826,30782,30738,30693,30649,30604,30560,
    30515,30471,30426,30382,30337,30293,30248,30204,
    30159,30114,30070,30025,29980,29936,29891,29846,
    29801,29757,29712,29667,29622,29577,29533,29488,
    29443,29398,29353,29308,29263,29218,29173,29128,
    29083,29038,28993,28948,28903,28858,28812,28767,
    28722,28677,28632,28586,28541,28496,28451,28405,
    28360,28315,28269,28224,28179,28133,28088,28042,
    27997,27952,27906,27861,27815,27770,27724,27678,
    27633,27587,27542,27496,27450,27405,27359,27313,
    27268,27222,27176,27131,27085,27039,26993,26947,
    26902,26856,26810,26764,26718,26672,26626,26580,
    26534,26488,26442,26396,26350,26304,26258,26212,
    26166,26120,26074,26028,25982,25936,25889,25843,
    25797,25751,25705,25658,25612,25566,25520,25473,
    25427,25381,25334,25288,25241,25195,25149,25102,
    25056,25009,24963,24916,24870,24823,24777,24730,
    24684,24637,24591,24544,24497,24451,24404,24357,
    24311,24264,24217,24171,24124,24077,24030,23984,
    23937,23890,23843,23796,23750,23703,23656,23609,
    23562,23515,23468,23421,23374,23327,23280,23233,
    23186,23139,23092,23045,22998,22951,22904,22857,
    22810,22763,22716,22668,22621,22574,22527,22480,
    22433,22385,22338,22291,22243,22196,22149,22102,
    22054,22007,21960,21912,21865,21817,21770,21723,
    21675,21628,21580,21533,21485,21438,21390,21343,
    21295,21248,21200,21153,21105,21057,21010,20962,
    20915,20867,20819,20772,20724,20676,20629,20581,
    20533,20485,20438,20390,20342,20294,20246,20199,
    20151,20103,20055,20007,19959,19912,19864,19816,
    19768,19720,19672,19624,19576,19528,19480,19432,
    19384,19336,19288,19240,19192,19144,19096,19048,
    19000,18951,18903,18855,18807,18759,18711,18663,
    18614,18566,18518,18470,18421,18373,18325,18277,
    18228,18180,18132,18084,18035,17987,17939,17890,
    17842,17793,17745,17697,17648,17600,17551,17503,
    17455,17406,17358,17309,17261,17212,17164,17115,
    17067,17018,16970,16921,16872,16824,16775,16727,
    16678,16629,16581,16532,16484,16435,16386,16338,
    16289,16240,16191,16143,16094,16045,15997,15948,
    15899,15850,15802,15753,15704,15655,15606,15557,
    15509,15460,15411,15362,15313,15264,15215,15167,
    15118,15069,15020,14971,14922,14873,14824,14775,
    14726,14677,14628,14579,14530,14481,14432,14383,
    14334,14285,14236,14187,14138,14089,14040,13990,
    13941,13892,13843,13794,13745,13696,13646,13597,
    13548,13499,13450,13401,13351,13302,13253,13204,
    13154,13105,13056,13007,12957,12908,12859,12810,
    12760,12711,12662,12612,12563,12514,12464,12415,
    12366,12316,12267,12218,12168,12119,12069,12020,
    11970,11921,11872,11822,11773,11723,11674,11624,
    11575,11525,11476,11426,11377,11327,11278,11228,
    11179,11129,11080,11030,10981,10931,10882,10832,
    10782,10733,10683,10634,10584,10534,10485,10435,
    10386,10336,10286,10237,10187,10137,10088,10038,
    9988,9939,9889,9839,9790,9740,9690,9640,
    9591,9541,9491,9442,9392,9342,9292,9243,
    9193,9143,9093,9043,8994,8944,8894,8844,
    8794,8745,8695,8645,8595,8545,8496,8446,
    8396,8346,8296,8246,8196,8147,8097,8047,
    7997,7947,7897,7847,7797,7747,7697,7648,
    7598,7548,7498,7448,7398,7348,7298,7248,
    7198,7148,7098,7048,6998,6948,6898,6848,
    6798,6748,6698,6648,6598,6548,6498,6448,
    6398,6348,6298,6248,6198,6148,6098,6048,
    5998,5948,5898,5848,5798,5748,5697,5647,
    5597,5547,5497,5447,5397,5347,5297,5247,
    5197,5146,5096,5046,4996,4946,4896,4846,
    4796,4745,4695,4645,4595,4545,4495,4445,
    4394,4344,4294,4244,4194,4144,4093,4043,
    3993,3943,3893,3843,3792,3742,3692,3642,
    3592,3541,3491,3441,3391,3341,3291,3240,
    3190,3140,3090,3039,2989,2939,2889,2839,
    2788,2738,2688,2638,2587,2537,2487,2437,
    2387,2336,2286,2236,2186,2135,2085,2035,
    1985,1934,1884,1834,1784,1733,1683,1633,
    1583,1532,1482,1432,1382,1331,1281,1231,
    1181,1130,1080,1030,980,929,879,829,
    779,728,678,628,578,527,477,427,
    376,326,276,226,175,125,75,25,
    -25,-75,-125,-175,-226,-276,-326,-376,
    -427,-477,-527,-578,-628,-678,-728,-779,
    -829,-879,-929,-980,-1030,-1080,-1130,-1181,
    -1231,-1281,-1331,-1382,-1432,-1482,-1532,-1583,
    -1633,-1683,-1733,-1784,-1834,-1884,-1934,-1985,
    -2035,-2085,-2135,-2186,-2236,-2286,-2336,-2387,
    -2437,-2487,-2537,-2588,-2638,-2688,-2738,-2788,
    -2839,-2889,-2939,-2989,-3039,-3090,-3140,-3190,
    -3240,-3291,-3341,-3391,-3441,-3491,-3541,-3592,
    -3642,-3692,-3742,-3792,-3843,-3893,-3943,-3993,
    -4043,-4093,-4144,-4194,-4244,-4294,-4344,-4394,
    -4445,-4495,-4545,-4595,-4645,-4695,-4745,-4796,
    -4846,-4896,-4946,-4996,-5046,-5096,-5146,-5197,
    -5247,-5297,-5347,-5397,-5447,-5497,-5547,-5597,
    -5647,-5697,-5748,-5798,-5848,-5898,-5948,-5998,
    -6048,-6098,-6148,-6198,-6248,-6298,-6348,-6398,
    -6448,-6498,-6548,-6598,-6648,-6698,-6748,-6798,
    -6848,-6898,-6948,-6998,-7048,-7098,-7148,-7198,
    -7248,-7298,-7348,-7398,-7448,-7498,-7548,-7598,
    -7648,-7697,-7747,-7797,-7847,-7897,-7947,-7997,
    -8047,-8097,-8147,-8196,-8246,-8296,-8346,-8396,
    -8446,-8496,-8545,-8595,-8645,-8695,-8745,-8794,
    -8844,-8894,-8944,-8994,-9043,-9093,-9143,-9193,
    -9243,-9292,-9342,-9392,-9442,-9491,-9541,-9591,
    -9640,-9690,-9740,-9790,-9839,-9889,-9939,-9988,
    -10038,-10088,-10137,-10187,-10237,-10286,-10336,-10386,
    -10435,-10485,-10534,-10584,-10634,-10683,-10733,-10782,
    -10832,-10882,-10931,-10981,-11030,-11080,-11129,-11179,
    -11228,-11278,-11327,-11377,-11426,-11476,-11525,-11575,
    -11624,-11674,-11723,-11773,-11822,-11872,-11921,-11970,
    -12020,-12069,-12119,-12168,-12218,-12267,-12316,-12366,
    -12415,-12464,-12514,-12563,-12612,-12662,-12711,-12760,
    -12810,-12859,-12908,-12957,-13007,-13056,-13105,-13154,
    -13204,-13253,-13302,-13351,-13401,-13450,-13499,-13548,
    -13597,-13647,-13696,-13745,-13794,-13843,-13892,-13941,
    -13990,-14040,-14089,-14138,-14187,-14236,-14285,-14334,
    -14383,-14432,-14481,-14530,-14579,-14628,-14677,-14726,
    -14775,-14824,-14873,-14922,-14971,-15020,-15069,-15118,
    -15167,-15215,-15264,-15313,-15362,-15411,-15460,-15509,
    -15557,-15606,-15655,-15704,-15753,-15802,-15850,-15899,
    -15948,-15997,-16045,-16094,-16143,-16191,-16240,-16289,
    -16338,-16386,-16435,-16484,-16532,-16581,-16629,-16678,
    -16727,-16775,-16824,-16872,-16921,-16970,-17018,-17067,
    -17115,-17164,-17212,-17261,-17309,-17358,-17406,-17455,
    -17503,-17551,-17600,-17648,-17697,-17745,-17793,-17842,
    -17890,-17939,-17987,-18035,-18084,-18132,-18180,-18228,
    -18277,-18325,-18373,-18421,-18470,-18518,-18566,-18614,
    -18663,-18711,-18759,-18807,-18855,-18903,-18951,-19000,
    -19048,-19096,-19144,-19192,-19240,-19288,-19336,-19384,
    -19432,-19480,-19528,-19576,-19624,-19672,-19720,-19768,
    -19816,-19864,-19912,-19959,-20007,-20055,-20103,-20151,
    -20199,-20246,-20294,-20342,-20390,-20438,-20485,-20533,
    -20581,-20629,-20676,-20724,-20772,-20819,-20867,-20915,
    -20962,-21010,-21057,-21105,-21153,-21200,-21248,-21295,
    -21343,-21390,-21438,-21485,-21533,-21580,-21628,-21675,
    -21723,-21770,-21817,-21865,-21912,-21960,-22007,-22054,
    -22102,-22149,-22196,-22243,-22291,-22338,-22385,-22433,
    -22480,-22527,-22574,-22621,-22668,-22716,-22763,-22810,
    -22857,-22904,-22951,-22998,-23045,-23092,-23139,-23186,
    -23233,-23280,-23327,-23374,-23421,-23468,-23515,-23562,
    -23609,-23656,-23703,-23750,-23796,-23843,-23890,-23937,
    -23984,-24030,-24077,-24124,-24171,-24217,-24264,-24311,
    -24357,-24404,-24451,-24497,-24544,-24591,-24637,-24684,
    -24730,-24777,-24823,-24870,-24916,-24963,-25009,-25056,
    -25102,-25149,-25195,-25241,-25288,-25334,-25381,-25427,
    -25473,-25520,-25566,-25612,-25658,-25705,-25751,-25797,
    -25843,-25889,-25936,-25982,-26028,-26074,-26120,-26166,
    -26212,-26258,-26304,-26350,-26396,-26442,-26488,-26534,
    -26580,-26626,-26672,-26718,-26764,-26810,-26856,-26902,
    -26947,-26993,-27039,-27085,-27131,-27176,-27222,-27268,
    -27313,-27359,-27405,-27450,-27496,-27542,-27587,-27633,
    -27678,-27724,-27770,-27815,-27861,-27906,-27952,-27997,
    -28042,-28088,-28133,-28179,-28224,-28269,-28315,-28360,
    -28405,-28451,-28496,-28541,-28586,-28632,-28677,-28722,
    -28767,-28812,-28858,-28903,-28948,-28993,-29038,-29083,
    -29128,-29173,-29218,-29263,-29308,-29353,-29398,-29443,
    -29488,-29533,-29577,-29622,-29667,-29712,-29757,-29801,
    -29846,-29891,-29936,-29980,-30025,-30070,-30114,-30159,
    -30204,-30248,-30293,-30337,-30382,-30426,-30471,-30515,
    -30560,-30604,-30649,-30693,-30738,-30782,-30826,-30871,
    -30915,-30959,-31004,-31048,-31092,-31136,-31181,-31225,
    -31269,-31313,-31357,-31402,-31446,-31490,-31534,-31578,
    -31622,-31666,-31710,-31754,-31798,-31842,-31886,-31930,
    -31974,-32017,-32061,-32105,-32149,-32193,-32236,-32280,
    -32324,-32368,-32411,-32455,-32499,-32542,-32586,-32630,
    -32673,-32717,-32760,-32804,-32847,-32891,-32934,-32978,
    -33021,-33065,-33108,-33151,-33195,-33238,-33281,-33325,
    -33368,-33411,-33454,-33498,-33541,-33584,-33627,-33670,
    -33713,-33756,-33799,-33843,-33886,-33929,-33972,-34015,
    -34057,-34100,-34143,-34186,-34229,-34272,-34315,-34358,
    -34400,-34443,-34486,-34529,-34571,-34614,-34657,-34699,
    -34742,-34785,-34827,-34870,-34912,-34955,-34997,-35040,
    -35082,-35125,-35167,-35210,-35252,-35294,-35337,-35379,
    -35421,-35464,-35506,-35548,-35590,-35633,-35675,-35717,
    -35759,-35801,-35843,-35885,-35927,-35969,-36011,-36053,
    -36095,-36137,-36179,-36221,-36263,-36305,-36347,-36388,
    -36430,-36472,-36514,-36555,-36597,-36639,-36681,-36722,
    -36764,-36805,-36847,-36889,-36930,-36972,-37013,-37055,
    -37096,-37137,-37179,-37220,-37262,-37303,-37344,-37386,
    -37427,-37468,-37509,-37551,-37592,-37633,-37674,-37715,
    -37756,-37797,-37838,-37879,-37920,-37961,-38002,-38043,
    -38084,-38125,-38166,-38207,-38248,-38288,-38329,-38370,
    -38411,-38451,-38492,-38533,-38573,-38614,-38655,-38695,
    -38736,-38776,-38817,-38857,-38898,-38938,-38979,-39019,
    -39059,-39100,-39140,-39180,-39221,-39261,-39301,-39341,
    -39382,-39422,-39462,-39502,-39542,-39582,-39622,-39662,
    -39702,-39742,-39782,-39822,-39862,-39902,-39942,-39982,
    -40021,-40061,-40101,-40141,-40180,-40220,-40260,-40299,
    -40339,-40379,-40418,-40458,-40497,-40537,-40576,-40616,
    -40655,-40695,-40734,-40773,-40813,-40852,-40891,-40931,
    -40970,-41009,-41048,-41087,-41127,-41166,-41205,-41244,
    -41283,-41322,-41361,-41400,-41439,-41478,-41517,-41556,
    -41595,-41633,-41672,-41711,-41750,-41788,-41827,-41866,
    -41904,-41943,-41982,-42020,-42059,-42097,-42136,-42174,
    -42213,-42251,-42290,-42328,-42366,-42405,-42443,-42481,
    -42520,-42558,-42596,-42634,-42672,-42711,-42749,-42787,
    -42825,-42863,-42901,-42939,-42977,-43015,-43053,-43091,
    -43128,-43166,-43204,-43242,-43280,-43317,-43355,-43393,
    -43430,-43468,-43506,-43543,-43581,-43618,-43656,-43693,
    -43731,-43768,-43806,-43843,-43880,-43918,-43955,-43992,
    -44029,-44067,-44104,-44141,-44178,-44215,-44252,-44289,
    -44326,-44363,-44400,-44437,-44474,-44511,-44548,-44585,
    -44622,-44659,-44695,-44732,-44769,-44806,-44842,-44879,
    -44915,-44952,-44989,-45025,-45062,-45098,-45135,-45171,
    -45207,-45244,-45280,-45316,-45353,-45389,-45425,-45462,
    -45498,-45534,-45570,-45606,-45642,-45678,-45714,-45750,
    -45786,-45822,-45858,-45894,-45930,-45966,-46002,-46037,
    -46073,-46109,-46145,-46180,-46216,-46252,-46287,-46323,
    -46358,-46394,-46429,-46465,-46500,-46536,-46571,-46606,
    -46642,-46677,-46712,-46747,-46783,-46818,-46853,-46888,
    -46923,-46958,-46993,-47028,-47063,-47098,-47133,-47168,
    -47203,-47238,-47273,-47308,-47342,-47377,-47412,-47446,
    -47481,-47516,-47550,-47585,-47619,-47654,-47688,-47723,
    -47757,-47792,-47826,-47860,-47895,-47929,-47963,-47998,
    -48032,-48066,-48100,-48134,-48168,-48202,-48236,-48271,
    -48304,-48338,-48372,-48406,-48440,-48474,-48508,-48542,
    -48575,-48609,-48643,-48676,-48710,-48744,-48777,-48811,
    -48844,-48878,-48911,-48945,-48978,-49012,-49045,-49078,
    -49112,-49145,-49178,-49211,-49244,-49278,-49311,-49344,
    -49377,-49410,-49443,-49476,-49509,-49542,-49575,-49608,
    -49640,-49673,-49706,-49739,-49771,-49804,-49837,-49869,
    -49902,-49935,-49967,-50000,-50032,-50065,-50097,-50129,
    -50162,-50194,-50226,-50259,-50291,-50323,-50355,-50387,
    -50420,-50452,-50484,-50516,-50548,-50580,-50612,-50644,
    -50675,-50707,-50739,-50771,-50803,-50834,-50866,-50898,
    -50929,-50961,-50993,-51024,-51056,-51087,-51119,-51150,
    -51182,-51213,-51244,-51276,-51307,-51338,-51369,-51401,
    -51432,-51463,-51494,-51525,-51556,-51587,-51618,-51649,
    -51680,-51711,-51742,-51773,-51803,-51834,-51865,-51896,
    -51926,-51957,-51988,-52018,-52049,-52079,-52110,-52140,
    -52171,-52201,-52231,-52262,-52292,-52322,-52353,-52383,
    -52413,-52443,-52473,-52503,-52534,-52564,-52594,-52624,
    -52653,-52683,-52713,-52743,-52773,-52803,-52832,-52862,
    -52892,-52922,-52951,-52981,-53010,-53040,-53069,-53099,
    -53128,-53158,-53187,-53216,-53246,-53275,-53304,-53334,
    -53363,-53392,-53421,-53450,-53479,-53508,-53537,-53566,
    -53595,-53624,-53653,-53682,-53711,-53739,-53768,-53797,
    -53826,-53854,-53883,-53911,-53940,-53969,-53997,-54026,
    -54054,-54082,-54111,-54139,-54167,-54196,-54224,-54252,
    -54280,-54308,-54337,-54365,-54393,-54421,-54449,-54477,
    -54505,-54533,-54560,-54588,-54616,-54644,-54672,-54699,
    -54727,-54755,-54782,-54810,-54837,-54865,-54892,-54920,
    -54947,-54974,-55002,-55029,-55056,-55084,-55111,-55138,
    -55165,-55192,-55219,-55246,-55274,-55300,-55327,-55354,
    -55381,-55408,-55435,-55462,-55489,-55515,-55542,-55569,
    -55595,-55622,-55648,-55675,-55701,-55728,-55754,-55781,
    -55807,-55833,-55860,-55886,-55912,-55938,-55965,-55991,
    -56017,-56043,-56069,-56095,-56121,-56147,-56173,-56199,
    -56225,-56250,-56276,-56302,-56328,-56353,-56379,-56404,
    -56430,-56456,-56481,-56507,-56532,-56557,-56583,-56608,
    -56633,-56659,-56684,-56709,-56734,-56760,-56785,-56810,
    -56835,-56860,-56885,-56910,-56935,-56959,-56984,-57009,
    -57034,-57059,-57083,-57108,-57133,-57157,-57182,-57206,
    -57231,-57255,-57280,-57304,-57329,-57353,-57377,-57402,
    -57426,-57450,-57474,-57498,-57522,-57546,-57570,-57594,
    -57618,-57642,-57666,-57690,-57714,-57738,-57762,-57785,
    -57809,-57833,-57856,-57880,-57903,-57927,-57950,-57974,
    -57997,-58021,-58044,-58067,-58091,-58114,-58137,-58160,
    -58183,-58207,-58230,-58253,-58276,-58299,-58322,-58345,
    -58367,-58390,-58413,-58436,-58459,-58481,-58504,-58527,
    -58549,-58572,-58594,-58617,-58639,-58662,-58684,-58706,
    -58729,-58751,-58773,-58795,-58818,-58840,-58862,-58884,
    -58906,-58928,-58950,-58972,-58994,-59016,-59038,-59059,
    -59081,-59103,-59125,-59146,-59168,-59190,-59211,-59233,
    -59254,-59276,-59297,-59318,-59340,-59361,-59382,-59404,
    -59425,-59446,-59467,-59488,-59509,-59530,-59551,-59572,
    -59593,-59614,-59635,-59656,-59677,-59697,-59718,-59739,
    -59759,-59780,-59801,-59821,-59842,-59862,-59883,-59903,
    -59923,-59944,-59964,-59984,-60004,-60025,-60045,-60065,
    -60085,-60105,-60125,-60145,-60165,-60185,-60205,-60225,
    -60244,-60264,-60284,-60304,-60323,-60343,-60363,-60382,
    -60402,-60421,-60441,-60460,-60479,-60499,-60518,-60537,
    -60556,-60576,-60595,-60614,-60633,-60652,-60671,-60690,
    -60709,-60728,-60747,-60766,-60785,-60803,-60822,-60841,
    -60859,-60878,-60897,-60915,-60934,-60952,-60971,-60989,
    -61007,-61026,-61044,-61062,-61081,-61099,-61117,-61135,
    -61153,-61171,-61189,-61207,-61225,-61243,-61261,-61279,
    -61297,-61314,-61332,-61350,-61367,-61385,-61403,-61420,
    -61438,-61455,-61473,-61490,-61507,-61525,-61542,-61559,
    -61577,-61594,-61611,-61628,-61645,-61662,-61679,-61696,
    -61713,-61730,-61747,-61764,-61780,-61797,-61814,-61831,
    -61847,-61864,-61880,-61897,-61913,-61930,-61946,-61963,
    -61979,-61995,-62012,-62028,-62044,-62060,-62076,-62092,
    -62108,-62125,-62141,-62156,-62172,-62188,-62204,-62220,
    -62236,-62251,-62267,-62283,-62298,-62314,-62329,-62345,
    -62360,-62376,-62391,-62407,-62422,-62437,-62453,-62468,
    -62483,-62498,-62513,-62528,-62543,-62558,-62573,-62588,
    -62603,-62618,-62633,-62648,-62662,-62677,-62692,-62706,
    -62721,-62735,-62750,-62764,-62779,-62793,-62808,-62822,
    -62836,-62850,-62865,-62879,-62893,-62907,-62921,-62935,
    -62949,-62963,-62977,-62991,-63005,-63019,-63032,-63046,
    -63060,-63074,-63087,-63101,-63114,-63128,-63141,-63155,
    -63168,-63182,-63195,-63208,-63221,-63235,-63248,-63261,
    -63274,-63287,-63300,-63313,-63326,-63339,-63352,-63365,
    -63378,-63390,-63403,-63416,-63429,-63441,-63454,-63466,
    -63479,-63491,-63504,-63516,-63528,-63541,-63553,-63565,
    -63578,-63590,-63602,-63614,-63626,-63638,-63650,-63662,
    -63674,-63686,-63698,-63709,-63721,-63733,-63745,-63756,
    -63768,-63779,-63791,-63803,-63814,-63825,-63837,-63848,
    -63859,-63871,-63882,-63893,-63904,-63915,-63927,-63938,
    -63949,-63960,-63971,-63981,-63992,-64003,-64014,-64025,
    -64035,-64046,-64057,-64067,-64078,-64088,-64099,-64109,
    -64120,-64130,-64140,-64151,-64161,-64171,-64181,-64192,
    -64202,-64212,-64222,-64232,-64242,-64252,-64261,-64271,
    -64281,-64291,-64301,-64310,-64320,-64330,-64339,-64349,
    -64358,-64368,-64377,-64387,-64396,-64405,-64414,-64424,
    -64433,-64442,-64451,-64460,-64469,-64478,-64487,-64496,
    -64505,-64514,-64523,-64532,-64540,-64549,-64558,-64566,
    -64575,-64584,-64592,-64601,-64609,-64617,-64626,-64634,
    -64642,-64651,-64659,-64667,-64675,-64683,-64691,-64699,
    -64707,-64715,-64723,-64731,-64739,-64747,-64754,-64762,
    -64770,-64777,-64785,-64793,-64800,-64808,-64815,-64822,
    -64830,-64837,-64844,-64852,-64859,-64866,-64873,-64880,
    -64887,-64895,-64902,-64908,-64915,-64922,-64929,-64936,
    -64943,-64949,-64956,-64963,-64969,-64976,-64982,-64989,
    -64995,-65002,-65008,-65015,-65021,-65027,-65033,-65040,
    -65046,-65052,-65058,-65064,-65070,-65076,-65082,-65088,
    -65094,-65099,-65105,-65111,-65117,-65122,-65128,-65133,
    -65139,-65144,-65150,-65155,-65161,-65166,-65171,-65177,
    -65182,-65187,-65192,-65197,-65202,-65207,-65212,-65217,
    -65222,-65227,-65232,-65237,-65242,-65246,-65251,-65256,
    -65260,-65265,-65270,-65274,-65279,-65283,-65287,-65292,
    -65296,-65300,-65305,-65309,-65313,-65317,-65321,-65325,
    -65329,-65333,-65337,-65341,-65345,-65349,-65352,-65356,
    -65360,-65363,-65367,-65371,-65374,-65378,-65381,-65385,
    -65388,-65391,-65395,-65398,-65401,-65404,-65408,-65411,
    -65414,-65417,-65420,-65423,-65426,-65429,-65431,-65434,
    -65437,-65440,-65442,-65445,-65448,-65450,-65453,-65455,
    -65458,-65460,-65463,-65465,-65467,-65470,-65472,-65474,
    -65476,-65478,-65480,-65482,-65484,-65486,-65488,-65490,
    -65492,-65494,-65496,-65497,-65499,-65501,-65502,-65504,
    -65505,-65507,-65508,-65510,-65511,-65513,-65514,-65515,
    -65516,-65518,-65519,-65520,-65521,-65522,-65523,-65524,
    -65525,-65526,-65527,-65527,-65528,-65529,-65530,-65530,
    -65531,-65531,-65532,-65532,-65533,-65533,-65534,-65534,
    -65534,-65535,-65535,-65535,-65535,-65535,-65535,-65535,
    -65535,-65535,-65535,-65535,-65535,-65535,-65535,-65534,
    -65534,-65534,-65533,-65533,-65532,-65532,-65531,-65531,
    -65530,-65530,-65529,-65528,-65527,-65527,-65526,-65525,
    -65524,-65523,-65522,-65521,-65520,-65519,-65518,-65516,
    -65515,-65514,-65513,-65511,-65510,-65508,-65507,-65505,
    -65504,-65502,-65501,-65499,-65497,-65496,-65494,-65492,
    -65490,-65488,-65486,-65484,-65482,-65480,-65478,-65476,
    -65474,-65472,-65470,-65467,-65465,-65463,-65460,-65458,
    -65455,-65453,-65450,-65448,-65445,-65442,-65440,-65437,
    -65434,-65431,-65429,-65426,-65423,-65420,-65417,-65414,
    -65411,-65408,-65404,-65401,-65398,-65395,-65391,-65388,
    -65385,-65381,-65378,-65374,-65371,-65367,-65363,-65360,
    -65356,-65352,-65349,-65345,-65341,-65337,-65333,-65329,
    -65325,-65321,-65317,-65313,-65309,-65305,-65300,-65296,
    -65292,-65287,-65283,-65279,-65274,-65270,-65265,-65260,
    -65256,-65251,-65246,-65242,-65237,-65232,-65227,-65222,
    -65217,-65212,-65207,-65202,-65197,-65192,-65187,-65182,
    -65177,-65171,-65166,-65161,-65155,-65150,-65144,-65139,
    -65133,-65128,-65122,-65117,-65111,-65105,-65099,-65094,
    -65088,-65082,-65076,-65070,-65064,-65058,-65052,-65046,
    -65040,-65033,-65027,-65021,-65015,-65008,-65002,-64995,
    -64989,-64982,-64976,-64969,-64963,-64956,-64949,-64943,
    -64936,-64929,-64922,-64915,-64908,-64902,-64895,-64887,
    -64880,-64873,-64866,-64859,-64852,-64844,-64837,-64830,
    -64822,-64815,-64808,-64800,-64793,-64785,-64777,-64770,
    -64762,-64754,-64747,-64739,-64731,-64723,-64715,-64707,
    -64699,-64691,-64683,-64675,-64667,-64659,-64651,-64642,
    -64634,-64626,-64617,-64609,-64601,-64592,-64584,-64575,
    -64566,-64558,-64549,-64540,-64532,-64523,-64514,-64505,
    -64496,-64487,-64478,-64469,-64460,-64451,-64442,-64433,
    -64424,-64414,-64405,-64396,-64387,-64377,-64368,-64358,
    -64349,-64339,-64330,-64320,-64310,-64301,-64291,-64281,
    -64271,-64261,-64252,-64242,-64232,-64222,-64212,-64202,
    -64192,-64181,-64171,-64161,-64151,-64140,-64130,-64120,
    -64109,-64099,-64088,-64078,-64067,-64057,-64046,-64035,
    -64025,-64014,-64003,-63992,-63981,-63971,-63960,-63949,
    -63938,-63927,-63915,-63904,-63893,-63882,-63871,-63859,
    -63848,-63837,-63825,-63814,-63803,-63791,-63779,-63768,
    -63756,-63745,-63733,-63721,-63709,-63698,-63686,-63674,
    -63662,-63650,-63638,-63626,-63614,-63602,-63590,-63578,
    -63565,-63553,-63541,-63528,-63516,-63504,-63491,-63479,
    -63466,-63454,-63441,-63429,-63416,-63403,-63390,-63378,
    -63365,-63352,-63339,-63326,-63313,-63300,-63287,-63274,
    -63261,-63248,-63235,-63221,-63208,-63195,-63182,-63168,
    -63155,-63141,-63128,-63114,-63101,-63087,-63074,-63060,
    -63046,-63032,-63019,-63005,-62991,-62977,-62963,-62949,
    -62935,-62921,-62907,-62893,-62879,-62865,-62850,-62836,
    -62822,-62808,-62793,-62779,-62764,-62750,-62735,-62721,
    -62706,-62692,-62677,-62662,-62648,-62633,-62618,-62603,
    -62588,-62573,-62558,-62543,-62528,-62513,-62498,-62483,
    -62468,-62453,-62437,-62422,-62407,-62391,-62376,-62360,
    -62345,-62329,-62314,-62298,-62283,-62267,-62251,-62236,
    -62220,-62204,-62188,-62172,-62156,-62141,-62125,-62108,
    -62092,-62076,-62060,-62044,-62028,-62012,-61995,-61979,
    -61963,-61946,-61930,-61913,-61897,-61880,-61864,-61847,
    -61831,-61814,-61797,-61780,-61764,-61747,-61730,-61713,
    -61696,-61679,-61662,-61645,-61628,-61611,-61594,-61577,
    -61559,-61542,-61525,-61507,-61490,-61473,-61455,-61438,
    -61420,-61403,-61385,-61367,-61350,-61332,-61314,-61297,
    -61279,-61261,-61243,-61225,-61207,-61189,-61171,-61153,
    -61135,-61117,-61099,-61081,-61062,-61044,-61026,-61007,
    -60989,-60971,-60952,-60934,-60915,-60897,-60878,-60859,
    -60841,-60822,-60803,-60785,-60766,-60747,-60728,-60709,
    -60690,-60671,-60652,-60633,-60614,-60595,-60576,-60556,
    -60537,-60518,-60499,-60479,-60460,-60441,-60421,-60402,
    -60382,-60363,-60343,-60323,-60304,-60284,-60264,-60244,
    -60225,-60205,-60185,-60165,-60145,-60125,-60105,-60085,
    -60065,-60045,-60025,-60004,-59984,-59964,-59944,-59923,
    -59903,-59883,-59862,-59842,-59821,-59801,-59780,-59759,
    -59739,-59718,-59697,-59677,-59656,-59635,-59614,-59593,
    -59572,-59551,-59530,-59509,-59488,-59467,-59446,-59425,
    -59404,-59382,-59361,-59340,-59318,-59297,-59276,-59254,
    -59233,-59211,-59189,-59168,-59146,-59125,-59103,-59081,
    -59059,-59038,-59016,-58994,-58972,-58950,-58928,-58906,
    -58884,-58862,-58840,-58818,-58795,-58773,-58751,-58729,
    -58706,-58684,-58662,-58639,-58617,-58594,-58572,-58549,
    -58527,-58504,-58481,-58459,-58436,-58413,-58390,-58367,
    -58345,-58322,-58299,-58276,-58253,-58230,-58207,-58183,
    -58160,-58137,-58114,-58091,-58067,-58044,-58021,-57997,
    -57974,-57950,-57927,-57903,-57880,-57856,-57833,-57809,
    -57785,-57762,-57738,-57714,-57690,-57666,-57642,-57618,
    -57594,-57570,-57546,-57522,-57498,-57474,-57450,-57426,
    -57402,-57377,-57353,-57329,-57304,-57280,-57255,-57231,
    -57206,-57182,-57157,-57133,-57108,-57083,-57059,-57034,
    -57009,-56984,-56959,-56935,-56910,-56885,-56860,-56835,
    -56810,-56785,-56760,-56734,-56709,-56684,-56659,-56633,
    -56608,-56583,-56557,-56532,-56507,-56481,-56456,-56430,
    -56404,-56379,-56353,-56328,-56302,-56276,-56250,-56225,
    -56199,-56173,-56147,-56121,-56095,-56069,-56043,-56017,
    -55991,-55965,-55938,-55912,-55886,-55860,-55833,-55807,
    -55781,-55754,-55728,-55701,-55675,-55648,-55622,-55595,
    -55569,-55542,-55515,-55489,-55462,-55435,-55408,-55381,
    -55354,-55327,-55300,-55274,-55246,-55219,-55192,-55165,
    -55138,-55111,-55084,-55056,-55029,-55002,-54974,-54947,
    -54920,-54892,-54865,-54837,-54810,-54782,-54755,-54727,
    -54699,-54672,-54644,-54616,-54588,-54560,-54533,-54505,
    -54477,-54449,-54421,-54393,-54365,-54337,-54308,-54280,
    -54252,-54224,-54196,-54167,-54139,-54111,-54082,-54054,
    -54026,-53997,-53969,-53940,-53911,-53883,-53854,-53826,
    -53797,-53768,-53739,-53711,-53682,-53653,-53624,-53595,
    -53566,-53537,-53508,-53479,-53450,-53421,-53392,-53363,
    -53334,-53304,-53275,-53246,-53216,-53187,-53158,-53128,
    -53099,-53069,-53040,-53010,-52981,-52951,-52922,-52892,
    -52862,-52832,-52803,-52773,-52743,-52713,-52683,-52653,
    -52624,-52594,-52564,-52534,-52503,-52473,-52443,-52413,
    -52383,-52353,-52322,-52292,-52262,-52231,-52201,-52171,
    -52140,-52110,-52079,-52049,-52018,-51988,-51957,-51926,
    -51896,-51865,-51834,-51803,-51773,-51742,-51711,-51680,
    -51649,-51618,-51587,-51556,-51525,-51494,-51463,-51432,
    -51401,-51369,-51338,-51307,-51276,-51244,-51213,-51182,
    -51150,-51119,-51087,-51056,-51024,-50993,-50961,-50929,
    -50898,-50866,-50834,-50803,-50771,-50739,-50707,-50675,
    -50644,-50612,-50580,-50548,-50516,-50484,-50452,-50420,
    -50387,-50355,-50323,-50291,-50259,-50226,-50194,-50162,
    -50129,-50097,-50065,-50032,-50000,-49967,-49935,-49902,
    -49869,-49837,-49804,-49771,-49739,-49706,-49673,-49640,
    -49608,-49575,-49542,-49509,-49476,-49443,-49410,-49377,
    -49344,-49311,-49278,-49244,-49211,-49178,-49145,-49112,
    -49078,-49045,-49012,-48978,-48945,-48911,-48878,-48844,
    -48811,-48777,-48744,-48710,-48676,-48643,-48609,-48575,
    -48542,-48508,-48474,-48440,-48406,-48372,-48338,-48305,
    -48271,-48237,-48202,-48168,-48134,-48100,-48066,-48032,
    -47998,-47963,-47929,-47895,-47860,-47826,-47792,-47757,
    -47723,-47688,-47654,-47619,-47585,-47550,-47516,-47481,
    -47446,-47412,-47377,-47342,-47307,-47273,-47238,-47203,
    -47168,-47133,-47098,-47063,-47028,-46993,-46958,-46923,
    -46888,-46853,-46818,-46783,-46747,-46712,-46677,-46642,
    -46606,-46571,-46536,-46500,-46465,-46429,-46394,-46358,
    -46323,-46287,-46251,-46216,-46180,-46145,-46109,-46073,
    -46037,-46002,-45966,-45930,-45894,-45858,-45822,-45786,
    -45750,-45714,-45678,-45642,-45606,-45570,-45534,-45498,
    -45462,-45425,-45389,-45353,-45316,-45280,-45244,-45207,
    -45171,-45135,-45098,-45062,-45025,-44989,-44952,-44915,
    -44879,-44842,-44806,-44769,-44732,-44695,-44659,-44622,
    -44585,-44548,-44511,-44474,-44437,-44400,-44363,-44326,
    -44289,-44252,-44215,-44178,-44141,-44104,-44067,-44029,
    -43992,-43955,-43918,-43880,-43843,-43806,-43768,-43731,
    -43693,-43656,-43618,-43581,-43543,-43506,-43468,-43430,
    -43393,-43355,-43317,-43280,-43242,-43204,-43166,-43128,
    -43091,-43053,-43015,-42977,-42939,-42901,-42863,-42825,
    -42787,-42749,-42711,-42672,-42634,-42596,-42558,-42520,
    -42481,-42443,-42405,-42366,-42328,-42290,-42251,-42213,
    -42174,-42136,-42097,-42059,-42020,-41982,-41943,-41904,
    -41866,-41827,-41788,-41750,-41711,-41672,-41633,-41595,
    -41556,-41517,-41478,-41439,-41400,-41361,-41322,-41283,
    -41244,-41205,-41166,-41127,-41087,-41048,-41009,-40970,
    -40931,-40891,-40852,-40813,-40773,-40734,-40695,-40655,
    -40616,-40576,-40537,-40497,-40458,-40418,-40379,-40339,
    -40299,-40260,-40220,-40180,-40141,-40101,-40061,-40021,
    -39982,-39942,-39902,-39862,-39822,-39782,-39742,-39702,
    -39662,-39622,-39582,-39542,-39502,-39462,-39422,-39382,
    -39341,-39301,-39261,-39221,-39180,-39140,-39100,-39059,
    -39019,-38979,-38938,-38898,-38857,-38817,-38776,-38736,
    -38695,-38655,-38614,-38573,-38533,-38492,-38451,-38411,
    -38370,-38329,-38288,-38248,-38207,-38166,-38125,-38084,
    -38043,-38002,-37961,-37920,-37879,-37838,-37797,-37756,
    -37715,-37674,-37633,-37592,-37550,-37509,-37468,-37427,
    -37386,-37344,-37303,-37262,-37220,-37179,-37137,-37096,
    -37055,-37013,-36972,-36930,-36889,-36847,-36805,-36764,
    -36722,-36681,-36639,-36597,-36556,-36514,-36472,-36430,
    -36388,-36347,-36305,-36263,-36221,-36179,-36137,-36095,
    -36053,-36011,-35969,-35927,-35885,-35843,-35801,-35759,
    -35717,-35675,-35633,-35590,-35548,-35506,-35464,-35421,
    -35379,-35337,-35294,-35252,-35210,-35167,-35125,-35082,
    -35040,-34997,-34955,-34912,-34870,-34827,-34785,-34742,
    -34699,-34657,-34614,-34571,-34529,-34486,-34443,-34400,
    -34358,-34315,-34272,-34229,-34186,-34143,-34100,-34057,
    -34015,-33972,-33929,-33886,-33843,-33799,-33756,-33713,
    -33670,-33627,-33584,-33541,-33498,-33454,-33411,-33368,
    -33325,-33281,-33238,-33195,-33151,-33108,-33065,-33021,
    -32978,-32934,-32891,-32847,-32804,-32760,-32717,-32673,
    -32630,-32586,-32542,-32499,-32455,-32411,-32368,-32324,
    -32280,-32236,-32193,-32149,-32105,-32061,-32017,-31974,
    -31930,-31886,-31842,-31798,-31754,-31710,-31666,-31622,
    -31578,-31534,-31490,-31446,-31402,-31357,-31313,-31269,
    -31225,-31181,-31136,-31092,-31048,-31004,-30959,-30915,
    -30871,-30826,-30782,-30738,-30693,-30649,-30604,-30560,
    -30515,-30471,-30426,-30382,-30337,-30293,-30248,-30204,
    -30159,-30114,-30070,-30025,-29980,-29936,-29891,-29846,
    -29801,-29757,-29712,-29667,-29622,-29577,-29533,-29488,
    -29443,-29398,-29353,-29308,-29263,-29218,-29173,-29128,
    -29083,-29038,-28993,-28948,-28903,-28858,-28812,-28767,
    -28722,-28677,-28632,-28586,-28541,-28496,-28451,-28405,
    -28360,-28315,-28269,-28224,-28179,-28133,-28088,-28042,
    -27997,-27952,-27906,-27861,-27815,-27770,-27724,-27678,
    -27633,-27587,-27542,-27496,-27450,-27405,-27359,-27313,
    -27268,-27222,-27176,-27131,-27085,-27039,-26993,-26947,
    -26902,-26856,-26810,-26764,-26718,-26672,-26626,-26580,
    -26534,-26488,-26442,-26396,-26350,-26304,-26258,-26212,
    -26166,-26120,-26074,-26028,-25982,-25936,-25889,-25843,
    -25797,-25751,-25705,-25658,-25612,-25566,-25520,-25473,
    -25427,-25381,-25334,-25288,-25241,-25195,-25149,-25102,
    -25056,-25009,-24963,-24916,-24870,-24823,-24777,-24730,
    -24684,-24637,-24591,-24544,-24497,-24451,-24404,-24357,
    -24311,-24264,-24217,-24171,-24124,-24077,-24030,-23984,
    -23937,-23890,-23843,-23796,-23750,-23703,-23656,-23609,
    -23562,-23515,-23468,-23421,-23374,-23327,-23280,-23233,
    -23186,-23139,-23092,-23045,-22998,-22951,-22904,-22857,
    -22810,-22763,-22716,-22668,-22621,-22574,-22527,-22480,
    -22432,-22385,-22338,-22291,-22243,-22196,-22149,-22102,
    -22054,-22007,-21960,-21912,-21865,-21817,-21770,-21723,
    -21675,-21628,-21580,-21533,-21485,-21438,-21390,-21343,
    -21295,-21248,-21200,-21153,-21105,-21057,-21010,-20962,
    -20915,-20867,-20819,-20772,-20724,-20676,-20629,-20581,
    -20533,-20485,-20438,-20390,-20342,-20294,-20246,-20199,
    -20151,-20103,-20055,-20007,-19959,-19912,-19864,-19816,
    -19768,-19720,-19672,-19624,-19576,-19528,-19480,-19432,
    -19384,-19336,-19288,-19240,-19192,-19144,-19096,-19048,
    -19000,-18951,-18903,-18855,-18807,-18759,-18711,-18663,
    -18614,-18566,-18518,-18470,-18421,-18373,-18325,-18277,
    -18228,-18180,-18132,-18084,-18035,-17987,-17939,-17890,
    -17842,-17793,-17745,-17697,-17648,-17600,-17551,-17503,
    -17455,-17406,-17358,-17309,-17261,-17212,-17164,-17115,
    -17067,-17018,-16970,-16921,-16872,-16824,-16775,-16727,
    -16678,-16629,-16581,-16532,-16484,-16435,-16386,-16338,
    -16289,-16240,-16191,-16143,-16094,-16045,-15997,-15948,
    -15899,-15850,-15802,-15753,-15704,-15655,-15606,-15557,
    -15509,-15460,-15411,-15362,-15313,-15264,-15215,-15167,
    -15118,-15069,-15020,-14971,-14922,-14873,-14824,-14775,
    -14726,-14677,-14628,-14579,-14530,-14481,-14432,-14383,
    -14334,-14285,-14236,-14187,-14138,-14089,-14040,-13990,
    -13941,-13892,-13843,-13794,-13745,-13696,-13647,-13597,
    -13548,-13499,-13450,-13401,-13351,-13302,-13253,-13204,
    -13154,-13105,-13056,-13007,-12957,-12908,-12859,-12810,
    -12760,-12711,-12662,-12612,-12563,-12514,-12464,-12415,
    -12366,-12316,-12267,-12217,-12168,-12119,-12069,-12020,
    -11970,-11921,-11872,-11822,-11773,-11723,-11674,-11624,
    -11575,-11525,-11476,-11426,-11377,-11327,-11278,-11228,
    -11179,-11129,-11080,-11030,-10981,-10931,-10882,-10832,
    -10782,-10733,-10683,-10634,-10584,-10534,-10485,-10435,
    -10386,-10336,-10286,-10237,-10187,-10137,-10088,-10038,
    -9988,-9939,-9889,-9839,-9790,-9740,-9690,-9640,
    -9591,-9541,-9491,-9442,-9392,-9342,-9292,-9243,
    -9193,-9143,-9093,-9043,-8994,-8944,-8894,-8844,
    -8794,-8745,-8695,-8645,-8595,-8545,-8496,-8446,
    -8396,-8346,-8296,-8246,-8196,-8147,-8097,-8047,
    -7997,-7947,-7897,-7847,-7797,-7747,-7697,-7648,
    -7598,-7548,-7498,-7448,-7398,-7348,-7298,-7248,
    -7198,-7148,-7098,-7048,-6998,-6948,-6898,-6848,
    -6798,-6748,-6698,-6648,-6598,-6548,-6498,-6448,
    -6398,-6348,-6298,-6248,-6198,-6148,-6098,-6048,
    -5998,-5948,-5898,-5848,-5798,-5747,-5697,-5647,
    -5597,-5547,-5497,-5447,-5397,-5347,-5297,-5247,
    -5197,-5146,-5096,-5046,-4996,-4946,-4896,-4846,
    -4796,-4745,-4695,-4645,-4595,-4545,-4495,-4445,
    -4394,-4344,-4294,-4244,-4194,-4144,-4093,-4043,
    -3993,-3943,-3893,-3843,-3792,-3742,-3692,-3642,
    -3592,-3541,-3491,-3441,-3391,-3341,-3291,-3240,
    -3190,-3140,-3090,-3039,-2989,-2939,-2889,-2839,
    -2788,-2738,-2688,-2638,-2588,-2537,-2487,-2437,
    -2387,-2336,-2286,-2236,-2186,-2135,-2085,-2035,
    -1985,-1934,-1884,-1834,-1784,-1733,-1683,-1633,
    -1583,-1532,-1482,-1432,-1382,-1331,-1281,-1231,
    -1181,-1130,-1080,-1030,-980,-929,-879,-829,
    -779,-728,-678,-628,-578,-527,-477,-427,
    -376,-326,-276,-226,-175,-125,-75,-25,
    25,75,125,175,226,276,326,376,
    427,477,527,578,628,678,728,779,
    829,879,929,980,1030,1080,1130,1181,
    1231,1281,1331,1382,1432,1482,1532,1583,
    1633,1683,1733,1784,1834,1884,1934,1985,
    2035,2085,2135,2186,2236,2286,2336,2387,
    2437,2487,2537,2587,2638,2688,2738,2788,
    2839,2889,2939,2989,3039,3090,3140,3190,
    3240,3291,3341,3391,3441,3491,3542,3592,
    3642,3692,3742,3792,3843,3893,3943,3993,
    4043,4093,4144,4194,4244,4294,4344,4394,
    4445,4495,4545,4595,4645,4695,4745,4796,
    4846,4896,4946,4996,5046,5096,5146,5197,
    5247,5297,5347,5397,5447,5497,5547,5597,
    5647,5697,5747,5798,5848,5898,5948,5998,
    6048,6098,6148,6198,6248,6298,6348,6398,
    6448,6498,6548,6598,6648,6698,6748,6798,
    6848,6898,6948,6998,7048,7098,7148,7198,
    7248,7298,7348,7398,7448,7498,7548,7598,
    7648,7697,7747,7797,7847,7897,7947,7997,
    8047,8097,8147,8196,8246,8296,8346,8396,
    8446,8496,8545,8595,8645,8695,8745,8794,
    8844,8894,8944,8994,9043,9093,9143,9193,
    9243,9292,9342,9392,9442,9491,9541,9591,
    9640,9690,9740,9790,9839,9889,9939,9988,
    10038,10088,10137,10187,10237,10286,10336,10386,
    10435,10485,10534,10584,10634,10683,10733,10782,
    10832,10882,10931,10981,11030,11080,11129,11179,
    11228,11278,11327,11377,11426,11476,11525,11575,
    11624,11674,11723,11773,11822,11872,11921,11970,
    12020,12069,12119,12168,12218,12267,12316,12366,
    12415,12464,12514,12563,12612,12662,12711,12760,
    12810,12859,12908,12957,13007,13056,13105,13154,
    13204,13253,13302,13351,13401,13450,13499,13548,
    13597,13647,13696,13745,13794,13843,13892,13941,
    13990,14040,14089,14138,14187,14236,14285,14334,
    14383,14432,14481,14530,14579,14628,14677,14726,
    14775,14824,14873,14922,14971,15020,15069,15118,
    15167,15215,15264,15313,15362,15411,15460,15509,
    15557,15606,15655,15704,15753,15802,15850,15899,
    15948,15997,16045,16094,16143,16191,16240,16289,
    16338,16386,16435,16484,16532,16581,16629,16678,
    16727,16775,16824,16872,16921,16970,17018,17067,
    17115,17164,17212,17261,17309,17358,17406,17455,
    17503,17551,17600,17648,17697,17745,17793,17842,
    17890,17939,17987,18035,18084,18132,18180,18228,
    18277,18325,18373,18421,18470,18518,18566,18614,
    18663,18711,18759,18807,18855,18903,18951,19000,
    19048,19096,19144,19192,19240,19288,19336,19384,
    19432,19480,19528,19576,19624,19672,19720,19768,
    19816,19864,19912,19959,20007,20055,20103,20151,
    20199,20246,20294,20342,20390,20438,20485,20533,
    20581,20629,20676,20724,20772,20819,20867,20915,
    20962,21010,21057,21105,21153,21200,21248,21295,
    21343,21390,21438,21485,21533,21580,21628,21675,
    21723,21770,21817,21865,21912,21960,22007,22054,
    22102,22149,22196,22243,22291,22338,22385,22432,
    22480,22527,22574,22621,22668,22716,22763,22810,
    22857,22904,22951,22998,23045,23092,23139,23186,
    23233,23280,23327,23374,23421,23468,23515,23562,
    23609,23656,23703,23750,23796,23843,23890,23937,
    23984,24030,24077,24124,24171,24217,24264,24311,
    24357,24404,24451,24497,24544,24591,24637,24684,
    24730,24777,24823,24870,24916,24963,25009,25056,
    25102,25149,25195,25241,25288,25334,25381,25427,
    25473,25520,25566,25612,25658,25705,25751,25797,
    25843,25889,25936,25982,26028,26074,26120,26166,
    26212,26258,26304,26350,26396,26442,26488,26534,
    26580,26626,26672,26718,26764,26810,26856,26902,
    26947,26993,27039,27085,27131,27176,27222,27268,
    27313,27359,27405,27450,27496,27542,27587,27633,
    27678,27724,27770,27815,27861,27906,27952,27997,
    28042,28088,28133,28179,28224,28269,28315,28360,
    28405,28451,28496,28541,28586,28632,28677,28722,
    28767,28812,28858,28903,28948,28993,29038,29083,
    29128,29173,29218,29263,29308,29353,29398,29443,
    29488,29533,29577,29622,29667,29712,29757,29801,
    29846,29891,29936,29980,30025,30070,30114,30159,
    30204,30248,30293,30337,30382,30427,30471,30516,
    30560,30604,30649,30693,30738,30782,30826,30871,
    30915,30959,31004,31048,31092,31136,31181,31225,
    31269,31313,31357,31402,31446,31490,31534,31578,
    31622,31666,31710,31754,31798,31842,31886,31930,
    31974,32017,32061,32105,32149,32193,32236,32280,
    32324,32368,32411,32455,32499,32542,32586,32630,
    32673,32717,32760,32804,32847,32891,32934,32978,
    33021,33065,33108,33151,33195,33238,33281,33325,
    33368,33411,33454,33498,33541,33584,33627,33670,
    33713,33756,33799,33843,33886,33929,33972,34015,
    34057,34100,34143,34186,34229,34272,34315,34358,
    34400,34443,34486,34529,34571,34614,34657,34699,
    34742,34785,34827,34870,34912,34955,34997,35040,
    35082,35125,35167,35210,35252,35294,35337,35379,
    35421,35464,35506,35548,35590,35633,35675,35717,
    35759,35801,35843,35885,35927,35969,36011,36053,
    36095,36137,36179,36221,36263,36305,36347,36388,
    36430,36472,36514,36556,36597,36639,36681,36722,
    36764,36805,36847,36889,36930,36972,37013,37055,
    37096,37137,37179,37220,37262,37303,37344,37386,
    37427,37468,37509,37551,37592,37633,37674,37715,
    37756,37797,37838,37879,37920,37961,38002,38043,
    38084,38125,38166,38207,38248,38288,38329,38370,
    38411,38451,38492,38533,38573,38614,38655,38695,
    38736,38776,38817,38857,38898,38938,38979,39019,
    39059,39100,39140,39180,39221,39261,39301,39341,
    39382,39422,39462,39502,39542,39582,39622,39662,
    39702,39742,39782,39822,39862,39902,39942,39982,
    40021,40061,40101,40141,40180,40220,40260,40299,
    40339,40379,40418,40458,40497,40537,40576,40616,
    40655,40695,40734,40773,40813,40852,40891,40931,
    40970,41009,41048,41087,41127,41166,41205,41244,
    41283,41322,41361,41400,41439,41478,41517,41556,
    41595,41633,41672,41711,41750,41788,41827,41866,
    41904,41943,41982,42020,42059,42097,42136,42174,
    42213,42251,42290,42328,42366,42405,42443,42481,
    42520,42558,42596,42634,42672,42711,42749,42787,
    42825,42863,42901,42939,42977,43015,43053,43091,
    43128,43166,43204,43242,43280,43317,43355,43393,
    43430,43468,43506,43543,43581,43618,43656,43693,
    43731,43768,43806,43843,43880,43918,43955,43992,
    44029,44067,44104,44141,44178,44215,44252,44289,
    44326,44363,44400,44437,44474,44511,44548,44585,
    44622,44659,44695,44732,44769,44806,44842,44879,
    44915,44952,44989,45025,45062,45098,45135,45171,
    45207,45244,45280,45316,45353,45389,45425,45462,
    45498,45534,45570,45606,45642,45678,45714,45750,
    45786,45822,45858,45894,45930,45966,46002,46037,
    46073,46109,46145,46180,46216,46252,46287,46323,
    46358,46394,46429,46465,46500,46536,46571,46606,
    46642,46677,46712,46747,46783,46818,46853,46888,
    46923,46958,46993,47028,47063,47098,47133,47168,
    47203,47238,47273,47308,47342,47377,47412,47446,
    47481,47516,47550,47585,47619,47654,47688,47723,
    47757,47792,47826,47861,47895,47929,47963,47998,
    48032,48066,48100,48134,48168,48202,48237,48271,
    48305,48338,48372,48406,48440,48474,48508,48542,
    48575,48609,48643,48676,48710,48744,48777,48811,
    48844,48878,48911,48945,48978,49012,49045,49078,
    49112,49145,49178,49211,49244,49278,49311,49344,
    49377,49410,49443,49476,49509,49542,49575,49608,
    49640,49673,49706,49739,49771,49804,49837,49869,
    49902,49935,49967,50000,50032,50064,50097,50129,
    50162,50194,50226,50259,50291,50323,50355,50387,
    50420,50452,50484,50516,50548,50580,50612,50644,
    50675,50707,50739,50771,50803,50834,50866,50898,
    50929,50961,50993,51024,51056,51087,51119,51150,
    51182,51213,51244,51276,51307,51338,51369,51401,
    51432,51463,51494,51525,51556,51587,51618,51649,
    51680,51711,51742,51773,51803,51834,51865,51896,
    51926,51957,51988,52018,52049,52079,52110,52140,
    52171,52201,52231,52262,52292,52322,52353,52383,
    52413,52443,52473,52503,52534,52564,52594,52624,
    52653,52683,52713,52743,52773,52803,52832,52862,
    52892,52922,52951,52981,53010,53040,53069,53099,
    53128,53158,53187,53216,53246,53275,53304,53334,
    53363,53392,53421,53450,53479,53508,53537,53566,
    53595,53624,53653,53682,53711,53739,53768,53797,
    53826,53854,53883,53912,53940,53969,53997,54026,
    54054,54082,54111,54139,54167,54196,54224,54252,
    54280,54309,54337,54365,54393,54421,54449,54477,
    54505,54533,54560,54588,54616,54644,54672,54699,
    54727,54755,54782,54810,54837,54865,54892,54920,
    54947,54974,55002,55029,55056,55084,55111,55138,
    55165,55192,55219,55246,55274,55300,55327,55354,
    55381,55408,55435,55462,55489,55515,55542,55569,
    55595,55622,55648,55675,55701,55728,55754,55781,
    55807,55833,55860,55886,55912,55938,55965,55991,
    56017,56043,56069,56095,56121,56147,56173,56199,
    56225,56250,56276,56302,56328,56353,56379,56404,
    56430,56456,56481,56507,56532,56557,56583,56608,
    56633,56659,56684,56709,56734,56760,56785,56810,
    56835,56860,56885,56910,56935,56959,56984,57009,
    57034,57059,57083,57108,57133,57157,57182,57206,
    57231,57255,57280,57304,57329,57353,57377,57402,
    57426,57450,57474,57498,57522,57546,57570,57594,
    57618,57642,57666,57690,57714,57738,57762,57785,
    57809,57833,57856,57880,57903,57927,57950,57974,
    57997,58021,58044,58067,58091,58114,58137,58160,
    58183,58207,58230,58253,58276,58299,58322,58345,
    58367,58390,58413,58436,58459,58481,58504,58527,
    58549,58572,58594,58617,58639,58662,58684,58706,
    58729,58751,58773,58795,58818,58840,58862,58884,
    58906,58928,58950,58972,58994,59016,59038,59059,
    59081,59103,59125,59146,59168,59190,59211,59233,
    59254,59276,59297,59318,59340,59361,59382,59404,
    59425,59446,59467,59488,59509,59530,59551,59572,
    59593,59614,59635,59656,59677,59697,59718,59739,
    59759,59780,59801,59821,59842,59862,59883,59903,
    59923,59944,59964,59984,60004,60025,60045,60065,
    60085,60105,60125,60145,60165,60185,60205,60225,
    60244,60264,60284,60304,60323,60343,60363,60382,
    60402,60421,60441,60460,60479,60499,60518,60537,
    60556,60576,60595,60614,60633,60652,60671,60690,
    60709,60728,60747,60766,60785,60803,60822,60841,
    60859,60878,60897,60915,60934,60952,60971,60989,
    61007,61026,61044,61062,61081,61099,61117,61135,
    61153,61171,61189,61207,61225,61243,61261,61279,
    61297,61314,61332,61350,61367,61385,61403,61420,
    61438,61455,61473,61490,61507,61525,61542,61559,
    61577,61594,61611,61628,61645,61662,61679,61696,
    61713,61730,61747,61764,61780,61797,61814,61831,
    61847,61864,61880,61897,61913,61930,61946,61963,
    61979,61995,62012,62028,62044,62060,62076,62092,
    62108,62125,62141,62156,62172,62188,62204,62220,
    62236,62251,62267,62283,62298,62314,62329,62345,
    62360,62376,62391,62407,62422,62437,62453,62468,
    62483,62498,62513,62528,62543,62558,62573,62588,
    62603,62618,62633,62648,62662,62677,62692,62706,
    62721,62735,62750,62764,62779,62793,62808,62822,
    62836,62850,62865,62879,62893,62907,62921,62935,
    62949,62963,62977,62991,63005,63019,63032,63046,
    63060,63074,63087,63101,63114,63128,63141,63155,
    63168,63182,63195,63208,63221,63235,63248,63261,
    63274,63287,63300,63313,63326,63339,63352,63365,
    63378,63390,63403,63416,63429,63441,63454,63466,
    63479,63491,63504,63516,63528,63541,63553,63565,
    63578,63590,63602,63614,63626,63638,63650,63662,
    63674,63686,63698,63709,63721,63733,63745,63756,
    63768,63779,63791,63803,63814,63825,63837,63848,
    63859,63871,63882,63893,63904,63915,63927,63938,
    63949,63960,63971,63981,63992,64003,64014,64025,
    64035,64046,64057,64067,64078,64088,64099,64109,
    64120,64130,64140,64151,64161,64171,64181,64192,
    64202,64212,64222,64232,64242,64252,64261,64271,
    64281,64291,64301,64310,64320,64330,64339,64349,
    64358,64368,64377,64387,64396,64405,64414,64424,
    64433,64442,64451,64460,64469,64478,64487,64496,
    64505,64514,64523,64532,64540,64549,64558,64566,
    64575,64584,64592,64600,64609,64617,64626,64634,
    64642,64651,64659,64667,64675,64683,64691,64699,
    64707,64715,64723,64731,64739,64747,64754,64762,
    64770,64777,64785,64793,64800,64808,64815,64822,
    64830,64837,64844,64852,64859,64866,64873,64880,
    64887,64895,64902,64908,64915,64922,64929,64936,
    64943,64949,64956,64963,64969,64976,64982,64989,
    64995,65002,65008,65015,65021,65027,65033,65040,
    65046,65052,65058,65064,65070,65076,65082,65088,
    65094,65099,65105,65111,65117,65122,65128,65133,
    65139,65144,65150,65155,65161,65166,65171,65177,
    65182,65187,65192,65197,65202,65207,65212,65217,
    65222,65227,65232,65237,65242,65246,65251,65256,
    65260,65265,65270,65274,65279,65283,65287,65292,
    65296,65300,65305,65309,65313,65317,65321,65325,
    65329,65333,65337,65341,65345,65349,65352,65356,
    65360,65363,65367,65371,65374,65378,65381,65385,
    65388,65391,65395,65398,65401,65404,65408,65411,
    65414,65417,65420,65423,65426,65429,65431,65434,
    65437,65440,65442,65445,65448,65450,65453,65455,
    65458,65460,65463,65465,65467,65470,65472,65474,
    65476,65478,65480,65482,65484,65486,65488,65490,
    65492,65494,65496,65497,65499,65501,65502,65504,
    65505,65507,65508,65510,65511,65513,65514,65515,
    65516,65518,65519,65520,65521,65522,65523,65524,
    65525,65526,65527,65527,65528,65529,65530,65530,
    65531,65531,65532,65532,65533,65533,65534,65534,
    65534,65535,65535,65535,65535,65535,65535,65535
};



angle_t tantoangle[2049] =
{
    0,333772,667544,1001315,1335086,1668857,2002626,2336395,
    2670163,3003929,3337694,3671457,4005219,4338979,4672736,5006492,
    5340245,5673995,6007743,6341488,6675230,7008968,7342704,7676435,
    8010164,8343888,8677609,9011325,9345037,9678744,10012447,10346145,
    10679838,11013526,11347209,11680887,12014558,12348225,12681885,13015539,
    13349187,13682829,14016464,14350092,14683714,15017328,15350936,15684536,
    16018129,16351714,16685291,17018860,17352422,17685974,18019518,18353054,
    18686582,19020100,19353610,19687110,20020600,20354080,20687552,21021014,
    21354466,21687906,22021338,22354758,22688168,23021568,23354956,23688332,
    24021698,24355052,24688396,25021726,25355046,25688352,26021648,26354930,
    26688200,27021456,27354702,27687932,28021150,28354356,28687548,29020724,
    29353888,29687038,30020174,30353296,30686404,31019496,31352574,31685636,
    32018684,32351718,32684734,33017736,33350722,33683692,34016648,34349584,
    34682508,35015412,35348300,35681172,36014028,36346868,36679688,37012492,
    37345276,37678044,38010792,38343524,38676240,39008936,39341612,39674272,
    40006912,40339532,40672132,41004716,41337276,41669820,42002344,42334848,
    42667332,42999796,43332236,43664660,43997060,44329444,44661800,44994140,
    45326456,45658752,45991028,46323280,46655512,46987720,47319908,47652072,
    47984212,48316332,48648428,48980500,49312548,49644576,49976580,50308556,
    50640512,50972444,51304352,51636236,51968096,52299928,52631740,52963524,
    53295284,53627020,53958728,54290412,54622068,54953704,55285308,55616888,
    55948444,56279972,56611472,56942948,57274396,57605816,57937212,58268576,
    58599916,58931228,59262512,59593768,59924992,60256192,60587364,60918508,
    61249620,61580704,61911760,62242788,62573788,62904756,63235692,63566604,
    63897480,64228332,64559148,64889940,65220696,65551424,65882120,66212788,
    66543420,66874024,67204600,67535136,67865648,68196120,68526568,68856984,
    69187360,69517712,69848024,70178304,70508560,70838776,71168960,71499112,
    71829224,72159312,72489360,72819376,73149360,73479304,73809216,74139096,
    74468936,74798744,75128520,75458264,75787968,76117632,76447264,76776864,
    77106424,77435952,77765440,78094888,78424304,78753688,79083032,79412336,
    79741608,80070840,80400032,80729192,81058312,81387392,81716432,82045440,
    82374408,82703336,83032224,83361080,83689896,84018664,84347400,84676096,
    85004760,85333376,85661952,85990488,86318984,86647448,86975864,87304240,
    87632576,87960872,88289128,88617344,88945520,89273648,89601736,89929792,
    90257792,90585760,90913688,91241568,91569408,91897200,92224960,92552672,
    92880336,93207968,93535552,93863088,94190584,94518040,94845448,95172816,
    95500136,95827416,96154648,96481832,96808976,97136080,97463136,97790144,
    98117112,98444032,98770904,99097736,99424520,99751256,100077944,100404592,
    100731192,101057744,101384248,101710712,102037128,102363488,102689808,103016080,
    103342312,103668488,103994616,104320696,104646736,104972720,105298656,105624552,
    105950392,106276184,106601928,106927624,107253272,107578872,107904416,108229920,
    108555368,108880768,109206120,109531416,109856664,110181872,110507016,110832120,
    111157168,111482168,111807112,112132008,112456856,112781648,113106392,113431080,
    113755720,114080312,114404848,114729328,115053760,115378136,115702464,116026744,
    116350960,116675128,116999248,117323312,117647320,117971272,118295176,118619024,
    118942816,119266560,119590248,119913880,120237456,120560984,120884456,121207864,
    121531224,121854528,122177784,122500976,122824112,123147200,123470224,123793200,
    124116120,124438976,124761784,125084528,125407224,125729856,126052432,126374960,
    126697424,127019832,127342184,127664472,127986712,128308888,128631008,128953072,
    129275080,129597024,129918912,130240744,130562520,130884232,131205888,131527480,
    131849016,132170496,132491912,132813272,133134576,133455816,133776992,134098120,
    134419184,134740176,135061120,135382000,135702816,136023584,136344272,136664912,
    136985488,137306016,137626464,137946864,138267184,138587456,138907664,139227808,
    139547904,139867920,140187888,140507776,140827616,141147392,141467104,141786752,
    142106336,142425856,142745312,143064720,143384048,143703312,144022512,144341664,
    144660736,144979744,145298704,145617584,145936400,146255168,146573856,146892480,
    147211040,147529536,147847968,148166336,148484640,148802880,149121056,149439152,
    149757200,150075168,150393072,150710912,151028688,151346400,151664048,151981616,
    152299136,152616576,152933952,153251264,153568496,153885680,154202784,154519824,
    154836784,155153696,155470528,155787296,156104000,156420624,156737200,157053696,
    157370112,157686480,158002768,158318976,158635136,158951216,159267232,159583168,
    159899040,160214848,160530592,160846256,161161840,161477376,161792832,162108208,
    162423520,162738768,163053952,163369040,163684080,163999040,164313936,164628752,
    164943504,165258176,165572784,165887312,166201776,166516160,166830480,167144736,
    167458912,167773008,168087040,168400992,168714880,169028688,169342432,169656096,
    169969696,170283216,170596672,170910032,171223344,171536576,171849728,172162800,
    172475808,172788736,173101600,173414384,173727104,174039728,174352288,174664784,
    174977200,175289536,175601792,175913984,176226096,176538144,176850096,177161984,
    177473792,177785536,178097200,178408784,178720288,179031728,179343088,179654368,
    179965568,180276704,180587744,180898720,181209616,181520448,181831184,182141856,
    182452448,182762960,183073408,183383760,183694048,184004240,184314368,184624416,
    184934400,185244288,185554096,185863840,186173504,186483072,186792576,187102000,
    187411344,187720608,188029808,188338912,188647936,188956896,189265760,189574560,
    189883264,190191904,190500448,190808928,191117312,191425632,191733872,192042016,
    192350096,192658096,192966000,193273840,193581584,193889264,194196848,194504352,
    194811792,195119136,195426400,195733584,196040688,196347712,196654656,196961520,
    197268304,197574992,197881616,198188144,198494592,198800960,199107248,199413456,
    199719584,200025616,200331584,200637456,200943248,201248960,201554576,201860128,
    202165584,202470960,202776256,203081456,203386592,203691632,203996592,204301472,
    204606256,204910976,205215600,205520144,205824592,206128960,206433248,206737456,
    207041584,207345616,207649568,207953424,208257216,208560912,208864512,209168048,
    209471488,209774832,210078112,210381296,210684384,210987408,211290336,211593184,
    211895936,212198608,212501184,212803680,213106096,213408432,213710672,214012816,
    214314880,214616864,214918768,215220576,215522288,215823920,216125472,216426928,
    216728304,217029584,217330784,217631904,217932928,218233856,218534704,218835472,
    219136144,219436720,219737216,220037632,220337952,220638192,220938336,221238384,
    221538352,221838240,222138032,222437728,222737344,223036880,223336304,223635664,
    223934912,224234096,224533168,224832160,225131072,225429872,225728608,226027232,
    226325776,226624240,226922608,227220880,227519056,227817152,228115168,228413088,
    228710912,229008640,229306288,229603840,229901312,230198688,230495968,230793152,
    231090256,231387280,231684192,231981024,232277760,232574416,232870960,233167440,
    233463808,233760096,234056288,234352384,234648384,234944304,235240128,235535872,
    235831504,236127056,236422512,236717888,237013152,237308336,237603424,237898416,
    238193328,238488144,238782864,239077488,239372016,239666464,239960816,240255072,
    240549232,240843312,241137280,241431168,241724960,242018656,242312256,242605776,
    242899200,243192512,243485744,243778896,244071936,244364880,244657744,244950496,
    245243168,245535744,245828224,246120608,246412912,246705104,246997216,247289216,
    247581136,247872960,248164688,248456320,248747856,249039296,249330640,249621904,
    249913056,250204128,250495088,250785968,251076736,251367424,251658016,251948512,
    252238912,252529200,252819408,253109520,253399536,253689456,253979280,254269008,
    254558640,254848176,255137632,255426976,255716224,256005376,256294432,256583392,
    256872256,257161024,257449696,257738272,258026752,258315136,258603424,258891600,
    259179696,259467696,259755600,260043392,260331104,260618704,260906224,261193632,
    261480960,261768176,262055296,262342320,262629248,262916080,263202816,263489456,
    263776000,264062432,264348784,264635024,264921168,265207216,265493168,265779024,
    266064784,266350448,266636000,266921472,267206832,267492096,267777264,268062336,
    268347312,268632192,268916960,269201632,269486208,269770688,270055072,270339360,
    270623552,270907616,271191616,271475488,271759296,272042976,272326560,272610048,
    272893440,273176736,273459936,273743040,274026048,274308928,274591744,274874432,
    275157024,275439520,275721920,276004224,276286432,276568512,276850528,277132416,
    277414240,277695936,277977536,278259040,278540448,278821728,279102944,279384032,
    279665056,279945952,280226752,280507456,280788064,281068544,281348960,281629248,
    281909472,282189568,282469568,282749440,283029248,283308960,283588544,283868032,
    284147424,284426720,284705920,284985024,285264000,285542912,285821696,286100384,
    286378976,286657440,286935840,287214112,287492320,287770400,288048384,288326240,
    288604032,288881696,289159264,289436768,289714112,289991392,290268576,290545632,
    290822592,291099456,291376224,291652896,291929440,292205888,292482272,292758528,
    293034656,293310720,293586656,293862496,294138240,294413888,294689440,294964864,
    295240192,295515424,295790560,296065600,296340512,296615360,296890080,297164704,
    297439200,297713632,297987936,298262144,298536256,298810240,299084160,299357952,
    299631648,299905248,300178720,300452128,300725408,300998592,301271680,301544640,
    301817536,302090304,302362976,302635520,302908000,303180352,303452608,303724768,
    303996800,304268768,304540608,304812320,305083968,305355520,305626944,305898272,
    306169472,306440608,306711616,306982528,307253344,307524064,307794656,308065152,
    308335552,308605856,308876032,309146112,309416096,309685984,309955744,310225408,
    310494976,310764448,311033824,311303072,311572224,311841280,312110208,312379040,
    312647776,312916416,313184960,313453376,313721696,313989920,314258016,314526016,
    314793920,315061728,315329408,315597024,315864512,316131872,316399168,316666336,
    316933408,317200384,317467232,317733984,318000640,318267200,318533632,318799968,
    319066208,319332352,319598368,319864288,320130112,320395808,320661408,320926912,
    321192320,321457632,321722816,321987904,322252864,322517760,322782528,323047200,
    323311744,323576192,323840544,324104800,324368928,324632992,324896928,325160736,
    325424448,325688096,325951584,326215008,326478304,326741504,327004608,327267584,
    327530464,327793248,328055904,328318496,328580960,328843296,329105568,329367712,
    329629760,329891680,330153536,330415264,330676864,330938400,331199808,331461120,
    331722304,331983392,332244384,332505280,332766048,333026752,333287296,333547776,
    333808128,334068384,334328544,334588576,334848512,335108352,335368064,335627712,
    335887200,336146624,336405920,336665120,336924224,337183200,337442112,337700864,
    337959552,338218112,338476576,338734944,338993184,339251328,339509376,339767296,
    340025120,340282848,340540480,340797984,341055392,341312704,341569888,341826976,
    342083968,342340832,342597600,342854272,343110848,343367296,343623648,343879904,
    344136032,344392064,344648000,344903808,345159520,345415136,345670656,345926048,
    346181344,346436512,346691616,346946592,347201440,347456224,347710880,347965440,
    348219872,348474208,348728448,348982592,349236608,349490528,349744320,349998048,
    350251648,350505152,350758528,351011808,351264992,351518048,351771040,352023872,
    352276640,352529280,352781824,353034272,353286592,353538816,353790944,354042944,
    354294880,354546656,354798368,355049952,355301440,355552800,355804096,356055264,
    356306304,356557280,356808128,357058848,357309504,357560032,357810464,358060768,
    358311008,358561088,358811104,359060992,359310784,359560480,359810048,360059520,
    360308896,360558144,360807296,361056352,361305312,361554144,361802880,362051488,
    362300032,362548448,362796736,363044960,363293056,363541024,363788928,364036704,
    364284384,364531936,364779392,365026752,365274016,365521152,365768192,366015136,
    366261952,366508672,366755296,367001792,367248192,367494496,367740704,367986784,
    368232768,368478656,368724416,368970080,369215648,369461088,369706432,369951680,
    370196800,370441824,370686752,370931584,371176288,371420896,371665408,371909792,
    372154080,372398272,372642336,372886304,373130176,373373952,373617600,373861152,
    374104608,374347936,374591168,374834304,375077312,375320224,375563040,375805760,
    376048352,376290848,376533248,376775520,377017696,377259776,377501728,377743584,
    377985344,378227008,378468544,378709984,378951328,379192544,379433664,379674688,
    379915584,380156416,380397088,380637696,380878176,381118560,381358848,381599040,
    381839104,382079072,382318912,382558656,382798304,383037856,383277280,383516640,
    383755840,383994976,384233984,384472896,384711712,384950400,385188992,385427488,
    385665888,385904160,386142336,386380384,386618368,386856224,387093984,387331616,
    387569152,387806592,388043936,388281152,388518272,388755296,388992224,389229024,
    389465728,389702336,389938816,390175200,390411488,390647680,390883744,391119712,
    391355584,391591328,391826976,392062528,392297984,392533312,392768544,393003680,
    393238720,393473632,393708448,393943168,394177760,394412256,394646656,394880960,
    395115136,395349216,395583200,395817088,396050848,396284512,396518080,396751520,
    396984864,397218112,397451264,397684288,397917248,398150080,398382784,398615424,
    398847936,399080320,399312640,399544832,399776928,400008928,400240832,400472608,
    400704288,400935872,401167328,401398720,401629984,401861120,402092192,402323136,
    402553984,402784736,403015360,403245888,403476320,403706656,403936896,404167008,
    404397024,404626944,404856736,405086432,405316032,405545536,405774912,406004224,
    406233408,406462464,406691456,406920320,407149088,407377760,407606336,407834784,
    408063136,408291392,408519520,408747584,408975520,409203360,409431072,409658720,
    409886240,410113664,410340992,410568192,410795296,411022304,411249216,411476032,
    411702720,411929312,412155808,412382176,412608480,412834656,413060736,413286720,
    413512576,413738336,413964000,414189568,414415040,414640384,414865632,415090784,
    415315840,415540800,415765632,415990368,416215008,416439552,416663968,416888288,
    417112512,417336640,417560672,417784576,418008384,418232096,418455712,418679200,
    418902624,419125920,419349120,419572192,419795200,420018080,420240864,420463552,
    420686144,420908608,421130976,421353280,421575424,421797504,422019488,422241344,
    422463104,422684768,422906336,423127776,423349120,423570400,423791520,424012576,
    424233536,424454368,424675104,424895744,425116288,425336736,425557056,425777280,
    425997408,426217440,426437376,426657184,426876928,427096544,427316064,427535488,
    427754784,427974016,428193120,428412128,428631040,428849856,429068544,429287168,
    429505664,429724064,429942368,430160576,430378656,430596672,430814560,431032352,
    431250048,431467616,431685120,431902496,432119808,432336992,432554080,432771040,
    432987936,433204736,433421408,433637984,433854464,434070848,434287104,434503296,
    434719360,434935360,435151232,435367008,435582656,435798240,436013696,436229088,
    436444352,436659520,436874592,437089568,437304416,437519200,437733856,437948416,
    438162880,438377248,438591520,438805696,439019744,439233728,439447584,439661344,
    439875008,440088576,440302048,440515392,440728672,440941824,441154880,441367872,
    441580736,441793472,442006144,442218720,442431168,442643552,442855808,443067968,
    443280032,443492000,443703872,443915648,444127296,444338880,444550336,444761696,
    444972992,445184160,445395232,445606176,445817056,446027840,446238496,446449088,
    446659552,446869920,447080192,447290400,447500448,447710432,447920320,448130112,
    448339776,448549376,448758848,448968224,449177536,449386720,449595808,449804800,
    450013664,450222464,450431168,450639776,450848256,451056640,451264960,451473152,
    451681248,451889248,452097152,452304960,452512672,452720288,452927808,453135232,
    453342528,453549760,453756864,453963904,454170816,454377632,454584384,454791008,
    454997536,455203968,455410304,455616544,455822688,456028704,456234656,456440512,
    456646240,456851904,457057472,457262912,457468256,457673536,457878688,458083744,
    458288736,458493600,458698368,458903040,459107616,459312096,459516480,459720768,
    459924960,460129056,460333056,460536960,460740736,460944448,461148064,461351584,
    461554976,461758304,461961536,462164640,462367680,462570592,462773440,462976160,
    463178816,463381344,463583776,463786144,463988384,464190560,464392608,464594560,
    464796448,464998208,465199872,465401472,465602944,465804320,466005600,466206816,
    466407904,466608896,466809824,467010624,467211328,467411936,467612480,467812896,
    468013216,468213440,468413600,468613632,468813568,469013440,469213184,469412832,
    469612416,469811872,470011232,470210528,470409696,470608800,470807776,471006688,
    471205472,471404192,471602784,471801312,471999712,472198048,472396288,472594400,
    472792448,472990400,473188256,473385984,473583648,473781216,473978688,474176064,
    474373344,474570528,474767616,474964608,475161504,475358336,475555040,475751648,
    475948192,476144608,476340928,476537184,476733312,476929376,477125344,477321184,
    477516960,477712640,477908224,478103712,478299104,478494400,478689600,478884704,
    479079744,479274656,479469504,479664224,479858880,480053408,480247872,480442240,
    480636512,480830656,481024736,481218752,481412640,481606432,481800128,481993760,
    482187264,482380704,482574016,482767264,482960416,483153472,483346432,483539296,
    483732064,483924768,484117344,484309856,484502240,484694560,484886784,485078912,
    485270944,485462880,485654720,485846464,486038144,486229696,486421184,486612576,
    486803840,486995040,487186176,487377184,487568096,487758912,487949664,488140320,
    488330880,488521312,488711712,488901984,489092160,489282240,489472256,489662176,
    489851968,490041696,490231328,490420896,490610336,490799712,490988960,491178144,
    491367232,491556224,491745120,491933920,492122656,492311264,492499808,492688256,
    492876608,493064864,493253056,493441120,493629120,493817024,494004832,494192544,
    494380160,494567712,494755136,494942496,495129760,495316928,495504000,495691008,
    495877888,496064704,496251424,496438048,496624608,496811040,496997408,497183680,
    497369856,497555936,497741920,497927840,498113632,498299360,498484992,498670560,
    498856000,499041376,499226656,499411840,499596928,499781920,499966848,500151680,
    500336416,500521056,500705600,500890080,501074464,501258752,501442944,501627040,
    501811072,501995008,502178848,502362592,502546240,502729824,502913312,503096704,
    503280000,503463232,503646368,503829408,504012352,504195200,504377984,504560672,
    504743264,504925760,505108192,505290496,505472736,505654912,505836960,506018944,
    506200832,506382624,506564320,506745952,506927488,507108928,507290272,507471552,
    507652736,507833824,508014816,508195744,508376576,508557312,508737952,508918528,
    509099008,509279392,509459680,509639904,509820032,510000064,510180000,510359872,
    510539648,510719328,510898944,511078432,511257856,511437216,511616448,511795616,
    511974688,512153664,512332576,512511392,512690112,512868768,513047296,513225792,
    513404160,513582432,513760640,513938784,514116800,514294752,514472608,514650368,
    514828064,515005664,515183168,515360608,515537952,515715200,515892352,516069440,
    516246432,516423328,516600160,516776896,516953536,517130112,517306592,517482976,
    517659264,517835488,518011616,518187680,518363648,518539520,518715296,518891008,
    519066624,519242144,519417600,519592960,519768256,519943424,520118528,520293568,
    520468480,520643328,520818112,520992800,521167392,521341888,521516320,521690656,
    521864896,522039072,522213152,522387168,522561056,522734912,522908640,523082304,
    523255872,523429376,523602784,523776096,523949312,524122464,524295552,524468512,
    524641440,524814240,524986976,525159616,525332192,525504640,525677056,525849344,
    526021568,526193728,526365792,526537760,526709632,526881440,527053152,527224800,
    527396352,527567840,527739200,527910528,528081728,528252864,528423936,528594880,
    528765760,528936576,529107296,529277920,529448480,529618944,529789344,529959648,
    530129856,530300000,530470048,530640000,530809888,530979712,531149440,531319072,
    531488608,531658080,531827488,531996800,532166016,532335168,532504224,532673184,
    532842080,533010912,533179616,533348288,533516832,533685312,533853728,534022048,
    534190272,534358432,534526496,534694496,534862400,535030240,535197984,535365632,
    535533216,535700704,535868128,536035456,536202720,536369888,536536992,536704000,
    536870912
};

byte* screens[5];

int dirtybox[4];

// Now where did these came from?
byte gammatable[5][256] =
{
    {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
     17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,
     33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
     49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,
     65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,
     81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,
     97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,
     113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,
     128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
     144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
     160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
     176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
     192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
     208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
     224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
     240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255},

    {2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23,24,25,26,27,29,30,31,
     32,33,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,54,55,
     56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,
     78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,
     99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,
     115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129,
     130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,
     146,147,148,148,149,150,151,152,153,154,155,156,157,158,159,160,
     161,162,163,163,164,165,166,167,168,169,170,171,172,173,174,175,
     175,176,177,178,179,180,181,182,183,184,185,186,186,187,188,189,
     190,191,192,193,194,195,196,196,197,198,199,200,201,202,203,204,
     205,205,206,207,208,209,210,211,212,213,214,214,215,216,217,218,
     219,220,221,222,222,223,224,225,226,227,228,229,230,230,231,232,
     233,234,235,236,237,237,238,239,240,241,242,243,244,245,245,246,
     247,248,249,250,251,252,252,253,254,255},

    {4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32,33,35,36,38,39,40,42,
     43,45,46,47,48,50,51,52,54,55,56,57,59,60,61,62,63,65,66,67,68,69,
     70,72,73,74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90,91,92,93,
     94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,
     113,114,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,
     129,130,131,132,133,133,134,135,136,137,138,139,140,141,142,143,144,
     144,145,146,147,148,149,150,151,152,153,153,154,155,156,157,158,159,
     160,160,161,162,163,164,165,166,166,167,168,169,170,171,172,172,173,
     174,175,176,177,178,178,179,180,181,182,183,183,184,185,186,187,188,
     188,189,190,191,192,193,193,194,195,196,197,197,198,199,200,201,201,
     202,203,204,205,206,206,207,208,209,210,210,211,212,213,213,214,215,
     216,217,217,218,219,220,221,221,222,223,224,224,225,226,227,228,228,
     229,230,231,231,232,233,234,235,235,236,237,238,238,239,240,241,241,
     242,243,244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,
     255},

    {8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45,47,49,50,52,53,55,
     57,58,60,61,63,64,65,67,68,70,71,72,74,75,76,77,79,80,81,82,84,85,
     86,87,88,90,91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107,
     108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,
     125,126,127,128,129,130,131,132,133,134,135,135,136,137,138,139,140,
     141,142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,
     155,156,157,158,159,160,160,161,162,163,164,165,165,166,167,168,169,
     169,170,171,172,173,173,174,175,176,176,177,178,179,180,180,181,182,
     183,183,184,185,186,186,187,188,189,189,190,191,192,192,193,194,195,
     195,196,197,197,198,199,200,200,201,202,202,203,204,205,205,206,207,
     207,208,209,210,210,211,212,212,213,214,214,215,216,216,217,218,219,
     219,220,221,221,222,223,223,224,225,225,226,227,227,228,229,229,230,
     231,231,232,233,233,234,235,235,236,237,237,238,238,239,240,240,241,
     242,242,243,244,244,245,246,246,247,247,248,249,249,250,251,251,252,
     253,253,254,254,255},

    {16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64,66,68,69,71,73,75,76,
     78,80,81,83,84,86,87,89,90,92,93,94,96,97,98,100,101,102,103,105,106,
     107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,
     125,126,128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,
     142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,
     156,157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169,
     169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180,181,
     182,182,183,184,184,185,186,187,187,188,189,189,190,191,191,192,193,
     193,194,195,195,196,196,197,198,198,199,200,200,201,202,202,203,203,
     204,205,205,206,207,207,208,208,209,210,210,211,211,212,213,213,214,
     214,215,216,216,217,217,218,219,219,220,220,221,221,222,223,223,224,
     224,225,225,226,227,227,228,228,229,229,230,230,231,232,232,233,233,
     234,234,235,235,236,236,237,237,238,239,239,240,240,241,241,242,242,
     243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,
     251,252,252,253,254,254,255,255}
};

int usegamma;


//
// V_MarkRect 
// 
void V_MarkRect(int x, int y, int width, int height)
{
    M_AddToBox(dirtybox, x, y);
    M_AddToBox(dirtybox, x + width - 1, y + height - 1);
}


//
// V_CopyRect 
// 
void V_CopyRect(int srcx,
                int srcy,
                int srcscrn,
                int width,
                int height,
                int destx,
                int desty,
                int destscrn)
{
    byte* src;
    byte* dest;

#ifdef RANGECHECK 
    if (srcx<0
        || srcx + width >SCREENWIDTH
        || srcy<0
        || srcy + height>SCREENHEIGHT
        || destx<0 || destx + width >SCREENWIDTH
        || desty<0
        || desty + height>SCREENHEIGHT
        || (unsigned)srcscrn > 4
        || (unsigned)destscrn > 4)
    {
        I_Error("Error: Bad V_CopyRect");
    }
#endif 
    V_MarkRect(destx, desty, width, height);

    src = screens[srcscrn] + SCREENWIDTH * srcy + srcx;
    dest = screens[destscrn] + SCREENWIDTH * desty + destx;

    for (; height > 0; height--)
    {
        doom_memcpy(dest, src, width);
        src += SCREENWIDTH;
        dest += SCREENWIDTH;
    }
}


//
// V_DrawPatch
// Masks a column based masked pic to the screen. 
//
void V_DrawPatch(int x, int y, int scrn, patch_t* patch)
{
    int count;
    int col;
    column_t* column;
    byte* desttop;
    byte* dest;
    byte* source;
    int w;

    y -= SHORT(patch->topoffset);
    x -= SHORT(patch->leftoffset);
#ifdef RANGECHECK 
    if (x<0
        || x + SHORT(patch->width) >SCREENWIDTH
        || y<0
        || y + SHORT(patch->height)>SCREENHEIGHT
        || (unsigned)scrn > 4)
    {
        //doom_print("Patch at %d,%d exceeds LFB\n", x, y);
        doom_print("Patch at ");
        doom_print(doom_itoa(x, 10));
        doom_print(",");
        doom_print(doom_itoa(y, 10));
        doom_print(" exceeds LFB\n");
        // No I_Error abort - what is up with TNT.WAD?
        doom_print("V_DrawPatch: bad patch (ignored)\n");
        return;
    }
#endif 

    if (!scrn)
        V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height));

    col = 0;
    desttop = screens[scrn] + y * SCREENWIDTH + x;

    w = SHORT(patch->width);

    for (; col < w; x++, col++, desttop++)
    {
        column = (column_t*)((byte*)patch + LONG(patch->columnofs[col]));

        // step through the posts in a column 
        while (column->topdelta != 0xff)
        {
            source = (byte*)column + 3;
            dest = desttop + column->topdelta * SCREENWIDTH;
            count = column->length;

            while (count--)
            {
                *dest = *source++;
                dest += SCREENWIDTH;
            }
            column = (column_t*)((byte*)column + column->length
                                 + 4);
        }
    }
}


//
// V_DrawPatchFlipped 
// Masks a column based masked pic to the screen.
// Flips horizontally, e.g. to mirror face.
//
void V_DrawPatchFlipped(int x, int y, int scrn, patch_t* patch)
{
    int count;
    int col;
    column_t* column;
    byte* desttop;
    byte* dest;
    byte* source;
    int w;

    y -= SHORT(patch->topoffset);
    x -= SHORT(patch->leftoffset);
#ifdef RANGECHECK 
    if (x<0
        || x + SHORT(patch->width) >SCREENWIDTH
        || y<0
        || y + SHORT(patch->height)>SCREENHEIGHT
        || (unsigned)scrn > 4)
    {
        //doom_print("Patch origin %d,%d exceeds LFB\n", x, y);
        doom_print("Patch origin ");
        doom_print(doom_itoa(x, 10));
        doom_print(",");
        doom_print(doom_itoa(y, 10));
        doom_print(" exceeds LFB\n");
        I_Error("Error: Bad V_DrawPatch in V_DrawPatchFlipped");
    }
#endif 

    if (!scrn)
        V_MarkRect(x, y, SHORT(patch->width), SHORT(patch->height));

    col = 0;
    desttop = screens[scrn] + y * SCREENWIDTH + x;

    w = SHORT(patch->width);

    for (; col < w; x++, col++, desttop++)
    {
        column = (column_t*)((byte*)patch + LONG(patch->columnofs[w - 1 - col]));

        // step through the posts in a column 
        while (column->topdelta != 0xff)
        {
            source = (byte*)column + 3;
            dest = desttop + column->topdelta * SCREENWIDTH;
            count = column->length;

            while (count--)
            {
                *dest = *source++;
                dest += SCREENWIDTH;
            }
            column = (column_t*)((byte*)column + column->length
                                 + 4);
        }
    }
}


void V_DrawPatchRectDirect(int x, int y, int scrn, patch_t* patch, int src_x, int src_w)
{
    int count;
    int col;
    column_t* column;
    byte* desttop;
    byte* dest;
    byte* source;
    int w;

    y -= SHORT(patch->topoffset);
    x -= SHORT(patch->leftoffset);
#ifdef RANGECHECK 
    if (x<0
        || x + SHORT(src_w) >SCREENWIDTH
        || y<0
        || y + SHORT(patch->height)>SCREENHEIGHT
        || (unsigned)scrn > 4)
    {
        //doom_print("Patch at %d,%d exceeds LFB\n", x, y);
        doom_print("Patch at ");
        doom_print(doom_itoa(x, 10));
        doom_print(",");
        doom_print(doom_itoa(y, 10));
        doom_print(" exceeds LFB\n");
        // No I_Error abort - what is up with TNT.WAD?
        doom_print("V_DrawPatch: bad patch (ignored)\n");
        return;
    }
#endif 

    if (!scrn)
        V_MarkRect(x, y, SHORT(src_w), SHORT(patch->height));

    col = 0;
    desttop = screens[scrn] + y * SCREENWIDTH + x;

    w = SHORT(src_w);

    for (; col < w; x++, col++, desttop++)
    {
        column = (column_t*)((byte*)patch + LONG(patch->columnofs[col + src_x]));

        // step through the posts in a column 
        while (column->topdelta != 0xff)
        {
            source = (byte*)column + 3;
            dest = desttop + column->topdelta * SCREENWIDTH;
            count = column->length;

            while (count--)
            {
                *dest = *source++;
                dest += SCREENWIDTH;
            }
            column = (column_t*)((byte*)column + column->length
                                 + 4);
        }
    }
}


//
// V_DrawPatchDirect
// Draws directly to the screen on the pc. 
//
void V_DrawPatchDirect(int x, int y, int scrn, patch_t* patch)
{
    V_DrawPatch(x, y, scrn, patch);
}


//
// V_DrawBlock
// Draw a linear block of pixels into the view buffer.
//
void V_DrawBlock(int x, int y, int scrn, int width, int height, byte* src)
{
    byte* dest;

#ifdef RANGECHECK 
    if (x<0
        || x + width >SCREENWIDTH
        || y<0
        || y + height>SCREENHEIGHT
        || (unsigned)scrn > 4)
    {
        I_Error("Error: Bad V_DrawBlock");
    }
#endif 

    V_MarkRect(x, y, width, height);

    dest = screens[scrn] + y * SCREENWIDTH + x;

    while (height--)
    {
        doom_memcpy(dest, src, width);
        src += width;
        dest += SCREENWIDTH;
    }
}


//
// V_GetBlock
// Gets a linear block of pixels from the view buffer.
//
void V_GetBlock(int x, int y, int scrn, int width, int height, byte* dest)
{
    byte* src;

#ifdef RANGECHECK 
    if (x<0
        || x + width >SCREENWIDTH
        || y<0
        || y + height>SCREENHEIGHT
        || (unsigned)scrn > 4)
    {
        I_Error("Error: Bad V_DrawBlock");
    }
#endif 

    src = screens[scrn] + y * SCREENWIDTH + x;

    while (height--)
    {
        doom_memcpy(dest, src, width);
        src += SCREENWIDTH;
        dest += width;
    }
}


//
// V_Init
// 
void V_Init(void)
{
    int i;
    byte* base;

    // stick these in low dos memory on PCs

    base = I_AllocLow(SCREENWIDTH * SCREENHEIGHT * 4);

    for (i = 0; i < 4; i++)
        screens[i] = base + i * SCREENWIDTH * SCREENHEIGHT;
}
lumpinfo_t* lumpinfo;
int numlumps;
void** lumpcache;
int reloadlump;
char* reloadname;
int info[2500][10];
int profilecount;


void doom_strupr(char* s)
{
    while (*s)
    {
        *s = doom_toupper(*s); s++;
    }
}


void ExtractFileBase(char* path, char* dest)
{
    char* src;
    int length;

    src = path + doom_strlen(path) - 1;

    // back up until a \ or the start
    while (src != path
           && *(src - 1) != '\\'
           && *(src - 1) != '/')
    {
        src--;
    }

    // copy up to eight characters
    doom_memset(dest, 0, 8);
    length = 0;

    while (*src && *src != '.')
    {
        if (++length == 9)
        {
            //I_Error("Error: Filename base of %s >8 chars", path);
            doom_strcpy(error_buf, "Error: Filename base of ");
            doom_concat(error_buf, path);
            doom_concat(error_buf, " >8 chars");
            I_Error(error_buf);
        }

        *dest++ = doom_toupper((int)*src++);
    }
}


//
// LUMP BASED ROUTINES.
//

//
// W_AddFile
// All files are optional, but at least one file must be
//  found (PWAD, if all required lumps are present).
// Files with a .wad extension are wadlink files
//  with multiple lumps.
// Other files are single lumps with the base filename
//  for the lump name.
//
// If filename starts with a tilde, the file is handled
//  specially to allow map reloads.
// But: the reload feature is a fragile hack...
void W_AddFile(char* filename)
{
    wadinfo_t header;
    lumpinfo_t* lump_p;
    unsigned i;
    void* handle;
    int length;
    int startlump;
    filelump_t* fileinfo;
    filelump_t singleinfo;
    void* storehandle;
    void* allocated = 0;

    // open the file and add to directory

    // handle reload indicator.
    if (filename[0] == '~')
    {
        filename++;
        reloadname = filename;
        reloadlump = numlumps;
    }

    if ((handle = doom_open(filename, "rb")) == 0)
    {
        //doom_print(" couldn't open %s\n", filename);
        doom_print(" couldn't open ");
        doom_print(filename);
        doom_print("\n");
        return;
    }

    //doom_print(" adding %s\n", filename);
    doom_print(" adding ");
    doom_print(filename);
    doom_print("\n");
    startlump = numlumps;

    if (doom_strcasecmp(filename + doom_strlen(filename) - 3, "wad"))
    {
        // single lump file
        fileinfo = &singleinfo;
        singleinfo.filepos = 0;
        //singleinfo.size = LONG(filelength(handle));
        doom_seek(handle, 0, DOOM_SEEK_END);
        singleinfo.size = doom_tell(handle);
        doom_seek(handle, 0, DOOM_SEEK_SET);
        ExtractFileBase(filename, singleinfo.name);
        numlumps++;
    }
    else
    {
        // WAD file
        doom_read(handle, &header, sizeof(header));
        if (doom_strncmp(header.identification, "IWAD", 4))
        {
            // Homebrew levels?
            if (doom_strncmp(header.identification, "PWAD", 4))
            {
                //I_Error("Error: Wad file %s doesn't have IWAD "
                //        "or PWAD id\n", filename);
                doom_strcpy(error_buf, "Error: Wad file ");
                doom_concat(error_buf, filename);
                doom_concat(error_buf, " doesn't have IWAD or PWAD id\n");
                I_Error(error_buf);
            }

            // ???modifiedgame = true;                
        }
        header.numlumps = LONG(header.numlumps);
        header.infotableofs = LONG(header.infotableofs);
        length = header.numlumps * sizeof(filelump_t);
        fileinfo = allocated = doom_malloc(length);
        doom_seek(handle, header.infotableofs, DOOM_SEEK_SET);
        doom_read(handle, fileinfo, length);
        numlumps += header.numlumps;
    }


    // Fill in lumpinfo
    static int previous_realloc_size = 1;
    void* new_lumpinfo = doom_malloc(numlumps * sizeof(lumpinfo_t));
    doom_memcpy(new_lumpinfo, lumpinfo, previous_realloc_size);
    previous_realloc_size = numlumps * sizeof(lumpinfo_t);
    lumpinfo = new_lumpinfo;

    if (!lumpinfo)
        I_Error("Error: Couldn't realloc lumpinfo");

    lump_p = &lumpinfo[startlump];

    storehandle = reloadname ? 0 : handle;

    for (i = startlump; i < (unsigned)numlumps; i++, lump_p++, fileinfo++)
    {
        lump_p->handle = storehandle;
        lump_p->position = LONG(fileinfo->filepos);
        lump_p->size = LONG(fileinfo->size);
        doom_strncpy(lump_p->name, fileinfo->name, 8);
    }

    if (reloadname)
        doom_close(handle);

    if (allocated) doom_free(allocated);
}


//
// W_Reload
// Flushes any of the reloadable lumps in memory
//  and reloads the directory.
//
void W_Reload(void)
{
    wadinfo_t header;
    int lumpcount;
    lumpinfo_t* lump_p;
    unsigned i;
    void* handle;
    int length;
    filelump_t* fileinfo;

    if (!reloadname)
        return;

    if ((handle = doom_open(reloadname, "rb")) == 0)
    {
        //I_Error("Error: W_Reload: couldn't open %s", reloadname);
        doom_strcpy(error_buf, "Error: W_Reload: couldn't open ");
        doom_concat(error_buf, reloadname);
        I_Error(error_buf);
    }

    doom_read(handle, &header, sizeof(header));
    lumpcount = LONG(header.numlumps);
    header.infotableofs = LONG(header.infotableofs);
    length = lumpcount * sizeof(filelump_t);
    fileinfo = doom_malloc(length);
    doom_seek(handle, header.infotableofs, DOOM_SEEK_SET);
    doom_read(handle, fileinfo, length);

    // Fill in lumpinfo
    lump_p = &lumpinfo[reloadlump];

    for (i = reloadlump;
         i < (unsigned)(reloadlump + lumpcount);
         i++, lump_p++, fileinfo++)
    {
        if (lumpcache[i])
            Z_Free(lumpcache[i]);

        lump_p->position = LONG(fileinfo->filepos);
        lump_p->size = LONG(fileinfo->size);
    }

    doom_close(handle);

    doom_free(fileinfo);
}


//
// W_InitMultipleFiles
// Pass a null terminated list of files to use.
// All files are optional, but at least one file
//  must be found.
// Files with a .wad extension are idlink files
//  with multiple lumps.
// Other files are single lumps with the base filename
//  for the lump name.
// Lump names can appear multiple times.
// The name searcher looks backwards, so a later file
//  does override all earlier ones.
//
void W_InitMultipleFiles(char** filenames)
{
    int                size;

    // open all the files, load headers, and count lumps
    numlumps = 0;

    // will be realloced as lumps are added
    lumpinfo = doom_malloc(1);

    for (; *filenames; filenames++)
        W_AddFile(*filenames);

    if (!numlumps)
        I_Error("Error: W_InitFiles: no files found");

    // set up caching
    size = numlumps * sizeof(*lumpcache);
    lumpcache = doom_malloc(size);

    if (!lumpcache)
        I_Error("Error: Couldn't allocate lumpcache");

    doom_memset(lumpcache, 0, size);
}


//
// W_InitFile
// Just initialize from a single file.
//
void W_InitFile(char* filename)
{
    char* names[2];

    names[0] = filename;
    names[1] = 0;
    W_InitMultipleFiles(names);
}


//
// W_NumLumps
//
int W_NumLumps(void)
{
    return numlumps;
}


//
// W_CheckNumForName
// Returns -1 if name not found.
//
int W_CheckNumForName(char* name)
{
    union
    {
        char s[9];
        int x[2];
    } name8;

    int v1;
    int v2;
    lumpinfo_t* lump_p;

    // make the name into two integers for easy compares
    doom_strncpy(name8.s, name, 8);

    // in case the name was a fill 8 chars
    name8.s[8] = 0;

    // case insensitive
    doom_strupr(name8.s);

    v1 = name8.x[0];
    v2 = name8.x[1];


    // scan backwards so patch lump files take precedence
    lump_p = lumpinfo + numlumps;

    while (lump_p-- != lumpinfo)
    {
        if (*(int*)lump_p->name == v1
            && *(int*)&lump_p->name[4] == v2)
        {
            return (int)(lump_p - lumpinfo);
        }
    }

    // TFB. Not found.
    return -1;
}


//
// W_GetNumForName
// Calls W_CheckNumForName, but bombs out if not found.
//
int W_GetNumForName(char* name)
{
    int i;

    i = W_CheckNumForName(name);

    if (i == -1)
    {
        if (doom_strcmp(name, "HELP2") == 0)
        {
            name = "HELP1"; // Ultimate Doom EXE was modified to use this instead
            i = W_CheckNumForName(name);
        }
        if (i == -1)
        {
            //I_Error("Error: W_GetNumForName, %s not found!", name);
            doom_strcpy(error_buf, "Error: W_GetNumForName, ");
            doom_concat(error_buf, name);
            doom_concat(error_buf, " not found!");
            I_Error(error_buf);
        }
    }

    return i;
}


//
// W_LumpLength
// Returns the buffer size needed to load the given lump.
//
int W_LumpLength(int lump)
{
    if (lump >= numlumps)
    {
        //I_Error("Error: W_LumpLength: %i >= numlumps", lump);
        doom_strcpy(error_buf, "Error: W_LumpLength: ");
        doom_concat(error_buf, doom_itoa(lump, 10));
        doom_concat(error_buf, " >= numlumps");
        I_Error(error_buf);
    }

    return lumpinfo[lump].size;
}


//
// W_ReadLump
// Loads the lump into the given buffer,
//  which must be >= W_LumpLength().
//
void W_ReadLump(int lump, void* dest)
{
    int c;
    lumpinfo_t* l;
    void* handle;

    if (lump >= numlumps)
    {
        //I_Error("Error: W_ReadLump: %i >= numlumps", lump);
        doom_strcpy(error_buf, "Error: W_ReadLump: ");
        doom_concat(error_buf, doom_itoa(lump, 10));
        doom_concat(error_buf, " >= numlump");
        I_Error(error_buf);
    }

    l = lumpinfo + lump;

    // ??? I_BeginRead ();

    if (l->handle == 0)
    {
        // reloadable file, so use open / read / close
        if ((handle = doom_open(reloadname, "rb")) == 0)
        {
            //I_Error("Error: W_ReadLump: couldn't open %s", reloadname);
            doom_strcpy(error_buf, "Error: W_ReadLump: couldn't open ");
            doom_concat(error_buf, reloadname);
            I_Error(error_buf);
        }
    }
    else
        handle = l->handle;

    doom_seek(handle, l->position, DOOM_SEEK_SET);
    c = doom_read(handle, dest, l->size);

    if (c < l->size)
    {
        //I_Error("Error: W_ReadLump: only read %i of %i on lump %i",
        //        c, l->size, lump);
        doom_strcpy(error_buf, "Error: W_ReadLump: only read ");
        doom_concat(error_buf, doom_itoa(c, 10));
        doom_concat(error_buf, " of ");
        doom_concat(error_buf, doom_itoa(l->size, 10));
        doom_concat(error_buf, " on lump ");
        doom_concat(error_buf, doom_itoa(lump, 10));
        I_Error(error_buf);
    }

    if (l->handle == 0)
        doom_close(handle);

    // ??? I_EndRead ();
}


//
// W_CacheLumpNum
//
void* W_CacheLumpNum(int lump, int tag)
{
    byte* ptr;

    if ((unsigned)lump >= (unsigned)numlumps)
    {
        //I_Error("Error: W_CacheLumpNum: %i >= numlumps", lump);
        doom_strcpy(error_buf, "Error: W_CacheLumpNum: ");
        doom_concat(error_buf, doom_itoa(lump, 10));
        doom_concat(error_buf, " >= numlumps");
        I_Error(error_buf);
    }

    if (!lumpcache[lump])
    {
        // read the lump in

        //doom_print ("cache miss on lump %i\n",lump);
        ptr = Z_Malloc(W_LumpLength(lump), tag, &lumpcache[lump]);
        W_ReadLump(lump, lumpcache[lump]);
    }
    else
    {
        //doom_print ("cache hit on lump %i\n",lump);
        Z_ChangeTag(lumpcache[lump], tag);
    }

    return lumpcache[lump];
}


//
// W_CacheLumpName
//
void* W_CacheLumpName(char* name, int tag)
{
    return W_CacheLumpNum(W_GetNumForName(name), tag);
}


//
// W_Profile
//
void W_Profile(void)
{
    int i;
    memblock_t* block;
    void* ptr;
    char ch;
    void* f;
    int j;
    char name[9];

    for (i = 0; i < numlumps; i++)
    {
        ptr = lumpcache[i];
        if (!ptr)
        {
            ch = ' ';
            continue;
        }
        else
        {
            block = (memblock_t*)((byte*)ptr - sizeof(memblock_t));
            if (block->tag < PU_PURGELEVEL)
                ch = 'S';
            else
                ch = 'P';
        }
        info[i][profilecount] = ch;
    }
    profilecount++;

    f = doom_open("waddump.txt", "w");
    name[8] = 0;

    for (i = 0; i < numlumps; i++)
    {
        doom_memcpy(name, lumpinfo[i].name, 8);

        for (j = 0; j < 8; j++)
            if (!name[j])
                break;

        for (; j < 8; j++)
            name[j] = ' ';

        doom_fprint(f, name);
        doom_fprint(f, " ");

        for (j = 0; j < profilecount; j++)
        {
            //fprintf(f, "    %c", info[i][j]);
            doom_fprint(f, "    ");
            doom_fprint(f, doom_ctoa(info[i][j]));
        }

        doom_fprint(f, "\n");
    }

    doom_close(f);
}
#define NUMEPISODES 4
#define NUMMAPS 9

// GLOBAL LOCATIONS
#define WI_TITLEY 2
#define WI_SPACINGY 33

// SINGPLE-PLAYER STUFF
#define SP_STATSX 50
#define SP_STATSY 50

#define SP_TIMEX 16
#define SP_TIMEY (SCREENHEIGHT-32)

// NET GAME STUFF
#define NG_STATSY 50
#define NG_STATSX (32 + SHORT(star->width)/2 + 32*!dofrags)

#define NG_SPACINGX 64

// DEATHMATCH STUFF
#define DM_MATRIXX 42
#define DM_MATRIXY 68

#define DM_SPACINGX 40

#define DM_TOTALSX 269

#define DM_KILLERSX 10
#define DM_KILLERSY 100
#define DM_VICTIMSX 5
#define DM_VICTIMSY 50

#define FB 0

// States for single-player
#define SP_KILLS                0
#define SP_ITEMS                2
#define SP_SECRET                4
#define SP_FRAGS                6 
#define SP_TIME                        8 
#define SP_PAR                        ST_TIME

#define SP_PAUSE                1

// in seconds
#define SHOWNEXTLOCDELAY        4
//#define SHOWLASTLOCDELAY        SHOWNEXTLOCDELAY


typedef enum
{
    ANIM_ALWAYS,
    ANIM_RANDOM,
    ANIM_LEVEL
} animenum_t;


typedef struct
{
    int x;
    int y;
} point_t;


//
// Animation.
// There is another anim_t used in p_spec.
//
typedef struct
{
    animenum_t type;

    // period in tics between animations
    int period;

    // number of animation frames
    int nanims;

    // location of animation
    point_t loc;

    // ALWAYS: n/a,
    // RANDOM: period deviation (<256),
    // LEVEL: level
    int data1;

    // ALWAYS: n/a,
    // RANDOM: random base period,
    // LEVEL: n/a
    int data2;

    // actual graphics for frames of animations
    patch_t* p[3];

    // following must be initialized to zero before use!

    // next value of bcnt (used in conjunction with period)
    int nexttic;

    // last drawn animation frame
    int lastdrawn;

    // next frame number to animate
    int ctr;

    // used by RANDOM and LEVEL when animating
    int state;
} anim_t_wi_stuff;


static point_t lnodes[NUMEPISODES][NUMMAPS] =
{
    // Episode 0 World Map
    {
        { 185, 164 },       // location of level 0 (CJ)
        { 148, 143 },       // location of level 1 (CJ)
        { 69, 122 },        // location of level 2 (CJ)
        { 209, 102 },       // location of level 3 (CJ)
        { 116, 89 },        // location of level 4 (CJ)
        { 166, 55 },        // location of level 5 (CJ)
        { 71, 56 },         // location of level 6 (CJ)
        { 135, 29 },        // location of level 7 (CJ)
        { 71, 24 }          // location of level 8 (CJ)
    },

    // Episode 1 World Map should go here
    {
        { 254, 25 },        // location of level 0 (CJ)
        { 97, 50 },         // location of level 1 (CJ)
        { 188, 64 },        // location of level 2 (CJ)
        { 128, 78 },        // location of level 3 (CJ)
        { 214, 92 },        // location of level 4 (CJ)
        { 133, 130 },       // location of level 5 (CJ)
        { 208, 136 },       // location of level 6 (CJ)
        { 148, 140 },       // location of level 7 (CJ)
        { 235, 158 }        // location of level 8 (CJ)
    },

    // Episode 2 World Map should go here
    {
        { 156, 168 },       // location of level 0 (CJ)
        { 48, 154 },        // location of level 1 (CJ)
        { 174, 95 },        // location of level 2 (CJ)
        { 265, 75 },        // location of level 3 (CJ)
        { 130, 48 },        // location of level 4 (CJ)
        { 279, 23 },        // location of level 5 (CJ)
        { 198, 48 },        // location of level 6 (CJ)
        { 140, 25 },        // location of level 7 (CJ)
        { 281, 136 }        // location of level 8 (CJ)
    }
};


//
// Animation locations for episode 0 (1).
// Using patches saves a lot of space,
//  as they replace 320x200 full screen frames.
//
static anim_t_wi_stuff epsd0animinfo[] =
{
    { ANIM_ALWAYS, TICRATE / 3, 3, { 224, 104 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 184, 160 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 112, 136 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 72, 112 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 88, 96 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 64, 48 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 192, 40 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 136, 16 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 80, 16 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 64, 24 } }
};

static anim_t_wi_stuff epsd1animinfo[] =
{
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 1 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 2 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 3 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 4 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 5 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 6 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 7 },
    { ANIM_LEVEL, TICRATE / 3, 3, { 192, 144 }, 8 },
    { ANIM_LEVEL, TICRATE / 3, 1, { 128, 136 }, 8 }
};

static anim_t_wi_stuff epsd2animinfo[] =
{
    { ANIM_ALWAYS, TICRATE / 3, 3, { 104, 168 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 40, 136 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 160, 96 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 104, 80 } },
    { ANIM_ALWAYS, TICRATE / 3, 3, { 120, 32 } },
    { ANIM_ALWAYS, TICRATE / 4, 3, { 40, 0 } }
};

static int NUMANIMS[NUMEPISODES] =
{
    sizeof(epsd0animinfo) / sizeof(anim_t_wi_stuff),
    sizeof(epsd1animinfo) / sizeof(anim_t_wi_stuff),
    sizeof(epsd2animinfo) / sizeof(anim_t_wi_stuff)
};

static anim_t_wi_stuff* anims_wi_stuff[NUMEPISODES] =
{
    epsd0animinfo,
    epsd1animinfo,
    epsd2animinfo
};


//
// GENERAL DATA
//

//
// Locally used stuff.
//

// used to accelerate or skip a stage
static int acceleratestage;

// wbs->pnum
static int me;

// specifies current state
static stateenum_t state;

// contains information passed into intermission
static wbstartstruct_t* wbs;

static wbplayerstruct_t* plrs;  // wbs->plyr[]

// used for general timing
static int cnt;

// used for timing of background animation
static int bcnt;

// signals to refresh everything for one frame
static int firstrefresh;

static int cnt_kills[MAXPLAYERS];
static int cnt_items[MAXPLAYERS];
static int cnt_secret[MAXPLAYERS];
static int cnt_time;
static int cnt_par;
static int cnt_pause;

// # of commercial levels
static int NUMCMAPS;


//
// GRAPHICS
//

// background (map of levels).
static patch_t* bg;

// You Are Here graphic
static patch_t* yah[2];

// splat
static patch_t* splat;

// %, : graphics
static patch_t* percent;
static patch_t* colon;

// 0-9 graphic
static patch_t* num[10];

// minus sign
static patch_t* wiminus;

// "Finished!" graphics
static patch_t* finished;

// "Entering" graphic
static patch_t* entering;

// "secret"
static patch_t* sp_secret;

// "Kills", "Scrt", "Items", "Frags"
static patch_t* kills;
static patch_t* secret;
static patch_t* items;
static patch_t* frags;

// Time sucks.
static patch_t* time_patch;
static patch_t* par;
static patch_t* sucks;

// "killers", "victims"
static patch_t* killers;
static patch_t* victims;

// "Total", your face, your dead face
static patch_t* total;
static patch_t* star;
static patch_t* bstar;

// "red P[1..MAXPLAYERS]"
static patch_t* p[MAXPLAYERS];

// "gray P[1..MAXPLAYERS]"
static patch_t* bp[MAXPLAYERS];

// Name graphics of each level (centered)
static patch_t** lnames;

static doom_boolean snl_pointeron = false;
static int dm_state;
static int dm_frags[MAXPLAYERS][MAXPLAYERS];
static int dm_totals[MAXPLAYERS];
static int cnt_frags[MAXPLAYERS];
static int dofrags;
static int ng_state;
static int sp_state;


//
// CODE
//

void WI_slamBackground(void)
{
    doom_memcpy(screens[0], screens[1], SCREENWIDTH * SCREENHEIGHT);
    V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT);
}


// The ticker is used to detect keys
//  because of timing issues in netgames.
doom_boolean WI_Responder(event_t* ev)
{
    return false;
}


// Draws "<Levelname> Finished!"
void WI_drawLF(void)
{
    int y = WI_TITLEY;

    // draw <LevelName> 
    V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->last]->width)) / 2,
                y, FB, lnames[wbs->last]);

    // draw "Finished!"
    y += (5 * SHORT(lnames[wbs->last]->height)) / 4;

    V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2,
                y, FB, finished);
}


// Draws "Entering <LevelName>"
void WI_drawEL(void)
{
    int y = WI_TITLEY;

    // draw "Entering"
    V_DrawPatch((SCREENWIDTH - SHORT(entering->width)) / 2,
                y, FB, entering);

    // draw level
    y += (5 * SHORT(lnames[wbs->next]->height)) / 4;

    V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->next]->width)) / 2,
                y, FB, lnames[wbs->next]);
}


void WI_drawOnLnode(int n, patch_t* c[])
{
    int                i;
    int                left;
    int                top;
    int                right;
    int                bottom;
    doom_boolean       fits = false;

    i = 0;
    do
    {
        left = lnodes[wbs->epsd][n].x - SHORT(c[i]->leftoffset);
        top = lnodes[wbs->epsd][n].y - SHORT(c[i]->topoffset);
        right = left + SHORT(c[i]->width);
        bottom = top + SHORT(c[i]->height);

        if (left >= 0
            && right < SCREENWIDTH
            && top >= 0
            && bottom < SCREENHEIGHT)
        {
            fits = true;
        }
        else
        {
            i++;
        }
    } while (!fits && i != 2);

    if (fits && i < 2)
    {
        V_DrawPatch(lnodes[wbs->epsd][n].x, lnodes[wbs->epsd][n].y,
                    FB, c[i]);
    }
    else
    {
        // DEBUG
        //doom_print("Could not place patch on level %d", n + 1);
        doom_print("Could not place patch on level ");
        doom_print(doom_itoa(n + 1, 10));
    }
}


void WI_initAnimatedBack(void)
{
    int i;
    anim_t_wi_stuff* a;

    if (gamemode == commercial)
        return;

    if (wbs->epsd > 2)
        return;

    for (i = 0; i < NUMANIMS[wbs->epsd]; i++)
    {
        a = &anims_wi_stuff[wbs->epsd][i];

        // init variables
        a->ctr = -1;

        // specify the next time to draw it
        if (a->type == ANIM_ALWAYS)
            a->nexttic = bcnt + 1 + (M_Random() % a->period);
        else if (a->type == ANIM_RANDOM)
            a->nexttic = bcnt + 1 + a->data2 + (M_Random() % a->data1);
        else if (a->type == ANIM_LEVEL)
            a->nexttic = bcnt + 1;
    }
}


void WI_updateAnimatedBack(void)
{
    int i;
    anim_t_wi_stuff* a;

    if (gamemode == commercial)
        return;

    if (wbs->epsd > 2)
        return;

    for (i = 0; i < NUMANIMS[wbs->epsd]; i++)
    {
        a = &anims_wi_stuff[wbs->epsd][i];

        if (bcnt == a->nexttic)
        {
            switch (a->type)
            {
                case ANIM_ALWAYS:
                    if (++a->ctr >= a->nanims) a->ctr = 0;
                    a->nexttic = bcnt + a->period;
                    break;

                case ANIM_RANDOM:
                    a->ctr++;
                    if (a->ctr == a->nanims)
                    {
                        a->ctr = -1;
                        a->nexttic = bcnt + a->data2 + (M_Random() % a->data1);
                    }
                    else a->nexttic = bcnt + a->period;
                    break;

                case ANIM_LEVEL:
                    // gawd-awful hack for level anims
                    if (!(state == StatCount && i == 7)
                        && wbs->next == a->data1)
                    {
                        a->ctr++;
                        if (a->ctr == a->nanims) a->ctr--;
                        a->nexttic = bcnt + a->period;
                    }
                    break;
            }
        }
    }
}


void WI_drawAnimatedBack(void)
{
    int i;
    anim_t_wi_stuff* a;

    if (commercial)
        return;

    if (wbs->epsd > 2)
        return;

    for (i = 0; i < NUMANIMS[wbs->epsd]; i++)
    {
        a = &anims_wi_stuff[wbs->epsd][i];

        if (a->ctr >= 0)
            V_DrawPatch(a->loc.x, a->loc.y, FB, a->p[a->ctr]);
    }
}


//
// Draws a number.
// If digits > 0, then use that many digits minimum,
//  otherwise only use as many as necessary.
// Returns new x position.
//
int WI_drawNum(int x, int y, int n, int digits)
{
    int fontwidth = SHORT(num[0]->width);
    int neg;
    int temp;

    if (digits < 0)
    {
        if (!n)
        {
            // make variable-length zeros 1 digit long
            digits = 1;
        }
        else
        {
            // figure out # of digits in #
            digits = 0;
            temp = n;

            while (temp)
            {
                temp /= 10;
                digits++;
            }
        }
    }

    neg = n < 0;
    if (neg)
        n = -n;

    // if non-number, do not draw it
    if (n == 1994)
        return 0;

    // draw the new number
    while (digits--)
    {
        x -= fontwidth;
        V_DrawPatch(x, y, FB, num[n % 10]);
        n /= 10;
    }

    // draw a minus sign if necessary
    if (neg)
        V_DrawPatch(x -= 8, y, FB, wiminus);

    return x;
}


void WI_drawPercent(int x, int y, int p)
{
    if (p < 0)
        return;

    V_DrawPatch(x, y, FB, percent);
    WI_drawNum(x, y, p, -1);
}


//
// Display level completion time and par,
//  or "sucks" message if overflow.
//
void WI_drawTime(int x, int y, int t)
{
    int div;
    int n;

    if (t < 0)
        return;

    if (t <= 61 * 59)
    {
        div = 1;

        do
        {
            n = (t / div) % 60;
            x = WI_drawNum(x, y, n, 2) - SHORT(colon->width);
            div *= 60;

            // draw
            if (div == 60 || t / div)
                V_DrawPatch(x, y, FB, colon);

        } while (t / div);
    }
    else
    {
        // "sucks"
        V_DrawPatch(x - SHORT(sucks->width), y, FB, sucks);
    }
}


void WI_End(void)
{
    void WI_unloadData(void);
    WI_unloadData();
}


void WI_initNoState(void)
{
    state = NoState;
    acceleratestage = 0;
    cnt = 10;
}


void WI_updateNoState(void)
{
    WI_updateAnimatedBack();

    if (!--cnt)
    {
        WI_End();
        G_WorldDone();
    }
}


void WI_initShowNextLoc(void)
{
    state = ShowNextLoc;
    acceleratestage = 0;
    cnt = SHOWNEXTLOCDELAY * TICRATE;

    WI_initAnimatedBack();
}


void WI_updateShowNextLoc(void)
{
    WI_updateAnimatedBack();

    if (!--cnt || acceleratestage)
        WI_initNoState();
    else
        snl_pointeron = (cnt & 31) < 20;
}


void WI_drawShowNextLoc(void)
{
    int i;
    int last;

    WI_slamBackground();

    // draw animated background
    WI_drawAnimatedBack();

    if (gamemode != commercial)
    {
        if (wbs->epsd > 2)
        {
            WI_drawEL();
            return;
        }

        last = (wbs->last == 8) ? wbs->next - 1 : wbs->last;

        // draw a splat on taken cities.
        for (i = 0; i <= last; i++)
            WI_drawOnLnode(i, &splat);

        // splat the secret level?
        if (wbs->didsecret)
            WI_drawOnLnode(8, &splat);

        // draw flashing ptr
        if (snl_pointeron)
            WI_drawOnLnode(wbs->next, yah);
    }

    // draws which level you are entering..
    if ((gamemode != commercial)
        || wbs->next != 30)
        WI_drawEL();
}


void WI_drawNoState(void)
{
    snl_pointeron = true;
    WI_drawShowNextLoc();
}


int WI_fragSum(int playernum)
{
    int i;
    int frags = 0;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (playeringame[i]
            && i != playernum)
        {
            frags += plrs[playernum].frags[i];
        }
    }

    // JDC hack - negative frags.
    frags -= plrs[playernum].frags[playernum];
    // UNUSED if (frags < 0)
    //         frags = 0;

    return frags;
}


void WI_initDeathmatchStats(void)
{
    int i;
    int j;

    state = StatCount;
    acceleratestage = 0;
    dm_state = 1;

    cnt_pause = TICRATE;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (playeringame[i])
        {
            for (j = 0; j < MAXPLAYERS; j++)
                if (playeringame[j])
                    dm_frags[i][j] = 0;

            dm_totals[i] = 0;
        }
    }

    WI_initAnimatedBack();
}


void WI_updateDeathmatchStats(void)
{
    int i;
    int j;

    doom_boolean stillticking;

    WI_updateAnimatedBack();

    if (acceleratestage && dm_state != 4)
    {
        acceleratestage = 0;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (playeringame[i])
            {
                for (j = 0; j < MAXPLAYERS; j++)
                    if (playeringame[j])
                        dm_frags[i][j] = plrs[i].frags[j];

                dm_totals[i] = WI_fragSum(i);
            }
        }


        S_StartSound(0, sfx_barexp);
        dm_state = 4;
    }


    if (dm_state == 2)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        stillticking = false;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (playeringame[i])
            {
                for (j = 0; j < MAXPLAYERS; j++)
                {
                    if (playeringame[j]
                        && dm_frags[i][j] != plrs[i].frags[j])
                    {
                        if (plrs[i].frags[j] < 0)
                            dm_frags[i][j]--;
                        else
                            dm_frags[i][j]++;

                        if (dm_frags[i][j] > 99)
                            dm_frags[i][j] = 99;

                        if (dm_frags[i][j] < -99)
                            dm_frags[i][j] = -99;

                        stillticking = true;
                    }
                }
                dm_totals[i] = WI_fragSum(i);

                if (dm_totals[i] > 99)
                    dm_totals[i] = 99;

                if (dm_totals[i] < -99)
                    dm_totals[i] = -99;
            }

        }
        if (!stillticking)
        {
            S_StartSound(0, sfx_barexp);
            dm_state++;
        }

    }
    else if (dm_state == 4)
    {
        if (acceleratestage)
        {
            S_StartSound(0, sfx_slop);

            if (gamemode == commercial)
                WI_initNoState();
            else
                WI_initShowNextLoc();
        }
    }
    else if (dm_state & 1)
    {
        if (!--cnt_pause)
        {
            dm_state++;
            cnt_pause = TICRATE;
        }
    }
}


void WI_drawDeathmatchStats(void)
{
    int i;
    int j;
    int x;
    int y;
    int w;

    int lh;        // line height

    lh = WI_SPACINGY;

    WI_slamBackground();

    // draw animated background
    WI_drawAnimatedBack();
    WI_drawLF();

    // draw stat titles (top line)
    V_DrawPatch(DM_TOTALSX - SHORT(total->width) / 2,
                DM_MATRIXY - WI_SPACINGY + 10,
                FB,
                total);

    V_DrawPatch(DM_KILLERSX, DM_KILLERSY, FB, killers);
    V_DrawPatch(DM_VICTIMSX, DM_VICTIMSY, FB, victims);

    // draw P?
    x = DM_MATRIXX + DM_SPACINGX;
    y = DM_MATRIXY;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (playeringame[i])
        {
            V_DrawPatch(x - SHORT(p[i]->width) / 2,
                        DM_MATRIXY - WI_SPACINGY,
                        FB,
                        p[i]);

            V_DrawPatch(DM_MATRIXX - SHORT(p[i]->width) / 2,
                        y,
                        FB,
                        p[i]);

            if (i == me)
            {
                V_DrawPatch(x - SHORT(p[i]->width) / 2,
                            DM_MATRIXY - WI_SPACINGY,
                            FB,
                            bstar);

                V_DrawPatch(DM_MATRIXX - SHORT(p[i]->width) / 2,
                            y,
                            FB,
                            star);
            }
        }
        else
        {
            // V_DrawPatch(x-SHORT(bp[i]->width)/2,
            //   DM_MATRIXY - WI_SPACINGY, FB, bp[i]);
            // V_DrawPatch(DM_MATRIXX-SHORT(bp[i]->width)/2,
            //   y, FB, bp[i]);
        }
        x += DM_SPACINGX;
        y += WI_SPACINGY;
    }

    // draw stats
    y = DM_MATRIXY + 10;
    w = SHORT(num[0]->width);

    for (i = 0; i < MAXPLAYERS; i++)
    {
        x = DM_MATRIXX + DM_SPACINGX;

        if (playeringame[i])
        {
            for (j = 0; j < MAXPLAYERS; j++)
            {
                if (playeringame[j])
                    WI_drawNum(x + w, y, dm_frags[i][j], 2);

                x += DM_SPACINGX;
            }
            WI_drawNum(DM_TOTALSX + w, y, dm_totals[i], 2);
        }
        y += WI_SPACINGY;
    }
}


void WI_initNetgameStats(void)
{
    int i;

    state = StatCount;
    acceleratestage = 0;
    ng_state = 1;

    cnt_pause = TICRATE;

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (!playeringame[i])
            continue;

        cnt_kills[i] = cnt_items[i] = cnt_secret[i] = cnt_frags[i] = 0;

        dofrags += WI_fragSum(i);
    }

    dofrags = !!dofrags;

    WI_initAnimatedBack();
}


void WI_updateNetgameStats(void)
{
    int i;
    int fsum;

    doom_boolean stillticking;

    WI_updateAnimatedBack();

    if (acceleratestage && ng_state != 10)
    {
        acceleratestage = 0;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (!playeringame[i])
                continue;

            cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills;
            cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems;
            cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret;

            if (dofrags)
                cnt_frags[i] = WI_fragSum(i);
        }
        S_StartSound(0, sfx_barexp);
        ng_state = 10;
    }

    if (ng_state == 2)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        stillticking = false;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (!playeringame[i])
                continue;

            cnt_kills[i] += 2;

            if (cnt_kills[i] >= (plrs[i].skills * 100) / wbs->maxkills)
                cnt_kills[i] = (plrs[i].skills * 100) / wbs->maxkills;
            else
                stillticking = true;
        }

        if (!stillticking)
        {
            S_StartSound(0, sfx_barexp);
            ng_state++;
        }
    }
    else if (ng_state == 4)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        stillticking = false;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (!playeringame[i])
                continue;

            cnt_items[i] += 2;
            if (cnt_items[i] >= (plrs[i].sitems * 100) / wbs->maxitems)
                cnt_items[i] = (plrs[i].sitems * 100) / wbs->maxitems;
            else
                stillticking = true;
        }
        if (!stillticking)
        {
            S_StartSound(0, sfx_barexp);
            ng_state++;
        }
    }
    else if (ng_state == 6)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        stillticking = false;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (!playeringame[i])
                continue;

            cnt_secret[i] += 2;

            if (cnt_secret[i] >= (plrs[i].ssecret * 100) / wbs->maxsecret)
                cnt_secret[i] = (plrs[i].ssecret * 100) / wbs->maxsecret;
            else
                stillticking = true;
        }

        if (!stillticking)
        {
            S_StartSound(0, sfx_barexp);
            ng_state += 1 + 2 * !dofrags;
        }
    }
    else if (ng_state == 8)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        stillticking = false;

        for (i = 0; i < MAXPLAYERS; i++)
        {
            if (!playeringame[i])
                continue;

            cnt_frags[i] += 1;

            if (cnt_frags[i] >= (fsum = WI_fragSum(i)))
                cnt_frags[i] = fsum;
            else
                stillticking = true;
        }

        if (!stillticking)
        {
            S_StartSound(0, sfx_pldeth);
            ng_state++;
        }
    }
    else if (ng_state == 10)
    {
        if (acceleratestage)
        {
            S_StartSound(0, sfx_sgcock);
            if (gamemode == commercial)
                WI_initNoState();
            else
                WI_initShowNextLoc();
        }
    }
    else if (ng_state & 1)
    {
        if (!--cnt_pause)
        {
            ng_state++;
            cnt_pause = TICRATE;
        }
    }
}


void WI_drawNetgameStats(void)
{
    int i;
    int x;
    int y;
    int pwidth = SHORT(percent->width);

    WI_slamBackground();

    // draw animated background
    WI_drawAnimatedBack();

    WI_drawLF();

    // draw stat titles (top line)
    V_DrawPatch(NG_STATSX + NG_SPACINGX - SHORT(kills->width),
                NG_STATSY, FB, kills);

    V_DrawPatch(NG_STATSX + 2 * NG_SPACINGX - SHORT(items->width),
                NG_STATSY, FB, items);

    V_DrawPatch(NG_STATSX + 3 * NG_SPACINGX - SHORT(secret->width),
                NG_STATSY, FB, secret);

    if (dofrags)
        V_DrawPatch(NG_STATSX + 4 * NG_SPACINGX - SHORT(frags->width),
                    NG_STATSY, FB, frags);

    // draw stats
    y = NG_STATSY + SHORT(kills->height);

    for (i = 0; i < MAXPLAYERS; i++)
    {
        if (!playeringame[i])
            continue;

        x = NG_STATSX;
        V_DrawPatch(x - SHORT(p[i]->width), y, FB, p[i]);

        if (i == me)
            V_DrawPatch(x - SHORT(p[i]->width), y, FB, star);

        x += NG_SPACINGX;
        WI_drawPercent(x - pwidth, y + 10, cnt_kills[i]);        x += NG_SPACINGX;
        WI_drawPercent(x - pwidth, y + 10, cnt_items[i]);        x += NG_SPACINGX;
        WI_drawPercent(x - pwidth, y + 10, cnt_secret[i]);        x += NG_SPACINGX;

        if (dofrags)
            WI_drawNum(x, y + 10, cnt_frags[i], -1);

        y += WI_SPACINGY;
    }
}


void WI_initStats(void)
{
    state = StatCount;
    acceleratestage = 0;
    sp_state = 1;
    cnt_kills[0] = cnt_items[0] = cnt_secret[0] = -1;
    cnt_time = cnt_par = -1;
    cnt_pause = TICRATE;

    WI_initAnimatedBack();
}


void WI_updateStats(void)
{
    WI_updateAnimatedBack();

    if (acceleratestage && sp_state != 10)
    {
        acceleratestage = 0;
        cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills;
        cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems;
        cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret;
        cnt_time = plrs[me].stime / TICRATE;
        cnt_par = wbs->partime / TICRATE;
        S_StartSound(0, sfx_barexp);
        sp_state = 10;
    }

    if (sp_state == 2)
    {
        cnt_kills[0] += 2;

        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs->maxkills)
        {
            cnt_kills[0] = (plrs[me].skills * 100) / wbs->maxkills;
            S_StartSound(0, sfx_barexp);
            sp_state++;
        }
    }
    else if (sp_state == 4)
    {
        cnt_items[0] += 2;

        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs->maxitems)
        {
            cnt_items[0] = (plrs[me].sitems * 100) / wbs->maxitems;
            S_StartSound(0, sfx_barexp);
            sp_state++;
        }
    }
    else if (sp_state == 6)
    {
        cnt_secret[0] += 2;

        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        if (cnt_secret[0] >= (plrs[me].ssecret * 100) / wbs->maxsecret)
        {
            cnt_secret[0] = (plrs[me].ssecret * 100) / wbs->maxsecret;
            S_StartSound(0, sfx_barexp);
            sp_state++;
        }
    }

    else if (sp_state == 8)
    {
        if (!(bcnt & 3))
            S_StartSound(0, sfx_pistol);

        cnt_time += 3;

        if (cnt_time >= plrs[me].stime / TICRATE)
            cnt_time = plrs[me].stime / TICRATE;

        cnt_par += 3;

        if (cnt_par >= wbs->partime / TICRATE)
        {
            cnt_par = wbs->partime / TICRATE;

            if (cnt_time >= plrs[me].stime / TICRATE)
            {
                S_StartSound(0, sfx_barexp);
                sp_state++;
            }
        }
    }
    else if (sp_state == 10)
    {
        if (acceleratestage)
        {
            S_StartSound(0, sfx_sgcock);

            if (gamemode == commercial)
                WI_initNoState();
            else
                WI_initShowNextLoc();
        }
    }
    else if (sp_state & 1)
    {
        if (!--cnt_pause)
        {
            sp_state++;
            cnt_pause = TICRATE;
        }
    }
}


void WI_drawStats(void)
{
    // line height
    int lh;

    lh = (3 * SHORT(num[0]->height)) / 2;

    WI_slamBackground();

    // draw animated background
    WI_drawAnimatedBack();

    WI_drawLF();

    V_DrawPatch(SP_STATSX, SP_STATSY, FB, kills);
    WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY, cnt_kills[0]);

    V_DrawPatch(SP_STATSX, SP_STATSY + lh, FB, items);
    WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY + lh, cnt_items[0]);

    V_DrawPatch(SP_STATSX, SP_STATSY + 2 * lh, FB, sp_secret);
    WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY + 2 * lh, cnt_secret[0]);

    V_DrawPatch(SP_TIMEX, SP_TIMEY, FB, time_patch);
    WI_drawTime(SCREENWIDTH / 2 - SP_TIMEX, SP_TIMEY, cnt_time);

    if (wbs->epsd < 3)
    {
        V_DrawPatch(SCREENWIDTH / 2 + SP_TIMEX, SP_TIMEY, FB, par);
        WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par);
    }
}


void WI_checkForAccelerate(void)
{
    int   i;
    player_t* player;

    // check for button presses to skip delays
    for (i = 0, player = players; i < MAXPLAYERS; i++, player++)
    {
        if (playeringame[i])
        {
            if (player->cmd.buttons & BT_ATTACK)
            {
                if (!player->attackdown)
                    acceleratestage = 1;
                player->attackdown = true;
            }
            else
                player->attackdown = false;
            if (player->cmd.buttons & BT_USE)
            {
                if (!player->usedown)
                    acceleratestage = 1;
                player->usedown = true;
            }
            else
                player->usedown = false;
        }
    }
}


// Updates stuff each tick
void WI_Ticker(void)
{
    // counter for general background animation
    bcnt++;

    if (bcnt == 1)
    {
        // intermission music
        if (gamemode == commercial)
            S_ChangeMusic(mus_dm2int, true);
        else
            S_ChangeMusic(mus_inter, true);
    }

    WI_checkForAccelerate();

    switch (state)
    {
        case StatCount:
            if (deathmatch) WI_updateDeathmatchStats();
            else if (netgame) WI_updateNetgameStats();
            else WI_updateStats();
            break;

        case ShowNextLoc:
            WI_updateShowNextLoc();
            break;

        case NoState:
            WI_updateNoState();
            break;
    }
}


void WI_loadData(void)
{
    int i;
    int j;
    char name[9];
    anim_t_wi_stuff* a;

    if (gamemode == commercial)
        doom_strcpy(name, "INTERPIC");
    else
    {
        //doom_sprintf(name, "WIMAP%d", wbs->epsd);
        doom_strcpy(name, "WIMAP");
        doom_concat(name, doom_itoa(wbs->epsd, 10));
    }

    if (gamemode == retail)
    {
        if (wbs->epsd == 3)
            doom_strcpy(name, "INTERPIC");
    }

    // background
    bg = W_CacheLumpName(name, PU_CACHE);
    V_DrawPatch(0, 0, 1, bg);

    if (gamemode == commercial)
    {
        NUMCMAPS = 32;
        lnames = (patch_t**)Z_Malloc(sizeof(patch_t*) * NUMCMAPS,
                                     PU_STATIC, 0);
        for (i = 0; i < NUMCMAPS; i++)
        {
            //doom_sprintf(name, "CWILV%2.2d", i);
            doom_strcpy(name, "CWILV");
            if (i < 10) doom_concat(name, "0");
            doom_concat(name, doom_itoa(i, 10));
            lnames[i] = W_CacheLumpName(name, PU_STATIC);
        }
    }
    else
    {
        lnames = (patch_t**)Z_Malloc(sizeof(patch_t*) * NUMMAPS,
                                     PU_STATIC, 0);
        for (i = 0; i < NUMMAPS; i++)
        {
            //doom_sprintf(name, "WILV%d%d", wbs->epsd, i);
            doom_strcpy(name, "WILV");
            doom_concat(name, doom_itoa(wbs->epsd, 10));
            doom_concat(name, doom_itoa(i, 10));
            lnames[i] = W_CacheLumpName(name, PU_STATIC);
        }

        // you are here
        yah[0] = W_CacheLumpName("WIURH0", PU_STATIC);

        // you are here (alt.)
        yah[1] = W_CacheLumpName("WIURH1", PU_STATIC);

        // splat
        splat = W_CacheLumpName("WISPLAT", PU_STATIC);

        if (wbs->epsd < 3)
        {
            for (j = 0; j < NUMANIMS[wbs->epsd]; j++)
            {
                a = &anims_wi_stuff[wbs->epsd][j];
                for (i = 0; i < a->nanims; i++)
                {
                    // MONDO HACK!
                    if (wbs->epsd != 1 || j != 8)
                    {
                        // animations
                        //doom_sprintf(name, "WIA%d%.2d%.2d", wbs->epsd, j, i);
                        doom_strcpy(name, "WIA");
                        doom_concat(name, doom_itoa(wbs->epsd, 10));
                        if (j < 10) doom_concat(name, "0");
                        doom_concat(name, doom_itoa(j, 10));
                        if (i < 10) doom_concat(name, "0");
                        doom_concat(name, doom_itoa(i, 10));
                        a->p[i] = W_CacheLumpName(name, PU_STATIC);
                    }
                    else
                    {
                        // HACK ALERT!
                        a->p[i] = anims_wi_stuff[1][4].p[i];
                    }
                }
            }
        }
    }

    // More hacks on minus sign.
    wiminus = W_CacheLumpName("WIMINUS", PU_STATIC);

    for (i = 0; i < 10; i++)
    {
        // numbers 0-9
        //doom_sprintf(name, "WINUM%d", i);
        doom_strcpy(name, "WINUM");
        doom_concat(name, doom_itoa(i, 10));
        num[i] = W_CacheLumpName(name, PU_STATIC);
    }

    // percent sign
    percent = W_CacheLumpName("WIPCNT", PU_STATIC);

    // "finished"
    finished = W_CacheLumpName("WIF", PU_STATIC);

    // "entering"
    entering = W_CacheLumpName("WIENTER", PU_STATIC);

    // "kills"
    kills = W_CacheLumpName("WIOSTK", PU_STATIC);

    // "scrt"
    secret = W_CacheLumpName("WIOSTS", PU_STATIC);

    // "secret"
    sp_secret = W_CacheLumpName("WISCRT2", PU_STATIC);

    // Yuck. 
    if (french)
    {
        // "items"
        if (netgame && !deathmatch)
            items = W_CacheLumpName("WIOBJ", PU_STATIC);
        else
            items = W_CacheLumpName("WIOSTI", PU_STATIC);
    }
    else
        items = W_CacheLumpName("WIOSTI", PU_STATIC);

    // "frgs"
    frags = W_CacheLumpName("WIFRGS", PU_STATIC);

    // ":"
    colon = W_CacheLumpName("WICOLON", PU_STATIC);

    // "time"
    time_patch = W_CacheLumpName("WITIME", PU_STATIC);

    // "sucks"
    sucks = W_CacheLumpName("WISUCKS", PU_STATIC);

    // "par"
    par = W_CacheLumpName("WIPAR", PU_STATIC);

    // "killers" (vertical)
    killers = W_CacheLumpName("WIKILRS", PU_STATIC);

    // "victims" (horiz)
    victims = W_CacheLumpName("WIVCTMS", PU_STATIC);

    // "total"
    total = W_CacheLumpName("WIMSTT", PU_STATIC);

    // your face
    star = W_CacheLumpName("STFST01", PU_STATIC);

    // dead face
    bstar = W_CacheLumpName("STFDEAD0", PU_STATIC);

    for (i = 0; i < MAXPLAYERS; i++)
    {
        // "1,2,3,4"
        //doom_sprintf(name, "STPB%d", i);
        doom_strcpy(name, "STPB");
        doom_concat(name, doom_itoa(i, 10));
        p[i] = W_CacheLumpName(name, PU_STATIC);

        // "1,2,3,4"
        //doom_sprintf(name, "WIBP%d", i + 1);
        doom_strcpy(name, "WIBP");
        doom_concat(name, doom_itoa(i + 1, 10));
        bp[i] = W_CacheLumpName(name, PU_STATIC);
    }
}


void WI_unloadData(void)
{
    int i;
    int j;

    Z_ChangeTag(wiminus, PU_CACHE);

    for (i = 0; i < 10; i++)
        Z_ChangeTag(num[i], PU_CACHE);

    if (gamemode == commercial)
    {
        for (i = 0; i < NUMCMAPS; i++)
            Z_ChangeTag(lnames[i], PU_CACHE);
    }
    else
    {
        Z_ChangeTag(yah[0], PU_CACHE);
        Z_ChangeTag(yah[1], PU_CACHE);

        Z_ChangeTag(splat, PU_CACHE);

        for (i = 0; i < NUMMAPS; i++)
            Z_ChangeTag(lnames[i], PU_CACHE);

        if (wbs->epsd < 3)
        {
            for (j = 0; j < NUMANIMS[wbs->epsd]; j++)
            {
                if (wbs->epsd != 1 || j != 8)
                    for (i = 0; i < anims_wi_stuff[wbs->epsd][j].nanims; i++)
                        Z_ChangeTag(anims_wi_stuff[wbs->epsd][j].p[i], PU_CACHE);
            }
        }
    }

    Z_Free(lnames);

    Z_ChangeTag(percent, PU_CACHE);
    Z_ChangeTag(colon, PU_CACHE);
    Z_ChangeTag(finished, PU_CACHE);
    Z_ChangeTag(entering, PU_CACHE);
    Z_ChangeTag(kills, PU_CACHE);
    Z_ChangeTag(secret, PU_CACHE);
    Z_ChangeTag(sp_secret, PU_CACHE);
    Z_ChangeTag(items, PU_CACHE);
    Z_ChangeTag(frags, PU_CACHE);
    Z_ChangeTag(time_patch, PU_CACHE);
    Z_ChangeTag(sucks, PU_CACHE);
    Z_ChangeTag(par, PU_CACHE);

    Z_ChangeTag(victims, PU_CACHE);
    Z_ChangeTag(killers, PU_CACHE);
    Z_ChangeTag(total, PU_CACHE);
    //  Z_ChangeTag(star, PU_CACHE);
    //  Z_ChangeTag(bstar, PU_CACHE);

    for (i = 0; i < MAXPLAYERS; i++)
        Z_ChangeTag(p[i], PU_CACHE);

    for (i = 0; i < MAXPLAYERS; i++)
        Z_ChangeTag(bp[i], PU_CACHE);
}


void WI_Drawer(void)
{
    switch (state)
    {
        case StatCount:
            if (deathmatch)
                WI_drawDeathmatchStats();
            else if (netgame)
                WI_drawNetgameStats();
            else
                WI_drawStats();
            break;

        case ShowNextLoc:
            WI_drawShowNextLoc();
            break;

        case NoState:
            WI_drawNoState();
            break;
    }
}


void WI_initVariables(wbstartstruct_t* wbstartstruct)
{
    wbs = wbstartstruct;

#ifdef RANGECHECKING
    if (gamemode != commercial)
    {
        if (gamemode == retail)
            RNGCHECK(wbs->epsd, 0, 3);
        else
            RNGCHECK(wbs->epsd, 0, 2);
    }
    else
    {
        RNGCHECK(wbs->last, 0, 8);
        RNGCHECK(wbs->next, 0, 8);
    }
    RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
    RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
#endif

    acceleratestage = 0;
    cnt = bcnt = 0;
    firstrefresh = 1;
    me = wbs->pnum;
    plrs = wbs->plyr;

    if (!wbs->maxkills)
        wbs->maxkills = 1;

    if (!wbs->maxitems)
        wbs->maxitems = 1;

    if (!wbs->maxsecret)
        wbs->maxsecret = 1;

    if (gamemode != retail)
        if (wbs->epsd > 2)
            wbs->epsd -= 3;
}


void WI_Start(wbstartstruct_t* wbstartstruct)
{

    WI_initVariables(wbstartstruct);
    WI_loadData();

    if (deathmatch)
        WI_initDeathmatchStats();
    else if (netgame)
        WI_initNetgameStats();
    else
        WI_initStats();
}
#define ZONEID 0x1d4a11
#define MINFRAGMENT 64
#define MEM_ALIGN sizeof(void *)


typedef struct
{
    // total bytes malloced, including header
    int size;

    // start / end cap for linked list
    memblock_t blocklist;

    memblock_t* rover;
} memzone_t;


memzone_t* mainzone;


//
// Z_ClearZone
//
void Z_ClearZone(memzone_t* zone)
{
    memblock_t* block;

    // set the entire zone to one free block
    zone->blocklist.next =
        zone->blocklist.prev =
        block = (memblock_t*)((byte*)zone + sizeof(memzone_t));

    zone->blocklist.user = (void*)zone;
    zone->blocklist.tag = PU_STATIC;
    zone->rover = block;

    block->prev = block->next = &zone->blocklist;

    // 0 indicates a free block.
    block->user = 0;

    block->size = zone->size - sizeof(memzone_t);
}


//
// Z_Init
//
void Z_Init(void)
{
    memblock_t* block;
    int size;

    mainzone = (memzone_t*)I_ZoneBase(&size);
    mainzone->size = size;

    // set the entire zone to one free block
    mainzone->blocklist.next =
        mainzone->blocklist.prev =
        block = (memblock_t*)((byte*)mainzone + sizeof(memzone_t));

    mainzone->blocklist.user = (void*)mainzone;
    mainzone->blocklist.tag = PU_STATIC;
    mainzone->rover = block;

    block->prev = block->next = &mainzone->blocklist;

    // 0 indicates a free block.
    block->user = 0;

    block->size = mainzone->size - sizeof(memzone_t);
}


//
// Z_Free
//
void Z_Free(void* ptr)
{
    memblock_t* block;
    memblock_t* other;

    block = (memblock_t*)((byte*)ptr - sizeof(memblock_t));

    if (block->id != ZONEID)
        I_Error("Error: Z_Free: freed a pointer without ZONEID");

    if (block->user > (void**)0x100)
    {
        // smaller values are not pointers
        // Note: OS-dependend?

        // clear the user's mark
        *block->user = 0;
    }

    // mark as free
    block->user = 0;
    block->tag = 0;
    block->id = 0;

    other = block->prev;

    if (!other->user)
    {
        // merge with previous free block
        other->size += block->size;
        other->next = block->next;
        other->next->prev = other;

        if (block == mainzone->rover)
            mainzone->rover = other;

        block = other;
    }

    other = block->next;
    if (!other->user)
    {
        // merge the next free block onto the end
        block->size += other->size;
        block->next = other->next;
        block->next->prev = block;

        if (other == mainzone->rover)
            mainzone->rover = block;
    }
}


//
// Z_Malloc
// You can pass a 0 user if the tag is < PU_PURGELEVEL.
//
void* Z_Malloc(int size, int tag, void* user)
{
    int extra;
    memblock_t* start;
    memblock_t* rover;
    memblock_t* newblock;
    memblock_t* base;

    size = (size + MEM_ALIGN - 1) & ~(MEM_ALIGN - 1);

    // scan through the block list,
    // looking for the first free block
    // of sufficient size,
    // throwing out any purgable blocks along the way.

    // account for size of block header
    size += sizeof(memblock_t);

    // if there is a free block behind the rover,
    //  back up over them
    base = mainzone->rover;

    if (!base->prev->user)
        base = base->prev;

    rover = base;
    start = base->prev;

    do
    {
        if (rover == start)
        {
            // scanned all the way around the list
            //I_Error("Error: Z_Malloc: failed on allocation of %i bytes", size);
            doom_strcpy(error_buf, "Error: Z_Malloc: failed on allocation of ");
            doom_concat(error_buf, doom_itoa(size, 10));
            doom_concat(error_buf, " bytes");
            I_Error(error_buf);
        }

        if (rover->user)
        {
            if (rover->tag < PU_PURGELEVEL)
            {
                // hit a block that can't be purged,
                //  so move base past it
                base = rover = rover->next;
            }
            else
            {
                // free the rover block (adding the size to base)

                // the rover can be the base block
                base = base->prev;
                Z_Free((byte*)rover + sizeof(memblock_t));
                base = base->next;
                rover = base->next;
            }
        }
        else
            rover = rover->next;
    } while (base->user || base->size < size);


    // found a block big enough
    extra = base->size - size;

    if (extra > MINFRAGMENT)
    {
        // there will be a free fragment after the allocated block
        newblock = (memblock_t*)((byte*)base + size);
        newblock->size = extra;

        // 0 indicates free block.
        newblock->user = 0;
        newblock->tag = 0;
        newblock->prev = base;
        newblock->next = base->next;
        newblock->next->prev = newblock;

        base->next = newblock;
        base->size = size;
    }

    if (user)
    {
        // mark as an in use block
        base->user = user;
        *(void**)user = (void*)((byte*)base + sizeof(memblock_t));
    }
    else
    {
        if (tag >= PU_PURGELEVEL)
            I_Error("Error: Z_Malloc: an owner is required for purgable blocks");

        // mark as in use, but unowned        
        base->user = (void*)2;
    }
    base->tag = tag;

    // next allocation will start looking here
    mainzone->rover = base->next;

    base->id = ZONEID;

    return (void*)((byte*)base + sizeof(memblock_t));
}


//
// Z_FreeTags
//
void Z_FreeTags(int lowtag, int hightag)
{
    memblock_t* block;
    memblock_t* next;

    for (block = mainzone->blocklist.next;
         block != &mainzone->blocklist;
         block = next)
    {
        // get link before freeing
        next = block->next;

        // free block?
        if (!block->user)
            continue;

        if (block->tag >= lowtag && block->tag <= hightag)
            Z_Free((byte*)block + sizeof(memblock_t));
    }
}


//
// Z_DumpHeap
// Note: TFileDumpHeap( stdout ) ?
//
void Z_DumpHeap(int lowtag, int hightag)
{
    memblock_t* block;

    //doom_print("zone size: %i  location: %p\n",
    //       mainzone->size, mainzone);
    doom_print("zone size: ");
    doom_print(doom_itoa(mainzone->size, 10));
    doom_print("  location: ");
    doom_print(doom_ptoa(mainzone));
    doom_print("\n");

    //doom_print("tag range: %i to %i\n",
    //       lowtag, hightag);
    doom_print("tag range: ");
    doom_print(doom_itoa(lowtag, 10));
    doom_print(" to ");
    doom_print(doom_itoa(hightag, 10));
    doom_print("\n");

    for (block = mainzone->blocklist.next; ; block = block->next)
    {
        if (block->tag >= lowtag && block->tag <= hightag)
        {
            //doom_print("block:%p    size:%7i    user:%p    tag:%3i\n", block, block->size, block->user, block->tag);
            doom_print("block");
            doom_print(doom_ptoa(block));
            doom_print("    size:");
            doom_print(doom_itoa(block->size, 10));
            doom_print("    user:");
            doom_print(doom_ptoa(block->user));
            doom_print("    tag:");
            doom_print(doom_itoa(block->tag, 10));
            doom_print("\n");
        }

        if (block->next == &mainzone->blocklist)
        {
            // all blocks have been hit
            break;
        }

        if ((byte*)block + block->size != (byte*)block->next)
            doom_print("ERROR: block size does not touch the next block\n");

        if (block->next->prev != block)
            doom_print("ERROR: next block doesn't have proper back link\n");

        if (!block->user && !block->next->user)
            doom_print("ERROR: two consecutive free blocks\n");
    }
}


//
// Z_FileDumpHeap
//
void Z_FileDumpHeap(void* f)
{
    memblock_t* block;

    //fprintf(f, "zone size: %i  location: %p\n", mainzone->size, mainzone);
    doom_fprint(f, "zone size: ");
    doom_fprint(f, doom_itoa(mainzone->size, 10));
    doom_fprint(f, "  location: ");
    doom_fprint(f, doom_ptoa(mainzone));
    doom_fprint(f, "\n");

    for (block = mainzone->blocklist.next; ; block = block->next)
    {
        //fprintf(f, "block:%p    size:%7i    user:%p    tag:%3i\n",
        //        block, block->size, block->user, block->tag);
        doom_fprint(f, "block:");
        doom_fprint(f, doom_ptoa(block));
        doom_fprint(f, "    size:");
        doom_fprint(f, doom_itoa(block->size, 10)); // Supposed to alignt 7, this is not worth the trouble
        doom_fprint(f, "    user:");
        doom_fprint(f, doom_ptoa(block->user));
        doom_fprint(f, "    tag:");
        doom_fprint(f, doom_itoa(block->tag, 10));
        doom_fprint(f, "\n");


        if (block->next == &mainzone->blocklist)
        {
            // all blocks have been hit
            break;
        }

        if ((byte*)block + block->size != (byte*)block->next)
            doom_fprint(f, "ERROR: block size does not touch the next block\n");

        if (block->next->prev != block)
            doom_fprint(f, "ERROR: next block doesn't have proper back link\n");

        if (!block->user && !block->next->user)
            doom_fprint(f, "ERROR: two consecutive free blocks\n");
    }
}


//
// Z_CheckHeap
//
void Z_CheckHeap(void)
{
    memblock_t* block;

    for (block = mainzone->blocklist.next; ; block = block->next)
    {
        if (block->next == &mainzone->blocklist)
        {
            // all blocks have been hit
            break;
        }

        if ((byte*)block + block->size != (byte*)block->next)
            I_Error("Error: Z_CheckHeap: block size does not touch the next block\n");

        if (block->next->prev != block)
            I_Error("Error: Z_CheckHeap: next block doesn't have proper back link\n");

        if (!block->user && !block->next->user)
            I_Error("Error: Z_CheckHeap: two consecutive free blocks\n");
    }
}


//
// Z_ChangeTag
//
void Z_ChangeTag2(void* ptr, int tag)
{
    memblock_t* block;

    block = (memblock_t*)((byte*)ptr - sizeof(memblock_t));

    if (block->id != ZONEID)
        I_Error("Error: Z_ChangeTag: freed a pointer without ZONEID");

    if (tag >= PU_PURGELEVEL && (unsigned long long)block->user < 0x100)
        I_Error("Error: Z_ChangeTag: an owner is required for purgable blocks");

    block->tag = tag;
}


//
// Z_FreeMemory
//
int Z_FreeMemory(void)
{
    memblock_t* block;
    int free;

    free = 0;

    for (block = mainzone->blocklist.next;
         block != &mainzone->blocklist;
         block = block->next)
    {
        if (!block->user || block->tag >= PU_PURGELEVEL)
            free += block->size;
    }
    return free;
}

#endif // DOOM_IMPLEMENTATION
