diff options
Diffstat (limited to 'src/heretic')
64 files changed, 47055 insertions, 0 deletions
diff --git a/src/heretic/.gitignore b/src/heretic/.gitignore new file mode 100644 index 00000000..76092240 --- /dev/null +++ b/src/heretic/.gitignore @@ -0,0 +1,6 @@ +Makefile +Makefile.in +.deps +tags +TAGS + diff --git a/src/heretic/Makefile.am b/src/heretic/Makefile.am new file mode 100644 index 00000000..e56ee806 --- /dev/null +++ b/src/heretic/Makefile.am @@ -0,0 +1,70 @@ + +AM_CFLAGS=-I.. \ + -I$(top_builddir)/textscreen \ + @SDL_CFLAGS@ @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@ + +noinst_LIBRARIES=libheretic.a + +SOURCE_FILES= \ + am_data.h \ +am_map.c am_map.h \ +ct_chat.c ct_chat.h \ +d_main.c \ +d_net.c \ + doomdata.h \ + doomdef.h \ + dstrings.h \ +f_finale.c \ +g_game.c \ +info.c info.h \ +in_lude.c \ +m_random.c m_random.h \ +mn_menu.c \ + p_action.h \ +p_ceilng.c \ +p_doors.c \ +p_enemy.c \ +p_floor.c \ +p_inter.c \ +p_lights.c \ +p_local.h \ +p_map.c \ +p_maputl.c \ +p_mobj.c \ +p_plats.c \ +p_pspr.c \ +p_setup.c \ +p_sight.c \ +p_spec.c p_spec.h \ +p_switch.c \ +p_telept.c \ +p_tick.c \ +p_user.c \ +r_bsp.c \ +r_data.c \ +r_draw.c \ + r_local.h \ +r_main.c \ +r_plane.c \ +r_segs.c \ +r_things.c \ +sb_bar.c \ +sounds.c sounds.h \ +s_sound.c s_sound.h + +EXTRA_DIST= \ +i_sound.c \ +i_ibm.c + +FEATURE_DEHACKED_SOURCE_FILES = \ +deh_ammo.c \ +deh_frame.c \ +deh_htext.c \ +deh_htic.c \ +deh_sound.c \ +deh_thing.c \ +deh_weapon.c + +libheretic_a_SOURCES=$(SOURCE_FILES) \ + $(FEATURE_DEHACKED_SOURCE_FILES) + diff --git a/src/heretic/am_data.h b/src/heretic/am_data.h new file mode 100644 index 00000000..342f06a9 --- /dev/null +++ b/src/heretic/am_data.h @@ -0,0 +1,111 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// AM_data.h : The vector graphics for the automap + +#ifndef __AMDATA_H__ +#define __AMDATA_H__ + +// a line drawing of the player pointing right, starting from the middle. + +#define R ((8*PLAYERRADIUS)/7) + +mline_t player_arrow[] = { + { { -R+R/4, 0 }, { 0, 0} }, // center line. + { { -R+R/4, R/8 }, { R, 0} }, // blade + { { -R+R/4, -R/8 }, { R, 0 } }, + { { -R+R/4, -R/4 }, { -R+R/4, R/4 } }, // crosspiece + { { -R+R/8, -R/4 }, { -R+R/8, R/4 } }, + { { -R+R/8, -R/4 }, { -R+R/4, -R/4} }, //crosspiece connectors + { { -R+R/8, R/4 }, { -R+R/4, R/4} }, + { { -R-R/4, R/8 }, { -R-R/4, -R/8 } }, //pommel + { { -R-R/4, R/8 }, { -R+R/8, R/8 } }, + { { -R-R/4, -R/8}, { -R+R/8, -R/8 } } + }; + +mline_t keysquare[] = { + { { 0, 0 }, { R/4, -R/2 } }, + { { R/4, -R/2 }, { R/2, -R/2 } }, + { { R/2, -R/2 }, { R/2, R/2 } }, + { { R/2, R/2 }, { R/4, R/2 } }, + { { R/4, R/2 }, { 0, 0 } }, // handle part type thing + { { 0, 0 }, { -R, 0 } }, // stem + { { -R, 0 }, { -R, -R/2 } }, // end lockpick part + { { -3*R/4, 0 }, { -3*R/4, -R/4 } } + }; + +/*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 NUMKEYSQUARELINES (sizeof(keysquare)/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) }, { (fixed_t)(0 ), (fixed_t)(R ) } }, + { { (fixed_t)(0 ), (fixed_t)(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) }, { (fixed_t)(R ), (fixed_t)(0 ) } }, + { { (fixed_t)(R ), (fixed_t)(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)) + +#endif diff --git a/src/heretic/am_map.c b/src/heretic/am_map.c new file mode 100644 index 00000000..aae30cc9 --- /dev/null +++ b/src/heretic/am_map.c @@ -0,0 +1,1515 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// AM_map.c + +#include <stdio.h> + +#include "doomdef.h" +#include "deh_str.h" +#include "i_video.h" +#include "m_controls.h" +#include "p_local.h" +#include "am_map.h" +#include "am_data.h" + +#include "doomkeys.h" +#include "v_video.h" + +vertex_t KeyPoints[NUMKEYS]; + +#define NUMALIAS 3 // Number of antialiased lines. + +char *LevelNames[] = { + // EPISODE 1 - THE CITY OF THE DAMNED + "E1M1: THE DOCKS", + "E1M2: THE DUNGEONS", + "E1M3: THE GATEHOUSE", + "E1M4: THE GUARD TOWER", + "E1M5: THE CITADEL", + "E1M6: THE CATHEDRAL", + "E1M7: THE CRYPTS", + "E1M8: HELL'S MAW", + "E1M9: THE GRAVEYARD", + // EPISODE 2 - HELL'S MAW + "E2M1: THE CRATER", + "E2M2: THE LAVA PITS", + "E2M3: THE RIVER OF FIRE", + "E2M4: THE ICE GROTTO", + "E2M5: THE CATACOMBS", + "E2M6: THE LABYRINTH", + "E2M7: THE GREAT HALL", + "E2M8: THE PORTALS OF CHAOS", + "E2M9: THE GLACIER", + // EPISODE 3 - THE DOME OF D'SPARIL + "E3M1: THE STOREHOUSE", + "E3M2: THE CESSPOOL", + "E3M3: THE CONFLUENCE", + "E3M4: THE AZURE FORTRESS", + "E3M5: THE OPHIDIAN LAIR", + "E3M6: THE HALLS OF FEAR", + "E3M7: THE CHASM", + "E3M8: D'SPARIL'S KEEP", + "E3M9: THE AQUIFER", + // EPISODE 4: THE OSSUARY + "E4M1: CATAFALQUE", + "E4M2: BLOCKHOUSE", + "E4M3: AMBULATORY", + "E4M4: SEPULCHER", + "E4M5: GREAT STAIR", + "E4M6: HALLS OF THE APOSTATE", + "E4M7: RAMPARTS OF PERDITION", + "E4M8: SHATTERED BRIDGE", + "E4M9: MAUSOLEUM", + // EPISODE 5: THE STAGNANT DEMESNE + "E5M1: OCHRE CLIFFS", + "E5M2: RAPIDS", + "E5M3: QUAY", + "E5M4: COURTYARD", + "E5M5: HYDRATYR", + "E5M6: COLONNADE", + "E5M7: FOETID MANSE", + "E5M8: FIELD OF JUDGEMENT", + "E5M9: SKEIN OF D'SPARIL" +}; + +static int cheating = 0; +static int grid = 0; + +static int leveljuststarted = 1; // kluge until AM_LevelInit() is called + +boolean automapactive = false; +static int finit_width = SCREENWIDTH; +static int finit_height = SCREENHEIGHT - 42; +static int f_x, f_y; // location of window on screen +static int f_w, f_h; // size of window on screen +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, m_h; +static fixed_t min_x, min_y; // based on level size +static fixed_t max_x, max_y; // based on level size +static fixed_t max_w, max_h; // max_x-min_x, max_y-min_y +static fixed_t min_w, min_h; // based on player size +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 vertex_t oldplr; + +//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 char cheat_amap[] = { 'r', 'a', 'v', 'm', 'a', 'p' }; + +static byte cheatcount = 0; + +extern boolean viewactive; + +static byte antialias[NUMALIAS][8] = { + {96, 97, 98, 99, 100, 101, 102, 103}, + {110, 109, 108, 107, 106, 105, 104, 103}, + {75, 76, 77, 78, 79, 80, 81, 103} +}; + +/* +static byte *aliasmax[NUMALIAS] = { + &antialias[0][7], &antialias[1][7], &antialias[2][7] +};*/ + +static byte *maplump; // pointer to the raw data for the automap background. +static short mapystart = 0; // y-value for the start of the map bitmap...used in the paralax stuff. +static short mapxstart = 0; //x-value for the bitmap. + +//byte screens[][SCREENWIDTH*SCREENHEIGHT]; +//void V_MarkRect (int x, int y, int width, int height); + +// Functions + +void DrawWuLine(int X0, int Y0, int X1, int Y1, byte * BaseColor, + int NumLevels, unsigned short IntensityBits); + +// 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. + +// Ripped out for Heretic +/* +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?-INT_MAX:INT_MAX); + else is->islp = FixedDiv(dx, dy); + if (!dx) is->slp = (dy<0?-INT_MAX:INT_MAX); + 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; + +} +*/ +void AM_findMinMaxBoundaries(void) +{ + int i; + fixed_t a, b; + + min_x = min_y = INT_MAX; + max_x = max_y = -INT_MAX; + 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; + 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 = INT_MAX; + } + + 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; + m_paninc.x = 0; + } + else if (m_x + m_w / 2 < min_x) + { + m_x = min_x - m_w / 2; + m_paninc.x = 0; + } + if (m_y + m_h / 2 > max_y) + { + m_y = max_y - m_h / 2; + m_paninc.y = 0; + } + else if (m_y + m_h / 2 < min_y) + { + m_y = min_y - m_h / 2; + m_paninc.y = 0; + } +/* + mapxstart += MTOF(m_paninc.x+FRACUNIT/2); + mapystart -= MTOF(m_paninc.y+FRACUNIT/2); + if(mapxstart >= finit_width) + mapxstart -= finit_width; + if(mapxstart < 0) + mapxstart += finit_width; + if(mapystart >= finit_height) + mapystart -= finit_height; + if(mapystart < 0) + mapystart += finit_height; +*/ + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; +} + +void AM_initVariables(void) +{ + int pnum; + thinker_t *think; + mobj_t *mo; + + //static event_t st_notify = { ev_keyup, AM_MSGENTERED }; + + automapactive = true; + fb = I_VideoBuffer; + + f_oldloc.x = INT_MAX; + 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]; + oldplr.x = plr->mo->x; + oldplr.y = plr->mo->y; + 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; + + // load in the location of keys, if in baby mode + + memset(KeyPoints, 0, sizeof(vertex_t) * 3); + if (gameskill == sk_baby) + { + for (think = thinkercap.next; think != &thinkercap; + think = think->next) + { + if (think->function != P_MobjThinker) + { //not a mobj + continue; + } + mo = (mobj_t *) think; + if (mo->type == MT_CKEY) + { + KeyPoints[0].x = mo->x; + KeyPoints[0].y = mo->y; + } + else if (mo->type == MT_AKYY) + { + KeyPoints[1].x = mo->x; + KeyPoints[1].y = mo->y; + } + else if (mo->type == MT_BKYY) + { + KeyPoints[2].x = mo->x; + KeyPoints[2].y = mo->y; + } + } + } + + // inform the status bar of the change +//c ST_Responder(&st_notify); +} + +void AM_loadPics(void) +{ + //int i; + //char namebuf[9]; +/* for (i=0;i<10;i++) + { + sprintf(namebuf, "AMMNUM%d", i); + marknums[i] = W_CacheLumpName(namebuf, PU_STATIC); + }*/ + maplump = W_CacheLumpName(DEH_String("AUTOPAGE"), 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; + mapxstart = mapystart = 0; + + +// 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); +} + +static boolean stopped = true; + +void AM_Stop(void) +{ + //static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED }; + +// AM_unloadPics(); + automapactive = false; +// ST_Responder(&st_notify); + stopped = true; + BorderNeedRefresh = true; +} + +void AM_Start(void) +{ + static int lastlevel = -1, lastepisode = -1; + + if (!stopped) + AM_Stop(); + stopped = false; + if (gamestate != GS_LEVEL) + { + return; // don't show automap if we aren't in a game! + } + 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(); +} + +boolean AM_Responder(event_t * ev) +{ + int rc; + int key; + static int cheatstate = 0; + static int bigstate = 0; + + key = ev->data1; + rc = false; + + if (!automapactive) + { + + if (ev->type == ev_keydown && key == key_map_toggle + && gamestate == GS_LEVEL) + { + AM_Start(); + viewactive = false; + // viewactive = true; + rc = true; + } + } + else if (ev->type == ev_keydown) + { + rc = true; + + if (key == key_map_east) // pan right + { + if (!followplayer) + m_paninc.x = FTOM(F_PANINC); + else + rc = false; + } + else if (key == key_map_west) // pan left + { + if (!followplayer) + m_paninc.x = -FTOM(F_PANINC); + else + rc = false; + } + else if (key == key_map_north) // pan up + { + if (!followplayer) + m_paninc.y = FTOM(F_PANINC); + else + rc = false; + } + else if (key == key_map_south) // pan down + { + if (!followplayer) + m_paninc.y = -FTOM(F_PANINC); + else + rc = false; + } + else if (key == key_map_zoomout) // zoom out + { + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } + else if (key == key_map_zoomin) // zoom in + { + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } + else if (key == key_map_toggle) // toggle map (tab) + { + bigstate = 0; + viewactive = true; + AM_Stop(); + } + else if (key == key_map_maxzoom) + { + bigstate = !bigstate; + if (bigstate) + { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } + else + AM_restoreScaleAndLoc(); + } + else if (key == key_map_follow) + { + followplayer = !followplayer; + f_oldloc.x = INT_MAX; + P_SetMessage(plr, + followplayer ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF, + true); + } + /* + else if (key == key_map_grid) + { + grid = !grid; + plr->message = grid ? AMSTR_GRIDON : AMSTR_GRIDOFF; + } + else if (key == key_map_mark) + { + sprintf(buffer, "%s %d", AMSTR_MARKEDSPOT, markpointnum); + plr->message = buffer; + AM_addMark(); + } + else if (key == key_map_clearmark) + { + AM_clearMarks(); + plr->message = AMSTR_MARKSCLEARED; + } + */ + else + { + cheatstate = 0; + rc = false; + } + + if (cheat_amap[cheatcount] == ev->data1 && !netgame) + cheatcount++; + else + cheatcount = 0; + if (cheatcount == 6) + { + cheatcount = 0; + rc = false; + cheating = (cheating + 1) % 3; + } + } + + else if (ev->type == ev_keyup) + { + rc = false; + + if (key == key_map_east) + { + if (!followplayer) + m_paninc.x = 0; + } + else if (key == key_map_east) + { + if (!followplayer) + m_paninc.x = 0; + } + else if (key == key_map_north) + { + if (!followplayer) + m_paninc.y = 0; + } + else if (key == key_map_south) + { + if (!followplayer) + m_paninc.y = 0; + } + else if (key == key_map_zoomout || key == key_map_zoomin) + { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } + } + + return rc; + +} + +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_x = plr->mo->x - m_w/2; +// m_y = plr->mo->y - m_h/2; + 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; + + // do the parallax parchment scrolling. +/* + dmapx = (MTOF(plr->mo->x)-MTOF(f_oldloc.x)); //fixed point + dmapy = (MTOF(f_oldloc.y)-MTOF(plr->mo->y)); + + if(f_oldloc.x == INT_MAX) //to eliminate an error when the user first + dmapx=0; //goes into the automap. + mapxstart += dmapx; + mapystart += dmapy; + + while(mapxstart >= finit_width) + mapxstart -= finit_width; + while(mapxstart < 0) + mapxstart += finit_width; + while(mapystart >= finit_height) + mapystart -= finit_height; + while(mapystart < 0) + mapystart += finit_height; +*/ + f_oldloc.x = plr->mo->x; + f_oldloc.y = plr->mo->y; + } +} + +// Ripped out for Heretic +/* +void AM_updateLightLev(void) +{ + static 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); + } +} +*/ + +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(); + +} + +void AM_clearFB(int color) +{ + int i, j; + int dmapx; + int dmapy; + + if (followplayer) + { + dmapx = (MTOF(plr->mo->x) - MTOF(oldplr.x)); //fixed point + dmapy = (MTOF(oldplr.y) - MTOF(plr->mo->y)); + + oldplr.x = plr->mo->x; + oldplr.y = plr->mo->y; +// if(f_oldloc.x == INT_MAX) //to eliminate an error when the user first +// dmapx=0; //goes into the automap. + mapxstart += dmapx >> 1; + mapystart += dmapy >> 1; + + while (mapxstart >= finit_width) + mapxstart -= finit_width; + while (mapxstart < 0) + mapxstart += finit_width; + while (mapystart >= finit_height) + mapystart -= finit_height; + while (mapystart < 0) + mapystart += finit_height; + } + else + { + mapxstart += (MTOF(m_paninc.x) >> 1); + mapystart -= (MTOF(m_paninc.y) >> 1); + if (mapxstart >= finit_width) + mapxstart -= finit_width; + if (mapxstart < 0) + mapxstart += finit_width; + if (mapystart >= finit_height) + mapystart -= finit_height; + if (mapystart < 0) + mapystart += finit_height; + } + + //blit the automap background to the screen. + j = mapystart * finit_width; + for (i = 0; i < finit_height; i++) + { + memcpy(I_VideoBuffer + i * finit_width, maplump + j + mapxstart, + finit_width - mapxstart); + memcpy(I_VideoBuffer + i * finit_width + finit_width - mapxstart, + maplump + j, mapxstart); + j += finit_width; + if (j >= finit_height * finit_width) + j = 0; + } + +// memcpy(I_VideoBuffer, maplump, finit_width*finit_height); +// memset(fb, color, f_w*f_h); +} + +// Based on Cohen-Sutherland clipping algorithm but with a slightly +// faster reject and precalculated slopes. If I need the speed, will +// hash algorithm to the common cases. + +boolean AM_clipMline(mline_t * ml, fline_t * fl) +{ + enum + { LEFT = 1, RIGHT = 2, BOTTOM = 4, TOP = 8 }; + int outcode1 = 0, outcode2 = 0, outside; + fpoint_t tmp = { 0, 0 }; + int dx, 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 I need for speed + +void AM_drawFline(fline_t * fl, int color) +{ + + register int x, y, dx, dy, sx, sy, ax, ay, d; + static int fuck = 0; + + switch (color) + { + case WALLCOLORS: + DrawWuLine(fl->a.x, fl->a.y, fl->b.x, fl->b.y, &antialias[0][0], + 8, 3); + break; + case FDWALLCOLORS: + DrawWuLine(fl->a.x, fl->a.y, fl->b.x, fl->b.y, &antialias[1][0], + 8, 3); + break; + case CDWALLCOLORS: + DrawWuLine(fl->a.x, fl->a.y, fl->b.x, fl->b.y, &antialias[2][0], + 8, 3); + break; + default: + { + // For debugging only + 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) + { + fprintf(stderr, "fuck %d \r", fuck++); + return; + } + +#define DOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc) //the MACRO! + + 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) + { + DOT(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) + { + DOT(x, y, color); + if (y == fl->b.y) + return; + if (d >= 0) + { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } + } + } +} + +/* Wu antialiased line drawer. + * (X0,Y0),(X1,Y1) = line to draw + * BaseColor = color # of first color in block used for antialiasing, the + * 100% intensity version of the drawing color + * NumLevels = size of color block, with BaseColor+NumLevels-1 being the + * 0% intensity version of the drawing color + * IntensityBits = log base 2 of NumLevels; the # of bits used to describe + * the intensity of the drawing color. 2**IntensityBits==NumLevels + */ +void PUTDOT(short xx, short yy, byte * cc, byte * cm) +{ + static int oldyy; + static int oldyyshifted; + byte *oldcc = cc; + + if (xx < 32) + cc += 7 - (xx >> 2); + else if (xx > (finit_width - 32)) + cc += 7 - ((finit_width - xx) >> 2); +// if(cc==oldcc) //make sure that we don't double fade the corners. +// { + if (yy < 32) + cc += 7 - (yy >> 2); + else if (yy > (finit_height - 32)) + cc += 7 - ((finit_height - yy) >> 2); +// } + if (cc > cm && cm != NULL) + { + cc = cm; + } + else if (cc > oldcc + 6) // don't let the color escape from the fade table... + { + cc = oldcc + 6; + } + if (yy == oldyy + 1) + { + oldyy++; + oldyyshifted += 320; + } + else if (yy == oldyy - 1) + { + oldyy--; + oldyyshifted -= 320; + } + else if (yy != oldyy) + { + oldyy = yy; + oldyyshifted = yy * 320; + } + fb[oldyyshifted + xx] = *(cc); +// fb[(yy)*f_w+(xx)]=*(cc); +} + +void DrawWuLine(int X0, int Y0, int X1, int Y1, byte * BaseColor, + int NumLevels, unsigned short IntensityBits) +{ + unsigned short IntensityShift, ErrorAdj, ErrorAcc; + unsigned short ErrorAccTemp, Weighting, WeightingComplementMask; + short DeltaX, DeltaY, Temp, XDir; + + /* Make sure the line runs top to bottom */ + if (Y0 > Y1) + { + Temp = Y0; + Y0 = Y1; + Y1 = Temp; + Temp = X0; + X0 = X1; + X1 = Temp; + } + /* Draw the initial pixel, which is always exactly intersected by + the line and so needs no weighting */ + PUTDOT(X0, Y0, &BaseColor[0], NULL); + + if ((DeltaX = X1 - X0) >= 0) + { + XDir = 1; + } + else + { + XDir = -1; + DeltaX = -DeltaX; /* make DeltaX positive */ + } + /* Special-case horizontal, vertical, and diagonal lines, which + require no weighting because they go right through the center of + every pixel */ + if ((DeltaY = Y1 - Y0) == 0) + { + /* Horizontal line */ + while (DeltaX-- != 0) + { + X0 += XDir; + PUTDOT(X0, Y0, &BaseColor[0], NULL); + } + return; + } + if (DeltaX == 0) + { + /* Vertical line */ + do + { + Y0++; + PUTDOT(X0, Y0, &BaseColor[0], NULL); + } + while (--DeltaY != 0); + return; + } + //diagonal line. + if (DeltaX == DeltaY) + { + do + { + X0 += XDir; + Y0++; + PUTDOT(X0, Y0, &BaseColor[0], NULL); + } + while (--DeltaY != 0); + return; + } + /* Line is not horizontal, diagonal, or vertical */ + ErrorAcc = 0; /* initialize the line error accumulator to 0 */ + /* # of bits by which to shift ErrorAcc to get intensity level */ + IntensityShift = 16 - IntensityBits; + /* Mask used to flip all bits in an intensity weighting, producing the + result (1 - intensity weighting) */ + WeightingComplementMask = NumLevels - 1; + /* Is this an X-major or Y-major line? */ + if (DeltaY > DeltaX) + { + /* Y-major line; calculate 16-bit fixed-point fractional part of a + pixel that X advances each time Y advances 1 pixel, truncating the + result so that we won't overrun the endpoint along the X axis */ + ErrorAdj = ((unsigned int) DeltaX << 16) / (unsigned int) DeltaY; + /* Draw all pixels other than the first and last */ + while (--DeltaY) + { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) + { + /* The error accumulator turned over, so advance the X coord */ + X0 += XDir; + } + Y0++; /* Y-major, so always advance Y */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + PUTDOT(X0, Y0, &BaseColor[Weighting], &BaseColor[7]); + PUTDOT(X0 + XDir, Y0, + &BaseColor[(Weighting ^ WeightingComplementMask)], + &BaseColor[7]); + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + PUTDOT(X1, Y1, &BaseColor[0], NULL); + return; + } + /* It's an X-major line; calculate 16-bit fixed-point fractional part of a + pixel that Y advances each time X advances 1 pixel, truncating the + result to avoid overrunning the endpoint along the X axis */ + ErrorAdj = ((unsigned int) DeltaY << 16) / (unsigned int) DeltaX; + /* Draw all pixels other than the first and last */ + while (--DeltaX) + { + ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ + ErrorAcc += ErrorAdj; /* calculate error for next pixel */ + if (ErrorAcc <= ErrorAccTemp) + { + /* The error accumulator turned over, so advance the Y coord */ + Y0++; + } + X0 += XDir; /* X-major, so always advance X */ + /* The IntensityBits most significant bits of ErrorAcc give us the + intensity weighting for this pixel, and the complement of the + weighting for the paired pixel */ + Weighting = ErrorAcc >> IntensityShift; + PUTDOT(X0, Y0, &BaseColor[Weighting], &BaseColor[7]); + PUTDOT(X0, Y0 + 1, + &BaseColor[(Weighting ^ WeightingComplementMask)], + &BaseColor[7]); + + } + /* Draw the final pixel, which is always exactly intersected by the line + and so needs no weighting */ + PUTDOT(X1, Y1, &BaseColor[0], NULL); +} + +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 + +} + +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); + } +} + +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, 0); + else + AM_drawMline(&l, WALLCOLORS + lightlev); + } + else if (lines[i].special > 25 && lines[i].special < 35) + { + switch (lines[i].special) + { + case 26: + case 32: + AM_drawMline(&l, BLUEKEY); + break; + case 27: + case 34: + AM_drawMline(&l, YELLOWKEY); + break; + case 28: + case 33: + AM_drawMline(&l, GREENKEY); + break; + default: + break; + } + } + 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); + } + } + +} + +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[] = { GREENKEY, YELLOWKEY, BLOODRED, BLUEKEY }; + 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); + *///cheat key player pointer is the same as non-cheat pointer.. + + 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 = 102; // *close* to the automap color + 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); + 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, marknums[i]); + } + } +} +*/ + +void AM_drawkeys(void) +{ + if (KeyPoints[0].x != 0 || KeyPoints[0].y != 0) + { + AM_drawLineCharacter(keysquare, NUMKEYSQUARELINES, 0, 0, YELLOWKEY, + KeyPoints[0].x, KeyPoints[0].y); + } + if (KeyPoints[1].x != 0 || KeyPoints[1].y != 0) + { + AM_drawLineCharacter(keysquare, NUMKEYSQUARELINES, 0, 0, GREENKEY, + KeyPoints[1].x, KeyPoints[1].y); + } + if (KeyPoints[2].x != 0 || KeyPoints[2].y != 0) + { + AM_drawLineCharacter(keysquare, NUMKEYSQUARELINES, 0, 0, BLUEKEY, + KeyPoints[2].x, KeyPoints[2].y); + } +} + +void AM_drawCrosshair(int color) +{ + fb[(f_w * (f_h + 1)) / 2] = color; // single point for now +} + +void AM_Drawer(void) +{ + char *level_name; + int numepisodes; + + if (!automapactive) + return; + + UpdateState |= I_FULLSCRN; + AM_clearFB(BACKGROUND); + if (grid) + AM_drawGrid(GRIDCOLORS); + AM_drawWalls(); + AM_drawPlayers(); + if (cheating == 2) + AM_drawThings(THINGCOLORS, THINGRANGE); +// AM_drawCrosshair(XHAIRCOLORS); + +// AM_drawMarks(); + if (gameskill == sk_baby) + { + AM_drawkeys(); + } + + if (gamemode == retail) + { + numepisodes = 5; + } + else + { + numepisodes = 3; + } + + if (gameepisode <= numepisodes && gamemap < 10) + { + level_name = LevelNames[(gameepisode - 1) * 9 + gamemap - 1]; + MN_DrTextA(DEH_String(level_name), 20, 145); + } +// I_Update(); +// V_MarkRect(f_x, f_y, f_w, f_h); +} diff --git a/src/heretic/am_map.h b/src/heretic/am_map.h new file mode 100644 index 00000000..cbd2935a --- /dev/null +++ b/src/heretic/am_map.h @@ -0,0 +1,119 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +// For use if I do walls with outsides/insides +#define REDS 12*8 +#define REDRANGE 1 //16 +#define BLUES (256-4*16+8) +#define BLUERANGE 1 //8 +#define GREENS (33*8) +#define GREENRANGE 1 //16 +#define GRAYS (5*8) +#define GRAYSRANGE 1 //16 +#define BROWNS (14*8) +#define BROWNRANGE 1 //16 +#define YELLOWS 10*8 +#define YELLOWRANGE 1 +#define BLACK 0 +#define WHITE 4*8 +#define PARCH 13*8-1 +#define BLOODRED 150 +#define BLUEKEY 197 +#define YELLOWKEY 144 +#define GREENKEY 220 + +// Automap colors +#define BACKGROUND PARCH +#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_NUMMARKPOINTS 10 + +#define AM_MSGHEADER (('a'<<24)+('m'<<16)) +#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8)) +#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8)) + +#define INITSCALEMTOF (.2*FRACUNIT) // scale on entry +// how much the automap moves window per tic in frame-buffer coordinates +#define F_PANINC 4 // moves 140 pixels in 1 second +// how much zoom-in per tic +#define M_ZOOMIN ((int) (1.02*FRACUNIT)) // goes to 2x in 1 second +// how much zoom-out per tic +#define M_ZOOMOUT ((int) (FRACUNIT/1.02)) // pulls out to 0.5x in 1 second + +// 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 vertex_t mpoint_t; + +typedef struct +{ + mpoint_t a, b; +} mline_t; + +typedef struct +{ + fixed_t slp, islp; +} islope_t; + +// extern int f_x, f_y, f_w, f_h; + +#endif diff --git a/src/heretic/ct_chat.c b/src/heretic/ct_chat.c new file mode 100644 index 00000000..8059b74d --- /dev/null +++ b/src/heretic/ct_chat.c @@ -0,0 +1,493 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Chat mode +// + +#include <string.h> +#include <ctype.h> +#include "doomdef.h" +#include "doomkeys.h" +#include "deh_str.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +#define QUEUESIZE 128 +#define MESSAGESIZE 128 +#define MESSAGELEN 265 + +#define CT_PLR_GREEN 1 +#define CT_PLR_YELLOW 2 +#define CT_PLR_RED 3 +#define CT_PLR_BLUE 4 +#define CT_PLR_ALL 5 + +#define CT_KEY_GREEN 'g' +#define CT_KEY_YELLOW 'y' +#define CT_KEY_RED 'r' +#define CT_KEY_BLUE 'b' +#define CT_KEY_ALL 't' +#define CT_ESCAPE 6 + +// Public data + +void CT_Init(void); +void CT_Drawer(void); +boolean CT_Responder(event_t * ev); +void CT_Ticker(void); +char CT_dequeueChatChar(void); + +boolean chatmodeon; + +// Private data + +void CT_queueChatChar(char ch); +void CT_ClearChatMessage(int player); +void CT_AddChar(int player, char c); +void CT_BackSpace(int player); + +int head; +int tail; +byte ChatQueue[QUEUESIZE]; +int chat_dest[MAXPLAYERS]; +char chat_msg[MAXPLAYERS][MESSAGESIZE]; +char plr_lastmsg[MAXPLAYERS][MESSAGESIZE + 9]; // add in the length of the pre-string +int msgptr[MAXPLAYERS]; +int msglen[MAXPLAYERS]; + +boolean cheated; + +static int FontABaseLump; + +char *CT_FromPlrText[MAXPLAYERS] = { + "GREEN: ", + "YELLOW: ", + "RED: ", + "BLUE: " +}; + +char *chat_macros[10]; + +boolean altdown; +boolean shiftdown; + + +//=========================================================================== +// +// CT_Init +// +// Initialize chat mode data +//=========================================================================== + +void CT_Init(void) +{ + int i; + + head = 0; //initialize the queue index + tail = 0; + chatmodeon = false; + memset(ChatQueue, 0, QUEUESIZE); + for (i = 0; i < MAXPLAYERS; i++) + { + chat_dest[i] = 0; + msgptr[i] = 0; + memset(plr_lastmsg[i], 0, MESSAGESIZE); + memset(chat_msg[i], 0, MESSAGESIZE); + } + FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1; + return; +} + +//=========================================================================== +// +// CT_Stop +// +//=========================================================================== + +void CT_Stop(void) +{ + chatmodeon = false; + return; +} + +//=========================================================================== +// +// CT_Responder +// +//=========================================================================== + +boolean CT_Responder(event_t * ev) +{ + char *macro; + + int sendto; + + if (!netgame) + { + return false; + } + if (ev->data1 == KEY_LALT || ev->data2 == KEY_RALT) + { + altdown = (ev->type == ev_keydown); + return false; + } + if (ev->data1 == KEY_RSHIFT) + { + shiftdown = (ev->type == ev_keydown); + return false; + } + if (ev->type != ev_keydown) + { + return false; + } + if (!chatmodeon) + { + sendto = 0; + if (ev->data1 == CT_KEY_ALL) + { + sendto = CT_PLR_ALL; + } + else if (ev->data1 == CT_KEY_GREEN) + { + sendto = CT_PLR_GREEN; + } + else if (ev->data1 == CT_KEY_YELLOW) + { + sendto = CT_PLR_YELLOW; + } + else if (ev->data1 == CT_KEY_RED) + { + sendto = CT_PLR_RED; + } + else if (ev->data1 == CT_KEY_BLUE) + { + sendto = CT_PLR_BLUE; + } + if (sendto == 0 || (sendto != CT_PLR_ALL && !playeringame[sendto - 1]) + || sendto == consoleplayer + 1) + { + return false; + } + CT_queueChatChar(sendto); + chatmodeon = true; + return true; + } + else + { + if (altdown) + { + if (ev->data1 >= '0' && ev->data1 <= '9') + { + if (ev->data1 == '0') + { // macro 0 comes after macro 9 + ev->data1 = '9' + 1; + } + macro = chat_macros[ev->data1 - '1']; + CT_queueChatChar(KEY_ENTER); //send old message + CT_queueChatChar(chat_dest[consoleplayer]); // chose the dest. + while (*macro) + { + CT_queueChatChar(toupper(*macro++)); + } + CT_queueChatChar(KEY_ENTER); //send it off... + CT_Stop(); + return true; + } + } + if (ev->data1 == KEY_ENTER) + { + CT_queueChatChar(KEY_ENTER); + CT_Stop(); + return true; + } + else if (ev->data1 == KEY_ESCAPE) + { + CT_queueChatChar(CT_ESCAPE); + CT_Stop(); + return true; + } + else if (ev->data1 >= 'a' && ev->data1 <= 'z') + { + CT_queueChatChar(ev->data1 - 32); + return true; + } + else if (shiftdown) + { + if (ev->data1 == '1') + { + CT_queueChatChar('!'); + return true; + } + else if (ev->data1 == '/') + { + CT_queueChatChar('?'); + return true; + } + } + else + { + if (ev->data1 == ' ' || ev->data1 == ',' || ev->data1 == '.' + || (ev->data1 >= '0' && ev->data1 <= '9') || ev->data1 == '\'' + || ev->data1 == KEY_BACKSPACE || ev->data1 == '-' + || ev->data1 == '=') + { + CT_queueChatChar(ev->data1); + return true; + } + } + } + return false; +} + +//=========================================================================== +// +// CT_Ticker +// +//=========================================================================== + +void CT_Ticker(void) +{ + int i; + int j; + char c; + int numplayers; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + { + continue; + } + if ((c = players[i].cmd.chatchar) != 0) + { + if (c <= 5) + { + chat_dest[i] = c; + continue; + } + else if (c == CT_ESCAPE) + { + CT_ClearChatMessage(i); + } + else if (c == KEY_ENTER) + { + numplayers = 0; + for (j = 0; j < MAXPLAYERS; j++) + { + numplayers += playeringame[j]; + } + CT_AddChar(i, 0); // set the end of message character + if (numplayers > 2) + { + strncpy(plr_lastmsg[i], DEH_String(CT_FromPlrText[i]), + MESSAGESIZE + 9); + plr_lastmsg[i][MESSAGESIZE + 8] = '\0'; + strcat(plr_lastmsg[i], chat_msg[i]); + } + else + { + strcpy(plr_lastmsg[i], chat_msg[i]); + } + if (i != consoleplayer && (chat_dest[i] == consoleplayer + 1 + || chat_dest[i] == CT_PLR_ALL) + && *chat_msg[i]) + { + P_SetMessage(&players[consoleplayer], plr_lastmsg[i], + true); + S_StartSound(NULL, sfx_chat); + } + else if (i == consoleplayer && (*chat_msg[i])) + { + if (numplayers > 1) + { + P_SetMessage(&players[consoleplayer], + DEH_String("-MESSAGE SENT-"), true); + S_StartSound(NULL, sfx_chat); + } + else + { + P_SetMessage(&players[consoleplayer], + DEH_String("THERE ARE NO OTHER PLAYERS IN THE GAME!"), + true); + S_StartSound(NULL, sfx_chat); + } + } + CT_ClearChatMessage(i); + } + else if (c == KEY_BACKSPACE) + { + CT_BackSpace(i); + } + else + { + CT_AddChar(i, c); + } + } + } + return; +} + +//=========================================================================== +// +// CT_Drawer +// +//=========================================================================== + +void CT_Drawer(void) +{ + int i; + int x; + patch_t *patch; + + if (chatmodeon) + { + x = 25; + for (i = 0; i < msgptr[consoleplayer]; i++) + { + if (chat_msg[consoleplayer][i] < 33) + { + x += 6; + } + else + { + patch = W_CacheLumpNum(FontABaseLump + + chat_msg[consoleplayer][i] - 33, + PU_CACHE); + V_DrawPatch(x, 10, patch); + x += patch->width; + } + } + V_DrawPatch(x, 10, W_CacheLumpName(DEH_String("FONTA59"), PU_CACHE)); + BorderTopRefresh = true; + UpdateState |= I_MESSAGES; + } +} + +//=========================================================================== +// +// CT_queueChatChar +// +//=========================================================================== + +void CT_queueChatChar(char ch) +{ + if (((tail + 1) & (QUEUESIZE - 1)) == head) + { // the queue is full + return; + } + ChatQueue[tail] = ch; + tail = (tail + 1) & (QUEUESIZE - 1); +} + +//=========================================================================== +// +// CT_dequeueChatChar +// +//=========================================================================== + +char CT_dequeueChatChar(void) +{ + byte temp; + + if (head == tail) + { // queue is empty + return 0; + } + temp = ChatQueue[head]; + head = (head + 1) & (QUEUESIZE - 1); + return temp; +} + +//=========================================================================== +// +// CT_AddChar +// +//=========================================================================== + +void CT_AddChar(int player, char c) +{ + patch_t *patch; + + if (msgptr[player] + 1 >= MESSAGESIZE || msglen[player] >= MESSAGELEN) + { // full. + return; + } + chat_msg[player][msgptr[player]] = c; + msgptr[player]++; + if (c < 33) + { + msglen[player] += 6; + } + else + { + patch = W_CacheLumpNum(FontABaseLump + c - 33, PU_CACHE); + msglen[player] += patch->width; + } +} + +//=========================================================================== +// +// CT_BackSpace +// +// Backs up a space, when the user hits (obviously) backspace +//=========================================================================== + +void CT_BackSpace(int player) +{ + patch_t *patch; + char c; + + if (msgptr[player] == 0) + { // message is already blank + return; + } + msgptr[player]--; + c = chat_msg[player][msgptr[player]]; + if (c < 33) + { + msglen[player] -= 6; + } + else + { + patch = W_CacheLumpNum(FontABaseLump + c - 33, PU_CACHE); + msglen[player] -= patch->width; + } + chat_msg[player][msgptr[player]] = 0; +} + +//=========================================================================== +// +// CT_ClearChatMessage +// +// Clears out the data for the chat message, but the player's message +// is still saved in plrmsg. +//=========================================================================== + +void CT_ClearChatMessage(int player) +{ + memset(chat_msg[player], 0, MESSAGESIZE); + msgptr[player] = 0; + msglen[player] = 0; +} diff --git a/src/heretic/ct_chat.h b/src/heretic/ct_chat.h new file mode 100644 index 00000000..84544e56 --- /dev/null +++ b/src/heretic/ct_chat.h @@ -0,0 +1,46 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Chat mode stuff +// + +#ifndef HERETIC_CT_CHAT_H +#define HERETIC_CT_CHAT_H + +#define CT_PLR_GREEN 1 +#define CT_PLR_YELLOW 2 +#define CT_PLR_RED 3 +#define CT_PLR_BLUE 4 +#define CT_PLR_ALL 5 + +#define CT_KEY_GREEN 'g' +#define CT_KEY_YELLOW 'y' +#define CT_KEY_RED 'r' +#define CT_KEY_BLUE 'b' +#define CT_KEY_ALL 't' + +extern char *chat_macros[10]; + +#endif /* #ifndef HERETIC_CT_CHAT_H */ + diff --git a/src/heretic/d_main.c b/src/heretic/d_main.c new file mode 100644 index 00000000..169a86ea --- /dev/null +++ b/src/heretic/d_main.c @@ -0,0 +1,1075 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// D_main.c + +#include <stdio.h> +#include <stdlib.h> + +#include "txt_main.h" +#include "txt_io.h" + +#include "config.h" +#include "ct_chat.h" +#include "doomdef.h" +#include "deh_main.h" +#include "d_iwad.h" +#include "i_endoom.h" +#include "i_joystick.h" +#include "i_sound.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "m_argv.h" +#include "m_config.h" +#include "m_controls.h" +#include "p_local.h" +#include "s_sound.h" +#include "w_main.h" +#include "v_video.h" + +#define STARTUP_WINDOW_X 17 +#define STARTUP_WINDOW_Y 7 + +GameMission_t gamemission = heretic; +GameMode_t gamemode = indetermined; +char *gamedescription = "unknown"; + +boolean nomonsters; // checkparm of -nomonsters +boolean respawnparm; // checkparm of -respawn +boolean debugmode; // checkparm of -debug +boolean ravpic; // checkparm of -ravpic +boolean cdrom; // true if cd-rom mode active +boolean singletics; // debug flag to cancel adaptiveness +boolean noartiskip; // whether shift-enter skips an artifact + +skill_t startskill; +int startepisode; +int startmap; +int UpdateState; +static int graphical_startup = 1; +static boolean using_graphical_startup; +boolean autostart; +extern boolean automapactive; + +boolean advancedemo; + +FILE *debugfile; + +static int show_endoom = 1; + +void D_CheckNetGame(void); +void D_ProcessEvents(void); +void G_BuildTiccmd(ticcmd_t * cmd); +void D_DoAdvanceDemo(void); +void D_PageDrawer(void); +void D_AdvanceDemo(void); +void F_Drawer(void); +boolean F_Responder(event_t * ev); + +//--------------------------------------------------------------------------- +// +// PROC D_ProcessEvents +// +// Send all the events of the given timestamp down the responder chain. +// +//--------------------------------------------------------------------------- + +void D_ProcessEvents(void) +{ + event_t *ev; + + while ((ev = D_PopEvent()) != NULL) + { + if (F_Responder(ev)) + { + continue; + } + if (MN_Responder(ev)) + { + continue; + } + G_Responder(ev); + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawMessage +// +//--------------------------------------------------------------------------- + +void DrawMessage(void) +{ + player_t *player; + + player = &players[consoleplayer]; + if (player->messageTics <= 0 || !player->message) + { // No message + return; + } + MN_DrTextA(player->message, 160 - MN_TextAWidth(player->message) / 2, 1); +} + +//--------------------------------------------------------------------------- +// +// PROC D_Display +// +// Draw current display, possibly wiping it from the previous. +// +//--------------------------------------------------------------------------- + +void R_ExecuteSetViewSize(void); + +extern boolean finalestage; + +void D_Display(void) +{ + extern boolean MenuActive; + extern boolean askforquit; + + // Change the view size if needed + if (setsizeneeded) + { + R_ExecuteSetViewSize(); + } + +// +// do buffered drawing +// + switch (gamestate) + { + case GS_LEVEL: + if (!gametic) + break; + if (automapactive) + AM_Drawer(); + else + R_RenderPlayerView(&players[displayplayer]); + CT_Drawer(); + UpdateState |= I_FULLVIEW; + SB_Drawer(); + break; + case GS_INTERMISSION: + IN_Drawer(); + break; + case GS_FINALE: + F_Drawer(); + break; + case GS_DEMOSCREEN: + D_PageDrawer(); + break; + } + + if (paused && !MenuActive && !askforquit) + { + if (!netgame) + { + V_DrawPatch(160, viewwindowy + 5, W_CacheLumpName(DEH_String("PAUSED"), + PU_CACHE)); + } + else + { + V_DrawPatch(160, 70, W_CacheLumpName(DEH_String("PAUSED"), PU_CACHE)); + } + } + // Handle player messages + DrawMessage(); + + // Menu drawing + MN_Drawer(); + + // Send out any new accumulation + NetUpdate(); + + // Flush buffered stuff to screen + I_FinishUpdate(); +} + +// +// D_GrabMouseCallback +// +// Called to determine whether to grab the mouse pointer +// + +boolean D_GrabMouseCallback(void) +{ + // when menu is active or game is paused, release the mouse + + if (MenuActive || paused) + return false; + + // only grab mouse when playing levels (but not demos) + + return (gamestate == GS_LEVEL) && !demoplayback; +} + +//--------------------------------------------------------------------------- +// +// PROC D_DoomLoop +// +//--------------------------------------------------------------------------- + +void D_DoomLoop(void) +{ + if (M_CheckParm("-debugfile")) + { + char filename[20]; + sprintf(filename, "debug%i.txt", consoleplayer); + debugfile = fopen(filename, "w"); + } + I_SetWindowTitle(gamedescription); + I_InitGraphics(); + I_SetGrabMouseCallback(D_GrabMouseCallback); + + 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(); + G_Ticker(); + gametic++; + maketic++; + } + else + { + // Will run at least one tic + TryRunTics(); + } + + // Move positional sounds + S_UpdateSounds(players[consoleplayer].mo); + D_Display(); + } +} + +/* +=============================================================================== + + DEMO LOOP + +=============================================================================== +*/ + +int demosequence; +int pagetic; +char *pagename; + + +/* +================ += += D_PageTicker += += Handles timing for warped projection += +================ +*/ + +void D_PageTicker(void) +{ + if (--pagetic < 0) + D_AdvanceDemo(); +} + + +/* +================ += += D_PageDrawer += +================ +*/ + +extern boolean MenuActive; + +void D_PageDrawer(void) +{ + V_DrawRawScreen(W_CacheLumpName(pagename, PU_CACHE)); + if (demosequence == 1) + { + V_DrawPatch(4, 160, W_CacheLumpName(DEH_String("ADVISOR"), PU_CACHE)); + } + UpdateState |= I_FULLSCRN; +} + +/* +================= += += D_AdvanceDemo += += Called after each demo or intro demosequence finishes +================= +*/ + +void D_AdvanceDemo(void) +{ + advancedemo = true; +} + +void D_DoAdvanceDemo(void) +{ + players[consoleplayer].playerstate = PST_LIVE; // don't reborn + advancedemo = false; + usergame = false; // can't save / end game here + paused = false; + gameaction = ga_nothing; + demosequence = (demosequence + 1) % 7; + switch (demosequence) + { + case 0: + pagetic = 210; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("TITLE"); + S_StartSong(mus_titl, false); + break; + case 1: + pagetic = 140; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("TITLE"); + break; + case 2: + BorderNeedRefresh = true; + UpdateState |= I_FULLSCRN; + G_DeferedPlayDemo(DEH_String("demo1")); + break; + case 3: + pagetic = 200; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("CREDIT"); + break; + case 4: + BorderNeedRefresh = true; + UpdateState |= I_FULLSCRN; + G_DeferedPlayDemo(DEH_String("demo2")); + break; + case 5: + pagetic = 200; + gamestate = GS_DEMOSCREEN; + if (gamemode == shareware) + { + pagename = DEH_String("ORDER"); + } + else + { + pagename = DEH_String("CREDIT"); + } + break; + case 6: + BorderNeedRefresh = true; + UpdateState |= I_FULLSCRN; + G_DeferedPlayDemo(DEH_String("demo3")); + break; + } +} + + +/* +================= += += D_StartTitle += +================= +*/ + +void D_StartTitle(void) +{ + gameaction = ga_nothing; + demosequence = -1; + D_AdvanceDemo(); +} + + +/* +============== += += D_CheckRecordFrom += += -recordfrom <savegame num> <demoname> +============== +*/ + +void D_CheckRecordFrom(void) +{ + int p; + char file[256]; + + p = M_CheckParm("-recordfrom"); + if (!p || p > myargc - 2) + return; + + if (cdrom) + { + sprintf(file, SAVEGAMENAMECD "%c.hsg", myargv[p + 1][0]); + } + else + { + sprintf(file, SAVEGAMENAME "%c.hsg", myargv[p + 1][0]); + } + G_LoadGame(file); + G_DoLoadGame(); // load the gameskill etc info from savegame + + G_RecordDemo(gameskill, 1, gameepisode, gamemap, myargv[p + 2]); + D_DoomLoop(); // never returns +} + +/* +=============== += += D_AddFile += +=============== +*/ + +// MAPDIR should be defined as the directory that holds development maps +// for the -wart # # command + +#define MAPDIR "\\data\\" + +#define SHAREWAREWADNAME "heretic1.wad" + +char *iwadfile; + +char *basedefault = "heretic.cfg"; + +void wadprintf(void) +{ + if (debugmode) + { + return; + } + // haleyjd FIXME: convert to textscreen code? +#ifdef __WATCOMC__ + _settextposition(23, 2); + _setbkcolor(1); + _settextcolor(0); + _outtext(exrnwads); + _settextposition(24, 2); + _outtext(exrnwads2); +#endif +} + +boolean D_AddFile(char *file) +{ + wad_file_t *handle; + + printf(" adding %s\n", file); + + handle = W_AddFile(file); + + return handle != NULL; +} + +//========================================================== +// +// Startup Thermo code +// +//========================================================== +#define MSG_Y 9 +#define THERM_X 14 +#define THERM_Y 14 + +int thermMax; +int thermCurrent; +char smsg[80]; // status bar line + +// +// Heretic startup screen shit +// + +static int startup_line = STARTUP_WINDOW_Y; + +void hprintf(char *string) +{ + if (using_graphical_startup) + { + TXT_BGColor(TXT_COLOR_CYAN, 0); + TXT_FGColor(TXT_COLOR_BRIGHT_WHITE); + + TXT_GotoXY(STARTUP_WINDOW_X, startup_line); + ++startup_line; + TXT_Puts(string); + + TXT_UpdateScreen(); + } + + // haleyjd: shouldn't be WATCOMC-only + if (debugmode) + puts(string); +} + +void drawstatus(void) +{ + int i; + + TXT_GotoXY(1, 24); + TXT_BGColor(TXT_COLOR_BLUE, 0); + TXT_FGColor(TXT_COLOR_BRIGHT_WHITE); + + for (i=0; smsg[i] != '\0'; ++i) + { + TXT_PutChar(smsg[i]); + } +} + +void status(char *string) +{ + if (using_graphical_startup) + { + strcat(smsg, string); + drawstatus(); + } +} + +void DrawThermo(void) +{ + static int last_progress = -1; + int progress; + int i; + + if (!using_graphical_startup) + { + return; + } + +#if 0 + progress = (98 * thermCurrent) / thermMax; + screen = (char *) 0xb8000 + (THERM_Y * 160 + THERM_X * 2); + for (i = 0; i < progress / 2; i++) + { + switch (i) + { + case 4: + case 9: + case 14: + case 19: + case 29: + case 34: + case 39: + case 44: + *screen++ = 0xb3; + *screen++ = (THERMCOLOR << 4) + 15; + break; + case 24: + *screen++ = 0xba; + *screen++ = (THERMCOLOR << 4) + 15; + break; + default: + *screen++ = 0xdb; + *screen++ = 0x40 + THERMCOLOR; + break; + } + } + if (progress & 1) + { + *screen++ = 0xdd; + *screen++ = 0x40 + THERMCOLOR; + } +#else + + // No progress? Don't update the screen. + + progress = (50 * thermCurrent) / thermMax + 2; + + if (last_progress == progress) + { + return; + } + + last_progress = progress; + + TXT_GotoXY(THERM_X, THERM_Y); + + TXT_FGColor(TXT_COLOR_BRIGHT_GREEN); + TXT_BGColor(TXT_COLOR_GREEN, 0); + + for (i = 0; i < progress; i++) + { + TXT_PutChar(0xdb); + } + + TXT_UpdateScreen(); +#endif +} + +void initStartup(void) +{ + byte *textScreen; + byte *loading; + + if (!graphical_startup || debugmode) + { + using_graphical_startup = false; + return; + } + + if (!TXT_Init()) + { + using_graphical_startup = false; + return; + } + + // Blit main screen + textScreen = TXT_GetScreenData(); + loading = W_CacheLumpName(DEH_String("LOADING"), PU_CACHE); + memcpy(textScreen, loading, 4000); + + // Print version string + + TXT_BGColor(TXT_COLOR_RED, 0); + TXT_FGColor(TXT_COLOR_YELLOW); + TXT_GotoXY(46, 2); + TXT_Puts(HERETIC_VERSION_TEXT); + + TXT_UpdateScreen(); + + using_graphical_startup = true; +} + +static void finishStartup(void) +{ + if (using_graphical_startup) + { + TXT_Shutdown(); + } +} + +char tmsg[300]; +void tprintf(char *msg, int initflag) +{ + // haleyjd FIXME: convert to textscreen code? +#ifdef __WATCOMC__ + char temp[80]; + int start; + int add; + int i; + + if (initflag) + tmsg[0] = 0; + strcat(tmsg, msg); + blitStartup(); + DrawThermo(); + _setbkcolor(4); + _settextcolor(15); + for (add = start = i = 0; i <= strlen(tmsg); i++) + if ((tmsg[i] == '\n') || (!tmsg[i])) + { + memset(temp, 0, 80); + strncpy(temp, tmsg + start, i - start); + _settextposition(MSG_Y + add, 40 - strlen(temp) / 2); + _outtext(temp); + start = i + 1; + add++; + } + _settextposition(25, 1); + drawstatus(); +#else + printf("%s", msg); +#endif +} + +// haleyjd: moved up, removed WATCOMC code +void CleanExit(void) +{ + DEH_printf("Exited from HERETIC.\n"); + exit(1); +} + +void CheckAbortStartup(void) +{ + // haleyjd: removed WATCOMC + // haleyjd FIXME: this should actually work in text mode too, but how to + // get input before SDL video init? + if(using_graphical_startup) + { + if(TXT_GetChar() == 27) + CleanExit(); + } +} + +void IncThermo(void) +{ + thermCurrent++; + DrawThermo(); + CheckAbortStartup(); +} + +void InitThermo(int max) +{ + thermMax = max; + thermCurrent = 0; +} + +// +// Add configuration file variable bindings. +// + +void D_BindVariables(void) +{ + extern int screenblocks; + extern int snd_Channels; + int i; + + M_ApplyPlatformDefaults(); + + I_BindVideoVariables(); + I_BindJoystickVariables(); + I_BindSoundVariables(); + + M_BindBaseControls(); + M_BindHereticControls(); + M_BindWeaponControls(); + M_BindChatControls(MAXPLAYERS); + + M_BindMenuControls(); + M_BindMapControls(); + + M_BindVariable("mouse_sensitivity", &mouseSensitivity); + M_BindVariable("sfx_volume", &snd_MaxVolume); + M_BindVariable("music_volume", &snd_MusicVolume); + M_BindVariable("screenblocks", &screenblocks); + M_BindVariable("snd_channels", &snd_Channels); + M_BindVariable("show_endoom", &show_endoom); + M_BindVariable("graphical_startup", &graphical_startup); + + for (i=0; i<10; ++i) + { + char buf[12]; + + sprintf(buf, "chatmacro%i", i); + M_BindVariable(buf, &chat_macros[i]); + } +} + +// +// Called at exit to display the ENDOOM screen (ENDTEXT in Heretic) +// + +static void D_Endoom(void) +{ + byte *endoom_data; + + // Disable ENDOOM? + + if (!show_endoom) + { + return; + } + + endoom_data = W_CacheLumpName(DEH_String("ENDTEXT"), PU_STATIC); + + I_Endoom(endoom_data); +} + +//--------------------------------------------------------------------------- +// +// PROC D_DoomMain +// +//--------------------------------------------------------------------------- + +void D_DoomMain(void) +{ + int p; + char file[256]; + + I_PrintBanner(PACKAGE_STRING); + + I_AtExit(D_Endoom, false); + + M_FindResponseFile(); + nomonsters = M_CheckParm("-nomonsters"); + respawnparm = M_CheckParm("-respawn"); + ravpic = M_CheckParm("-ravpic"); + noartiskip = M_CheckParm("-noartiskip"); + debugmode = M_CheckParm("-debug"); + startskill = sk_medium; + startepisode = 1; + startmap = 1; + autostart = false; + +// +// get skill / episode / map from parms +// + if (M_CheckParm("-deathmatch")) + { + deathmatch = true; + } + + 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("-warp"); + if (p && p < myargc - 2) + { + startepisode = myargv[p + 1][0] - '0'; + startmap = myargv[p + 2][0] - '0'; + autostart = true; + } + +// +// init subsystems +// + DEH_printf("V_Init: allocate screens.\n"); + V_Init(); + + // Check for -CDROM + + cdrom = false; + +#ifdef _WIN32 + + //! + // @platform windows + // @vanilla + // + // Save configuration data and savegames in c:\heretic.cd, + // allowing play from CD. + // + + if (M_CheckParm("-cdrom")) + { + cdrom = true; + } +#endif + + if (cdrom) + { + M_SetConfigDir(DEH_String("c:\\heretic.cd")); + } + else + { + M_SetConfigDir(NULL); + } + + // Load defaults before initing other systems + DEH_printf("M_LoadDefaults: Load system defaults.\n"); + D_BindVariables(); + M_SetConfigFilenames("heretic.cfg", PROGRAM_PREFIX "heretic.cfg"); + M_LoadDefaults(); + + I_AtExit(M_SaveDefaults, false); + + DEH_printf("Z_Init: Init zone memory allocation daemon.\n"); + Z_Init(); + +#ifdef FEATURE_DEHACKED + printf("DEH_Init: Init Dehacked support.\n"); + DEH_Init(); +#endif + + DEH_printf("W_Init: Init WADfiles.\n"); + + iwadfile = D_FindIWAD(IWAD_MASK_HERETIC, &gamemission); + + if (iwadfile == NULL) + { + I_Error("Game mode indeterminate. No IWAD was found. Try specifying\n" + "one with the '-iwad' command line parameter."); + } + + D_AddFile(iwadfile); + W_ParseCommandLine(); + + p = M_CheckParm("-playdemo"); + if (!p) + { + p = M_CheckParm("-timedemo"); + } + if (p && p < myargc - 1) + { + DEH_snprintf(file, sizeof(file), "%s.lmp", myargv[p + 1]); + D_AddFile(file); + DEH_printf("Playing demo %s.lmp.\n", myargv[p + 1]); + } + + if (W_CheckNumForName(DEH_String("E2M1")) == -1) + { + gamemode = shareware; + gamedescription = "Heretic (shareware)"; + } + else if (W_CheckNumForName("EXTENDED") != -1) + { + // Presence of the EXTENDED lump indicates the retail version + + gamemode = retail; + gamedescription = "Heretic: Shadow of the Serpent Riders"; + } + else + { + gamemode = registered; + gamedescription = "Heretic (registered)"; + } + + I_PrintStartupBanner(gamedescription); + + // haleyjd: removed WATCOMC + initStartup(); + + // + // Build status bar line! + // + smsg[0] = 0; + if (deathmatch) + status(DEH_String("DeathMatch...")); + if (nomonsters) + status(DEH_String("No Monsters...")); + if (respawnparm) + status(DEH_String("Respawning...")); + if (autostart) + { + char temp[64]; + DEH_snprintf(temp, sizeof(temp), + "Warp to Episode %d, Map %d, Skill %d ", + startepisode, startmap, startskill + 1); + status(temp); + } + wadprintf(); // print the added wadfiles + + tprintf(DEH_String("MN_Init: Init menu system.\n"), 1); + MN_Init(); + + CT_Init(); + + tprintf(DEH_String("R_Init: Init Heretic refresh daemon."), 1); + hprintf(DEH_String("Loading graphics")); + R_Init(); + tprintf("\n", 0); + + tprintf(DEH_String("P_Init: Init Playloop state.\n"), 1); + hprintf(DEH_String("Init game engine.")); + P_Init(); + IncThermo(); + + tprintf(DEH_String("I_Init: Setting up machine state.\n"), 1); + I_CheckIsScreensaver(); + I_InitTimer(); + I_InitJoystick(); + IncThermo(); + + tprintf(DEH_String("S_Init: Setting up sound.\n"), 1); + S_Init(); + //IO_StartupTimer(); + S_Start(); + + tprintf(DEH_String("D_CheckNetGame: Checking network game status.\n"), 1); + hprintf(DEH_String("Checking network game status.")); + D_CheckNetGame(); + IncThermo(); + + // haleyjd: removed WATCOMC + + tprintf(DEH_String("SB_Init: Loading patches.\n"), 1); + SB_Init(); + IncThermo(); + +// +// start the apropriate game based on parms +// + + D_CheckRecordFrom(); + + p = M_CheckParm("-record"); + if (p && p < myargc - 1) + { + G_RecordDemo(startskill, 1, startepisode, startmap, myargv[p + 1]); + D_DoomLoop(); // Never returns + } + + 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 (cdrom) + { + sprintf(file, SAVEGAMENAMECD "%c.hsg", myargv[p + 1][0]); + } + else + { + sprintf(file, SAVEGAMENAME "%c.hsg", myargv[p + 1][0]); + } + G_LoadGame(file); + } + + // Check valid episode and map + if (autostart || netgame) + { + if (!D_ValidEpisodeMap(gamemission, gamemode, startepisode, startmap)) + { + startepisode = 1; + startmap = 1; + } + } + + if (gameaction != ga_loadgame) + { + UpdateState |= I_FULLSCRN; + BorderNeedRefresh = true; + if (autostart || netgame) + { + G_InitNew(startskill, startepisode, startmap); + } + else + { + D_StartTitle(); + } + } + + finishStartup(); + + D_DoomLoop(); // Never returns +} diff --git a/src/heretic/d_net.c b/src/heretic/d_net.c new file mode 100644 index 00000000..9258b7d7 --- /dev/null +++ b/src/heretic/d_net.c @@ -0,0 +1,848 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// d_net.c +// This version has the fixed ticdup code + +#include <stdlib.h> + +#include "doomdef.h" +#include "doomkeys.h" + +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" + +#define NCMD_EXIT 0x80000000 +#define NCMD_RETRANSMIT 0x40000000 +#define NCMD_SETUP 0x20000000 +#define NCMD_KILL 0x10000000 // kill game +#define NCMD_CHECKSUM 0x0fffffff + + +doomcom_t *doomcom; +doomdata_t *netbuffer; // points inside doomcom + + +/* +============================================================================== + + NETWORKING + +gametic is the tic about to (or currently being) run +maketic is the tick that hasn't had control made for it yet +nettics[] has the maketics for all players + +a gametic cannot be run until nettics[] > gametic for all players + +============================================================================== +*/ + +#define RESENDCOUNT 10 +#define PL_DRONE 0x80 // bit flag in doomdata->player + +ticcmd_t localcmds[BACKUPTICS]; + +ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; +int nettics[MAXNETNODES]; +boolean nodeingame[MAXNETNODES]; // set false as nodes leave game +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, skiptics; +int ticdup; +int maxsend; // BACKUPTICS/(2*ticdup)-1 + +void D_ProcessEvents(void); +void G_BuildTiccmd(ticcmd_t * cmd); +void D_DoAdvanceDemo(void); + +boolean reboundpacket; +doomdata_t reboundstore; + + +int NetbufferSize(void) +{ + return (int) &(((doomdata_t *) 0)->cmds[netbuffer->numtics]); +} + +unsigned NetbufferChecksum(void) +{ + unsigned c; + int i, l; + + c = 0x1234567; + +#if defined(NeXT) || defined(NORMALUNIX) + return 0; // byte order problems +#endif + + l = (NetbufferSize() - (int) &(((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("ExpandTics: strange value %i at maketic %i", low, maketic); + 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("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); + for (i = 0; i < doomcom->datalength; i++) + fprintf(debugfile, "%i ", ((byte *) netbuffer)[i]); + fprintf(debugfile, "\n"); + } + +#ifdef I_NET + I_NetCmd(); +#endif +} + +/* +============== += += HGetPacket += += Returns false if no packet is waiting += +============== +*/ + +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; +#ifdef I_NET + I_NetCmd(); +#endif + if (doomcom->remotenode == -1) + return false; + + if (doomcom->datalength != NetbufferSize()) + { + if (debugfile) + fprintf(debugfile, "bad packet length %i\n", doomcom->datalength); + return false; + } + + if (NetbufferChecksum() != (netbuffer->checksum & NCMD_CHECKSUM)) + { + if (debugfile) + fprintf(debugfile, "bad packet checksum\n"); + return false; + } + + if (debugfile) + { + int realretrans; + int i; + + if (netbuffer->checksum & NCMD_SETUP) + fprintf(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); + for (i = 0; i < doomcom->datalength; i++) + fprintf(debugfile, "%i ", ((byte *) netbuffer)[i]); + fprintf(debugfile, "\n"); + } + } + return true; +} + + +/* +=================== += += GetPackets += +=================== +*/ + +char exitmsg[80]; + +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; + 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("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]); + 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); + 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]); + 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 +============= +*/ + +int gametime; + +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 +//printf ("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("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(); + + while ((ev = D_PopEvent()) != NULL) + { + if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE) + I_Error("Network game synchronization aborted."); + } +} + +/* +===================== += += D_ArbitrateNetStart += +===================== +*/ + +void D_ArbitrateNetStart(void) +{ + int i; + boolean gotinfo[MAXNETNODES]; + + autostart = true; + memset(gotinfo, 0, sizeof(gotinfo)); + + if (doomcom->consoleplayer) + { // listen for setup info from key player +// mprintf ("listening for network start info...\n"); + while (1) + { + CheckAbort(); + if (!HGetPacket()) + continue; + if (netbuffer->checksum & NCMD_SETUP) + { + if (netbuffer->player != HERETIC_VERSION) + I_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; + startmap = netbuffer->starttic & 15; + startepisode = netbuffer->starttic >> 4; + return; + } + } + } + else + { // key player, send the setup info +// mprintf ("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->starttic = (startepisode << 4) + startmap; + netbuffer->player = HERETIC_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); + } +} + +/* +==================== += += I_InitNetwork += +==================== +*/ + +static void I_InitNetwork(void) +{ + doomcom = malloc(sizeof(*doomcom)); + memset(doomcom, 0, sizeof(*doomcom)); + netgame = false; + doomcom->id = DOOMCOM_ID; + doomcom->numplayers = 1; + doomcom->numnodes = 1; + doomcom->deathmatch = false; + doomcom->consoleplayer = 0; + doomcom->ticdup = 1; + doomcom->extratics = 0; +} + +/* +=================== += += D_CheckNetGame += += Works out player numbers among the net participants +=================== +*/ + +extern int viewangleoffset; + +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("Doomcom buffer invalid!"); + netbuffer = &doomcom->data; + consoleplayer = displayplayer = doomcom->consoleplayer; + if (netgame) + D_ArbitrateNetStart(); +//printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", startskill, deathmatch, startmap, startepisode); + +// 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; + +//printf ("player %i of %i (%i nodes)\n", consoleplayer+1, doomcom->numplayers, doomcom->numnodes); + +} + +/* +================== += += D_QuitNetGame += += Called before quitting to leave a net game without hanging the += other players += +================== +*/ + +void D_QuitNetGame(void) +{ + int i, j; + + if (debugfile) + fclose(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 += +=============== +*/ + +int frametics[4], frameon; +int frameskip[4]; +int oldnettics; +extern boolean advancedemo; + +void TryRunTics(void) +{ + int i; + int lowtic; + int entertic; + static int oldentertics; + int realtics, availabletics; + int counts; + int numplaying; + +// +// get real tics +// + entertic = I_GetTime() / ticdup; + realtics = entertic - oldentertics; + oldentertics = entertic; + +// +// get available tics +// + NetUpdate(); + + lowtic = INT_MAX; + 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); + + 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--; + // printf ("-"); + } + frameskip[frameon & 3] = (oldnettics > nettics[nodeforplayer[i]]); + oldnettics = nettics[0]; + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) + { + skiptics = 1; + // printf ("+"); + } + } + //============================================================================= + } // demoplayback + + // + // wait for new tics if needed + // + while (lowtic < gametic / ticdup + counts) + { + + NetUpdate(); + lowtic = INT_MAX; + + for (i = 0; i < doomcom->numnodes; i++) + if (nodeingame[i] && nettics[i] < lowtic) + lowtic = nettics[i]; + + if (lowtic < gametic / ticdup) + I_Error("TryRunTics: lowtic < gametic"); + + // don't stay in here forever -- give the menu a chance to work + if (I_GetTime() / ticdup - entertic >= 20) + { + MN_Ticker(); + return; + } + + // Don't hog the CPU + I_Sleep(1); + } + +// +// run the count * ticdup dics +// + while (counts--) + { + for (i = 0; i < ticdup; i++) + { + if (gametic / ticdup > lowtic) + I_Error("gametic>lowtic"); + if (advancedemo) + D_DoAdvanceDemo(); + MN_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 + } +} diff --git a/src/heretic/deh_ammo.c b/src/heretic/deh_ammo.c new file mode 100644 index 00000000..fe86c757 --- /dev/null +++ b/src/heretic/deh_ammo.c @@ -0,0 +1,122 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses "Ammo" sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "doomdef.h" +#include "doomtype.h" +#include "deh_defs.h" +#include "deh_io.h" +#include "deh_main.h" +#include "p_local.h" + +static void *DEH_AmmoStart(deh_context_t *context, char *line) +{ + int ammo_number = 0; + + if (sscanf(line, "Ammo %i", &ammo_number) != 1) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + if (ammo_number < 0 || ammo_number >= NUMAMMO) + { + DEH_Warning(context, "Invalid ammo number: %i", ammo_number); + return NULL; + } + + return &maxammo[ammo_number]; +} + +static void DEH_AmmoParseLine(deh_context_t *context, char *line, void *tag) +{ + char *variable_name, *value; + int ivalue; + int ammo_number; + + if (tag == NULL) + return; + + ammo_number = ((int *) tag) - maxammo; + + // Parse the assignment + + if (!DEH_ParseAssignment(line, &variable_name, &value)) + { + // Failed to parse + + DEH_Warning(context, "Failed to parse assignment"); + return; + } + + ivalue = atoi(value); + + if (!strcasecmp(variable_name, "Per ammo")) + { + // Heretic doesn't have a "per clip" ammo array, instead + // it is per weapon. However, the weapon number lines + // up with the ammo number if we add one. + + GetWeaponAmmo[ammo_number + 1] = ivalue; + } + else if (!strcasecmp(variable_name, "Max ammo")) + { + maxammo[ammo_number] = ivalue; + } + else + { + DEH_Warning(context, "Field named '%s' not found", variable_name); + } +} + +static void DEH_AmmoMD5Hash(md5_context_t *context) +{ + int i; + + for (i=0; i<NUMAMMO; ++i) + { + MD5_UpdateInt32(context, maxammo[i]); + } + + for (i=0; i<NUMWEAPONS; ++i) + { + MD5_UpdateInt32(context, GetWeaponAmmo[i]); + } +} + +deh_section_t deh_section_ammo = +{ + "Ammo", + NULL, + DEH_AmmoStart, + DEH_AmmoParseLine, + NULL, + DEH_AmmoMD5Hash, +}; + diff --git a/src/heretic/deh_frame.c b/src/heretic/deh_frame.c new file mode 100644 index 00000000..8623ab0c --- /dev/null +++ b/src/heretic/deh_frame.c @@ -0,0 +1,344 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses "Frame" sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> + +#include "doomtype.h" +#include "info.h" + +#include "deh_defs.h" +#include "deh_io.h" +#include "deh_main.h" +#include "deh_mapping.h" +#include "deh_htic.h" + +#include "p_action.h" + +typedef struct +{ + int offsets[deh_hhe_num_versions]; + void (*func)(); +} hhe_action_pointer_t; + +// Offsets of action pointers within the Heretic executables. +// Different versions have different offsets. +// (Seriously Greg, was this really necessary? What was wrong with the +// "copying action pointer from another frame" technique used in dehacked?) + +// Offset Action function +// v1.0 v1.2 v1.3 + +static const hhe_action_pointer_t action_pointers[] = +{ + { { 77680, 80144, 80208 }, A_AccTeleGlitter }, + { { 78608, 81104, 81168 }, A_AddPlayerCorpse }, + { { 115808, 118000, 118240 }, A_AddPlayerRain }, + { { 112272, 114480, 114720 }, A_BeakAttackPL1 }, + { { 112448, 114656, 114896 }, A_BeakAttackPL2 }, + { { 111856, 114176, 114416 }, A_BeakRaise }, + { { 111568, 113888, 114128 }, A_BeakReady }, + { { 74640, 77120, 77184 }, A_BeastAttack }, + { { 70480, 72992, 73056 }, A_BeastPuff }, + { { 73120, 75600, 75664 }, A_BlueSpark }, + { { 115456, 117648, 117888 }, A_BoltSpark }, + { { 77344, 79808, 79872 }, A_BossDeath }, + { { 69328, 71856, 71920 }, A_Chase }, + { { 0, 80976, 81040 }, A_CheckBurnGone }, + { { 78480, 80944, 81008 }, A_CheckSkullDone }, + { { 78448, 80912, 80976 }, A_CheckSkullFloor }, + { { 71376, 73888, 73952 }, A_ChicAttack }, + { { 71488, 74000, 74064 }, A_ChicChase }, + { { 71456, 73968, 74032 }, A_ChicLook }, + { { 71520, 74032, 74096 }, A_ChicPain }, + { { 75792, 78208, 78272 }, A_ClinkAttack }, + { { 108432, 110816, 111056 }, A_ContMobjSound }, + { { 114752, 116944, 117184 }, A_DeathBallImpact }, + { { 70016, 72528, 72592 }, A_DripBlood }, + { { 77472, 79936, 80000 }, A_ESound }, + { { 76784, 79248, 79312 }, A_Explode }, + { { 69872, 72400, 72464 }, A_FaceTarget }, + { { 71568, 74080, 74144 }, A_Feathers }, + { { 112928, 115136, 115376 }, A_FireBlasterPL1 }, + { { 113072, 115280, 115520 }, A_FireBlasterPL2 }, + { { 115232, 117424, 117664 }, A_FireCrossbowPL1 }, + { { 115312, 117504, 117744 }, A_FireCrossbowPL2 }, + { { 113152, 115360, 115600 }, A_FireGoldWandPL1 }, + { { 113296, 115504, 115744 }, A_FireGoldWandPL2 }, + { { 113760, 115968, 116208 }, A_FireMacePL1 }, + { { 114624, 116816, 117056 }, A_FireMacePL2 }, + { { 116368, 118544, 118784 }, A_FirePhoenixPL1 }, + { { 116736, 118896, 119136 }, A_FirePhoenixPL2 }, + { { 115568, 117760, 118000 }, A_FireSkullRodPL1 }, + { { 115648, 117840, 118080 }, A_FireSkullRodPL2 }, + { { 117120, 119280, 119520 }, A_FlameEnd }, + { { 78704, 81200, 81264 }, A_FlameSnd }, + { { 117152, 119312, 119552 }, A_FloatPuff }, + { { 78512, 81008, 81072 }, A_FreeTargMobj }, + { { 117184, 119344, 119584 }, A_GauntletAttack }, + { { 73232, 75712, 75776 }, A_GenWizard }, + { { 75872, 78304, 78368 }, A_GhostOff }, + { { 74752, 77232, 77296 }, A_HeadAttack }, + { { 75488, 77984, 78048 }, A_HeadFireGrow }, + { { 75328, 77824, 77888 }, A_HeadIceImpact }, + { { 116336, 118512, 118752 }, A_HideInCeiling }, + { { 78736, 81232, 81296 }, A_HideThing }, + { { 70976, 73488, 73552 }, A_ImpDeath }, + { { 70304, 72816, 72880 }, A_ImpExplode }, + { { 70592, 73104, 73168 }, A_ImpMeAttack }, + { { 70672, 73184, 73248 }, A_ImpMsAttack }, + { { 70880, 73392, 73456 }, A_ImpMsAttack2 }, + { { 71024, 73536, 73600 }, A_ImpXDeath1 }, + { { 71072, 73584, 73648 }, A_ImpXDeath2 }, + { { 77728, 80192, 80256 }, A_InitKeyGizmo }, + { { 116720, 118880, 119120 }, A_InitPhoenixPL2 }, + { { 70160, 72672, 72736 }, A_KnightAttack }, + { { 117648, 119824, 120064 }, A_Light0 }, + { { 69200, 71728, 71792 }, A_Look }, + { { 111760, 114080, 114320 }, A_Lower }, + { { 114032, 116224, 116464 }, A_MaceBallImpact }, + { { 114192, 116384, 116624 }, A_MaceBallImpact2 }, + { { 113904, 116112, 116352 }, A_MacePL1Check }, + { { 77104, 79568, 79632 }, A_MakePod }, + { { 73648, 76128, 76192 }, A_MinotaurAtk1 }, + { { 74112, 76592, 76656 }, A_MinotaurAtk2 }, + { { 74352, 76832, 76896 }, A_MinotaurAtk3 }, + { { 74032, 76512, 76576 }, A_MinotaurCharge }, + { { 73760, 76240, 76304 }, A_MinotaurDecide }, + { { 74528, 77008, 77072 }, A_MntrFloorFire }, + { { 71808, 74288, 74352 }, A_MummyAttack }, + { { 71920, 74400, 74464 }, A_MummyAttack2 }, + { { 72016, 74496, 74560 }, A_MummyFX1Seek }, + { { 72048, 74528, 74592 }, A_MummySoul }, + { { 76400, 78832, 78896 }, A_NoBlocking }, + { { 69984, 72496, 72560 }, A_Pain }, + { { 116496, 118656, 118896 }, A_PhoenixPuff }, + { { 76896, 79360, 79424 }, A_PodPain }, + { { 116272, 118448, 118688 }, A_RainImpact }, + { { 111920, 114240, 114480 }, A_Raise }, + { { 111696, 114016, 114256 }, A_ReFire }, + { { 77056, 79520, 79584 }, A_RemovePod }, + { { 116480, 0, 0 }, A_RemovedPhoenixFunc }, + { { 81952, 84464, 84528 }, A_RestoreArtifact }, + { { 82048, 84544, 84608 }, A_RestoreSpecialThing1 }, + { { 82128, 84592, 84656 }, A_RestoreSpecialThing2 }, + { { 76144, 78576, 78640 }, A_Scream }, + { { 117104, 119264, 119504 }, A_ShutdownPhoenixPL2 }, + { { 78288, 80752, 80816 }, A_SkullPop }, + { { 115776, 117968, 118208 }, A_SkullRodPL2Seek }, + { { 115984, 118176, 118416 }, A_SkullRodStorm }, + { { 75632, 78048, 78112 }, A_SnakeAttack }, + { { 75712, 78128, 78192 }, A_SnakeAttack2 }, + { { 72144, 74624, 74688 }, A_Sor1Chase }, + { { 72096, 74576, 74640 }, A_Sor1Pain }, + { { 73392, 75872, 75936 }, A_Sor2DthInit }, + { { 73424, 75904, 75968 }, A_Sor2DthLoop }, + { { 73584, 76064, 76128 }, A_SorDBon }, + { { 73552, 76032, 76096 }, A_SorDExp }, + { { 73520, 76000, 76064 }, A_SorDSph }, + { { 73488, 75968, 76032 }, A_SorRise }, + { { 73616, 76096, 76160 }, A_SorSightSnd }, + { { 73456, 75936, 76000 }, A_SorZap }, + { { 72480, 74960, 75024 }, A_SorcererRise }, + { { 115088, 117280, 117520 }, A_SpawnRippers }, + { { 77520, 79984, 80048 }, A_SpawnTeleGlitter }, + { { 77600, 80064, 80128 }, A_SpawnTeleGlitter2 }, + { { 72192, 74672, 74736 }, A_Srcr1Attack }, + { { 72896, 75376, 75440 }, A_Srcr2Attack }, + { { 72816, 75296, 75360 }, A_Srcr2Decide }, + { { 112640, 114848, 115088 }, A_StaffAttackPL1 }, + { { 112784, 114992, 115232 }, A_StaffAttackPL2 }, + { { 78752, 81248, 81312 }, A_UnHideThing }, + { { 78080, 80544, 80608 }, A_VolcBallImpact }, + { { 77856, 80320, 80384 }, A_VolcanoBlast }, + { { 77824, 80288, 80352 }, A_VolcanoSet }, + { { 111168, 113488, 113728 }, A_WeaponReady }, + { { 75168, 77664, 77728 }, A_WhirlwindSeek }, + { { 75888, 78320, 78384 }, A_WizAtk1 }, + { { 75920, 78352, 78416 }, A_WizAtk2 }, + { { 75952, 78384, 78448 }, A_WizAtk3 }, +}; + +DEH_BEGIN_MAPPING(state_mapping, state_t) + DEH_MAPPING("Sprite number", sprite) + DEH_MAPPING("Sprite subnumber", frame) + DEH_MAPPING("Duration", tics) + DEH_MAPPING("Next frame", nextstate) + DEH_MAPPING("Unknown 1", misc1) + DEH_MAPPING("Unknown 2", misc2) +DEH_END_MAPPING + +static void DEH_FrameInit(void) +{ + // Bit of a hack here: + DEH_HereticInit(); +} + +static void *DEH_FrameStart(deh_context_t *context, char *line) +{ + int frame_number = 0; + int mapped_frame_number; + state_t *state; + + if (sscanf(line, "Frame %i", &frame_number) != 1) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + // Map the HHE frame number (which assumes a Heretic 1.0 state table) + // to the internal frame number (which is is the Heretic 1.3 state table): + + mapped_frame_number = DEH_MapHereticFrameNumber(frame_number); + + if (mapped_frame_number < 0 || mapped_frame_number >= DEH_HERETIC_NUMSTATES) + { + DEH_Warning(context, "Invalid frame number: %i", frame_number); + return NULL; + } + + state = &states[mapped_frame_number]; + + return state; +} + +static boolean GetActionPointerForOffset(int offset, void **result) +{ + int i; + + // Special case. + + if (offset == 0) + { + *result = NULL; + return true; + } + + for (i=0; i<arrlen(action_pointers); ++i) + { + if (action_pointers[i].offsets[deh_hhe_version] == offset) + { + *result = action_pointers[i].func; + return true; + } + } + + return false; +} + +// If an invalid action pointer is specified, the patch may be for a +// different version from the version we are currently set to. Try to +// suggest a different version to use. + +static void SuggestOtherVersions(unsigned int offset) +{ + unsigned int i, v; + + for (i=0; i<arrlen(action_pointers); ++i) + { + for (v=0; v<deh_hhe_num_versions; ++v) + { + if (action_pointers[i].offsets[v] == offset) + { + DEH_SuggestHereticVersion(v); + } + } + } +} + +static void DEH_FrameParseLine(deh_context_t *context, char *line, void *tag) +{ + state_t *state; + char *variable_name, *value; + int ivalue; + + if (tag == NULL) + return; + + state = (state_t *) tag; + + // Parse the assignment + + if (!DEH_ParseAssignment(line, &variable_name, &value)) + { + // Failed to parse + + DEH_Warning(context, "Failed to parse assignment"); + return; + } + + // all values are integers + + ivalue = atoi(value); + + // Action pointer field is a special case: + + if (!strcasecmp(variable_name, "Action pointer")) + { + void *func; + + if (!GetActionPointerForOffset(ivalue, &func)) + { + SuggestOtherVersions(ivalue); + DEH_Error(context, "Unknown action pointer: %i", ivalue); + return; + } + + state->action = func; + } + else + { + // "Next frame" numbers need to undergo mapping. + + if (!strcasecmp(variable_name, "Next frame")) + { + ivalue = DEH_MapHereticFrameNumber(ivalue); + } + + DEH_SetMapping(context, &state_mapping, state, variable_name, ivalue); + } +} + +static void DEH_FrameMD5Sum(md5_context_t *context) +{ + int i; + + for (i=0; i<NUMSTATES; ++i) + { + DEH_StructMD5Sum(context, &state_mapping, &states[i]); + } +} + +deh_section_t deh_section_frame = +{ + "Frame", + DEH_FrameInit, + DEH_FrameStart, + DEH_FrameParseLine, + NULL, + DEH_FrameMD5Sum, +}; + diff --git a/src/heretic/deh_htext.c b/src/heretic/deh_htext.c new file mode 100644 index 00000000..5dfddac3 --- /dev/null +++ b/src/heretic/deh_htext.c @@ -0,0 +1,856 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005-2010 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses Text substitution sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <string.h> + +#include "doomtype.h" +#include "dstrings.h" + +#include "z_zone.h" + +#include "deh_defs.h" +#include "deh_io.h" +#include "deh_htic.h" +#include "deh_main.h" + +// +// Ok, Greg, the action pointers thing was bad enough, but this really +// takes the biscuit. Why does HHE's text replacement address strings +// by offset??!! The dehacked way was much nicer, why change it? +// + +typedef struct +{ + unsigned int offsets[deh_hhe_num_versions]; + char *string; +} hhe_string_t; + +// Offsets String +// v1.0 v1.2 v1.3 + +static const hhe_string_t strings[] = +{ + { { 228, 228, 228 }, "PLAYPAL" }, + { { 1240, 1252, 1252 }, "E1M1: THE DOCKS" }, + { { 1260, 1272, 1272 }, "E1M2: THE DUNGEONS" }, + { { 1280, 1292, 1292 }, "E1M3: THE GATEHOUSE" }, + { { 1304, 1316, 1316 }, "E1M4: THE GUARD TOWER" }, + { { 1328, 1340, 1340 }, "E1M5: THE CITADEL" }, + { { 1348, 1360, 1360 }, "E1M6: THE CATHEDRAL" }, + { { 1372, 1384, 1384 }, "E1M7: THE CRYPTS" }, + { { 1392, 1404, 1404 }, "E1M8: HELL'S MAW" }, + { { 1412, 1424, 1424 }, "E1M9: THE GRAVEYARD" }, + { { 1436, 1448, 1448 }, "E2M1: THE CRATER" }, + { { 1456, 1468, 1468 }, "E2M2: THE LAVA PITS" }, + { { 1480, 1492, 1492 }, "E2M3: THE RIVER OF FIRE" }, + { { 1508, 1520, 1520 }, "E2M4: THE ICE GROTTO" }, + { { 1532, 1544, 1544 }, "E2M5: THE CATACOMBS" }, + { { 1556, 1568, 1568 }, "E2M6: THE LABYRINTH" }, + { { 1580, 1592, 1592 }, "E2M7: THE GREAT HALL" }, + { { 1604, 1616, 1616 }, "E2M8: THE PORTALS OF CHAOS" }, + { { 1632, 1644, 1644 }, "E2M9: THE GLACIER" }, + { { 1652, 1664, 1664 }, "E3M1: THE STOREHOUSE" }, + { { 1676, 1688, 1688 }, "E3M2: THE CESSPOOL" }, + { { 1696, 1708, 1708 }, "E3M3: THE CONFLUENCE" }, + { { 1720, 1732, 1732 }, "E3M4: THE AZURE FORTRESS" }, + { { 1748, 1760, 1760 }, "E3M5: THE OPHIDIAN LAIR" }, + { { 1776, 1788, 1788 }, "E3M6: THE HALLS OF FEAR" }, + { { 1804, 1816, 1816 }, "E3M7: THE CHASM" }, + { { 1824, 1836, 1836 }, "E3M8: D'SPARIL'S KEEP" }, + { { 1848, 1860, 1860 }, "E3M9: THE AQUIFER" }, + { { 0, 1880, 1880 }, "E4M1: CATAFALQUE" }, + { { 0, 1900, 1900 }, "E4M2: BLOCKHOUSE" }, + { { 0, 1920, 1920 }, "E4M3: AMBULATORY" }, + { { 0, 1940, 1940 }, "E4M4: SEPULCHER" }, + { { 0, 1960, 1960 }, "E4M5: GREAT STAIR" }, + { { 0, 1980, 1980 }, "E4M6: HALLS OF THE APOSTATE" }, + { { 0, 2012, 2012 }, "E4M7: RAMPARTS OF PERDITION" }, + { { 0, 2044, 2044 }, "E4M8: SHATTERED BRIDGE" }, + { { 0, 2068, 2068 }, "E4M9: MAUSOLEUM" }, + { { 0, 2088, 2088 }, "E5M1: OCHRE CLIFFS" }, + { { 0, 2108, 2108 }, "E5M2: RAPIDS" }, + { { 0, 2124, 2124 }, "E5M3: QUAY" }, + { { 0, 2136, 2136 }, "E5M4: COURTYARD" }, + { { 0, 2156, 2156 }, "E5M5: HYDRATYR" }, + { { 0, 2172, 2172 }, "E5M6: COLONNADE" }, + { { 0, 2192, 2192 }, "E5M7: FOETID MANSE" }, + { { 0, 2212, 2212 }, "E5M8: FIELD OF JUDGEMENT" }, + { { 0, 2240, 2240 }, "E5M9: SKEIN OF D'SPARIL" }, + { { 1868, 2268, 2268 }, "AUTOPAGE" }, + { { 1880, 2280, 2280 }, "FOLLOW MODE ON" }, + { { 1896, 2296, 2296 }, "FOLLOW MODE OFF" }, + { { 1924, 2324, 2324 }, "GREEN: " }, + { { 1936, 2336, 2336 }, "YELLOW: " }, + { { 1948, 2348, 2348 }, "RED: " }, + { { 1956, 2356, 2356 }, "BLUE: " }, + { { 1964, 2364, 2364 }, "FONTA_S" }, + { { 1972, 2372, 2372 }, "-MESSAGE SENT-" }, + { { 1988, 2388, 2388 }, "THERE ARE NO OTHER PLAYERS IN THE GAME!" }, + { { 2028, 2428, 2428 }, "FONTA59" }, + { { 2036, 2504, 2504 }, "PAUSED" }, + { { 2072, 2540, 2540 }, "ADVISOR" }, + { { 2080, 2548, 2548 }, "TITLE" }, + { { 2088, 2556, 2556 }, "demo1" }, + { { 2096, 2564, 2564 }, "CREDIT" }, + { { 2104, 2572, 2572 }, "demo2" }, + { { 2112, 2580, 2580 }, "ORDER" }, + { { 2120, 2588, 2588 }, "demo3" }, + { { 2304, 2696, 2696 }, "Exited from HERETIC.\n" }, + { { 2412, 2800, 2800 }, "c:\\heretic.cd" }, + { { 2528, 2916, 2916 }, "Playing demo %s.lmp.\n" }, + { { 2592, 2980, 2980 }, "V_Init: allocate screens.\n" }, + { { 2620, 3008, 3008 }, "M_LoadDefaults: Load system defaults.\n" }, + { { 2660, 3048, 3048 }, "Z_Init: Init zone memory allocation daemon.\n" }, + { { 2708, 3096, 3096 }, "W_Init: Init WADfiles.\n" }, + { { 2732, 3120, 3120 }, "E2M1" }, + { { 0, 3128, 3128 }, "EXTENDED" }, + { { 2740, 3140, 3140 }, "LOADING" }, + { { 2748, 3148, 3148 }, "DeathMatch..." }, + { { 2764, 3164, 3164 }, "No Monsters..." }, + { { 2780, 3180, 3180 }, "Respawning..." }, + { { 2796, 3196, 3196 }, "Warp to Episode %d, Map %d, Skill %d " }, + { { 2836, 3236, 3236 }, "MN_Init: Init menu system.\n" }, + { { 2864, 3264, 3264 }, "R_Init: Init Heretic refresh daemon." }, + { { 2904, 3304, 3304 }, "Loading graphics" }, + { { 2924, 3324, 3324 }, "P_Init: Init Playloop state." }, + { { 2956, 3356, 3356 }, "Init game engine." }, + { { 2976, 3376, 3376 }, "I_Init: Setting up machine state.\n" }, + { { 3012, 3412, 3412 }, "D_CheckNetGame: Checking network game status.\n" }, + { { 3060, 3460, 3460 }, "Checking network game status." }, + { { 3092, 3492, 3492 }, "SB_Init: Loading patches.\n" }, + { { 0, 3752, 3752 }, "PLAYER 1 LEFT THE GAME" }, + { { 3508, 3932, 3932 }, "Network game synchronization aborted." }, + { { 0, 3972, 3972 }, "Different DOOM versions cannot play a net game!" }, + { { 3908, 4132, 4132 }, "SKY1" }, + { { 3916, 4140, 4140 }, "SKY2" }, + { { 3924, 4148, 4148 }, "SKY3" }, + { { 3736, 4196, 4196 }, "NET GAME" }, + { { 3748, 4208, 4208 }, "SAVE GAME" }, + { { 3760, 4220, 4220 }, "Only %i deathmatch spots, 4 required" }, + { { 3800, 4260, 4260 }, "version %i" }, + { { 3828, 4372, 4372 }, "c:\\heretic.cd\\hticsav%d.hsg" }, + { { 3856, 4400, 4400 }, "hticsav%d.hsg" }, + { { 3896, 4416, 4416 }, "GAME SAVED" }, + { { 4016, 4456, 4456 }, E1TEXT }, + { { 4536, 4976, 4976 }, E2TEXT }, + { { 5068, 5508, 5508 }, E3TEXT }, + { { 0, 6072, 6072 }, E4TEXT }, + { { 0, 6780, 6780 }, E5TEXT }, + { { 5632, 7468, 7468 }, "FLOOR25" }, + { { 5640, 7476, 7476 }, "FLATHUH1" }, + { { 5652, 7488, 7488 }, "FLTWAWA2" }, + { { 0, 7500, 7500 }, "FLOOR28" }, + { { 0, 7508, 7508 }, "FLOOR08" }, + { { 5664, 7516, 7516 }, "FONTA_S" }, + { { 5704, 7524, 7524 }, "PLAYPAL" }, + { { 5672, 7532, 7532 }, "FINAL1" }, + { { 5680, 7540, 7540 }, "FINAL2" }, + { { 5688, 7548, 7548 }, "E2PAL" }, + { { 5696, 7556, 7556 }, "E2END" }, + { { 7884, 7564, 7564 }, "TITLE" }, + { { 5712, 7572, 7572 }, "ORDER" }, + { { 0, 7580, 7580 }, "CREDIT" }, + { { 5720, 7588, 7588 }, "IMPX" }, + { { 5728, 7596, 7596 }, "ACLO" }, + { { 5736, 7604, 7604 }, "PTN1" }, + { { 5744, 7612, 7612 }, "SHLD" }, + { { 5752, 7620, 7620 }, "SHD2" }, + { { 5760, 7628, 7628 }, "BAGH" }, + { { 5768, 7636, 7636 }, "SPMP" }, + { { 5776, 7644, 7644 }, "INVS" }, + { { 5784, 7652, 7652 }, "PTN2" }, + { { 5792, 7660, 7660 }, "SOAR" }, + { { 5800, 7668, 7668 }, "INVU" }, + { { 5808, 7676, 7676 }, "PWBK" }, + { { 5816, 7684, 7684 }, "EGGC" }, + { { 5824, 7692, 7692 }, "EGGM" }, + { { 5832, 7700, 7700 }, "FX01" }, + { { 5840, 7708, 7708 }, "SPHL" }, + { { 5848, 7716, 7716 }, "TRCH" }, + { { 5856, 7724, 7724 }, "FBMB" }, + { { 5864, 7732, 7732 }, "XPL1" }, + { { 5872, 7740, 7740 }, "ATLP" }, + { { 5880, 7748, 7748 }, "PPOD" }, + { { 5888, 7756, 7756 }, "AMG1" }, + { { 5896, 7764, 7764 }, "SPSH" }, + { { 5904, 7772, 7772 }, "LVAS" }, + { { 5912, 7780, 7780 }, "SLDG" }, + { { 5920, 7788, 7788 }, "SKH1" }, + { { 5928, 7796, 7796 }, "SKH2" }, + { { 5936, 7804, 7804 }, "SKH3" }, + { { 5944, 7812, 7812 }, "SKH4" }, + { { 5952, 7820, 7820 }, "CHDL" }, + { { 5960, 7828, 7828 }, "SRTC" }, + { { 5968, 7836, 7836 }, "SMPL" }, + { { 5976, 7844, 7844 }, "STGS" }, + { { 5984, 7852, 7852 }, "STGL" }, + { { 5992, 7860, 7860 }, "STCS" }, + { { 6000, 7868, 7868 }, "STCL" }, + { { 6008, 7876, 7876 }, "KFR1" }, + { { 6016, 7884, 7884 }, "BARL" }, + { { 6024, 7892, 7892 }, "BRPL" }, + { { 6032, 7900, 7900 }, "MOS1" }, + { { 6040, 7908, 7908 }, "MOS2" }, + { { 6048, 7916, 7916 }, "WTRH" }, + { { 6056, 7924, 7924 }, "HCOR" }, + { { 6064, 7932, 7932 }, "KGZ1" }, + { { 6072, 7940, 7940 }, "KGZB" }, + { { 6080, 7948, 7948 }, "KGZG" }, + { { 6088, 7956, 7956 }, "KGZY" }, + { { 6096, 7964, 7964 }, "VLCO" }, + { { 6104, 7972, 7972 }, "VFBL" }, + { { 6112, 7980, 7980 }, "VTFB" }, + { { 6120, 7988, 7988 }, "SFFI" }, + { { 6128, 7996, 7996 }, "TGLT" }, + { { 6136, 8004, 8004 }, "TELE" }, + { { 6144, 8012, 8012 }, "STFF" }, + { { 6152, 8020, 8020 }, "PUF3" }, + { { 6160, 8028, 8028 }, "PUF4" }, + { { 6168, 8036, 8036 }, "BEAK" }, + { { 6176, 8044, 8044 }, "WGNT" }, + { { 6184, 8052, 8052 }, "GAUN" }, + { { 6192, 8060, 8060 }, "PUF1" }, + { { 6200, 8068, 8068 }, "WBLS" }, + { { 6208, 8076, 8076 }, "BLSR" }, + { { 6216, 8084, 8084 }, "FX18" }, + { { 6224, 8092, 8092 }, "FX17" }, + { { 6232, 8100, 8100 }, "WMCE" }, + { { 6240, 8108, 8108 }, "MACE" }, + { { 6248, 8116, 8116 }, "FX02" }, + { { 6256, 8124, 8124 }, "WSKL" }, + { { 6264, 8132, 8132 }, "HROD" }, + { { 6272, 8140, 8140 }, "FX00" }, + { { 6280, 8148, 8148 }, "FX20" }, + { { 6288, 8156, 8156 }, "FX21" }, + { { 6296, 8164, 8164 }, "FX22" }, + { { 6304, 8172, 8172 }, "FX23" }, + { { 6312, 8180, 8180 }, "GWND" }, + { { 6320, 8188, 8188 }, "PUF2" }, + { { 6328, 8196, 8196 }, "WPHX" }, + { { 6336, 8204, 8204 }, "PHNX" }, + { { 6344, 8212, 8212 }, "FX04" }, + { { 6352, 8220, 8220 }, "FX08" }, + { { 6360, 8228, 8228 }, "FX09" }, + { { 6368, 8236, 8236 }, "WBOW" }, + { { 6376, 8244, 8244 }, "CRBW" }, + { { 6384, 8252, 8252 }, "FX03" }, + { { 6392, 8260, 8260 }, "BLOD" }, + { { 6400, 8268, 8268 }, "PLAY" }, + { { 6408, 8276, 8276 }, "FDTH" }, + { { 6416, 8284, 8284 }, "BSKL" }, + { { 6424, 8292, 8292 }, "CHKN" }, + { { 6432, 8300, 8300 }, "MUMM" }, + { { 6440, 8308, 8308 }, "FX15" }, + { { 6448, 8316, 8316 }, "BEAS" }, + { { 6456, 8324, 8324 }, "FRB1" }, + { { 6464, 8332, 8332 }, "SNKE" }, + { { 6472, 8340, 8340 }, "SNFX" }, + { { 6480, 8348, 8348 }, "HEAD" }, + { { 6488, 8356, 8356 }, "FX05" }, + { { 6496, 8364, 8364 }, "FX06" }, + { { 6504, 8372, 8372 }, "FX07" }, + { { 6512, 8380, 8380 }, "CLNK" }, + { { 6520, 8388, 8388 }, "WZRD" }, + { { 6528, 8396, 8396 }, "FX11" }, + { { 6536, 8404, 8404 }, "FX10" }, + { { 6544, 8412, 8412 }, "KNIG" }, + { { 6552, 8420, 8420 }, "SPAX" }, + { { 6560, 8428, 8428 }, "RAXE" }, + { { 6568, 8436, 8436 }, "SRCR" }, + { { 6576, 8444, 8444 }, "FX14" }, + { { 6584, 8452, 8452 }, "SOR2" }, + { { 6592, 8460, 8460 }, "SDTH" }, + { { 6600, 8468, 8468 }, "FX16" }, + { { 6608, 8476, 8476 }, "MNTR" }, + { { 6616, 8484, 8484 }, "FX12" }, + { { 6624, 8492, 8492 }, "FX13" }, + { { 6632, 8500, 8500 }, "AKYY" }, + { { 6640, 8508, 8508 }, "BKYY" }, + { { 6648, 8516, 8516 }, "CKYY" }, + { { 6656, 8524, 8524 }, "AMG2" }, + { { 6664, 8532, 8532 }, "AMM1" }, + { { 6672, 8540, 8540 }, "AMM2" }, + { { 6680, 8548, 8548 }, "AMC1" }, + { { 6688, 8556, 8556 }, "AMC2" }, + { { 6696, 8564, 8564 }, "AMS1" }, + { { 6704, 8572, 8572 }, "AMS2" }, + { { 6712, 8580, 8580 }, "AMP1" }, + { { 6720, 8588, 8588 }, "AMP2" }, + { { 6728, 8596, 8596 }, "AMB1" }, + { { 6736, 8604, 8604 }, "AMB2" }, + { { 6744, 8612, 8612 }, "K" }, + { { 6748, 8616, 8616 }, "I" }, + { { 6752, 8620, 8620 }, "L" }, + { { 6756, 8624, 8624 }, "E" }, + { { 6760, 8628, 8628 }, "R" }, + { { 6764, 8632, 8632 }, "S" }, + { { 6768, 8636, 8636 }, "PLAYPAL" }, + { { 6776, 8644, 8644 }, "MAPE1" }, + { { 6784, 8652, 8652 }, "MAPE2" }, + { { 6792, 8660, 8660 }, "MAPE3" }, + { { 6800, 8668, 8668 }, "IN_X" }, + { { 6808, 8676, 8676 }, "IN_YAH" }, + { { 6816, 8684, 8684 }, "FONTB16" }, + { { 6824, 8692, 8692 }, "FONTB_S" }, + { { 6832, 8700, 8700 }, "FONTB13" }, + { { 6840, 8708, 8708 }, "FONTB15" }, + { { 6848, 8716, 8716 }, "FONTB05" }, + { { 6856, 8724, 8724 }, "FACEA0" }, + { { 6864, 8732, 8732 }, "FACEB0" }, + { { 6940, 8808, 8808 }, "FLOOR16" }, + { { 6948, 8816, 8816 }, "FINISHED" }, + { { 6960, 8828, 8828 }, "NOW ENTERING:" }, + { { 6976, 8844, 8844 }, "KILLS" }, + { { 6984, 8852, 8852 }, "ITEMS" }, + { { 6992, 8860, 8860 }, "SECRETS" }, + { { 7000, 8868, 8868 }, "TIME" }, + { { 7008, 8876, 8876 }, "BONUS" }, + { { 7016, 8884, 8884 }, "SECRET" }, + { { 7024, 8892, 8892 }, "TOTAL" }, + { { 7032, 8900, 8900 }, "VICTIMS" }, + { { 7040, 8908, 8908 }, ":" }, + { { 7044, 8912, 8912 }, "NEW GAME" }, + { { 7056, 8924, 8924 }, "OPTIONS" }, + { { 7064, 8932, 8932 }, "GAME FILES" }, + { { 7076, 8944, 8944 }, "INFO" }, + { { 7084, 8952, 8952 }, "QUIT GAME" }, + { { 7096, 8964, 8964 }, "CITY OF THE DAMNED" }, + { { 7116, 8984, 8984 }, "HELL'S MAW" }, + { { 7128, 8996, 8996 }, "THE DOME OF D'SPARIL" }, + { { 0, 9020, 9020 }, "THE OSSUARY" }, + { { 0, 9032, 9032 }, "THE STAGNANT DEMESNE" }, + { { 7152, 9056, 9056 }, "LOAD GAME" }, + { { 7164, 9068, 9068 }, "SAVE GAME" }, + { { 7176, 9080, 9080 }, "THOU NEEDETH A WET-NURSE" }, + { { 7204, 9108, 9108 }, "YELLOWBELLIES-R-US" }, + { { 7224, 9128, 9128 }, "BRINGEST THEM ONETH" }, + { { 7244, 9148, 9148 }, "THOU ART A SMITE-MEISTER" }, + { { 7272, 9176, 9176 }, "BLACK PLAGUE POSSESSES THEE" }, + { { 7300, 9204, 9204 }, "END GAME" }, + { { 7312, 9216, 9216 }, "MESSAGES : " }, + { { 7324, 9228, 9228 }, "MOUSE SENSITIVITY" }, + { { 7344, 9248, 9248 }, "MORE..." }, + { { 7352, 9256, 9256 }, "SCREEN SIZE" }, + { { 7364, 9268, 9268 }, "SFX VOLUME" }, + { { 7376, 9280, 9280 }, "MUSIC VOLUME" }, + { { 7416, 9296, 9296 }, "ARE YOU SURE YOU WANT TO QUIT?" }, + { { 7448, 9328, 9328 }, "ARE YOU SURE YOU WANT TO END THE GAME?" }, + { { 7488, 9368, 9368 }, "DO YOU WANT TO QUICKSAVE THE GAME NAMED" }, + { { 7528, 9408, 9408 }, "DO YOU WANT TO QUICKLOAD THE GAME NAMED" }, + { { 7392, 9448, 9448 }, "M_SKL00" }, + { { 7400, 9456, 9456 }, "FONTA_S" }, + { { 7408, 9464, 9464 }, "FONTB_S" }, + { { 7568, 9472, 9472 }, "?" }, + { { 7572, 9476, 9476 }, "M_SLCTR1" }, + { { 7584, 9488, 9488 }, "M_SLCTR2" }, + { { 7596, 9500, 9500 }, "M_HTIC" }, + { { 7604, 9508, 9508 }, "c:\\heretic.cd\\hticsav%d.hsg" }, + { { 7632, 9536, 9536 }, "hticsav%d.hsg" }, + { { 7652, 9556, 9556 }, "M_FSLOT" }, + { { 7660, 9564, 9564 }, "ON" }, + { { 7664, 9568, 9568 }, "OFF" }, + { { 0, 9572, 9572 }, "YOU CAN'T START A NEW GAME IN NETPLAY!" }, + { { 0, 9612, 9612 }, "YOU CAN'T LOAD A GAME IN NETPLAY!" }, + { { 7668, 9648, 9648 }, "MESSAGES ON" }, + { { 7680, 9660, 9660 }, "MESSAGES OFF" }, + { { 7748, 9676, 9676 }, "ONLY AVAILABLE IN THE REGISTERED VERSION" }, + { { 7792, 9720, 9720 }, "PLAYPAL" }, + { { 7800, 9728, 9728 }, "QUICKSAVING...." }, + { { 7816, 9744, 9744 }, "QUICKLOADING...." }, + { { 7836, 9764, 9764 }, "CHOOSE A QUICKSAVE SLOT" }, + { { 7860, 9788, 9788 }, "CHOOSE A QUICKLOAD SLOT" }, + { { 0, 9812, 9812 }, "TITLE" }, + { { 7892, 9820, 9820 }, "M_SLDLT" }, + { { 7900, 9828, 9828 }, "M_SLDMD1" }, + { { 7912, 9840, 9840 }, "M_SLDMD2" }, + { { 7924, 9852, 9852 }, "M_SLDRT" }, + { { 7932, 9860, 9860 }, "M_SLDKB" }, + { { 9016, 10944, 10944 }, "SCREEN SHOT" }, + { { 9028, 10956, 10956 }, "YOU NEED A BLUE KEY TO OPEN THIS DOOR" }, + { { 9068, 10996, 10996 }, "YOU NEED A YELLOW KEY TO OPEN THIS DOOR" }, + { { 9108, 11036, 11036 }, "YOU NEED A GREEN KEY TO OPEN THIS DOOR" }, + { { 9244, 11172, 11172 }, "CRYSTAL VIAL" }, + { { 9260, 11188, 11188 }, "SILVER SHIELD" }, + { { 9276, 11204, 11204 }, "ENCHANTED SHIELD" }, + { { 9296, 11224, 11224 }, "BAG OF HOLDING" }, + { { 9312, 11240, 11240 }, "MAP SCROLL" }, + { { 9324, 11252, 11252 }, "BLUE KEY" }, + { { 9336, 11264, 11264 }, "YELLOW KEY" }, + { { 9348, 11276, 11276 }, "GREEN KEY" }, + { { 9360, 11288, 11288 }, "QUARTZ FLASK" }, + { { 9376, 11304, 11304 }, "WINGS OF WRATH" }, + { { 9392, 11320, 11320 }, "RING OF INVINCIBILITY" }, + { { 9416, 11344, 11344 }, "TOME OF POWER" }, + { { 9432, 11360, 11360 }, "SHADOWSPHERE" }, + { { 9448, 11376, 11376 }, "MORPH OVUM" }, + { { 9460, 11388, 11388 }, "MYSTIC URN" }, + { { 9472, 11400, 11400 }, "TORCH" }, + { { 9480, 11408, 11408 }, "TIME BOMB OF THE ANCIENTS" }, + { { 9508, 11436, 11436 }, "CHAOS DEVICE" }, + { { 9524, 11452, 11452 }, "WAND CRYSTAL" }, + { { 9540, 11468, 11468 }, "CRYSTAL GEODE" }, + { { 9556, 11484, 11484 }, "MACE SPHERES" }, + { { 9572, 11500, 11500 }, "PILE OF MACE SPHERES" }, + { { 9596, 11524, 11524 }, "ETHEREAL ARROWS" }, + { { 9612, 11540, 11540 }, "QUIVER OF ETHEREAL ARROWS" }, + { { 9640, 11568, 11568 }, "CLAW ORB" }, + { { 9652, 11580, 11580 }, "ENERGY ORB" }, + { { 9664, 11592, 11592 }, "LESSER RUNES" }, + { { 9680, 11608, 11608 }, "GREATER RUNES" }, + { { 9696, 11624, 11624 }, "FLAME ORB" }, + { { 9708, 11636, 11636 }, "INFERNO ORB" }, + { { 9720, 11648, 11648 }, "FIREMACE" }, + { { 9732, 11660, 11660 }, "ETHEREAL CROSSBOW" }, + { { 9752, 11680, 11680 }, "DRAGON CLAW" }, + { { 9764, 11692, 11692 }, "HELLSTAFF" }, + { { 9776, 11704, 11704 }, "PHOENIX ROD" }, + { { 9788, 11716, 11716 }, "GAUNTLETS OF THE NECROMANCER" }, + { { 10088, 12016, 12016 }, "FLTWAWA1" }, + { { 10100, 12028, 12028 }, "FLTFLWW1" }, + { { 10112, 12040, 12040 }, "FLTLAVA1" }, + { { 10124, 12052, 12052 }, "FLATHUH1" }, + { { 10136, 12064, 12064 }, "FLTSLUD1" }, + { { 10148, 12076, 12076 }, "END" }, + { { 10236, 12164, 12164 }, "texture2" }, + { { 10444, 12372, 12372 }, "PLAYPAL" }, + { { 10596, 12488, 12488 }, "PNAMES" }, + { { 10604, 12496, 12496 }, "TEXTURE1" }, + { { 10616, 12508, 12508 }, "TEXTURE2" }, + { { 10628, 12520, 12520 }, "S_END" }, + { { 10636, 12528, 12528 }, "S_START" }, + { { 10728, 12620, 12620 }, "F_START" }, + { { 10736, 12628, 12628 }, "F_END" }, + { { 10744, 12636, 12636 }, "COLORMAP" }, + { { 10756, 12648, 12648 }, "\nR_InitTextures " }, + { { 10776, 12668, 12668 }, "R_InitFlats\n" }, + { { 10792, 12684, 12684 }, "R_InitSpriteLumps " }, + { { 10948, 12772, 12772 }, "TINTTAB" }, + { { 10984, 12780, 12780 }, "FLOOR04" }, + { { 10992, 12788, 12788 }, "FLAT513" }, + { { 11000, 12796, 12796 }, "bordt" }, + { { 11008, 12804, 12804 }, "bordb" }, + { { 11016, 12812, 12812 }, "bordl" }, + { { 11024, 12820, 12820 }, "bordr" }, + { { 11032, 12828, 12828 }, "bordtl" }, + { { 11040, 12836, 12836 }, "bordtr" }, + { { 11048, 12844, 12844 }, "bordbr" }, + { { 11056, 12852, 12852 }, "bordbl" }, + { { 11064, 12860, 12860 }, "R_InitData " }, + { { 11076, 12872, 12872 }, "R_InitPointToAngle\n" }, + { { 11096, 12892, 12892 }, "R_InitTables " }, + { { 11112, 12908, 12908 }, "R_InitPlanes\n" }, + { { 11128, 12924, 12924 }, "R_InitLightTables " }, + { { 11148, 12944, 12944 }, "R_InitSkyMap\n" }, + { { 11164, 12960, 12960 }, "F_SKY1" }, + { { 12120, 13484, 13484 }, "LTFACE" }, + { { 12128, 13492, 13492 }, "RTFACE" }, + { { 12136, 13500, 13500 }, "BARBACK" }, + { { 12144, 13508, 13508 }, "INVBAR" }, + { { 12152, 13516, 13516 }, "CHAIN" }, + { { 12160, 13524, 13524 }, "STATBAR" }, + { { 12168, 13532, 13532 }, "LIFEBAR" }, + { { 12176, 13540, 13540 }, "LIFEGEM2" }, + { { 12188, 13552, 13552 }, "LIFEGEM0" }, + { { 12200, 13564, 13564 }, "LTFCTOP" }, + { { 12208, 13572, 13572 }, "RTFCTOP" }, + { { 12224, 13580, 13580 }, "SELECTBOX" }, + { { 12236, 13592, 13592 }, "INVGEML1" }, + { { 12248, 13604, 13604 }, "INVGEML2" }, + { { 12260, 13616, 13616 }, "INVGEMR1" }, + { { 12272, 13628, 13628 }, "INVGEMR2" }, + { { 12284, 13640, 13640 }, "BLACKSQ" }, + { { 12292, 13648, 13648 }, "ARMCLEAR" }, + { { 12304, 13660, 13660 }, "CHAINBACK" }, + { { 12316, 13672, 13672 }, "IN0" }, + { { 12320, 13676, 13676 }, "NEGNUM" }, + { { 12328, 13684, 13684 }, "FONTB16" }, + { { 12336, 13692, 13692 }, "SMALLIN0" }, + { { 12348, 13704, 13704 }, "PLAYPAL" }, + { { 12356, 13712, 13712 }, "SPINBK0" }, + { { 12364, 13720, 13720 }, "SPFLY0" }, + { { 12372, 13728, 13728 }, "LAME" }, + { { 12380, 13736, 13736 }, "*** SOUND DEBUG INFO ***" }, + { { 12408, 13764, 13764 }, "NAME" }, + { { 12416, 13772, 13772 }, "MO.T" }, + { { 12424, 13780, 13780 }, "MO.X" }, + { { 12432, 13788, 13788 }, "MO.Y" }, + { { 12440, 13796, 13796 }, "ID" }, + { { 12444, 13800, 13800 }, "PRI" }, + { { 12448, 13804, 13804 }, "DIST" }, + { { 12456, 13812, 13812 }, "------" }, + { { 12464, 13820, 13820 }, "%s" }, + { { 12468, 13824, 13824 }, "%d" }, + { { 12472, 13828, 13828 }, "GOD1" }, + { { 12480, 13836, 13836 }, "GOD2" }, + { { 12488, 13844, 13844 }, "useartia" }, + { { 12500, 13856, 13856 }, "ykeyicon" }, + { { 12512, 13868, 13868 }, "gkeyicon" }, + { { 12524, 13880, 13880 }, "bkeyicon" }, + { { 12216, 13892, 13892 }, "ARTIBOX" }, + { { 12536, 13900, 13900 }, "GOD MODE ON" }, + { { 12548, 13912, 13912 }, "GOD MODE OFF" }, + { { 12564, 13928, 13928 }, "NO CLIPPING ON" }, + { { 12580, 13944, 13944 }, "NO CLIPPING OFF" }, + { { 12596, 13960, 13960 }, "ALL WEAPONS" }, + { { 12608, 13972, 13972 }, "POWER OFF" }, + { { 12620, 13984, 13984 }, "POWER ON" }, + { { 12632, 13996, 13996 }, "FULL HEALTH" }, + { { 12644, 14008, 14008 }, "ALL KEYS" }, + { { 12656, 14020, 14020 }, "SOUND DEBUG ON" }, + { { 12672, 14036, 14036 }, "SOUND DEBUG OFF" }, + { { 12688, 14052, 14052 }, "TICKER ON" }, + { { 12700, 14064, 14064 }, "TICKER OFF" }, + { { 12712, 14076, 14076 }, "CHOOSE AN ARTIFACT ( A - J )" }, + { { 12744, 14108, 14108 }, "HOW MANY ( 1 - 9 )" }, + { { 12764, 14128, 14128 }, "YOU GOT IT" }, + { { 12776, 14140, 14140 }, "BAD INPUT" }, + { { 12788, 14152, 14152 }, "LEVEL WARP" }, + { { 12800, 14164, 14164 }, "CHICKEN OFF" }, + { { 12812, 14176, 14176 }, "CHICKEN ON" }, + { { 12824, 14188, 14188 }, "MASSACRE" }, + { { 12836, 14200, 14200 }, "CHEATER - YOU DON'T DESERVE WEAPONS" }, + { { 12872, 14236, 14236 }, "TRYING TO CHEAT, EH? NOW YOU DIE!" }, +}; + +// String offsets that are valid but we don't support. + +static const int unsupported_strings_1_0[] = +{ + 0, 4, 64, 104, 160, 200, 220, 236, + 244, 252, 272, 288, 296, 316, 332, 372, + 436, 500, 504, 536, 544, 560, 576, 584, + 592, 612, 640, 664, 708, 712, 744, 764, + 808, 820, 828, 840, 876, 884, 908, 952, + 992, 1028, 1036, 1048, 1088, 1128, 1160, 1192, + 1212, 1912, 2044, 2056, 2068, 2128, 2140, 2168, + 2184, 2196, 2212, 2228, 2240, 2252, 2260, 2264, + 2284, 2292, 2296, 2300, 2328, 2340, 2352, 2364, + 2372, 2384, 2388, 2404, 2428, 2436, 2444, 2464, + 2496, 2508, 2520, 2552, 2564, 2572, 2584, 3120, + 3128, 3140, 3184, 3220, 3248, 3252, 3256, 3280, + 3304, 3320, 3352, 3380, 3400, 3432, 3464, 3548, + 3600, 3624, 3664, 3696, 3812, 3872, 3932, 3940, + 3976, 3996, 6872, 6896, 7648, 7696, 7940, 7964, + 7968, 7992, 8020, 8028, 8052, 8056, 8076, 8088, + 8104, 8116, 8128, 8136, 8148, 8164, 8180, 8192, + 8204, 8220, 8232, 8248, 8264, 8276, 8292, 8308, + 8320, 8328, 8340, 8352, 8364, 8376, 8392, 8408, + 8424, 8436, 8448, 8460, 8472, 8488, 8504, 8520, + 8536, 8548, 8560, 8572, 8584, 8596, 8608, 8612, + 8624, 8648, 8660, 8668, 8680, 8708, 8720, 8728, + 8740, 8752, 8764, 8788, 8800, 8812, 8824, 8848, + 8860, 8864, 8868, 8876, 8888, 8896, 8916, 8944, + 8948, 8960, 8964, 8968, 8980, 9148, 9172, 9212, + 9216, 9220, 9820, 9860, 9892, 9940, 9972, 10012, + 10036, 10040, 10052, 10080, 10152, 10192, 10248, 10284, + 10320, 10360, 10392, 10452, 10488, 10508, 10556, 10644, + 10684, 10812, 10844, 10880, 10912, 10956, 11172, 11200, + 11232, 11272, 11312, 11348, 11380, 11404, 11436, 11492, + 11548, 11616, 11684, 11748, 11792, 11840, 11896, 11936, + 11980, 12028, 12072, 12908, 12924, 12956, 12960, 12968, + 12976, 13020, 13048, 13076, 13104, 13136, 13168, 13196, + 13240, 13272, 13292, 13296, 13308, 13312, 13320, 13324, + 13364, 13408, 13460, 13492, 13516, 13560, 13612, 13664, + 13700, 13744, 13796, 13848, 13884, 13940, 13996, 14040, + 14084, 14140, 14148, 14156, 14164, 14184, 14192, 14204, + 14208, 14212, 14256, 14272, 14284, 14296, 14300, 14312, + 14320, 14324, 14348, 14356, 14360, 14372, 14380, 14392, + 14432, 14440, 14444, 14472, 14496, 14516, 14536, 14548, + 14560, 14572, 14580, 14588, 14596, 14604, 14612, 14620, + 14636, 14660, 14704, 14740, 14748, 14756, 14760, 14768, + -1, +}; + +static const int unsupported_strings_1_2[] = +{ + 0, 4, 64, 104, 160, 200, 220, 236, + 244, 252, 272, 288, 296, 316, 332, 372, + 436, 500, 504, 536, 544, 560, 576, 584, + 592, 612, 640, 664, 708, 712, 744, 756, + 776, 820, 832, 840, 852, 888, 896, 920, + 964, 1004, 1040, 1048, 1060, 1100, 1140, 1172, + 1204, 1224, 2312, 2436, 2448, 2464, 2480, 2492, + 2512, 2524, 2536, 2596, 2608, 2636, 2652, 2656, + 2676, 2684, 2688, 2720, 2732, 2744, 2752, 2764, + 2772, 2776, 2792, 2816, 2824, 2832, 2852, 2884, + 2896, 2908, 2940, 2952, 2960, 2972, 3520, 3528, + 3540, 3584, 3620, 3648, 3652, 3656, 3680, 3704, + 3720, 3776, 3804, 3824, 3856, 3888, 4020, 4044, + 4084, 4116, 4156, 4272, 4288, 4296, 4332, 4352, + 4428, 4432, 8740, 8764, 9552, 9868, 9888, 9900, + 9916, 9928, 9940, 9948, 9960, 9976, 9992, 10004, + 10016, 10032, 10044, 10060, 10076, 10088, 10104, 10120, + 10132, 10140, 10152, 10164, 10176, 10188, 10204, 10220, + 10236, 10248, 10260, 10272, 10284, 10300, 10316, 10332, + 10348, 10360, 10372, 10384, 10396, 10408, 10420, 10424, + 10436, 10460, 10472, 10480, 10492, 10520, 10532, 10540, + 10552, 10564, 10576, 10600, 10612, 10624, 10636, 10660, + 10672, 10676, 10700, 10704, 10728, 10756, 10764, 10788, + 10792, 10796, 10804, 10816, 10824, 10844, 10872, 10876, + 10888, 10892, 10896, 10908, 11076, 11100, 11140, 11144, + 11148, 11748, 11788, 11820, 11868, 11900, 11940, 11964, + 11968, 11980, 12008, 12080, 12120, 12176, 12212, 12248, + 12288, 12320, 12380, 12400, 12448, 12536, 12576, 12704, + 12736, 12968, 13000, 13024, 13080, 13136, 13204, 13272, + 13336, 13380, 13428, 14272, 14288, 14320, 14324, 14332, + 14340, 14384, 14412, 14440, 14468, 14500, 14532, 14560, + 14604, 14636, 14656, 14696, 14740, 14792, 14824, 14848, + 14892, 14944, 14996, 15032, 15076, 15128, 15180, 15216, + 15272, 15328, 15372, 15416, 15472, 15480, 15488, 15496, + 15516, 15524, 15536, 15540, 15544, 15588, 15604, 15616, + 15628, 15632, 15644, 15652, 15656, 15680, 15688, 15692, + 15704, 15712, 15724, 15764, 15772, 15776, 15804, 15828, + 15848, 15868, 15880, 15892, 15904, 15912, 15920, 15928, + 15936, -1, +}; + +static const int unsupported_strings_1_3[] = +{ + 0, 4, 64, 104, 160, 200, 220, 236, + 244, 252, 272, 288, 296, 316, 332, 372, + 436, 500, 504, 536, 544, 560, 576, 584, + 592, 612, 640, 664, 708, 712, 744, 756, + 776, 820, 832, 840, 852, 888, 896, 920, + 964, 1004, 1040, 1048, 1060, 1100, 1140, 1172, + 1204, 1224, 2312, 2436, 2448, 2464, 2480, 2492, + 2512, 2524, 2536, 2596, 2608, 2636, 2652, 2656, + 2676, 2684, 2688, 2720, 2732, 2744, 2752, 2764, + 2772, 2776, 2792, 2816, 2824, 2832, 2852, 2884, + 2896, 2908, 2940, 2952, 2960, 2972, 3520, 3528, + 3540, 3584, 3620, 3648, 3652, 3656, 3680, 3704, + 3720, 3776, 3804, 3824, 3856, 3888, 4020, 4044, + 4084, 4116, 4156, 4272, 4288, 4296, 4332, 4352, + 4428, 4432, 8740, 8764, 9552, 9868, 9888, 9900, + 9916, 9928, 9940, 9948, 9960, 9976, 9992, 10004, + 10016, 10032, 10044, 10060, 10076, 10088, 10104, 10120, + 10132, 10140, 10152, 10164, 10176, 10188, 10204, 10220, + 10236, 10248, 10260, 10272, 10284, 10300, 10316, 10332, + 10348, 10360, 10372, 10384, 10396, 10408, 10420, 10424, + 10436, 10460, 10472, 10480, 10492, 10520, 10532, 10540, + 10552, 10564, 10576, 10600, 10612, 10624, 10636, 10660, + 10672, 10676, 10700, 10704, 10728, 10756, 10764, 10788, + 10792, 10796, 10804, 10816, 10824, 10844, 10872, 10876, + 10888, 10892, 10896, 10908, 11076, 11100, 11140, 11144, + 11148, 11748, 11788, 11820, 11868, 11900, 11940, 11964, + 11968, 11980, 12008, 12080, 12120, 12176, 12212, 12248, + 12288, 12320, 12380, 12400, 12448, 12536, 12576, 12704, + 12736, 12968, 13000, 13024, 13080, 13136, 13204, 13272, + 13336, 13380, 13428, 14272, 14288, 14320, 14324, 14332, + 14340, 14384, 14412, 14440, 14468, 14500, 14532, 14560, + 14604, 14636, 14656, 14696, 14740, 14792, 14824, 14848, + 14892, 14944, 14996, 15032, 15076, 15128, 15180, 15216, + 15272, 15328, 15372, 15416, 15472, 15480, 15488, 15496, + 15516, 15524, 15536, 15540, 15544, 15588, 15604, 15616, + 15628, 15632, 15644, 15652, 15656, 15680, 15688, 15692, + 15704, 15712, 15724, 15764, 15772, 15776, 15804, 15828, + 15848, 15868, 15880, 15892, 15904, 15912, 15920, 15928, + 15936, -1, +}; + +static const int *unsupported_strings[] = +{ + unsupported_strings_1_0, + unsupported_strings_1_2, + unsupported_strings_1_3, +}; + +static boolean StringIsUnsupported(unsigned int offset) +{ + const int *string_list; + int i; + + string_list = unsupported_strings[deh_hhe_version]; + + for (i=0; string_list[i] >= 0; ++i) + { + if ((unsigned int) string_list[i] == offset) + { + return true; + } + } + + return false; +} + +static boolean GetStringByOffset(unsigned int offset, char **result) +{ + int i; + + for (i=0; i<arrlen(strings); ++i) + { + if (strings[i].offsets[deh_hhe_version] == offset) + { + *result = strings[i].string; + return true; + } + } + + return false; +} + +// Given a string length, find the maximum length of a +// string that can replace it. + +static int MaxStringLength(int len) +{ + // Enough bytes for the string and the NUL terminator + + len += 1; + + // All strings in doom.exe are on 4-byte boundaries, so we may be able + // to support a slightly longer string. + // Extend up to the next 4-byte boundary + + len += (4 - (len % 4)) % 4; + + // Less one for the NUL terminator. + + return len - 1; +} + +// If a string offset does not match any string, it may be because +// we are running in the wrong version mode, and the patch was generated +// for a different Heretic version. Search the lookup tables to find +// versiosn that match. + +static void SuggestOtherVersions(unsigned int offset) +{ + const int *string_list; + unsigned int i; + unsigned int v; + + // Check main string table. + + for (i=0; i<arrlen(strings); ++i) + { + for (v=0; v<deh_hhe_num_versions; ++v) + { + if (strings[i].offsets[v] == offset) + { + DEH_SuggestHereticVersion(v); + } + } + } + + // Check unsupported string tables. + + for (v=0; v<deh_hhe_num_versions; ++v) + { + string_list = unsupported_strings[v]; + + for (i=0; string_list[i] >= 0; ++i) + { + if (string_list[i] == offset) + { + DEH_SuggestHereticVersion(v); + } + } + } +} + +static void *DEH_TextStart(deh_context_t *context, char *line) +{ + char *repl_text; + char *orig_text; + int orig_offset, repl_len; + int i; + + if (sscanf(line, "Text %i %i", &orig_offset, &repl_len) != 2) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + repl_text = Z_Malloc(repl_len + 1, PU_STATIC, NULL); + + // read in the "to" text + + for (i=0; i<repl_len; ++i) + { + int c; + + c = DEH_GetChar(context); + + repl_text[i] = c; + } + repl_text[repl_len] = '\0'; + + // We don't support all strings, but at least recognise them: + + if (StringIsUnsupported(orig_offset)) + { + DEH_Warning(context, "Unsupported string replacement: %i", orig_offset); + } + + // Find the string to replace: + + else if (!GetStringByOffset(orig_offset, &orig_text)) + { + SuggestOtherVersions(orig_offset); + DEH_Error(context, "Unknown string offset: %i", orig_offset); + } + + // Only allow string replacements that are possible in Vanilla Doom. + // Chocolate Doom is unforgiving! + + else if (!deh_allow_long_strings + && repl_len > MaxStringLength(strlen(orig_text))) + { + DEH_Error(context, "Replacement string is longer than the maximum " + "possible in heretic.exe"); + } + else + { + // Success. + + DEH_AddStringReplacement(orig_text, repl_text); + + return NULL; + } + + // Failure. + + Z_Free(repl_text); + + return NULL; +} + +static void DEH_TextParseLine(deh_context_t *context, char *line, void *tag) +{ + // not used +} + +deh_section_t deh_section_heretic_text = +{ + "Text", + NULL, + DEH_TextStart, + DEH_TextParseLine, + NULL, + NULL, +}; + diff --git a/src/heretic/deh_htic.c b/src/heretic/deh_htic.c new file mode 100644 index 00000000..440fde96 --- /dev/null +++ b/src/heretic/deh_htic.c @@ -0,0 +1,186 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Top-level dehacked definitions for Heretic dehacked (HHE). +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "deh_defs.h" +#include "deh_main.h" +#include "deh_htic.h" +#include "info.h" +#include "m_argv.h" + +char *deh_signatures[] = +{ + "Patch File for HHE v1.0", + "Patch File for HHE v1.1", + NULL +}; + +static char *hhe_versions[] = +{ + "1.0", "1.2", "1.3" +}; + +// Version number for patches. + +deh_hhe_version_t deh_hhe_version = deh_hhe_1_0; + +// deh_ammo.c: +extern deh_section_t deh_section_ammo; +// deh_frame.c: +extern deh_section_t deh_section_frame; +// deh_ptr.c: +extern deh_section_t deh_section_pointer; +// deh_sound.c +extern deh_section_t deh_section_sound; +// deh_htext.c: +extern deh_section_t deh_section_heretic_text; +// deh_thing.c: +extern deh_section_t deh_section_thing; +// deh_weapon.c: +extern deh_section_t deh_section_weapon; + +// +// List of section types: +// + +deh_section_t *deh_section_types[] = +{ + &deh_section_ammo, + &deh_section_frame, +// &deh_section_pointer, TODO + &deh_section_sound, + &deh_section_heretic_text, + &deh_section_thing, + &deh_section_weapon, + NULL +}; + +static void SetHHEVersionByName(char *name) +{ + int i; + + for (i=0; i<arrlen(hhe_versions); ++i) + { + if (!strcmp(hhe_versions[i], name)) + { + deh_hhe_version = i; + return; + } + } + + fprintf(stderr, "Unknown Heretic version: %s\n", name); + fprintf(stderr, "Valid versions:\n"); + + for (i=0; i<arrlen(hhe_versions); ++i) + { + fprintf(stderr, "\t%s\n", hhe_versions[i]); + } +} + +// Initialize Heretic(HHE)-specific dehacked bits. + +void DEH_HereticInit(void) +{ + int i; + + //! + // @arg <version> + // + // Select the Heretic version number that was used to generate the + // HHE patch to be loaded. Patches for each of the Vanilla + // Heretic versions (1.0, 1.2, 1.3) can be loaded, but the correct + // version number must be specified. + + i = M_CheckParm("-hhever"); + + if (i > 0) + { + SetHHEVersionByName(myargv[i + 1]); + } + + // For v1.0 patches, we must apply a slight change to the states[] + // table. The table was changed between 1.0 and 1.3 to add two extra + // frames to the player "burning death" animation. + // + // If we are using a v1.0 patch, we must change the table to cut + // these out again. + + if (deh_hhe_version < deh_hhe_1_2) + { + states[S_PLAY_FDTH18].nextstate = S_NULL; + } +} + +int DEH_MapHereticFrameNumber(int frame) +{ + if (deh_hhe_version < deh_hhe_1_2) + { + // Between Heretic 1.0 and 1.2, two new frames + // were added to the "states" table, to extend the "flame death" + // animation displayed when the player is killed by fire. Therefore, + // we must map Heretic 1.0 frame numbers to corresponding indexes + // for our state table. + + if (frame >= S_PLAY_FDTH19) + { + frame = (frame - S_PLAY_FDTH19) + S_BLOODYSKULL1; + } + } + else + { + // After Heretic 1.2, three unused frames were removed from the + // states table, unused phoenix rod frames. Our state table includes + // these missing states for backwards compatibility. We must therefore + // adjust frame numbers for v1.2/v1.3 to corresponding indexes for + // our state table. + + if (frame >= S_PHOENIXFXIX_1) + { + frame = (frame - S_PHOENIXFXIX_1) + S_PHOENIXPUFF1; + } + } + + return frame; +} + +void DEH_SuggestHereticVersion(deh_hhe_version_t version) +{ + fprintf(stderr, + "\n" + "This patch may be for version %s. You are currently running in\n" + "Heretic %s mode. For %s mode, this mode, add this to your command line:\n" + "\n" + "\t-hhever %s\n" + "\n", + hhe_versions[version], + hhe_versions[deh_hhe_version], + hhe_versions[version], + hhe_versions[version]); +} + diff --git a/src/heretic/deh_htic.h b/src/heretic/deh_htic.h new file mode 100644 index 00000000..7855da8c --- /dev/null +++ b/src/heretic/deh_htic.h @@ -0,0 +1,60 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2010 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Common header for Heretic dehacked (HHE) support. +// +//----------------------------------------------------------------------------- + +#ifndef DEH_HTIC_H +#define DEH_HTIC_H + +#include "info.h" + +// HHE executable version. Loading HHE patches is (unfortunately) +// dependent on the version of the Heretic executable used to make them. + +typedef enum +{ + deh_hhe_1_0, + deh_hhe_1_2, + deh_hhe_1_3, + deh_hhe_num_versions +} deh_hhe_version_t; + +// HHE doesn't know about the last two states in the state table, so +// these are considered invalid. + +#define DEH_HERETIC_NUMSTATES (NUMSTATES - 2) + +// It also doesn't know about the last two things in the mobjinfo table +// (which correspond to the states above) + +#define DEH_HERETIC_NUMMOBJTYPES (NUMMOBJTYPES - 2) + +void DEH_HereticInit(void); +int DEH_MapHereticFrameNumber(int frame); +void DEH_SuggestHereticVersion(deh_hhe_version_t version); + +extern deh_hhe_version_t deh_hhe_version; + +#endif /* #ifndef DEH_HTIC_H */ + diff --git a/src/heretic/deh_sound.c b/src/heretic/deh_sound.c new file mode 100644 index 00000000..d1f266dd --- /dev/null +++ b/src/heretic/deh_sound.c @@ -0,0 +1,118 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses "Sound" sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> + +#include "doomfeatures.h" +#include "doomtype.h" +#include "deh_defs.h" +#include "deh_main.h" +#include "deh_mapping.h" + +#include "doomdef.h" +#include "i_sound.h" + +#include "sounds.h" + +DEH_BEGIN_MAPPING(sound_mapping, sfxinfo_t) + DEH_MAPPING_STRING("Name", name) + DEH_UNSUPPORTED_MAPPING("Special") + DEH_MAPPING("Value", priority) + DEH_MAPPING("Unknown 1", usefulness) + DEH_UNSUPPORTED_MAPPING("Unknown 2") + DEH_UNSUPPORTED_MAPPING("Unknown 3") + DEH_MAPPING("One/Two", numchannels) +DEH_END_MAPPING + +static void *DEH_SoundStart(deh_context_t *context, char *line) +{ + int sound_number = 0; + + if (sscanf(line, "Sound %i", &sound_number) != 1) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + if (sound_number < 0 || sound_number >= NUMSFX) + { + DEH_Warning(context, "Invalid sound number: %i", sound_number); + return NULL; + } + + if (sound_number >= DEH_VANILLA_NUMSFX) + { + DEH_Warning(context, "Attempt to modify SFX %i. This will cause " + "problems in Vanilla dehacked.", sound_number); + } + + return &S_sfx[sound_number]; +} + +static void DEH_SoundParseLine(deh_context_t *context, char *line, void *tag) +{ + sfxinfo_t *sfx; + char *variable_name, *value; + + if (tag == NULL) + return; + + sfx = (sfxinfo_t *) tag; + + // Parse the assignment + + if (!DEH_ParseAssignment(line, &variable_name, &value)) + { + // Failed to parse + DEH_Warning(context, "Failed to parse assignment"); + return; + } + + // Set the field value: + + if (!strcasecmp(variable_name, "Name")) + { + DEH_SetStringMapping(context, &sound_mapping, sfx, + variable_name, value); + } + else + { + DEH_SetMapping(context, &sound_mapping, sfx, + variable_name, atoi(value)); + } +} + +deh_section_t deh_section_sound = +{ + "Sound", + NULL, + DEH_SoundStart, + DEH_SoundParseLine, + NULL, + NULL, +}; + diff --git a/src/heretic/deh_thing.c b/src/heretic/deh_thing.c new file mode 100644 index 00000000..ffededf2 --- /dev/null +++ b/src/heretic/deh_thing.c @@ -0,0 +1,150 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses "Thing" sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> + +#include "doomtype.h" +#include "m_misc.h" + +#include "deh_defs.h" +#include "deh_main.h" +#include "deh_mapping.h" +#include "deh_htic.h" + +#include "info.h" + +DEH_BEGIN_MAPPING(thing_mapping, mobjinfo_t) + DEH_MAPPING("ID #", doomednum) + DEH_MAPPING("Initial frame", spawnstate) + DEH_MAPPING("Hit points", spawnhealth) + DEH_MAPPING("First moving frame", seestate) + DEH_MAPPING("Alert sound", seesound) + DEH_MAPPING("Reaction time", reactiontime) + DEH_MAPPING("Attack sound", attacksound) + DEH_MAPPING("Injury frame", painstate) + DEH_MAPPING("Pain chance", painchance) + DEH_MAPPING("Pain sound", painsound) + DEH_MAPPING("Close attack frame", meleestate) + DEH_MAPPING("Far attack frame", missilestate) + DEH_MAPPING("Burning frame", crashstate) + DEH_MAPPING("Death frame", deathstate) + DEH_MAPPING("Exploding frame", xdeathstate) + DEH_MAPPING("Death sound", deathsound) + DEH_MAPPING("Speed", speed) + DEH_MAPPING("Width", radius) + DEH_MAPPING("Height", height) + DEH_MAPPING("Mass", mass) + DEH_MAPPING("Missile damage", damage) + DEH_MAPPING("Action sound", activesound) + DEH_MAPPING("Bits 1", flags) + DEH_MAPPING("Bits 2", flags2) +DEH_END_MAPPING + +static void *DEH_ThingStart(deh_context_t *context, char *line) +{ + int thing_number = 0; + mobjinfo_t *mobj; + + if (sscanf(line, "Thing %i", &thing_number) != 1) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + // HHE thing numbers are indexed from 1 + --thing_number; + + if (thing_number < 0 || thing_number >= DEH_HERETIC_NUMMOBJTYPES) + { + DEH_Warning(context, "Invalid thing number: %i", thing_number); + return NULL; + } + + mobj = &mobjinfo[thing_number]; + + return mobj; +} + +static void DEH_ThingParseLine(deh_context_t *context, char *line, void *tag) +{ + mobjinfo_t *mobj; + char *variable_name, *value; + int ivalue; + + if (tag == NULL) + return; + + mobj = (mobjinfo_t *) tag; + + // Parse the assignment + + if (!DEH_ParseAssignment(line, &variable_name, &value)) + { + // Failed to parse + + DEH_Warning(context, "Failed to parse assignment"); + return; + } + + // all values are integers + + ivalue = atoi(value); + + // If the value to be set is a frame, the frame number must + // undergo transformation from a Heretic 1.0 index to a + // Heretic 1.3 index. + + if (M_StrCaseStr(variable_name, "frame") != NULL) + { + ivalue = DEH_MapHereticFrameNumber(ivalue); + } + + // Set the field value + + DEH_SetMapping(context, &thing_mapping, mobj, variable_name, ivalue); +} + +static void DEH_ThingMD5Sum(md5_context_t *context) +{ + int i; + + for (i=0; i<NUMMOBJTYPES; ++i) + { + DEH_StructMD5Sum(context, &thing_mapping, &mobjinfo[i]); + } +} + +deh_section_t deh_section_thing = +{ + "Thing", + NULL, + DEH_ThingStart, + DEH_ThingParseLine, + NULL, + DEH_ThingMD5Sum, +}; + diff --git a/src/heretic/deh_weapon.c b/src/heretic/deh_weapon.c new file mode 100644 index 00000000..28a90c68 --- /dev/null +++ b/src/heretic/deh_weapon.c @@ -0,0 +1,131 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2005 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// Parses "Weapon" sections in dehacked files +// +//----------------------------------------------------------------------------- + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "doomtype.h" +#include "m_misc.h" + +#include "doomdef.h" + +#include "deh_defs.h" +#include "deh_main.h" +#include "deh_mapping.h" +#include "deh_htic.h" + +DEH_BEGIN_MAPPING(weapon_mapping, weaponinfo_t) + DEH_MAPPING("Ammo type", ammo) + DEH_MAPPING("Deselect frame", upstate) + DEH_MAPPING("Select frame", downstate) + DEH_MAPPING("Bobbing frame", readystate) + DEH_MAPPING("Shooting frame", atkstate) + DEH_MAPPING("Firing frame", holdatkstate) + DEH_MAPPING("Unknown frame", flashstate) +DEH_END_MAPPING + +static void *DEH_WeaponStart(deh_context_t *context, char *line) +{ + int weapon_number = 0; + + if (sscanf(line, "Weapon %i", &weapon_number) != 1) + { + DEH_Warning(context, "Parse error on section start"); + return NULL; + } + + if (weapon_number < 0 || weapon_number >= NUMWEAPONS * 2) + { + DEH_Warning(context, "Invalid weapon number: %i", weapon_number); + return NULL; + } + + // Because of the tome of power, we have two levels of weapons: + + if (weapon_number < NUMWEAPONS) + { + return &wpnlev1info[weapon_number]; + } + else + { + return &wpnlev2info[weapon_number - NUMWEAPONS]; + } +} + +static void DEH_WeaponParseLine(deh_context_t *context, char *line, void *tag) +{ + char *variable_name, *value; + weaponinfo_t *weapon; + int ivalue; + + if (tag == NULL) + return; + + weapon = (weaponinfo_t *) tag; + + if (!DEH_ParseAssignment(line, &variable_name, &value)) + { + // Failed to parse + + DEH_Warning(context, "Failed to parse assignment"); + return; + } + + ivalue = atoi(value); + + // If this is a frame field, we need to map from Heretic 1.0 frame + // numbers to Heretic 1.3 frame numbers. + + if (M_StrCaseStr(variable_name, "frame") != NULL) + { + ivalue = DEH_MapHereticFrameNumber(ivalue); + } + + DEH_SetMapping(context, &weapon_mapping, weapon, variable_name, ivalue); +} + +static void DEH_WeaponMD5Sum(md5_context_t *context) +{ + int i; + + for (i=0; i<NUMWEAPONS ;++i) + { + DEH_StructMD5Sum(context, &weapon_mapping, &wpnlev1info[i]); + DEH_StructMD5Sum(context, &weapon_mapping, &wpnlev2info[i]); + } +} + +deh_section_t deh_section_weapon = +{ + "Weapon", + NULL, + DEH_WeaponStart, + DEH_WeaponParseLine, + NULL, + DEH_WeaponMD5Sum, +}; + diff --git a/src/heretic/doomdata.h b/src/heretic/doomdata.h new file mode 100644 index 00000000..ac84ec46 --- /dev/null +++ b/src/heretic/doomdata.h @@ -0,0 +1,200 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// DoomData.h + +// all external data is defined here +// most of the data is loaded into different structures at run time + +#ifndef __DOOMDATA__ +#define __DOOMDATA__ + +#include "doomtype.h" + +/* +=============================================================================== + + map level types + +=============================================================================== +*/ + +// lump order in a map wad +enum +{ + ML_LABEL, + ML_THINGS, + ML_LINEDEFS, + ML_SIDEDEFS, + ML_VERTEXES, + ML_SEGS, + ML_SSECTORS, + ML_NODES, + ML_SECTORS, + ML_REJECT, + ML_BLOCKMAP +}; + + +typedef struct +{ + short x, y; +} PACKEDATTR mapvertex_t; + +typedef struct +{ + short textureoffset; + short rowoffset; + char toptexture[8], bottomtexture[8], midtexture[8]; + short sector; // on viewer's side +} PACKEDATTR mapsidedef_t; + +typedef struct +{ + short v1, v2; + short flags; + short special, tag; + short sidenum[2]; // sidenum[1] will be -1 if one sided +} PACKEDATTR maplinedef_t; + +#define ML_BLOCKING 1 +#define ML_BLOCKMONSTERS 2 +#define ML_TWOSIDED 4 // backside will not be present at all + // if not two sided + +// 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 (windows) +#define ML_DONTPEGTOP 8 +#define ML_DONTPEGBOTTOM 16 + +#define ML_SECRET 32 // don't map as two sided: IT'S A SECRET! +#define ML_SOUNDBLOCK 64 // don't let sound cross two of these +#define ML_DONTDRAW 128 // don't draw on the automap +#define ML_MAPPED 256 // set if allready drawn in automap + + +typedef struct +{ + short floorheight, ceilingheight; + char floorpic[8], ceilingpic[8]; + short lightlevel; + short special, tag; +} PACKEDATTR mapsector_t; + +typedef struct +{ + short numsegs; + short firstseg; // segs are stored sequentially +} PACKEDATTR mapsubsector_t; + +typedef struct +{ + short v1, v2; + short angle; + short linedef, side; + short offset; +} PACKEDATTR mapseg_t; + +#define NF_SUBSECTOR 0x8000 +typedef struct +{ + short x, y, dx, dy; // partition line + short bbox[2][4]; // bounding box for each child + unsigned short children[2]; // if NF_SUBSECTOR its a subsector +} PACKEDATTR mapnode_t; + +typedef struct +{ + short x, y; + short angle; + short type; + short options; +} PACKEDATTR mapthing_t; + +#define MTF_EASY 1 +#define MTF_NORMAL 2 +#define MTF_HARD 4 +#define MTF_AMBUSH 8 + +/* +=============================================================================== + + texture definition + +=============================================================================== +*/ + +typedef struct +{ + short originx; + short originy; + short patch; + short stepdir; + short colormap; +} PACKEDATTR mappatch_t; + +typedef struct +{ + char name[8]; + boolean masked; + short width; + short height; + int obsolete; + short patchcount; + mappatch_t patches[1]; +} PACKEDATTR maptexture_t; + + +/* +=============================================================================== + + graphics + +=============================================================================== +*/ + +// a pic is an unmasked block of pixels +typedef struct +{ + byte width, height; + byte data; +} pic_t; + + + + +/* +=============================================================================== + + status + +=============================================================================== +*/ + + + + +#endif // __DOOMDATA__ diff --git a/src/heretic/doomdef.h b/src/heretic/doomdef.h new file mode 100644 index 00000000..aa1d0c0d --- /dev/null +++ b/src/heretic/doomdef.h @@ -0,0 +1,898 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// DoomDef.h + +#ifndef __DOOMDEF__ +#define __DOOMDEF__ +#include <stdio.h> +#include <string.h> +//haleyjd: removed WATCOMC +#include <limits.h> + +#define HERETIC_VERSION 130 +#define HERETIC_VERSION_TEXT "v1.3" + +// if rangecheck is undefined, most parameter validation debugging code +// will not be compiled +//#define RANGECHECK + +// all external data is defined here +#include "doomdata.h" + +// all important printed strings +#include "dstrings.h" + +// header generated by multigen utility +#include "info.h" + +// WAD file access +#include "w_wad.h" + +// fixed_t +#include "m_fixed.h" + +// angle_t +#include "tables.h" + +// events +#include "d_event.h" + +// gamemode/mission +#include "d_mode.h" + +// ticcmd_t +#include "d_ticcmd.h" + +#define SAVEGAMENAME "hticsav" +#define SAVEGAMENAMECD "c:\\heretic.cd\\hticsav" + +/* +=============================================================================== + + GLOBAL TYPES + +=============================================================================== +*/ + +#define NUMARTIFCTS 28 +#define MAXPLAYERS 4 + +#define BT_ATTACK 1 +#define BT_USE 2 +#define BT_CHANGE 4 // if true, the next 3 bits hold weapon num +#define BT_WEAPONMASK (8+16+32) +#define BT_WEAPONSHIFT 3 + +#define BT_SPECIAL 128 // game events, not really buttons +#define BTS_SAVEMASK (4+8+16) +#define BTS_SAVESHIFT 2 +#define BT_SPECIALMASK 3 +#define BTS_PAUSE 1 // pause the game +#define BTS_SAVEGAME 2 // save the game at each console +// savegame slot numbers occupy the second byte of buttons + +typedef enum +{ + GS_LEVEL, + GS_INTERMISSION, + GS_FINALE, + GS_DEMOSCREEN +} gamestate_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; + +typedef enum +{ + wipe_0, + wipe_1, + wipe_2, + wipe_3, + wipe_4, + NUMWIPES, + wipe_random +} wipe_t; + +/* +=============================================================================== + + MAPOBJ DATA + +=============================================================================== +*/ + +// think_t is a function pointer to a routine to handle an actor +typedef void (*think_t) (); + +typedef struct thinker_s +{ + struct thinker_s *prev, *next; + think_t function; +} thinker_t; + +struct player_s; + +typedef struct mobj_s +{ + thinker_t thinker; // thinker links + +// info for drawing + fixed_t x, y, z; + struct mobj_s *snext, *sprev; // links in sector (if needed) + angle_t angle; + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ord with FF_FULLBRIGHT + +// interaction info + struct mobj_s *bnext, *bprev; // links in blocks (if needed) + struct subsector_s *subsector; + fixed_t floorz, ceilingz; // closest together of contacted secs + fixed_t radius, height; // for movement checking + fixed_t momx, momy, momz; // momentums + + int validcount; // if == validcount, already checked + + mobjtype_t type; + mobjinfo_t *info; // &mobjinfo[mobj->type] + int tics; // state tic counter + state_t *state; + int damage; // For missiles + int flags; + int flags2; // Heretic flags + int special1; // Special info + int special2; // Special info + int health; + int movedir; // 0-7 + int movecount; // when 0, select a new dir + struct mobj_s *target; // thing being chased/attacked (or NULL) + // also the originator for missiles + int reactiontime; // if non 0, don't attack yet + // used by player to freeze a bit after + // teleporting + int threshold; // if >0, the target will be chased + // no matter what (even if shot) + struct player_s *player; // only valid if type == MT_PLAYER + int lastlook; // player number last looked for + + mapthing_t spawnpoint; // for nightmare respawn +} mobj_t; + +// each sector has a degenmobj_t in it's center for sound origin purposes +typedef struct +{ + thinker_t thinker; // not used for anything + fixed_t x, y, z; +} degenmobj_t; + +// +// frame flags +// +#define FF_FULLBRIGHT 0x8000 // flag in thing->frame +#define FF_FRAMEMASK 0x7fff + +// --- mobj.flags --- + +#define MF_SPECIAL 1 // call P_SpecialThing when touched +#define MF_SOLID 2 +#define MF_SHOOTABLE 4 +#define MF_NOSECTOR 8 // don't use the sector links + // (invisible but touchable) +#define MF_NOBLOCKMAP 16 // don't use the blocklinks + // (inert but displayable) +#define MF_AMBUSH 32 +#define MF_JUSTHIT 64 // try to attack right back +#define MF_JUSTATTACKED 128 // take at least one step before attacking +#define MF_SPAWNCEILING 256 // hang from ceiling instead of floor +#define MF_NOGRAVITY 512 // don't apply gravity every tic + +// movement flags +#define MF_DROPOFF 0x400 // allow jumps from high places +#define MF_PICKUP 0x800 // for players to pick up items +#define MF_NOCLIP 0x1000 // player cheat +#define MF_SLIDE 0x2000 // keep info about sliding along walls +#define MF_FLOAT 0x4000 // allow moves to any height, no gravity +#define MF_TELEPORT 0x8000 // don't cross lines or look at heights +#define MF_MISSILE 0x10000 // don't hit same species, explode on block + +#define MF_DROPPED 0x20000 // dropped by a demon, not level spawned +#define MF_SHADOW 0x40000 // use translucent draw (shadow demons / invis) +#define MF_NOBLOOD 0x80000 // don't bleed when shot (use puff) +#define MF_CORPSE 0x100000 // don't stop moving halfway off a step +#define MF_INFLOAT 0x200000 // floating to a height for a move, don't + // auto float to target's height + +#define MF_COUNTKILL 0x400000 // count towards intermission kill total +#define MF_COUNTITEM 0x800000 // count towards intermission item total + +#define MF_SKULLFLY 0x1000000 // skull in flight +#define MF_NOTDMATCH 0x2000000 // don't spawn in death match (key cards) + +#define MF_TRANSLATION 0xc000000 // if 0x4 0x8 or 0xc, use a translation +#define MF_TRANSSHIFT 26 // table for player colormaps + +// --- mobj.flags2 --- + +#define MF2_LOGRAV 0x00000001 // alternate gravity setting +#define MF2_WINDTHRUST 0x00000002 // gets pushed around by the wind + // specials +#define MF2_FLOORBOUNCE 0x00000004 // bounces off the floor +#define MF2_THRUGHOST 0x00000008 // missile will pass through ghosts +#define MF2_FLY 0x00000010 // fly mode is active +#define MF2_FOOTCLIP 0x00000020 // if feet are allowed to be clipped +#define MF2_SPAWNFLOAT 0x00000040 // spawn random float z +#define MF2_NOTELEPORT 0x00000080 // does not teleport +#define MF2_RIP 0x00000100 // missile rips through solid + // targets +#define MF2_PUSHABLE 0x00000200 // can be pushed by other moving + // mobjs +#define MF2_SLIDE 0x00000400 // slides against walls +#define MF2_ONMOBJ 0x00000800 // mobj is resting on top of another + // mobj +#define MF2_PASSMOBJ 0x00001000 // Enable z block checking. If on, + // this flag will allow the mobj to + // pass over/under other mobjs. +#define MF2_CANNOTPUSH 0x00002000 // cannot push other pushable mobjs +#define MF2_FEETARECLIPPED 0x00004000 // a mobj's feet are now being cut +#define MF2_BOSS 0x00008000 // mobj is a major boss +#define MF2_FIREDAMAGE 0x00010000 // does fire damage +#define MF2_NODMGTHRUST 0x00020000 // does not thrust target when + // damaging +#define MF2_TELESTOMP 0x00040000 // mobj can stomp another +#define MF2_FLOATBOB 0x00080000 // use float bobbing z movement +#define MF2_DONTDRAW 0X00100000 // don't generate a vissprite + +//============================================================================= +typedef enum +{ + PST_LIVE, // playing + PST_DEAD, // dead on the ground + PST_REBORN // ready to restart +} playerstate_t; + +// psprites are scaled shapes 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 NULL state means not active + int tics; + fixed_t sx, sy; +} pspdef_t; + +typedef enum +{ + key_yellow, + key_green, + key_blue, + NUMKEYS +} keytype_t; + +typedef enum +{ + wp_staff, + wp_goldwand, + wp_crossbow, + wp_blaster, + wp_skullrod, + wp_phoenixrod, + wp_mace, + wp_gauntlets, + wp_beak, + NUMWEAPONS, + wp_nochange +} weapontype_t; + +#define AMMO_GWND_WIMPY 10 +#define AMMO_GWND_HEFTY 50 +#define AMMO_CBOW_WIMPY 5 +#define AMMO_CBOW_HEFTY 20 +#define AMMO_BLSR_WIMPY 10 +#define AMMO_BLSR_HEFTY 25 +#define AMMO_SKRD_WIMPY 20 +#define AMMO_SKRD_HEFTY 100 +#define AMMO_PHRD_WIMPY 1 +#define AMMO_PHRD_HEFTY 10 +#define AMMO_MACE_WIMPY 20 +#define AMMO_MACE_HEFTY 100 + +typedef enum +{ + am_goldwand, + am_crossbow, + am_blaster, + am_skullrod, + am_phoenixrod, + am_mace, + NUMAMMO, + am_noammo // staff, gauntlets +} ammotype_t; + +typedef struct +{ + ammotype_t ammo; + int upstate; + int downstate; + int readystate; + int atkstate; + int holdatkstate; + int flashstate; +} weaponinfo_t; + +extern weaponinfo_t wpnlev1info[NUMWEAPONS]; +extern weaponinfo_t wpnlev2info[NUMWEAPONS]; + +typedef enum +{ + arti_none, + arti_invulnerability, + arti_invisibility, + arti_health, + arti_superhealth, + arti_tomeofpower, + arti_torch, + arti_firebomb, + arti_egg, + arti_fly, + arti_teleport, + NUMARTIFACTS +} artitype_t; + +typedef enum +{ + pw_None, + pw_invulnerability, + pw_invisibility, + pw_allmap, + pw_infrared, + pw_weaponlevel2, + pw_flight, + pw_shield, + pw_health2, + NUMPOWERS +} powertype_t; + +#define INVULNTICS (30*35) +#define INVISTICS (60*35) +#define INFRATICS (120*35) +#define IRONTICS (60*35) +#define WPNLEV2TICS (40*35) +#define FLIGHTTICS (60*35) + +#define CHICKENTICS (40*35) + +#define MESSAGETICS (4*35) +#define BLINKTHRESHOLD (4*32) + +#define NUMINVENTORYSLOTS 14 +typedef struct +{ + int type; + int count; +} inventory_t; + +/* +================ += += player_t += +================ +*/ + +typedef struct player_s +{ + mobj_t *mo; + playerstate_t playerstate; + ticcmd_t cmd; + + fixed_t viewz; // focal origin above r.z + fixed_t viewheight; // base height above floor for viewz + fixed_t deltaviewheight; // squat speed + fixed_t bob; // bounded/scaled total momentum + + int flyheight; + int lookdir; + boolean centering; + int health; // only used between levels, mo->health + // is used during levels + int armorpoints, armortype; // armor type is 0-2 + + inventory_t inventory[NUMINVENTORYSLOTS]; + artitype_t readyArtifact; + int artifactCount; + int inventorySlotNum; + int powers[NUMPOWERS]; + boolean keys[NUMKEYS]; + boolean backpack; + signed int frags[MAXPLAYERS]; // kills of other players + weapontype_t readyweapon; + weapontype_t pendingweapon; // wp_nochange if not changing + boolean weaponowned[NUMWEAPONS]; + int ammo[NUMAMMO]; + int maxammo[NUMAMMO]; + int attackdown, usedown; // true if button down last tic + int cheats; // bit flags + + int refire; // refired shots are less accurate + + int killcount, itemcount, secretcount; // for intermission + char *message; // hint messages + int messageTics; // counter for showing messages + int damagecount, bonuscount; // for screen flashing + int flamecount; // for flame thrower duration + mobj_t *attacker; // who did damage (NULL for floors) + int extralight; // so gun flashes light up areas + int fixedcolormap; // can be set to REDCOLORMAP, etc + int colormap; // 0-3 for which color to draw player + pspdef_t psprites[NUMPSPRITES]; // view sprites (gun, etc) + boolean didsecret; // true if secret level has been done + int chickenTics; // player is a chicken if > 0 + int chickenPeck; // chicken peck countdown + mobj_t *rain1; // active rain maker 1 + mobj_t *rain2; // active rain maker 2 +} player_t; + +#define CF_NOCLIP 1 +#define CF_GODMODE 2 +#define CF_NOMOMENTUM 4 // not really a cheat, just a debug aid + + +#define BACKUPTICS 12 // CHANGED FROM 12 !?!? + +typedef struct +{ + unsigned checksum; // high bit is retransmit request + byte retransmitfrom; // only valid if NCMD_RETRANSMIT + byte starttic; + byte player, numtics; + ticcmd_t cmds[BACKUPTICS]; +} doomdata_t; + +typedef struct +{ + int id; + short intnum; // DOOM executes an int to execute commands + +// communication between DOOM and the driver + short command; // CMD_SEND or CMD_GET + short remotenode; // dest for send, set by get (-1 = no packet) + short datalength; // bytes in doomdata to be sent + +// info common to all nodes + short numnodes; // console is allways node 0 + short ticdup; // 1 = no duplication, 2-5 = dup for slow nets + short extratics; // 1 = send a backup tic in every packet + short deathmatch; // 1 = deathmatch + short savegame; // -1 = new game, 0-5 = load savegame + short episode; // 1-3 + short map; // 1-9 + short skill; // 1-5 + +// info specific to this node + short consoleplayer; + short numplayers; + short angleoffset; // 1 = left, 0 = center, -1 = right + short drone; // 1 = drone + +// packet data to be sent + doomdata_t data; +} doomcom_t; + +#define DOOMCOM_ID 0x12345678l + +extern doomcom_t *doomcom; +extern doomdata_t *netbuffer; // points inside doomcom + +#define MAXNETNODES 8 // max computers in a game + +#define CMD_SEND 1 +#define CMD_GET 2 + +#define SBARHEIGHT 42 // status bar height at bottom of screen + + +/* +=============================================================================== + + GLOBAL VARIABLES + +=============================================================================== +*/ + +#define TELEFOGHEIGHT (32*FRACUNIT) + +extern gameaction_t gameaction; + +extern boolean paused; + +extern GameMode_t gamemode; +extern GameMission_t gamemission; + +extern boolean ExtendedWAD; // true if main WAD is the extended version + +extern boolean nomonsters; // checkparm of -nomonsters + +extern boolean respawnparm; // checkparm of -respawn + +extern boolean debugmode; // checkparm of -debug + +extern boolean usergame; // ok to save / end game + +extern boolean ravpic; // checkparm of -ravpic + +extern boolean altpal; // checkparm to use an alternate palette routine + +extern boolean cdrom; // true if cd-rom mode active ("-cdrom") + +extern boolean deathmatch; // only if started as net death + +extern boolean netgame; // only true if >1 player + +extern boolean playeringame[MAXPLAYERS]; + +extern int consoleplayer; // player taking events and displaying + +extern int displayplayer; + +extern int viewangleoffset; // ANG90 = left side, ANG270 = right + +extern player_t players[MAXPLAYERS]; + +extern boolean singletics; // debug flag to cancel adaptiveness + +extern boolean DebugSound; // debug flag for displaying sound info + +extern int maxammo[NUMAMMO]; +extern int GetWeaponAmmo[NUMWEAPONS]; + +extern boolean demoplayback; +extern int skytexture; + +extern gamestate_t gamestate; +extern skill_t gameskill; +extern boolean respawnmonsters; +extern int gameepisode; +extern int gamemap; +extern int prevmap; +extern int totalkills, totalitems, totalsecret; // for intermission +extern int levelstarttic; // gametic at level start +extern int leveltime; // tics in game play for par + +extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; +extern int ticdup; + +#define MAXNETNODES 8 +extern ticcmd_t localcmds[BACKUPTICS]; +extern int gametic, maketic; +extern int nettics[MAXNETNODES]; + +#define SAVEGAMESIZE 0x30000 +#define SAVESTRINGSIZE 24 +extern byte *savebuffer; +extern byte *save_p; + +extern mapthing_t *deathmatch_p; +extern mapthing_t deathmatchstarts[10]; +extern mapthing_t playerstarts[MAXPLAYERS]; + +extern int viewwindowx; +extern int viewwindowy; +extern int viewwidth; +extern int scaledviewwidth; +extern int viewheight; + +extern int mouseSensitivity; + +extern boolean precache; // if true, load all graphics at level load + +extern boolean singledemo; // quit after playing a demo from cmdline + +extern FILE *debugfile; +extern int bodyqueslot; +extern skill_t startskill; +extern int startepisode; +extern int startmap; +extern boolean autostart; + +/* +=============================================================================== + + GLOBAL FUNCTIONS + +=============================================================================== +*/ + +#include "z_zone.h" + +//---------- +//BASE LEVEL +//---------- +void D_DoomMain(void); +void IncThermo(void); +void InitThermo(int max); +void tprintf(char *string, int initflag); +// not a globally visible function, just included for source reference +// calls all startup code +// parses command line options +// if not overrided, calls N_AdvanceDemo + +void D_DoomLoop(void); +// not a globally visible function, just included for source reference +// called by D_DoomMain, never exits +// manages timing and IO +// calls all ?_Responder, ?_Ticker, and ?_Drawer functions +// calls I_GetTime, I_StartFrame, and I_StartTic + +void NetUpdate(void); +// create any new ticcmds and broadcast to other players + +void D_QuitNetGame(void); +// broadcasts special packets to other players to notify of game exit + +void TryRunTics(void); + +//--------- +//SYSTEM IO +//--------- + +byte *I_ZoneBase(int *size); +// called by startup code to get the ammount of memory to malloc +// for the zone management + +// asyncronous interrupt functions should maintain private ques that are +// read by the syncronous functions to be converted into events + +// Copy buffer to video + +byte *I_AllocLow(int length); +// allocates from low memory under dos, just mallocs under unix + +// haleyjd: was WATCOMC, preserved for historical interest. +// This is similar to the -control structure in DOOM v1.4 and Strife. +#if 0 +extern boolean useexterndriver; + +#define EBT_FIRE 1 +#define EBT_OPENDOOR 2 +#define EBT_SPEED 4 +#define EBT_STRAFE 8 +#define EBT_MAP 0x10 +#define EBT_INVENTORYLEFT 0x20 +#define EBT_INVENTORYRIGHT 0x40 +#define EBT_USEARTIFACT 0x80 +#define EBT_FLYDROP 0x100 +#define EBT_CENTERVIEW 0x200 +#define EBT_PAUSE 0x400 +#define EBT_WEAPONCYCLE 0x800 + +typedef struct +{ + short vector; // Interrupt vector + + signed char moveForward; // forward/backward (maxes at 50) + signed char moveSideways; // strafe (maxes at 24) + short angleTurn; // turning speed (640 [slow] 1280 [fast]) + short angleHead; // head angle (+2080 [left] : 0 [center] : -2048 [right]) + signed char pitch; // look up/down (-110 : +90) + signed char flyDirection; // flyheight (+1/-1) + unsigned short buttons; // EBT_* flags +} externdata_t; +#endif + +//---- +//GAME +//---- + +void G_DeathMatchSpawnPlayer(int playernum); + +void G_InitNew(skill_t skill, int episode, int map); + +void G_DeferedInitNew(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_DeferedPlayDemo(char *demo); + +void G_LoadGame(char *name); +// can be called by the startup code or M_Responder +// calls P_SetupLevel or W_EnterWorld +void G_DoLoadGame(void); + +void G_SaveGame(int slot, char *description); +// called by M_Responder + +// Support routines for saving games +void SV_Open(char *fileName); +void SV_Close(char *fileName); +void SV_Write(void *buffer, int size); +void SV_WriteByte(byte val); +void SV_WriteWord(unsigned short val); +void SV_WriteLong(unsigned int val); + +void G_RecordDemo(skill_t skill, int numplayers, int episode, int map, + char *name); +// only called by startup code + +void G_PlayDemo(char *name); +void G_TimeDemo(char *name); + +void G_ExitLevel(void); +void G_SecretExitLevel(void); + +void G_WorldDone(void); + +void G_Ticker(void); +boolean G_Responder(event_t * ev); + +void G_ScreenShot(void); + +//----- +//PLAY +//----- + +void P_Ticker(void); +// called by C_Ticker +// can call G_PlayerExited +// carries out all thinking of monsters and players + +void P_SetupLevel(int episode, int map, int playermask, skill_t skill); +// called by W_Ticker + +void P_Init(void); +// called by startup code + +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); +// load / save game routines + + +//------- +//REFRESH +//------- + +extern boolean setsizeneeded; + +extern boolean BorderNeedRefresh; +extern boolean BorderTopRefresh; + +extern int UpdateState; +// define the different areas for the dirty map +#define I_NOUPDATE 0 +#define I_FULLVIEW 1 +#define I_STATBAR 2 +#define I_MESSAGES 4 +#define I_FULLSCRN 8 + +void R_RenderPlayerView(player_t * player); +// called by G_Drawer + +void R_Init(void); +// called by startup code + +void R_DrawViewBorder(void); +void R_DrawTopBorder(void); +// if the view size is not full screen, draws a border around it + +void R_SetViewSize(int blocks, int detail); +// called by M_Responder + +int R_FlatNumForName(char *name); + +int R_TextureNumForName(char *name); +int R_CheckTextureNumForName(char *name); +// called by P_Ticker for switches and animations +// returns the texture number for the texture name + + +//---- +//MISC +//---- +// returns the position of the given parameter in the arg list (0 if not found) + +int M_DrawText(int x, int y, boolean direct, char *string); + +//---------------------- +// Interlude (IN_lude.c) +//---------------------- + +extern boolean intermission; + +void IN_Start(void); +void IN_Ticker(void); +void IN_Drawer(void); + +//---------------------- +// Chat mode (CT_chat.c) +//---------------------- + +void CT_Init(void); +void CT_Drawer(void); +boolean CT_Responder(event_t * ev); +void CT_Ticker(void); +char CT_dequeueChatChar(void); + +extern boolean chatmodeon; +extern boolean ultimatemsg; + +//-------------------- +// Finale (F_finale.c) +//-------------------- + +void F_Drawer(void); +void F_Ticker(void); +void F_StartFinale(void); + +//---------------------- +// STATUS BAR (SB_bar.c) +//---------------------- + +extern int SB_state; +void SB_Init(void); +boolean SB_Responder(event_t * event); +void SB_Ticker(void); +void SB_Drawer(void); + +//----------------- +// MENU (MN_menu.c) +//----------------- + +extern boolean MenuActive; + +void MN_Init(void); +void MN_ActivateMenu(void); +void MN_DeactivateMenu(void); +boolean MN_Responder(event_t * event); +void MN_Ticker(void); +void MN_Drawer(void); +void MN_DrTextA(char *text, int x, int y); +int MN_TextAWidth(char *text); +void MN_DrTextB(char *text, int x, int y); +int MN_TextBWidth(char *text); + +#include "sounds.h" + +#endif // __DOOMDEF__ diff --git a/src/heretic/dstrings.h b/src/heretic/dstrings.h new file mode 100644 index 00000000..93900f9d --- /dev/null +++ b/src/heretic/dstrings.h @@ -0,0 +1,252 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// DStrings.h + +//--------------------------------------------------------------------------- +// +// P_inter.c +// +//--------------------------------------------------------------------------- + +// Keys + +#define TXT_GOTBLUEKEY "BLUE KEY" +#define TXT_GOTYELLOWKEY "YELLOW KEY" +#define TXT_GOTGREENKEY "GREEN KEY" + +// Artifacts + +#define TXT_ARTIHEALTH "QUARTZ FLASK" +#define TXT_ARTIFLY "WINGS OF WRATH" +#define TXT_ARTIINVULNERABILITY "RING OF INVINCIBILITY" +#define TXT_ARTITOMEOFPOWER "TOME OF POWER" +#define TXT_ARTIINVISIBILITY "SHADOWSPHERE" +#define TXT_ARTIEGG "MORPH OVUM" +#define TXT_ARTISUPERHEALTH "MYSTIC URN" +#define TXT_ARTITORCH "TORCH" +#define TXT_ARTIFIREBOMB "TIME BOMB OF THE ANCIENTS" +#define TXT_ARTITELEPORT "CHAOS DEVICE" + +// Items + +#define TXT_ITEMHEALTH "CRYSTAL VIAL" +#define TXT_ITEMBAGOFHOLDING "BAG OF HOLDING" +#define TXT_ITEMSHIELD1 "SILVER SHIELD" +#define TXT_ITEMSHIELD2 "ENCHANTED SHIELD" +#define TXT_ITEMSUPERMAP "MAP SCROLL" + +// Ammo + +#define TXT_AMMOGOLDWAND1 "WAND CRYSTAL" +#define TXT_AMMOGOLDWAND2 "CRYSTAL GEODE" +#define TXT_AMMOMACE1 "MACE SPHERES" +#define TXT_AMMOMACE2 "PILE OF MACE SPHERES" +#define TXT_AMMOCROSSBOW1 "ETHEREAL ARROWS" +#define TXT_AMMOCROSSBOW2 "QUIVER OF ETHEREAL ARROWS" +#define TXT_AMMOBLASTER1 "CLAW ORB" +#define TXT_AMMOBLASTER2 "ENERGY ORB" +#define TXT_AMMOSKULLROD1 "LESSER RUNES" +#define TXT_AMMOSKULLROD2 "GREATER RUNES" +#define TXT_AMMOPHOENIXROD1 "FLAME ORB" +#define TXT_AMMOPHOENIXROD2 "INFERNO ORB" + +// Weapons + +#define TXT_WPNMACE "FIREMACE" +#define TXT_WPNCROSSBOW "ETHEREAL CROSSBOW" +#define TXT_WPNBLASTER "DRAGON CLAW" +#define TXT_WPNSKULLROD "HELLSTAFF" +#define TXT_WPNPHOENIXROD "PHOENIX ROD" +#define TXT_WPNGAUNTLETS "GAUNTLETS OF THE NECROMANCER" + +//--------------------------------------------------------------------------- +// +// SB_bar.c +// +//--------------------------------------------------------------------------- + +#define TXT_CHEATGODON "GOD MODE ON" +#define TXT_CHEATGODOFF "GOD MODE OFF" +#define TXT_CHEATNOCLIPON "NO CLIPPING ON" +#define TXT_CHEATNOCLIPOFF "NO CLIPPING OFF" +#define TXT_CHEATWEAPONS "ALL WEAPONS" +#define TXT_CHEATFLIGHTON "FLIGHT ON" +#define TXT_CHEATFLIGHTOFF "FLIGHT OFF" +#define TXT_CHEATPOWERON "POWER ON" +#define TXT_CHEATPOWEROFF "POWER OFF" +#define TXT_CHEATHEALTH "FULL HEALTH" +#define TXT_CHEATKEYS "ALL KEYS" +#define TXT_CHEATSOUNDON "SOUND DEBUG ON" +#define TXT_CHEATSOUNDOFF "SOUND DEBUG OFF" +#define TXT_CHEATTICKERON "TICKER ON" +#define TXT_CHEATTICKEROFF "TICKER OFF" +#define TXT_CHEATARTIFACTS1 "CHOOSE AN ARTIFACT ( A - J )" +#define TXT_CHEATARTIFACTS2 "HOW MANY ( 1 - 9 )" +#define TXT_CHEATARTIFACTS3 "YOU GOT IT" +#define TXT_CHEATARTIFACTSFAIL "BAD INPUT" +#define TXT_CHEATWARP "LEVEL WARP" +#define TXT_CHEATSCREENSHOT "SCREENSHOT" +#define TXT_CHEATCHICKENON "CHICKEN ON" +#define TXT_CHEATCHICKENOFF "CHICKEN OFF" +#define TXT_CHEATMASSACRE "MASSACRE" +#define TXT_CHEATIDDQD "TRYING TO CHEAT, EH? NOW YOU DIE!" +#define TXT_CHEATIDKFA "CHEATER - YOU DON'T DESERVE WEAPONS" + +//--------------------------------------------------------------------------- +// +// P_doors.c +// +//--------------------------------------------------------------------------- + +#define TXT_NEEDBLUEKEY "YOU NEED A BLUE KEY TO OPEN THIS DOOR" +#define TXT_NEEDGREENKEY "YOU NEED A GREEN KEY TO OPEN THIS DOOR" +#define TXT_NEEDYELLOWKEY "YOU NEED A YELLOW KEY TO OPEN THIS DOOR" + +//--------------------------------------------------------------------------- +// +// G_game.c +// +//--------------------------------------------------------------------------- + +#define TXT_GAMESAVED "GAME SAVED" + +//--------------------------------------------------------------------------- +// +// 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" + +//--------------------------------------------------------------------------- +// +// F_finale.c +// +//--------------------------------------------------------------------------- + +#define E1TEXT "with the destruction of the iron\n"\ + "liches and their minions, the last\n"\ + "of the undead are cleared from this\n"\ + "plane of existence.\n\n"\ + "those creatures had to come from\n"\ + "somewhere, though, and you have the\n"\ + "sneaky suspicion that the fiery\n"\ + "portal of hell's maw opens onto\n"\ + "their home dimension.\n\n"\ + "to make sure that more undead\n"\ + "(or even worse things) don't come\n"\ + "through, you'll have to seal hell's\n"\ + "maw from the other side. of course\n"\ + "this means you may get stuck in a\n"\ + "very unfriendly world, but no one\n"\ + "ever said being a Heretic was easy!" + +#define E2TEXT "the mighty maulotaurs have proved\n"\ + "to be no match for you, and as\n"\ + "their steaming corpses slide to the\n"\ + "ground you feel a sense of grim\n"\ + "satisfaction that they have been\n"\ + "destroyed.\n\n"\ + "the gateways which they guarded\n"\ + "have opened, revealing what you\n"\ + "hope is the way home. but as you\n"\ + "step through, mocking laughter\n"\ + "rings in your ears.\n\n"\ + "was some other force controlling\n"\ + "the maulotaurs? could there be even\n"\ + "more horrific beings through this\n"\ + "gate? the sweep of a crystal dome\n"\ + "overhead where the sky should be is\n"\ + "certainly not a good sign...." + +#define E3TEXT "the death of d'sparil has loosed\n"\ + "the magical bonds holding his\n"\ + "creatures on this plane, their\n"\ + "dying screams overwhelming his own\n"\ + "cries of agony.\n\n"\ + "your oath of vengeance fulfilled,\n"\ + "you enter the portal to your own\n"\ + "world, mere moments before the dome\n"\ + "shatters into a million pieces.\n\n"\ + "but if d'sparil's power is broken\n"\ + "forever, why don't you feel safe?\n"\ + "was it that last shout just before\n"\ + "his death, the one that sounded\n"\ + "like a curse? or a summoning? you\n"\ + "can't really be sure, but it might\n"\ + "just have been a scream.\n\n"\ + "then again, what about the other\n"\ + "serpent riders?" + +#define E4TEXT "you thought you would return to your\n"\ + "own world after d'sparil died, but\n"\ + "his final act banished you to his\n"\ + "own plane. here you entered the\n"\ + "shattered remnants of lands\n"\ + "conquered by d'sparil. you defeated\n"\ + "the last guardians of these lands,\n"\ + "but now you stand before the gates\n"\ + "to d'sparil's stronghold. until this\n"\ + "moment you had no doubts about your\n"\ + "ability to face anything you might\n"\ + "encounter, but beyond this portal\n"\ + "lies the very heart of the evil\n"\ + "which invaded your world. d'sparil\n"\ + "might be dead, but the pit where he\n"\ + "was spawned remains. now you must\n"\ + "enter that pit in the hopes of\n"\ + "finding a way out. and somewhere,\n"\ + "in the darkest corner of d'sparil's\n"\ + "demesne, his personal bodyguards\n"\ + "await your arrival ..." + +#define E5TEXT "as the final maulotaur bellows his\n"\ + "death-agony, you realize that you\n"\ + "have never come so close to your own\n"\ + "destruction. not even the fight with\n"\ + "d'sparil and his disciples had been\n"\ + "this desperate. grimly you stare at\n"\ + "the gates which open before you,\n"\ + "wondering if they lead home, or if\n"\ + "they open onto some undreamed-of\n"\ + "horror. you find yourself wondering\n"\ + "if you have the strength to go on,\n"\ + "if nothing but death and pain await\n"\ + "you. but what else can you do, if\n"\ + "the will to fight is gone? can you\n"\ + "force yourself to continue in the\n"\ + "face of such despair? do you have\n"\ + "the courage? you find, in the end,\n"\ + "that it is not within you to\n"\ + "surrender without a fight. eyes\n"\ + "wide, you go to meet your fate." + diff --git a/src/heretic/f_finale.c b/src/heretic/f_finale.c new file mode 100644 index 00000000..4077bb82 --- /dev/null +++ b/src/heretic/f_finale.c @@ -0,0 +1,433 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// F_finale.c + +#include <ctype.h> + +#include "doomdef.h" +#include "deh_str.h" +#include "i_swap.h" +#include "i_video.h" +#include "s_sound.h" +#include "v_video.h" + +int finalestage; // 0 = text, 1 = art screen +int finalecount; + +#define TEXTSPEED 3 +#define TEXTWAIT 250 + +char *finaletext; +char *finaleflat; + +int FontABaseLump; + +extern boolean automapactive; +extern boolean viewactive; + +extern void D_StartTitle(void); + +/* +======================= += += F_StartFinale += +======================= +*/ + +void F_StartFinale(void) +{ + gameaction = ga_nothing; + gamestate = GS_FINALE; + viewactive = false; + automapactive = false; + players[consoleplayer].messageTics = 1; + players[consoleplayer].message = NULL; + + switch (gameepisode) + { + case 1: + finaleflat = DEH_String("FLOOR25"); + finaletext = DEH_String(E1TEXT); + break; + case 2: + finaleflat = DEH_String("FLATHUH1"); + finaletext = DEH_String(E2TEXT); + break; + case 3: + finaleflat = DEH_String("FLTWAWA2"); + finaletext = DEH_String(E3TEXT); + break; + case 4: + finaleflat = DEH_String("FLOOR28"); + finaletext = DEH_String(E4TEXT); + break; + case 5: + finaleflat = DEH_String("FLOOR08"); + finaletext = DEH_String(E5TEXT); + break; + } + + finalestage = 0; + finalecount = 0; + FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1; + +// S_ChangeMusic(mus_victor, true); + S_StartSong(mus_cptd, true); +} + + + +boolean F_Responder(event_t * event) +{ + if (event->type != ev_keydown) + { + return false; + } + if (finalestage == 1 && gameepisode == 2) + { // we're showing the water pic, make any key kick to demo mode + finalestage++; + /* + memset((byte *) 0xa0000, 0, SCREENWIDTH * SCREENHEIGHT); + memset(I_VideoBuffer, 0, SCREENWIDTH * SCREENHEIGHT); + I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE)); + */ + return true; + } + return false; +} + + +/* +======================= += += F_Ticker += +======================= +*/ + +void F_Ticker(void) +{ + finalecount++; + if (!finalestage + && finalecount > strlen(finaletext) * TEXTSPEED + TEXTWAIT) + { + finalecount = 0; + if (!finalestage) + { + finalestage = 1; + } + +// wipegamestate = -1; // force a wipe +/* + if (gameepisode == 3) + S_StartMusic (mus_bunny); +*/ + } +} + + +/* +======================= += += F_TextWrite += +======================= +*/ + +//#include "hu_stuff.h" +//extern patch_t *hu_font[HU_FONTSIZE]; + +void F_TextWrite(void) +{ + byte *src, *dest; + int x, y; + int count; + char *ch; + int c; + int cx, cy; + patch_t *w; + +// +// erase the entire screen to a tiled background +// + src = W_CacheLumpName(finaleflat, PU_CACHE); + dest = I_VideoBuffer; + for (y = 0; y < SCREENHEIGHT; y++) + { + for (x = 0; x < SCREENWIDTH / 64; x++) + { + memcpy(dest, src + ((y & 63) << 6), 64); + dest += 64; + } + if (SCREENWIDTH & 63) + { + 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 = 20; + cy = 5; + ch = finaletext; + + count = (finalecount - 10) / TEXTSPEED; + if (count < 0) + count = 0; + for (; count; count--) + { + c = *ch++; + if (!c) + break; + if (c == '\n') + { + cx = 20; + cy += 9; + continue; + } + + c = toupper(c); + if (c < 33) + { + cx += 5; + continue; + } + + w = W_CacheLumpNum(FontABaseLump + c - 33, PU_CACHE); + if (cx + w->width > SCREENWIDTH) + break; + V_DrawPatch(cx, cy, w); + cx += w->width; + } + +} + + +void F_DrawPatchCol(int x, patch_t * patch, int col) +{ + column_t *column; + byte *source, *dest, *desttop; + int count; + + column = (column_t *) ((byte *) patch + LONG(patch->columnofs[col])); + desttop = I_VideoBuffer + 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_DemonScroll += +================== +*/ + +void F_DemonScroll(void) +{ + byte *p1, *p2; + static int yval = 0; + static int nextscroll = 0; + + if (finalecount < nextscroll) + { + return; + } + p1 = W_CacheLumpName(DEH_String("FINAL1"), PU_LEVEL); + p2 = W_CacheLumpName(DEH_String("FINAL2"), PU_LEVEL); + if (finalecount < 70) + { + memcpy(I_VideoBuffer, p1, SCREENHEIGHT * SCREENWIDTH); + nextscroll = finalecount; + return; + } + if (yval < 64000) + { + memcpy(I_VideoBuffer, p2 + SCREENHEIGHT * SCREENWIDTH - yval, yval); + memcpy(I_VideoBuffer + yval, p1, SCREENHEIGHT * SCREENWIDTH - yval); + yval += SCREENWIDTH; + nextscroll = finalecount + 3; + } + else + { //else, we'll just sit here and wait, for now + memcpy(I_VideoBuffer, p2, SCREENWIDTH * SCREENHEIGHT); + } +} + +/* +================== += += F_DrawUnderwater += +================== +*/ + +void F_DrawUnderwater(void) +{ + static boolean underwawa; + extern boolean MenuActive; + extern boolean askforquit; + + switch (finalestage) + { + case 1: + if (!underwawa) + { + underwawa = true; + memset((byte *) 0xa0000, 0, SCREENWIDTH * SCREENHEIGHT); + I_SetPalette(W_CacheLumpName(DEH_String("E2PAL"), PU_CACHE)); + V_DrawRawScreen(W_CacheLumpName(DEH_String("E2END"), PU_CACHE)); + } + paused = false; + MenuActive = false; + askforquit = false; + + break; + case 2: + V_DrawRawScreen(W_CacheLumpName(DEH_String("TITLE"), PU_CACHE)); + //D_StartTitle(); // go to intro/demo mode. + } +} + + +#if 0 +/* +================== += += F_BunnyScroll += +================== +*/ + +void F_BunnyScroll(void) +{ + int scrolled, x; + patch_t *p1, *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(NULL, sfx_pistol); + laststage = stage; + } + + sprintf(name, "END%i", stage); + V_DrawPatch((SCREENWIDTH - 13 * 8) / 2, (SCREENHEIGHT - 8 * 8) / 2, + W_CacheLumpName(name, PU_CACHE)); +} +#endif + +/* +======================= += += F_Drawer += +======================= +*/ + +void F_Drawer(void) +{ + UpdateState |= I_FULLSCRN; + if (!finalestage) + F_TextWrite(); + else + { + switch (gameepisode) + { + case 1: + if (gamemode == shareware) + { + V_DrawRawScreen(W_CacheLumpName("ORDER", PU_CACHE)); + } + else + { + V_DrawRawScreen(W_CacheLumpName("CREDIT", PU_CACHE)); + } + break; + case 2: + F_DrawUnderwater(); + break; + case 3: + F_DemonScroll(); + break; + case 4: // Just show credits screen for extended episodes + case 5: + V_DrawRawScreen(W_CacheLumpName("CREDIT", PU_CACHE)); + break; + } + } +} diff --git a/src/heretic/g_game.c b/src/heretic/g_game.c new file mode 100644 index 00000000..b37f95e0 --- /dev/null +++ b/src/heretic/g_game.c @@ -0,0 +1,1798 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// G_game.c + +#include <stdio.h> +#include <string.h> +#include "doomdef.h" +#include "doomkeys.h" +#include "deh_str.h" +#include "i_timer.h" +#include "i_system.h" +#include "m_controls.h" +#include "m_misc.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +// Macros + +#define SVG_RAM 0 +#define SVG_FILE 1 +#define SAVE_GAME_TERMINATOR 0x1d +#define AM_STARTKEY 9 + +// Functions + +boolean G_CheckDemoStatus(void); +void G_ReadDemoTiccmd(ticcmd_t * cmd); +void G_WriteDemoTiccmd(ticcmd_t * cmd); +void G_PlayerReborn(int player); +void G_InitNew(skill_t skill, int episode, int map); + +void G_DoReborn(int playernum); + +void G_DoLoadLevel(void); +void G_DoNewGame(void); +void G_DoLoadGame(void); +void G_DoPlayDemo(void); +void G_DoCompleted(void); +void G_DoVictory(void); +void G_DoWorldDone(void); +void G_DoSaveGame(void); + +void D_PageTicker(void); +void D_AdvanceDemo(void); + +struct +{ + mobjtype_t type; + int speed[2]; +} MonsterMissileInfo[] = { + { MT_IMPBALL, { 10, 20 } }, + { MT_MUMMYFX1, { 9, 18 } }, + { MT_KNIGHTAXE, { 9, 18 } }, + { MT_REDAXE, { 9, 18 } }, + { MT_BEASTBALL, { 12, 20 } }, + { MT_WIZFX1, { 18, 24 } }, + { MT_SNAKEPRO_A, { 14, 20 } }, + { MT_SNAKEPRO_B, { 14, 20 } }, + { MT_HEADFX1, { 13, 20 } }, + { MT_HEADFX3, { 10, 18 } }, + { MT_MNTRFX1, { 20, 26 } }, + { MT_MNTRFX2, { 14, 20 } }, + { MT_SRCRFX1, { 20, 28 } }, + { MT_SOR2FX1, { 20, 28 } }, + { -1, { -1, -1 } } // Terminator +}; + +FILE *SaveGameFP; +int SaveGameType; + +gameaction_t gameaction; +gamestate_t gamestate; +skill_t gameskill; +boolean respawnmonsters; +int gameepisode; +int gamemap; +int prevmap; + +boolean paused; +boolean sendpause; // send a pause event next tic +boolean sendsave; // send a save event next tic +boolean usergame; // ok to save / end game + +boolean timingdemo; // if true, exit with report on completion +int starttime; // for comparative timing purposes + +boolean viewactive; + +boolean deathmatch; // only if started as net death +boolean netgame; // only true if packets are broadcast +boolean playeringame[MAXPLAYERS]; +player_t players[MAXPLAYERS]; + +int consoleplayer; // player taking events and displaying +int displayplayer; // view being displayed +int gametic; +int levelstarttic; // gametic at level start +int totalkills, totalitems, totalsecret; // for intermission + +int mouseSensitivity; + +char demoname[32]; +boolean demorecording; +boolean demoplayback; +byte *demobuffer, *demo_p; +boolean singledemo; // quit after playing a demo from cmdline + +boolean precache = true; // if true, load all graphics at start + +short consistancy[MAXPLAYERS][BACKUPTICS]; + +byte *savebuffer, *save_p; + + +// +// controls (have defaults) +// + + + +#define MAXPLMOVE 0x32 + +fixed_t forwardmove[2] = { 0x19, 0x32 }; +fixed_t sidemove[2] = { 0x18, 0x28 }; +fixed_t angleturn[3] = { 640, 1280, 320 }; // + slow turn + +static int *weapon_keys[] = +{ + &key_weapon1, + &key_weapon2, + &key_weapon3, + &key_weapon4, + &key_weapon5, + &key_weapon6, + &key_weapon7 +}; + +#define SLOWTURNTICS 6 + +#define NUMKEYS 256 +boolean gamekeydown[NUMKEYS]; +int turnheld; // for accelerative turning +int lookheld; + + +boolean mousearray[4]; +boolean *mousebuttons = &mousearray[1]; + // allow [-1] +int mousex, mousey; // mouse values are used once +int dclicktime, dclickstate, dclicks; +int dclicktime2, dclickstate2, dclicks2; + +#define MAX_JOY_BUTTONS 20 + +int joyxmove, joyymove; // joystick values are repeated +boolean joyarray[MAX_JOY_BUTTONS + 1]; +boolean *joybuttons = &joyarray[1]; // allow [-1] + +int savegameslot; +char savedescription[32]; + +int inventoryTics; + +// haleyjd: removed WATCOMC + +//============================================================================= +// Not used - ripped out for Heretic +/* +int G_CmdChecksum(ticcmd_t *cmd) +{ + int i; + int sum; + + 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 +==================== +*/ + +extern boolean inventory; +extern int curpos; +extern int inv_ptr; + +boolean usearti = true; + +void G_BuildTiccmd(ticcmd_t * cmd) +{ + int i; + boolean strafe, bstrafe; + int speed, tspeed, lspeed; + int forward, side; + int look, arti; + int flyheight; + + extern boolean noartiskip; + + // haleyjd: removed externdriver crap + + memset(cmd, 0, sizeof(*cmd)); + //cmd->consistancy = + // consistancy[consoleplayer][(maketic*ticdup)%BACKUPTICS]; + cmd->consistancy = consistancy[consoleplayer][maketic % BACKUPTICS]; + +//printf ("cons: %i\n",cmd->consistancy); + + strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] + || joybuttons[joybstrafe]; + speed = joybspeed >= MAX_JOY_BUTTONS + || gamekeydown[key_speed] + || joybuttons[joybspeed]; + + // haleyjd: removed externdriver crap + + forward = side = look = arti = flyheight = 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; + + if (gamekeydown[key_lookdown] || gamekeydown[key_lookup]) + { + lookheld += ticdup; + } + else + { + lookheld = 0; + } + if (lookheld < SLOWTURNTICS) + { + lspeed = 1; + } + else + { + lspeed = 2; + } + +// +// 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] || mousebuttons[mousebstraferight] + || joybuttons[joybstraferight]) + side += sidemove[speed]; + if (gamekeydown[key_strafeleft] || mousebuttons[mousebstrafeleft] + || joybuttons[joybstrafeleft]) + side -= sidemove[speed]; + + // Look up/down/center keys + if (gamekeydown[key_lookup]) + { + look = lspeed; + } + if (gamekeydown[key_lookdown]) + { + look = -lspeed; + } + // haleyjd: removed externdriver crap + if (gamekeydown[key_lookcenter]) + { + look = TOCENTER; + } + + // haleyjd: removed externdriver crap + + // Fly up/down/drop keys + if (gamekeydown[key_flyup]) + { + flyheight = 5; // note that the actual flyheight will be twice this + } + if (gamekeydown[key_flydown]) + { + flyheight = -5; + } + if (gamekeydown[key_flycenter]) + { + flyheight = TOCENTER; + // haleyjd: removed externdriver crap + look = TOCENTER; + } + + // Use artifact key + if (gamekeydown[key_useartifact]) + { + if (gamekeydown[key_speed] && !noartiskip) + { + if (players[consoleplayer].inventory[inv_ptr].type != arti_none) + { + gamekeydown[key_useartifact] = false; + cmd->arti = 0xff; // skip artifact code + } + } + else + { + if (inventory) + { + players[consoleplayer].readyArtifact = + players[consoleplayer].inventory[inv_ptr].type; + inventory = false; + cmd->arti = 0; + usearti = false; + } + else if (usearti) + { + cmd->arti = players[consoleplayer].inventory[inv_ptr].type; + usearti = false; + } + } + } + if (gamekeydown[127] && !cmd->arti + && !players[consoleplayer].powers[pw_weaponlevel2]) + { + gamekeydown[127] = false; + cmd->arti = arti_tomeofpower; + } + +// +// buttons +// + cmd->chatchar = CT_dequeueChatChar(); + + if (gamekeydown[key_fire] || mousebuttons[mousebfire] + || joybuttons[joybfire]) + cmd->buttons |= BT_ATTACK; + + if (gamekeydown[key_use] || joybuttons[joybuse] || mousebuttons[mousebuse]) + { + cmd->buttons |= BT_USE; + dclicks = 0; // clear double clicks if hit use button + } + + for (i=0; i<arrlen(weapon_keys); ++i) + { + int key = *weapon_keys[i]; + + if (gamekeydown[key]) + { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= i<<BT_WEAPONSHIFT; + break; + } + } + + +// +// mouse +// + if (mousebuttons[mousebforward]) + { + forward += forwardmove[speed]; + } + + if (mousebuttons[mousebbackward]) + { + forward -= forwardmove[speed]; + } + + // Double click to use can be disabled + + if (dclick_use) + { + // + // forward double click + // + if (mousebuttons[mousebforward] != dclickstate && dclicktime > 1) + { + dclickstate = mousebuttons[mousebforward]; + if (dclickstate) + dclicks++; + if (dclicks == 2) + { + cmd->buttons |= BT_USE; + dclicks = 0; + } + else + dclicktime = 0; + } + else + { + dclicktime += ticdup; + if (dclicktime > 20) + { + dclicks = 0; + dclickstate = 0; + } + } + + // + // strafe double click + // + + bstrafe = mousebuttons[mousebstrafe] || joybuttons[joybstrafe]; + if (bstrafe != dclickstate2 && dclicktime2 > 1) + { + dclickstate2 = bstrafe; + if (dclickstate2) + dclicks2++; + if (dclicks2 == 2) + { + cmd->buttons |= BT_USE; + dclicks2 = 0; + } + else + dclicktime2 = 0; + } + else + { + dclicktime2 += ticdup; + if (dclicktime2 > 20) + { + dclicks2 = 0; + dclickstate2 = 0; + } + } + } + + if (strafe) + { + side += mousex * 2; + } + else + { + cmd->angleturn -= mousex * 0x8; + } + + forward += mousey; + 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; + if (players[consoleplayer].playerstate == PST_LIVE) + { + if (look < 0) + { + look += 16; + } + cmd->lookfly = look; + } + if (flyheight < 0) + { + flyheight += 16; + } + cmd->lookfly |= flyheight << 4; + +// +// 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; + + levelstarttic = gametic; // for time calculation + gamestate = GS_LEVEL; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].playerstate == PST_DEAD) + players[i].playerstate = PST_REBORN; + memset(players[i].frags, 0, sizeof(players[i].frags)); + } + + P_SetupLevel(gameepisode, gamemap, 0, gameskill); + displayplayer = consoleplayer; // view the guy you are playing + starttime = I_GetTime(); + gameaction = ga_nothing; + Z_CheckHeap(); + +// +// clear cmd building stuff +// + + memset(gamekeydown, 0, sizeof(gamekeydown)); + joyxmove = joyymove = 0; + mousex = mousey = 0; + sendpause = sendsave = paused = false; + memset(mousebuttons, 0, sizeof(mousebuttons)); + memset(joybuttons, 0, sizeof(joybuttons)); +} + +static void SetJoyButtons(unsigned int buttons_mask) +{ + int i; + + for (i=0; i<MAX_JOY_BUTTONS; ++i) + { + joybuttons[i] = (buttons_mask & (1 << i)) != 0; + } +} + +/* +=============================================================================== += += G_Responder += += get info needed to make ticcmd_ts for the players += +=============================================================================== +*/ + +boolean G_Responder(event_t * ev) +{ + player_t *plr; + extern boolean MenuActive; + + plr = &players[consoleplayer]; + if (ev->type == ev_keyup && ev->data1 == key_useartifact) + { // flag to denote that it's okay to use an artifact + if (!inventory) + { + plr->readyArtifact = plr->inventory[inv_ptr].type; + } + usearti = true; + } + + // Check for spy mode player cycle + if (gamestate == GS_LEVEL && ev->type == ev_keydown + && ev->data1 == KEY_F12 && !deathmatch) + { // Cycle the display player + do + { + displayplayer++; + if (displayplayer == MAXPLAYERS) + { + displayplayer = 0; + } + } + while (!playeringame[displayplayer] + && displayplayer != consoleplayer); + return (true); + } + + if (gamestate == GS_LEVEL) + { + if (CT_Responder(ev)) + { // Chat ate the event + return (true); + } + if (SB_Responder(ev)) + { // Status bar ate the event + return (true); + } + if (AM_Responder(ev)) + { // Automap ate the event + return (true); + } + } + + switch (ev->type) + { + case ev_keydown: + if (ev->data1 == key_invleft) + { + inventoryTics = 5 * 35; + if (!inventory) + { + inventory = true; + break; + } + inv_ptr--; + if (inv_ptr < 0) + { + inv_ptr = 0; + } + else + { + curpos--; + if (curpos < 0) + { + curpos = 0; + } + } + return (true); + } + if (ev->data1 == key_invright) + { + inventoryTics = 5 * 35; + if (!inventory) + { + inventory = true; + break; + } + inv_ptr++; + if (inv_ptr >= plr->inventorySlotNum) + { + inv_ptr--; + if (inv_ptr < 0) + inv_ptr = 0; + } + else + { + curpos++; + if (curpos > 6) + { + curpos = 6; + } + } + return (true); + } + if (ev->data1 == KEY_PAUSE && !MenuActive) + { + 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: + SetJoyButtons(ev->data1); + joyxmove = ev->data2; + joyymove = ev->data3; + return (true); // eat events + + default: + break; + } + return (false); +} + +/* +=============================================================================== += += G_Ticker += +=============================================================================== +*/ + +void G_Ticker(void) +{ + int i, buf; + ticcmd_t *cmd = NULL; + +// +// 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_screenshot: + V_ScreenShot("HTIC%02i.pcx"); + gameaction = ga_nothing; + break; + case ga_completed: + G_DoCompleted(); + break; + case ga_worlddone: + G_DoWorldDone(); + break; + case ga_victory: + F_StartFinale(); + break; + default: + break; + } + } + + +// +// get commands, check consistancy, and build new consistancy check +// + //buf = gametic%BACKUPTICS; + buf = (gametic / ticdup) % BACKUPTICS; + + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + { + cmd = &players[i].cmd; + + memcpy(cmd, &netcmds[i][buf], sizeof(ticcmd_t)); + + if (demoplayback) + G_ReadDemoTiccmd(cmd); + if (demorecording) + G_WriteDemoTiccmd(cmd); + + if (netgame && !(gametic % ticdup)) + { + if (gametic > BACKUPTICS + && consistancy[i][buf] != cmd->consistancy) + { + I_Error("consistency failure (%i should be %i)", + cmd->consistancy, consistancy[i][buf]); + } + if (players[i].mo) + consistancy[i][buf] = players[i].mo->x; + else + consistancy[i][buf] = rndindex; + } + } + +// +// check for special buttons +// + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + { + if (players[i].cmd.buttons & BT_SPECIAL) + { + switch (players[i].cmd.buttons & BT_SPECIALMASK) + { + case BTS_PAUSE: + paused ^= 1; + if (paused) + { + S_PauseSound(); + } + else + { + S_ResumeSound(); + } + break; + + case BTS_SAVEGAME: + if (!savedescription[0]) + { + if (netgame) + { + strncpy(savedescription, DEH_String("NET GAME"), + sizeof(savedescription)); + } + else + { + strncpy(savedescription, DEH_String("SAVE GAME"), + sizeof(savedescription)); + } + + savedescription[sizeof(savedescription) - 1] = '\0'; + } + savegameslot = + (players[i].cmd. + buttons & BTS_SAVEMASK) >> BTS_SAVESHIFT; + gameaction = ga_savegame; + break; + } + } + } + // turn inventory off after a certain amount of time + if (inventory && !(--inventoryTics)) + { + players[consoleplayer].readyArtifact = + players[consoleplayer].inventory[inv_ptr].type; + inventory = false; + cmd->arti = 0; + } +// +// do main actions +// +// +// do main actions +// + switch (gamestate) + { + case GS_LEVEL: + P_Ticker(); + SB_Ticker(); + AM_Ticker(); + CT_Ticker(); + break; + case GS_INTERMISSION: + IN_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 +==================== +*/ +extern int curpos; +extern int inv_ptr; +extern int playerkeys; + +void G_PlayerFinishLevel(int player) +{ + player_t *p; + int i; + +/* // BIG HACK + inv_ptr = 0; + curpos = 0; +*/ + // END HACK + p = &players[player]; + for (i = 0; i < p->inventorySlotNum; i++) + { + p->inventory[i].count = 1; + } + p->artifactCount = p->inventorySlotNum; + + if (!deathmatch) + { + for (i = 0; i < 16; i++) + { + P_PlayerUseArtifact(p, arti_fly); + } + } + memset(p->powers, 0, sizeof(p->powers)); + memset(p->keys, 0, sizeof(p->keys)); + playerkeys = 0; +// memset(p->inventory, 0, sizeof(p->inventory)); + if (p->chickenTics) + { + p->readyweapon = p->mo->special1; // Restore weapon + p->chickenTics = 0; + } + p->messageTics = 0; + p->lookdir = 0; + p->mo->flags &= ~MF_SHADOW; // Remove invisibility + p->extralight = 0; // Remove weapon flashes + p->fixedcolormap = 0; // Remove torch + p->damagecount = 0; // No palette changes + p->bonuscount = 0; + p->rain1 = NULL; + p->rain2 = NULL; + if (p == &players[consoleplayer]) + { + SB_state = -1; // refresh the status bar + } +} + +/* +==================== += += 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, itemcount, secretcount; + boolean secret; + + secret = false; + memcpy(frags, players[player].frags, sizeof(frags)); + killcount = players[player].killcount; + itemcount = players[player].itemcount; + secretcount = players[player].secretcount; + + p = &players[player]; + if (p->didsecret) + { + secret = true; + } + memset(p, 0, sizeof(*p)); + + memcpy(players[player].frags, frags, sizeof(players[player].frags)); + players[player].killcount = killcount; + players[player].itemcount = itemcount; + players[player].secretcount = secretcount; + + p->usedown = p->attackdown = true; // don't do anything immediately + p->playerstate = PST_LIVE; + p->health = MAXHEALTH; + p->readyweapon = p->pendingweapon = wp_goldwand; + p->weaponowned[wp_staff] = true; + p->weaponowned[wp_goldwand] = true; + p->messageTics = 0; + p->lookdir = 0; + p->ammo[am_goldwand] = 50; + for (i = 0; i < NUMAMMO; i++) + { + p->maxammo[i] = maxammo[i]; + } + if (gamemap == 9 || secret) + { + p->didsecret = true; + } + if (p == &players[consoleplayer]) + { + SB_state = -1; // refresh the status bar + inv_ptr = 0; // reset the inventory pointer + curpos = 0; + } +} + +/* +==================== += += G_CheckSpot += += Returns false if the player cannot be respawned at the given mapthing_t spot += because something is occupying it +==================== +*/ + +void P_SpawnPlayer(mapthing_t * mthing); + +boolean G_CheckSpot(int playernum, mapthing_t * mthing) +{ + fixed_t x, y; + subsector_t *ss; + unsigned an; + mobj_t *mo; + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + players[playernum].mo->flags2 &= ~MF2_PASSMOBJ; + if (!P_CheckPosition(players[playernum].mo, x, y)) + { + players[playernum].mo->flags2 |= MF2_PASSMOBJ; + return false; + } + players[playernum].mo->flags2 |= MF2_PASSMOBJ; + +// 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 + TELEFOGHEIGHT, MT_TFOG); + + if (players[consoleplayer].viewz != 1) + S_StartSound(mo, sfx_telept); // don't start sound on first frame + + return true; +} + +/* +==================== += += G_DeathMatchSpawnPlayer += += Spawns a player at one of the random death match spots += called at level load and each death +==================== +*/ + +void G_DeathMatchSpawnPlayer(int playernum) +{ + int i, j; + int selections; + + selections = deathmatch_p - deathmatchstarts; + if (selections < 4) + I_Error("Only %i deathmatch spots, 4 required", selections); + + for (j = 0; j < 20; j++) + { + i = P_Random() % selections; + if (G_CheckSpot(playernum, &deathmatchstarts[i])) + { + deathmatchstarts[i].type = playernum + 1; + P_SpawnPlayer(&deathmatchstarts[i]); + return; + } + } + +// no good spot, so the player will probably get stuck + P_SpawnPlayer(&playerstarts[playernum]); +} + +/* +==================== += += G_DoReborn += +==================== +*/ + +void G_DoReborn(int playernum) +{ + int i; + + if (G_CheckDemoStatus()) + return; + if (!netgame) + gameaction = ga_loadlevel; // reload the level from scratch + else + { // respawn at the start + players[playernum].mo->player = NULL; // dissasociate the corpse + + // 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 += +==================== +*/ + +boolean secretexit; + +void G_ExitLevel(void) +{ + secretexit = false; + gameaction = ga_completed; +} + +void G_SecretExitLevel(void) +{ + secretexit = true; + gameaction = ga_completed; +} + +void G_DoCompleted(void) +{ + int i; + static int afterSecret[5] = { 7, 5, 5, 5, 4 }; + + gameaction = ga_nothing; + if (G_CheckDemoStatus()) + { + return; + } + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + G_PlayerFinishLevel(i); + } + } + prevmap = gamemap; + if (secretexit == true) + { + gamemap = 9; + } + else if (gamemap == 9) + { // Finished secret level + gamemap = afterSecret[gameepisode - 1]; + } + else if (gamemap == 8) + { + gameaction = ga_victory; + return; + } + else + { + gamemap++; + } + gamestate = GS_INTERMISSION; + IN_Start(); +} + +//============================================================================ +// +// G_WorldDone +// +//============================================================================ + +void G_WorldDone(void) +{ + gameaction = ga_worlddone; +} + +//============================================================================ +// +// G_DoWorldDone +// +//============================================================================ + +void G_DoWorldDone(void) +{ + gamestate = GS_LEVEL; + G_DoLoadLevel(); + gameaction = ga_nothing; + viewactive = true; +} + +//--------------------------------------------------------------------------- +// +// PROC G_LoadGame +// +// Can be called by the startup code or the menu task. +// +//--------------------------------------------------------------------------- + +char savename[256]; + +void G_LoadGame(char *name) +{ + strcpy(savename, name); + gameaction = ga_loadgame; +} + +//--------------------------------------------------------------------------- +// +// PROC G_DoLoadGame +// +// Called by G_Ticker based on gameaction. +// +//--------------------------------------------------------------------------- + +#define VERSIONSIZE 16 + +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 + memset(vcheck, 0, sizeof(vcheck)); + + DEH_snprintf(vcheck, VERSIONSIZE, "version %i", HERETIC_VERSION); + + if (strcmp((char *) save_p, vcheck) != 0) + { // Bad version + return; + } + 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); + + // Create leveltime + a = *save_p++; + b = *save_p++; + c = *save_p++; + leveltime = (a << 16) + (b << 8) + c; + + // De-archive all the modifications + P_UnArchivePlayers(); + P_UnArchiveWorld(); + P_UnArchiveThinkers(); + P_UnArchiveSpecials(); + + if (*save_p != SAVE_GAME_TERMINATOR) + { // Missing savegame termination marker + I_Error("Bad savegame"); + } + Z_Free(savebuffer); +} + + +/* +==================== += += G_InitNew += += Can be called by the startup code or the menu task += consoleplayer, displayplayer, playeringame[] should be set +==================== +*/ + +skill_t d_skill; +int d_episode; +int d_map; + +void G_DeferedInitNew(skill_t skill, int episode, int map) +{ + d_skill = skill; + d_episode = episode; + d_map = map; + gameaction = ga_newgame; +} + +void G_DoNewGame(void) +{ + G_InitNew(d_skill, d_episode, d_map); + gameaction = ga_nothing; +} + +extern int skytexture; + +void G_InitNew(skill_t skill, int episode, int map) +{ + int i; + int speed; + static char *skyLumpNames[5] = { + "SKY1", "SKY2", "SKY3", "SKY1", "SKY3" + }; + + if (paused) + { + paused = false; + S_ResumeSound(); + } + if (skill < sk_baby) + skill = sk_baby; + if (skill > sk_nightmare) + skill = sk_nightmare; + if (episode < 1) + episode = 1; + // Up to 9 episodes for testing + if (episode > 9) + episode = 9; + if (map < 1) + map = 1; + if (map > 9) + map = 9; + M_ClearRandom(); + if (respawnparm) + { + respawnmonsters = true; + } + else + { + respawnmonsters = false; + } + // Set monster missile speeds + speed = skill == sk_nightmare; + for (i = 0; MonsterMissileInfo[i].type != -1; i++) + { + mobjinfo[MonsterMissileInfo[i].type].speed + = MonsterMissileInfo[i].speed[speed] << FRACBITS; + } + // Force players to be initialized upon first level load + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].playerstate = PST_REBORN; + players[i].didsecret = false; + } + // Set up a bunch of globals + usergame = true; // will be set false if a demo + paused = false; + demorecording = false; + demoplayback = false; + viewactive = true; + gameepisode = episode; + gamemap = map; + gameskill = skill; + viewactive = true; + BorderNeedRefresh = true; + + // Set the sky map + if (episode > 5) + { + skytexture = R_TextureNumForName(DEH_String("SKY1")); + } + else + { + skytexture = R_TextureNumForName(DEH_String(skyLumpNames[episode - 1])); + } + +// +// give one null ticcmd_t +// +#if 0 + gametic = 0; + maketic = 1; + for (i = 0; i < MAXPLAYERS; i++) + nettics[i] = 1; // one null event for this gametic + memset(localcmds, 0, sizeof(localcmds)); + memset(netcmds, 0, sizeof(netcmds)); +#endif + G_DoLoadLevel(); +} + + +/* +=============================================================================== + + DEMO RECORDING + +=============================================================================== +*/ + +#define DEMOMARKER 0x80 + +void G_ReadDemoTiccmd(ticcmd_t * cmd) +{ + if (*demo_p == DEMOMARKER) + { // end of demo data stream + G_CheckDemoStatus(); + return; + } + cmd->forwardmove = ((signed char) *demo_p++); + cmd->sidemove = ((signed char) *demo_p++); + cmd->angleturn = ((unsigned char) *demo_p++) << 8; + cmd->buttons = (unsigned char) *demo_p++; + cmd->lookfly = (unsigned char) *demo_p++; + cmd->arti = (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 >> 8; + *demo_p++ = cmd->buttons; + *demo_p++ = cmd->lookfly; + *demo_p++ = cmd->arti; + demo_p -= 6; + G_ReadDemoTiccmd(cmd); // make SURE it is exactly the same +} + + + +/* +=================== += += G_RecordDemo += +=================== +*/ + +void G_RecordDemo(skill_t skill, int numplayers, int episode, int map, + char *name) +{ + int i; + + G_InitNew(skill, episode, map); + usergame = false; + strcpy(demoname, name); + strcat(demoname, ".lmp"); + demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL); + *demo_p++ = skill; + *demo_p++ = episode; + *demo_p++ = map; + + for (i = 0; i < MAXPLAYERS; i++) + *demo_p++ = playeringame[i]; + + demorecording = true; +} + + +/* +=================== += += G_PlayDemo += +=================== +*/ + +char *defdemoname; + +void G_DeferedPlayDemo(char *name) +{ + defdemoname = name; + gameaction = ga_playdemo; +} + +void G_DoPlayDemo(void) +{ + skill_t skill; + int i, episode, map; + + gameaction = ga_nothing; + demobuffer = demo_p = W_CacheLumpName(defdemoname, PU_STATIC); + skill = *demo_p++; + episode = *demo_p++; + map = *demo_p++; + + for (i = 0; i < MAXPLAYERS; i++) + playeringame[i] = *demo_p++; + + precache = false; // don't spend a lot of time in loadlevel + G_InitNew(skill, episode, map); + precache = true; + usergame = false; + demoplayback = true; +} + + +/* +=================== += += G_TimeDemo += +=================== +*/ + +void G_TimeDemo(char *name) +{ + skill_t skill; + int episode, map; + + demobuffer = demo_p = W_CacheLumpName(name, PU_STATIC); + skill = *demo_p++; + episode = *demo_p++; + map = *demo_p++; + G_InitNew(skill, episode, map); + usergame = false; + demoplayback = true; + timingdemo = true; + singletics = true; +} + + +/* +=================== += += G_CheckDemoStatus += += Called after a death or level completion to allow demos to be cleaned up += Returns true if a new demo loop action will take place +=================== +*/ + +boolean G_CheckDemoStatus(void) +{ + int endtime; + + if (timingdemo) + { + endtime = I_GetTime(); + I_Error("timed %i gametics in %i realtics", gametic, + endtime - starttime); + } + + if (demoplayback) + { + if (singledemo) + I_Quit(); + + W_ReleaseLumpName(defdemoname); + demoplayback = false; + D_AdvanceDemo(); + return true; + } + + if (demorecording) + { + *demo_p++ = DEMOMARKER; + M_WriteFile(demoname, demobuffer, demo_p - demobuffer); + Z_Free(demobuffer); + demorecording = false; + I_Error("Demo %s recorded", demoname); + } + + return false; +} + +/**************************************************************************/ +/**************************************************************************/ + +//========================================================================== +// +// G_SaveGame +// +// Called by the menu task. <description> is a 24 byte text string. +// +//========================================================================== + +void G_SaveGame(int slot, char *description) +{ + savegameslot = slot; + strcpy(savedescription, description); + sendsave = true; +} + +//========================================================================== +// +// G_DoSaveGame +// +// Called by G_Ticker based on gameaction. +// +//========================================================================== + +void G_DoSaveGame(void) +{ + int i; + char name[100]; + char verString[VERSIONSIZE]; + char *description; + + if (cdrom) + { + sprintf(name, SAVEGAMENAMECD "%d.hsg", savegameslot); + } + else + { + sprintf(name, SAVEGAMENAME "%d.hsg", savegameslot); + } + description = savedescription; + + SV_Open(name); + SV_Write(description, SAVESTRINGSIZE); + memset(verString, 0, sizeof(verString)); + DEH_snprintf(verString, VERSIONSIZE, "version %i", HERETIC_VERSION); + SV_Write(verString, VERSIONSIZE); + SV_WriteByte(gameskill); + SV_WriteByte(gameepisode); + SV_WriteByte(gamemap); + for (i = 0; i < MAXPLAYERS; i++) + { + SV_WriteByte(playeringame[i]); + } + SV_WriteByte(leveltime >> 16); + SV_WriteByte(leveltime >> 8); + SV_WriteByte(leveltime); + P_ArchivePlayers(); + P_ArchiveWorld(); + P_ArchiveThinkers(); + P_ArchiveSpecials(); + SV_Close(name); + + gameaction = ga_nothing; + savedescription[0] = 0; + P_SetMessage(&players[consoleplayer], DEH_String(TXT_GAMESAVED), true); +} + +//========================================================================== +// +// SV_Open +// +//========================================================================== + +void SV_Open(char *fileName) +{ + SaveGameType = SVG_FILE; + SaveGameFP = fopen(fileName, "wb"); +} + +//========================================================================== +// +// SV_Close +// +//========================================================================== + +void SV_Close(char *fileName) +{ + int length; + + SV_WriteByte(SAVE_GAME_TERMINATOR); + if (SaveGameType == SVG_RAM) + { + length = save_p - savebuffer; + if (length > SAVEGAMESIZE) + { + I_Error("Savegame buffer overrun"); + } + M_WriteFile(fileName, savebuffer, length); + Z_Free(savebuffer); + } + else + { // SVG_FILE + fclose(SaveGameFP); + } +} + +//========================================================================== +// +// SV_Write +// +//========================================================================== + +void SV_Write(void *buffer, int size) +{ + if (SaveGameType == SVG_RAM) + { + memcpy(save_p, buffer, size); + save_p += size; + } + else + { // SVG_FILE + fwrite(buffer, size, 1, SaveGameFP); + } +} + +void SV_WriteByte(byte val) +{ + SV_Write(&val, sizeof(byte)); +} + +void SV_WriteWord(unsigned short val) +{ + SV_Write(&val, sizeof(unsigned short)); +} + +void SV_WriteLong(unsigned int val) +{ + SV_Write(&val, sizeof(int)); +} diff --git a/src/heretic/i_ibm.c b/src/heretic/i_ibm.c new file mode 100644 index 00000000..cf668680 --- /dev/null +++ b/src/heretic/i_ibm.c @@ -0,0 +1,1650 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// I_IBM.C + +#include <dos.h> +#include <conio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <graph.h> +#include "doomdef.h" +#include "r_local.h" +#include "dmx.h" +#include "v_video.h" + +// Macros + +#define DPMI_INT 0x31 +//#define NOKBD +//#define NOTIMER + +// Public Data + +int DisplayTicker = 0; + +// Code + +void main(int argc, char **argv) +{ + myargc = argc; + myargv = argv; + D_DoomMain(); +} + +void I_StartupNet(void); +void I_ShutdownNet(void); +void I_ReadExternDriver(void); + +typedef struct +{ + unsigned edi, esi, ebp, reserved, ebx, edx, ecx, eax; + unsigned short flags, es, ds, fs, gs, ip, cs, sp, ss; +} dpmiregs_t; + +extern dpmiregs_t dpmiregs; + +void I_ReadMouse(void); +void I_InitDiskFlash(void); + +extern int usemouse, usejoystick; + +extern void **lumpcache; + +/* +============================================================================= + + CONSTANTS + +============================================================================= +*/ + +#define SC_INDEX 0x3C4 +#define SC_RESET 0 +#define SC_CLOCK 1 +#define SC_MAPMASK 2 +#define SC_CHARMAP 3 +#define SC_MEMMODE 4 + +#define CRTC_INDEX 0x3D4 +#define CRTC_H_TOTAL 0 +#define CRTC_H_DISPEND 1 +#define CRTC_H_BLANK 2 +#define CRTC_H_ENDBLANK 3 +#define CRTC_H_RETRACE 4 +#define CRTC_H_ENDRETRACE 5 +#define CRTC_V_TOTAL 6 +#define CRTC_OVERFLOW 7 +#define CRTC_ROWSCAN 8 +#define CRTC_MAXSCANLINE 9 +#define CRTC_CURSORSTART 10 +#define CRTC_CURSOREND 11 +#define CRTC_STARTHIGH 12 +#define CRTC_STARTLOW 13 +#define CRTC_CURSORHIGH 14 +#define CRTC_CURSORLOW 15 +#define CRTC_V_RETRACE 16 +#define CRTC_V_ENDRETRACE 17 +#define CRTC_V_DISPEND 18 +#define CRTC_OFFSET 19 +#define CRTC_UNDERLINE 20 +#define CRTC_V_BLANK 21 +#define CRTC_V_ENDBLANK 22 +#define CRTC_MODE 23 +#define CRTC_LINECOMPARE 24 + + +#define GC_INDEX 0x3CE +#define GC_SETRESET 0 +#define GC_ENABLESETRESET 1 +#define GC_COLORCOMPARE 2 +#define GC_DATAROTATE 3 +#define GC_READMAP 4 +#define GC_MODE 5 +#define GC_MISCELLANEOUS 6 +#define GC_COLORDONTCARE 7 +#define GC_BITMASK 8 + +#define ATR_INDEX 0x3c0 +#define ATR_MODE 16 +#define ATR_OVERSCAN 17 +#define ATR_COLORPLANEENABLE 18 +#define ATR_PELPAN 19 +#define ATR_COLORSELECT 20 + +#define STATUS_REGISTER_1 0x3da + +#define PEL_WRITE_ADR 0x3c8 +#define PEL_READ_ADR 0x3c7 +#define PEL_DATA 0x3c9 +#define PEL_MASK 0x3c6 + +boolean grmode; + +//================================================== +// +// joystick vars +// +//================================================== + +boolean joystickpresent; +extern unsigned joystickx, joysticky; +boolean I_ReadJoystick(void); // returns false if not connected + + +//================================================== + +#define VBLCOUNTER 34000 // hardware tics to a frame + + +#define TIMERINT 8 +#define KEYBOARDINT 9 + +#define CRTCOFF (_inbyte(STATUS_REGISTER_1)&1) +#define CLI _disable() +#define STI _enable() + +#define _outbyte(x,y) (outp(x,y)) +#define _outhword(x,y) (outpw(x,y)) + +#define _inbyte(x) (inp(x)) +#define _inhword(x) (inpw(x)) + +#define MOUSEB1 1 +#define MOUSEB2 2 +#define MOUSEB3 4 + +boolean mousepresent; +//static int tsm_ID = -1; // tsm init flag + +//=============================== + +int ticcount; + +// REGS stuff used for int calls +union REGS regs; +struct SREGS segregs; + +boolean novideo; // if true, stay in text mode for debugging + +#define KBDQUESIZE 32 +byte keyboardque[KBDQUESIZE]; +int kbdtail, kbdhead; + +#define KEY_LSHIFT 0xfe + +#define KEY_INS (0x80+0x52) +#define KEY_DEL (0x80+0x53) +#define KEY_PGUP (0x80+0x49) +#define KEY_PGDN (0x80+0x51) +#define KEY_HOME (0x80+0x47) +#define KEY_END (0x80+0x4f) + +#define SC_RSHIFT 0x36 +#define SC_LSHIFT 0x2a + +byte scantokey[128] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', KEY_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', 13, KEY_RCTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + 39, '`', KEY_LSHIFT, 92, 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '/', KEY_RSHIFT, '*', + KEY_RALT, ' ', 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, // 3 + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, 0, 0, KEY_HOME, + KEY_UPARROW, KEY_PGUP, '-', KEY_LEFTARROW, '5', KEY_RIGHTARROW, '+', KEY_END, //4 + KEY_DOWNARROW, KEY_PGDN, KEY_INS, KEY_DEL, 0, 0, 0, KEY_F11, + KEY_F12, 0, 0, 0, 0, 0, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +//========================================================================== + +//-------------------------------------------------------------------------- +// +// FUNC I_GetTime +// +// Returns time in 1/35th second tics. +// +//-------------------------------------------------------------------------- + +int I_GetTime(void) +{ +#ifdef NOTIMER + ticcount++; +#endif + return (ticcount); +} + +//-------------------------------------------------------------------------- +// +// PROC I_ColorBorder +// +//-------------------------------------------------------------------------- + +void I_ColorBorder(void) +{ + int i; + + I_WaitVBL(1); + _outbyte(PEL_WRITE_ADR, 0); + for (i = 0; i < 3; i++) + { + _outbyte(PEL_DATA, 63); + } +} + +//-------------------------------------------------------------------------- +// +// PROC I_UnColorBorder +// +//-------------------------------------------------------------------------- + +void I_UnColorBorder(void) +{ + int i; + + I_WaitVBL(1); + _outbyte(PEL_WRITE_ADR, 0); + for (i = 0; i < 3; i++) + { + _outbyte(PEL_DATA, 0); + } +} + +/* +============================================================================ + + USER INPUT + +============================================================================ +*/ + +//-------------------------------------------------------------------------- +// +// PROC I_WaitVBL +// +//-------------------------------------------------------------------------- + +void I_WaitVBL(int vbls) +{ + int i; + int old; + int stat; + + if (novideo) + { + return; + } + while (vbls--) + { + do + { + stat = inp(STATUS_REGISTER_1); + if (stat & 8) + { + break; + } + } + while (1); + do + { + stat = inp(STATUS_REGISTER_1); + if ((stat & 8) == 0) + { + break; + } + } + while (1); + } +} + +//-------------------------------------------------------------------------- +// +// PROC I_SetPalette +// +// Palette source must use 8 bit RGB elements. +// +//-------------------------------------------------------------------------- + +void I_SetPalette(byte * palette) +{ + int i; + + if (novideo) + { + return; + } + I_WaitVBL(1); + _outbyte(PEL_WRITE_ADR, 0); + for (i = 0; i < 768; i++) + { + _outbyte(PEL_DATA, (gammatable[usegamma][*palette++]) >> 2); + } +} + +/* +============================================================================ + + GRAPHICS MODE + +============================================================================ +*/ + +byte *pcscreen, *destscreen, *destview; + + +/* +============== += += I_Update += +============== +*/ + +int UpdateState; +extern int screenblocks; + +void I_Update(void) +{ + int i; + byte *dest; + int tics; + static int lasttic; + +// +// blit screen to video +// + if (DisplayTicker) + { + if (screenblocks > 9 || UpdateState & (I_FULLSCRN | I_MESSAGES)) + { + dest = (byte *) screen; + } + else + { + dest = (byte *) pcscreen; + } + tics = ticcount - lasttic; + lasttic = ticcount; + if (tics > 20) + { + tics = 20; + } + for (i = 0; i < tics; i++) + { + *dest = 0xff; + dest += 2; + } + for (i = tics; i < 20; i++) + { + *dest = 0x00; + dest += 2; + } + } + if (UpdateState == I_NOUPDATE) + { + return; + } + if (UpdateState & I_FULLSCRN) + { + memcpy(pcscreen, screen, SCREENWIDTH * SCREENHEIGHT); + UpdateState = I_NOUPDATE; // clear out all draw types + } + if (UpdateState & I_FULLVIEW) + { + if (UpdateState & I_MESSAGES && screenblocks > 7) + { + for (i = 0; i < + (viewwindowy + viewheight) * SCREENWIDTH; i += SCREENWIDTH) + { + memcpy(pcscreen + i, screen + i, SCREENWIDTH); + } + UpdateState &= ~(I_FULLVIEW | I_MESSAGES); + } + else + { + for (i = viewwindowy * SCREENWIDTH + viewwindowx; i < + (viewwindowy + viewheight) * SCREENWIDTH; i += SCREENWIDTH) + { + memcpy(pcscreen + i, screen + i, viewwidth); + } + UpdateState &= ~I_FULLVIEW; + } + } + if (UpdateState & I_STATBAR) + { + memcpy(pcscreen + SCREENWIDTH * (SCREENHEIGHT - SBARHEIGHT), + screen + SCREENWIDTH * (SCREENHEIGHT - SBARHEIGHT), + SCREENWIDTH * SBARHEIGHT); + UpdateState &= ~I_STATBAR; + } + if (UpdateState & I_MESSAGES) + { + memcpy(pcscreen, screen, SCREENWIDTH * 28); + UpdateState &= ~I_MESSAGES; + } + +// memcpy(pcscreen, screen, SCREENHEIGHT*SCREENWIDTH); +} + +//-------------------------------------------------------------------------- +// +// PROC I_InitGraphics +// +//-------------------------------------------------------------------------- + +void I_InitGraphics(void) +{ + if (novideo) + { + return; + } + grmode = true; + regs.w.ax = 0x13; + int386(0x10, (const union REGS *) ®s, ®s); + pcscreen = destscreen = (byte *) 0xa0000; + I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE)); + I_InitDiskFlash(); +} + +//-------------------------------------------------------------------------- +// +// PROC I_ShutdownGraphics +// +//-------------------------------------------------------------------------- + +void I_ShutdownGraphics(void) +{ + + if (*(byte *) 0x449 == 0x13) // don't reset mode if it didn't get set + { + regs.w.ax = 3; + int386(0x10, ®s, ®s); // back to text mode + } +} + +//-------------------------------------------------------------------------- +// +// PROC I_ReadScreen +// +// Reads the screen currently displayed into a linear buffer. +// +//-------------------------------------------------------------------------- + +void I_ReadScreen(byte * scr) +{ + memcpy(scr, screen, SCREENWIDTH * SCREENHEIGHT); +} + + +//=========================================================================== + +/* +=================== += += I_StartTic += +// called by D_DoomLoop +// called before processing each tic in a frame +// can call D_PostEvent +// asyncronous interrupt functions should maintain private ques that are +// read by the syncronous functions to be converted into events +=================== +*/ + +/* + OLD STARTTIC STUFF + +void I_StartTic (void) +{ + int k; + event_t ev; + + + I_ReadMouse (); + +// +// keyboard events +// + while (kbdtail < kbdhead) + { + k = keyboardque[kbdtail&(KBDQUESIZE-1)]; + +// if (k==14) +// I_Error ("exited"); + + kbdtail++; + + // extended keyboard shift key bullshit + if ( (k&0x7f)==KEY_RSHIFT ) + { + if ( keyboardque[(kbdtail-2)&(KBDQUESIZE-1)]==0xe0 ) + continue; + k &= 0x80; + k |= KEY_RSHIFT; + } + + if (k==0xe0) + continue; // special / pause keys + if (keyboardque[(kbdtail-2)&(KBDQUESIZE-1)] == 0xe1) + continue; // pause key bullshit + + if (k==0xc5 && keyboardque[(kbdtail-2)&(KBDQUESIZE-1)] == 0x9d) + { + ev.type = ev_keydown; + ev.data1 = KEY_PAUSE; + D_PostEvent (&ev); + continue; + } + + if (k&0x80) + ev.type = ev_keyup; + else + ev.type = ev_keydown; + k &= 0x7f; + + ev.data1 = k; + //ev.data1 = scantokey[k]; + + D_PostEvent (&ev); + } +} +*/ + +#define SC_UPARROW 0x48 +#define SC_DOWNARROW 0x50 +#define SC_LEFTARROW 0x4b +#define SC_RIGHTARROW 0x4d + +void I_StartTic(void) +{ + int k; + event_t ev; + + + I_ReadMouse(); + +// +// keyboard events +// + while (kbdtail < kbdhead) + { + k = keyboardque[kbdtail & (KBDQUESIZE - 1)]; + kbdtail++; + + // extended keyboard shift key bullshit + if ((k & 0x7f) == SC_LSHIFT || (k & 0x7f) == SC_RSHIFT) + { + if (keyboardque[(kbdtail - 2) & (KBDQUESIZE - 1)] == 0xe0) + continue; + k &= 0x80; + k |= SC_RSHIFT; + } + + if (k == 0xe0) + continue; // special / pause keys + if (keyboardque[(kbdtail - 2) & (KBDQUESIZE - 1)] == 0xe1) + continue; // pause key bullshit + + if (k == 0xc5 + && keyboardque[(kbdtail - 2) & (KBDQUESIZE - 1)] == 0x9d) + { + ev.type = ev_keydown; + ev.data1 = KEY_PAUSE; + D_PostEvent(&ev); + continue; + } + + if (k & 0x80) + ev.type = ev_keyup; + else + ev.type = ev_keydown; + k &= 0x7f; + switch (k) + { + case SC_UPARROW: + ev.data1 = KEY_UPARROW; + break; + case SC_DOWNARROW: + ev.data1 = KEY_DOWNARROW; + break; + case SC_LEFTARROW: + ev.data1 = KEY_LEFTARROW; + break; + case SC_RIGHTARROW: + ev.data1 = KEY_RIGHTARROW; + break; + default: + ev.data1 = scantokey[k]; + break; + } + D_PostEvent(&ev); + } + +} + + +void I_ReadKeys(void) +{ + int k; + event_t ev; + + + while (1) + { + while (kbdtail < kbdhead) + { + k = keyboardque[kbdtail & (KBDQUESIZE - 1)]; + kbdtail++; + printf("0x%x\n", k); + if (k == 1) + I_Quit(); + } + } +} + +/* +=============== += += I_StartFrame += +=============== +*/ + +void I_StartFrame(void) +{ + I_JoystickEvents(); + I_ReadExternDriver(); +} + +/* +============================================================================ + + TIMER INTERRUPT + +============================================================================ +*/ + +void I_ColorBlack(int r, int g, int b) +{ + _outbyte(PEL_WRITE_ADR, 0); + _outbyte(PEL_DATA, r); + _outbyte(PEL_DATA, g); + _outbyte(PEL_DATA, b); +} + + +/* +================ += += I_TimerISR += +================ +*/ + +int I_TimerISR(void) +{ + ticcount++; + return 0; +} + +/* +============================================================================ + + KEYBOARD + +============================================================================ +*/ + +void (__interrupt __far * oldkeyboardisr) () = NULL; + +int lastpress; + +/* +================ += += I_KeyboardISR += +================ +*/ + +void __interrupt I_KeyboardISR(void) +{ +// Get the scan code + + keyboardque[kbdhead & (KBDQUESIZE - 1)] = lastpress = _inbyte(0x60); + kbdhead++; + +// acknowledge the interrupt + + _outbyte(0x20, 0x20); +} + + + +/* +=============== += += I_StartupKeyboard += +=============== +*/ + +void I_StartupKeyboard(void) +{ +#ifndef NOKBD + oldkeyboardisr = _dos_getvect(KEYBOARDINT); + _dos_setvect(0x8000 | KEYBOARDINT, I_KeyboardISR); +#endif + +//I_ReadKeys (); +} + + +void I_ShutdownKeyboard(void) +{ + if (oldkeyboardisr) + _dos_setvect(KEYBOARDINT, oldkeyboardisr); + *(short *) 0x41c = *(short *) 0x41a; // clear bios key buffer +} + + + +/* +============================================================================ + + MOUSE + +============================================================================ +*/ + + +int I_ResetMouse(void) +{ + regs.w.ax = 0; // reset + int386(0x33, ®s, ®s); + return regs.w.ax; +} + + + +/* +================ += += StartupMouse += +================ +*/ + +void I_StartupCyberMan(void); + +void I_StartupMouse(void) +{ + int (far * function) (); + + // + // General mouse detection + // + mousepresent = 0; + if (M_CheckParm("-nomouse") || !usemouse) + return; + + if (I_ResetMouse() != 0xffff) + { + tprintf("Mouse: not present ", 0); + return; + } + tprintf("Mouse: detected ", 0); + + mousepresent = 1; + + I_StartupCyberMan(); +} + + +/* +================ += += ShutdownMouse += +================ +*/ + +void I_ShutdownMouse(void) +{ + if (!mousepresent) + return; + + I_ResetMouse(); +} + + +/* +================ += += I_ReadMouse += +================ +*/ + +void I_ReadMouse(void) +{ + event_t ev; + +// +// mouse events +// + if (!mousepresent) + return; + + ev.type = ev_mouse; + + memset(&dpmiregs, 0, sizeof(dpmiregs)); + dpmiregs.eax = 3; // read buttons / position + DPMIInt(0x33); + ev.data1 = dpmiregs.ebx; + + dpmiregs.eax = 11; // read counters + DPMIInt(0x33); + ev.data2 = (short) dpmiregs.ecx; + ev.data3 = -(short) dpmiregs.edx; + + D_PostEvent(&ev); +} + +/* +============================================================================ + + JOYSTICK + +============================================================================ +*/ + +int joyxl, joyxh, joyyl, joyyh; + +boolean WaitJoyButton(void) +{ + int oldbuttons, buttons; + + oldbuttons = 0; + do + { + I_WaitVBL(1); + buttons = ((inp(0x201) >> 4) & 1) ^ 1; + if (buttons != oldbuttons) + { + oldbuttons = buttons; + continue; + } + + if ((lastpress & 0x7f) == 1) + { + joystickpresent = false; + return false; + } + } + while (!buttons); + + do + { + I_WaitVBL(1); + buttons = ((inp(0x201) >> 4) & 1) ^ 1; + if (buttons != oldbuttons) + { + oldbuttons = buttons; + continue; + } + + if ((lastpress & 0x7f) == 1) + { + joystickpresent = false; + return false; + } + } + while (buttons); + + return true; +} + + + +/* +=============== += += I_StartupJoystick += +=============== +*/ + +int basejoyx, basejoyy; + +void I_StartupJoystick(void) +{ + int buttons; + int count; + int centerx, centery; + + joystickpresent = 0; + if (M_CheckParm("-nojoy") || !usejoystick) + return; + + if (!I_ReadJoystick()) + { + joystickpresent = false; + tprintf("joystick not found ", 0); + return; + } + printf("joystick found\n"); + joystickpresent = true; + + printf("CENTER the joystick and press button 1:"); + if (!WaitJoyButton()) + return; + I_ReadJoystick(); + centerx = joystickx; + centery = joysticky; + + printf + ("\nPush the joystick to the UPPER LEFT corner and press button 1:"); + if (!WaitJoyButton()) + return; + I_ReadJoystick(); + joyxl = (centerx + joystickx) / 2; + joyyl = (centerx + joysticky) / 2; + + printf + ("\nPush the joystick to the LOWER RIGHT corner and press button 1:"); + if (!WaitJoyButton()) + return; + I_ReadJoystick(); + joyxh = (centerx + joystickx) / 2; + joyyh = (centery + joysticky) / 2; + printf("\n"); +} + +/* +=============== += += I_JoystickEvents += +=============== +*/ + +void I_JoystickEvents(void) +{ + event_t ev; + +// +// joystick events +// + if (!joystickpresent) + return; + + I_ReadJoystick(); + ev.type = ev_joystick; + ev.data1 = ((inp(0x201) >> 4) & 15) ^ 15; + + if (joystickx < joyxl) + ev.data2 = -1; + else if (joystickx > joyxh) + ev.data2 = 1; + else + ev.data2 = 0; + if (joysticky < joyyl) + ev.data3 = -1; + else if (joysticky > joyyh) + ev.data3 = 1; + else + ev.data3 = 0; + + D_PostEvent(&ev); +} + + + +/* +============================================================================ + + DPMI STUFF + +============================================================================ +*/ + +#define REALSTACKSIZE 1024 + +dpmiregs_t dpmiregs; + +unsigned realstackseg; + +void I_DivException(void); +int I_SetDivException(void); + +void DPMIFarCall(void) +{ + segread(&segregs); + regs.w.ax = 0x301; + regs.w.bx = 0; + regs.w.cx = 0; + regs.x.edi = (unsigned) &dpmiregs; + segregs.es = segregs.ds; + int386x(DPMI_INT, ®s, ®s, &segregs); +} + + +void DPMIInt(int i) +{ + dpmiregs.ss = realstackseg; + dpmiregs.sp = REALSTACKSIZE - 4; + + segread(&segregs); + regs.w.ax = 0x300; + regs.w.bx = i; + regs.w.cx = 0; + regs.x.edi = (unsigned) &dpmiregs; + segregs.es = segregs.ds; + int386x(DPMI_INT, ®s, ®s, &segregs); +} + + +/* +============== += += I_StartupDPMI += +============== +*/ + +void I_StartupDPMI(void) +{ + extern char __begtext; + extern char ___argc; + int n, d; + +// +// allocate a decent stack for real mode ISRs +// + realstackseg = (int) I_AllocLow(1024) >> 4; + +// +// lock the entire program down +// + +// _dpmi_lockregion (&__begtext, &___argc - &__begtext); + + +// +// catch divide by 0 exception +// +#if 0 + segread(&segregs); + regs.w.ax = 0x0203; // DPMI set processor exception handler vector + regs.w.bx = 0; // int 0 + regs.w.cx = segregs.cs; + regs.x.edx = (int) &I_DivException; + printf("%x : %x\n", regs.w.cx, regs.x.edx); + int386(DPMI_INT, ®s, ®s); +#endif + +#if 0 + n = I_SetDivException(); + printf("return: %i\n", n); + n = 100; + d = 0; + printf("100 / 0 = %i\n", n / d); + + exit(1); +#endif +} + + + +/* +============================================================================ + + TIMER INTERRUPT + +============================================================================ +*/ + +void (__interrupt __far * oldtimerisr) (); + + +void IO_ColorBlack(int r, int g, int b) +{ + _outbyte(PEL_WRITE_ADR, 0); + _outbyte(PEL_DATA, r); + _outbyte(PEL_DATA, g); + _outbyte(PEL_DATA, b); +} + + +/* +================ += += IO_TimerISR += +================ +*/ + +//void __interrupt IO_TimerISR (void) + +void __interrupt __far IO_TimerISR(void) +{ + ticcount++; + _outbyte(0x20, 0x20); // Ack the interrupt +} + +/* +===================== += += IO_SetTimer0 += += Sets system timer 0 to the specified speed += +===================== +*/ + +void IO_SetTimer0(int speed) +{ + if (speed > 0 && speed < 150) + I_Error("INT_SetTimer0: %i is a bad value", speed); + + _outbyte(0x43, 0x36); // Change timer 0 + _outbyte(0x40, speed); + _outbyte(0x40, speed >> 8); +} + + + +/* +=============== += += IO_StartupTimer += +=============== +*/ + +void IO_StartupTimer(void) +{ + oldtimerisr = _dos_getvect(TIMERINT); + + _dos_setvect(0x8000 | TIMERINT, IO_TimerISR); + IO_SetTimer0(VBLCOUNTER); +} + +void IO_ShutdownTimer(void) +{ + if (oldtimerisr) + { + IO_SetTimer0(0); // back to 18.4 ips + _dos_setvect(TIMERINT, oldtimerisr); + } +} + +//=========================================================================== + + +/* +=============== += += I_Init += += hook interrupts and set graphics mode += +=============== +*/ + +void I_Init(void) +{ + extern void I_StartupTimer(void); + + novideo = M_CheckParm("novideo"); + tprintf("I_StartupDPMI", 1); + I_StartupDPMI(); + tprintf("I_StartupMouse ", 1); + I_StartupMouse(); +// tprintf("I_StartupJoystick ",1); +// I_StartupJoystick(); +// tprintf("I_StartupKeyboard ",1); +// I_StartupKeyboard(); +} + + +/* +=============== += += I_Shutdown += += return to default system state += +=============== +*/ + +void I_Shutdown(void) +{ + I_ShutdownGraphics(); + IO_ShutdownTimer(); + S_ShutDown(); + I_ShutdownMouse(); + I_ShutdownKeyboard(); + + IO_SetTimer0(0); +} + + +/* +================ += += I_Error += +================ +*/ + +void I_Error(char *error, ...) +{ + union REGS regs; + + va_list argptr; + + D_QuitNetGame(); + I_Shutdown(); + va_start(argptr, error); + regs.x.eax = 0x3; + int386(0x10, ®s, ®s); + vprintf(error, argptr); + va_end(argptr); + printf("\n"); + exit(1); +} + +//-------------------------------------------------------------------------- +// +// I_Quit +// +// Shuts down net game, saves defaults, prints the exit text message, +// goes to text mode, and exits. +// +//-------------------------------------------------------------------------- + +void I_Quit(void) +{ + byte *scr; + char *lumpName; + int r; + + D_QuitNetGame(); + M_SaveDefaults(); + scr = (byte *) W_CacheLumpName("ENDTEXT", PU_CACHE); + I_Shutdown(); + memcpy((void *) 0xb8000, scr, 80 * 25 * 2); + regs.w.ax = 0x0200; + regs.h.bh = 0; + regs.h.dl = 0; + regs.h.dh = 23; + int386(0x10, (const union REGS *) ®s, ®s); // Set text pos + _settextposition(24, 1); + exit(0); +} + +/* +=============== += += I_ZoneBase += +=============== +*/ + +byte *I_ZoneBase(int *size) +{ + int meminfo[32]; + int heap; + int i; + int block; + byte *ptr; + + memset(meminfo, 0, sizeof(meminfo)); + segread(&segregs); + segregs.es = segregs.ds; + regs.w.ax = 0x500; // get memory info + regs.x.edi = (int) &meminfo; + int386x(0x31, ®s, ®s, &segregs); + + heap = meminfo[0]; + printf("DPMI memory: 0x%x, ", heap); + + do + { + heap -= 0x10000; // leave 64k alone + if (heap > 0x800000) + heap = 0x800000; + ptr = malloc(heap); + } + while (!ptr); + + printf("0x%x allocated for zone\n", heap); + if (heap < 0x180000) + I_Error("Insufficient DPMI memory!"); +#if 0 + regs.w.ax = 0x501; // allocate linear block + regs.w.bx = heap >> 16; + regs.w.cx = heap & 0xffff; + int386(0x31, ®s, ®s); + if (regs.w.cflag) + I_Error("Couldn't allocate DPMI memory!"); + + block = (regs.w.si << 16) + regs.w.di; +#endif + + *size = heap; + return ptr; +} + +/* +============================================================================= + + DISK ICON FLASHING + +============================================================================= +*/ + +void I_InitDiskFlash(void) +{ +#if 0 + void *pic; + byte *temp; + + pic = W_CacheLumpName("STDISK", PU_CACHE); + temp = destscreen; + destscreen = (byte *) 0xac000; + V_DrawPatchDirect(SCREENWIDTH - 16, SCREENHEIGHT - 16, 0, pic); + destscreen = temp; +#endif +} + +// draw disk icon +void I_BeginRead(void) +{ +#if 0 + byte *src, *dest; + int y; + + if (!grmode) + return; + +// write through all planes + outp(SC_INDEX, SC_MAPMASK); + outp(SC_INDEX + 1, 15); +// set write mode 1 + outp(GC_INDEX, GC_MODE); + outp(GC_INDEX + 1, inp(GC_INDEX + 1) | 1); + +// copy to backup + src = currentscreen + 184 * 80 + 304 / 4; + dest = (byte *) 0xac000 + 184 * 80 + 288 / 4; + for (y = 0; y < 16; y++) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; + src += 80; + dest += 80; + } + +// copy disk over + dest = currentscreen + 184 * 80 + 304 / 4; + src = (byte *) 0xac000 + 184 * 80 + 304 / 4; + for (y = 0; y < 16; y++) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; + src += 80; + dest += 80; + } + + +// set write mode 0 + outp(GC_INDEX, GC_MODE); + outp(GC_INDEX + 1, inp(GC_INDEX + 1) & ~1); +#endif +} + +// erase disk icon +void I_EndRead(void) +{ +#if 0 + byte *src, *dest; + int y; + + if (!grmode) + return; + +// write through all planes + outp(SC_INDEX, SC_MAPMASK); + outp(SC_INDEX + 1, 15); +// set write mode 1 + outp(GC_INDEX, GC_MODE); + outp(GC_INDEX + 1, inp(GC_INDEX + 1) | 1); + + +// copy disk over + dest = currentscreen + 184 * 80 + 304 / 4; + src = (byte *) 0xac000 + 184 * 80 + 288 / 4; + for (y = 0; y < 16; y++) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; + src += 80; + dest += 80; + } + +// set write mode 0 + outp(GC_INDEX, GC_MODE); + outp(GC_INDEX + 1, inp(GC_INDEX + 1) & ~1); +#endif +} + + + +/* +============= += += I_AllocLow += +============= +*/ + +byte *I_AllocLow(int length) +{ + byte *mem; + + // DPMI call 100h allocates DOS memory + segread(&segregs); + regs.w.ax = 0x0100; // DPMI allocate DOS memory + regs.w.bx = (length + 15) / 16; + int386(DPMI_INT, ®s, ®s); +// segment = regs.w.ax; +// selector = regs.w.dx; + if (regs.w.cflag != 0) + I_Error("I_AllocLow: DOS alloc of %i failed, %i free", + length, regs.w.bx * 16); + + + mem = (void *) ((regs.x.eax & 0xFFFF) << 4); + + memset(mem, 0, length); + return mem; +} + +/* +============================================================================ + + NETWORKING + +============================================================================ +*/ + +/* // FUCKED LINES +typedef struct +{ + char priv[508]; + } doomdata_t; +*/// FUCKED LINES + +#define DOOMCOM_ID 0x12345678l + +/* // FUCKED LINES +typedef struct +{ + long id; + short intnum; // DOOM executes an int to execute commands + +// communication between DOOM and the driver + short command; // CMD_SEND or CMD_GET + short remotenode; // dest for send, set by get (-1 = no packet) + short datalength; // bytes in doomdata to be sent + +// info common to all nodes + short numnodes; // console is allways node 0 + short ticdup; // 1 = no duplication, 2-5 = dup for slow nets + short extratics; // 1 = send a backup tic in every packet + short deathmatch; // 1 = deathmatch + short savegame; // -1 = new game, 0-5 = load savegame + short episode; // 1-3 + short map; // 1-9 + short skill; // 1-5 + +// info specific to this node + short consoleplayer; + short numplayers; + short angleoffset; // 1 = left, 0 = center, -1 = right + short drone; // 1 = drone + +// packet data to be sent + doomdata_t data; + } doomcom_t; +*/// FUCKED LINES + +extern doomcom_t *doomcom; + +/* +==================== += += I_InitNetwork += +==================== +*/ + +void I_InitNetwork(void) +{ + int i; + + i = M_CheckParm("-net"); + if (!i) + { + // + // single player game + // + doomcom = malloc(sizeof(*doomcom)); + memset(doomcom, 0, sizeof(*doomcom)); + netgame = false; + doomcom->id = DOOMCOM_ID; + doomcom->numplayers = doomcom->numnodes = 1; + doomcom->deathmatch = false; + doomcom->consoleplayer = 0; + doomcom->ticdup = 1; + doomcom->extratics = 0; + return; + } + + netgame = true; + doomcom = (doomcom_t *) atoi(myargv[i + 1]); +//DEBUG + doomcom->skill = startskill; + doomcom->episode = startepisode; + doomcom->map = startmap; + doomcom->deathmatch = deathmatch; + +} + +void I_NetCmd(void) +{ + if (!netgame) + I_Error("I_NetCmd when not in netgame"); + DPMIInt(doomcom->intnum); +} + +int i_Vector; +externdata_t *i_ExternData; +boolean useexterndriver; + +//========================================================================= +// +// I_CheckExternDriver +// +// Checks to see if a vector, and an address for an external driver +// have been passed. +//========================================================================= + +void I_CheckExternDriver(void) +{ + int i; + + if (!(i = M_CheckParm("-externdriver"))) + { + return; + } + i_ExternData = (externdata_t *) atoi(myargv[i + 1]); + i_Vector = i_ExternData->vector; + + useexterndriver = true; +} + +//========================================================================= +// +// I_ReadExternDriver +// +// calls the external interrupt, which should then update i_ExternDriver +//========================================================================= + +void I_ReadExternDriver(void) +{ + event_t ev; + + if (useexterndriver) + { + DPMIInt(i_Vector); + } +} diff --git a/src/heretic/i_sound.c b/src/heretic/i_sound.c new file mode 100644 index 00000000..d3f04767 --- /dev/null +++ b/src/heretic/i_sound.c @@ -0,0 +1,432 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// I_SOUND.C + +#include <stdio.h> +#include "doomdef.h" +#include "dmx.h" +#include "sounds.h" +#include "i_sound.h" +#include "v_video.h" + +/* +=============== += += I_StartupTimer += +=============== +*/ + +int tsm_ID = -1; + +void I_StartupTimer(void) +{ +#ifndef NOTIMER + extern int I_TimerISR(void); + + tprintf("I_StartupTimer()\n", 0); + // installs master timer. Must be done before StartupTimer()! + TSM_Install(SND_TICRATE); + tsm_ID = TSM_NewService(I_TimerISR, 35, 255, 0); // max priority + if (tsm_ID == -1) + { + I_Error("Can't register 35 Hz timer w/ DMX library"); + } +#endif +} + +void I_ShutdownTimer(void) +{ + TSM_DelService(tsm_ID); + TSM_Remove(); +} + +/* + * + * SOUND HEADER & DATA + * + * + */ + +// sound information +#if 0 +const char *dnames[] = { "None", + "PC_Speaker", + "Adlib", + "Sound_Blaster", + "ProAudio_Spectrum16", + "Gravis_Ultrasound", + "MPU", + "AWE32" +}; +#endif + +const char snd_prefixen[] = { 'P', 'P', 'A', 'S', 'S', 'S', 'M', + 'M', 'M', 'S' +}; + +int snd_Channels; +int snd_DesiredMusicDevice, snd_DesiredSfxDevice; +int snd_MusicDevice, // current music card # (index to dmxCodes) + snd_SfxDevice, // current sfx card # (index to dmxCodes) + snd_MaxVolume, // maximum volume for sound + snd_MusicVolume; // maximum volume for music +int dmxCodes[NUM_SCARDS]; // the dmx code for a given card + +int snd_SBport, snd_SBirq, snd_SBdma; // sound blaster variables +int snd_Mport; // midi variables + +extern boolean snd_MusicAvail, // whether music is available + snd_SfxAvail; // whether sfx are available + +void I_PauseSong(int handle) +{ + MUS_PauseSong(handle); +} + +void I_ResumeSong(int handle) +{ + MUS_ResumeSong(handle); +} + +void I_SetMusicVolume(int volume) +{ + MUS_SetMasterVolume(volume * 8); +// snd_MusicVolume = volume; +} + +void I_SetSfxVolume(int volume) +{ + snd_MaxVolume = volume; // THROW AWAY? +} + +/* + * + * SONG API + * + */ + +int I_RegisterSong(void *data) +{ + int rc = MUS_RegisterSong(data); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_Reg() returned %d\n", rc); +#endif + return rc; +} + +void I_UnRegisterSong(int handle) +{ + int rc = MUS_UnregisterSong(handle); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_Unreg() returned %d\n", rc); +#endif +} + +int I_QrySongPlaying(int handle) +{ + int rc = MUS_QrySongPlaying(handle); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_QrySP() returned %d\n", rc); +#endif + return rc; +} + +// Stops a song. MUST be called before I_UnregisterSong(). + +void I_StopSong(int handle) +{ + int rc; + rc = MUS_StopSong(handle); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_StopSong() returned %d\n", rc); +#endif + // Fucking kluge pause + { + int s; + extern volatile int ticcount; + for (s = ticcount; ticcount - s < 10;); + } +} + +void I_PlaySong(int handle, boolean looping) +{ + int rc; + rc = MUS_ChainSong(handle, looping ? handle : -1); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_ChainSong() returned %d\n", rc); +#endif + rc = MUS_PlaySong(handle, snd_MusicVolume); +#ifdef SNDDEBUG + if (rc < 0) + printf("MUS_PlaySong() returned %d\n", rc); +#endif + +} + +/* + * + * SOUND FX API + * + */ + +// Gets lump nums of the named sound. Returns pointer which will be +// passed to I_StartSound() when you want to start an SFX. Must be +// sure to pass this to UngetSoundEffect() so that they can be +// freed! + + +int I_GetSfxLumpNum(sfxinfo_t * sound) +{ + char namebuf[9]; + + if (sound->name == 0) + return 0; + if (sound->link) + sound = sound->link; +// sprintf(namebuf, "d%c%s", snd_prefixen[snd_SfxDevice], sound->name); + return W_GetNumForName(sound->name); + +} + +int I_StartSound(int id, void *data, int vol, int sep, int pitch, + int priority) +{ +/* + // hacks out certain PC sounds + if (snd_SfxDevice == PC + && (data == S_sfx[sfx_posact].data + || data == S_sfx[sfx_bgact].data + || data == S_sfx[sfx_dmact].data + || data == S_sfx[sfx_dmpain].data + || data == S_sfx[sfx_popain].data + || data == S_sfx[sfx_sawidl].data)) return -1; + + else + */ + return SFX_PlayPatch(data, pitch, sep, vol, 0, 0); + +} + +void I_StopSound(int handle) +{ +// extern volatile long gDmaCount; +// long waittocount; + SFX_StopPatch(handle); +// waittocount = gDmaCount + 2; +// while (gDmaCount < waittocount) ; +} + +int I_SoundIsPlaying(int handle) +{ + return SFX_Playing(handle); +} + +void I_UpdateSoundParams(int handle, int vol, int sep, int pitch) +{ + SFX_SetOrigin(handle, pitch, sep, vol); +} + +/* + * + * SOUND STARTUP STUFF + * + * + */ + +// +// Why PC's Suck, Reason #8712 +// + +void I_sndArbitrateCards(void) +{ + char tmp[160]; + boolean gus, adlib, pc, sb, midi; + int i, rc, mputype, p, opltype, wait, dmxlump; + +// snd_MaxVolume = 127; + //Damn you, Dave Taylor! + + snd_MusicDevice = snd_DesiredMusicDevice; + snd_SfxDevice = snd_DesiredSfxDevice; + + // check command-line parameters- overrides config file + // + if (M_CheckParm("-nosound")) + snd_MusicDevice = snd_SfxDevice = snd_none; + if (M_CheckParm("-nosfx")) + snd_SfxDevice = snd_none; + if (M_CheckParm("-nomusic")) + snd_MusicDevice = snd_none; + + if (snd_MusicDevice > snd_MPU && snd_MusicDevice <= snd_MPU3) + snd_MusicDevice = snd_MPU; + if (snd_MusicDevice == snd_SB) + snd_MusicDevice = snd_Adlib; + if (snd_MusicDevice == snd_PAS) + snd_MusicDevice = snd_Adlib; + + // figure out what i've got to initialize + // + gus = snd_MusicDevice == snd_GUS || snd_SfxDevice == snd_GUS; + sb = snd_SfxDevice == snd_SB || snd_MusicDevice == snd_SB; + adlib = snd_MusicDevice == snd_Adlib; + pc = snd_SfxDevice == snd_PC; + midi = snd_MusicDevice == snd_MPU; + + // initialize whatever i've got + // + if (gus) + { + if (GF1_Detect()) + tprintf("Dude. The GUS ain't responding.\n", 1); + else + { + dmxlump = W_GetNumForName("dmxgus"); + GF1_SetMap(W_CacheLumpNum(dmxlump, PU_CACHE), + lumpinfo[dmxlump].size); + } + + } + if (sb) + { + if (debugmode) + { + sprintf(tmp, "cfg p=0x%x, i=%d, d=%d\n", + snd_SBport, snd_SBirq, snd_SBdma); + tprintf(tmp, 0); + } + if (SB_Detect(&snd_SBport, &snd_SBirq, &snd_SBdma, 0)) + { + sprintf(tmp, "SB isn't responding at p=0x%x, i=%d, d=%d\n", + snd_SBport, snd_SBirq, snd_SBdma); + tprintf(tmp, 0); + } + else + SB_SetCard(snd_SBport, snd_SBirq, snd_SBdma); + + if (debugmode) + { + sprintf(tmp, "SB_Detect returned p=0x%x,i=%d,d=%d\n", + snd_SBport, snd_SBirq, snd_SBdma); + tprintf(tmp, 0); + } + } + + if (adlib) + { + if (AL_Detect(&wait, 0)) + tprintf("Dude. The Adlib isn't responding.\n", 1); + else + AL_SetCard(wait, W_CacheLumpName("genmidi", PU_STATIC)); + } + + if (midi) + { + if (debugmode) + { + sprintf(tmp, "cfg p=0x%x\n", snd_Mport); + tprintf(tmp, 0); + } + + if (MPU_Detect(&snd_Mport, &i)) + { + sprintf(tmp, "The MPU-401 isn't reponding @ p=0x%x.\n", + snd_Mport); + tprintf(tmp, 0); + } + else + MPU_SetCard(snd_Mport); + } + +} + +// inits all sound stuff + +void I_StartupSound(void) +{ + char tmp[80]; + int rc, i; + + if (debugmode) + tprintf("I_StartupSound: Hope you hear a pop.\n", 1); + + // initialize dmxCodes[] + dmxCodes[0] = 0; + dmxCodes[snd_PC] = AHW_PC_SPEAKER; + dmxCodes[snd_Adlib] = AHW_ADLIB; + dmxCodes[snd_SB] = AHW_SOUND_BLASTER; + dmxCodes[snd_PAS] = AHW_MEDIA_VISION; + dmxCodes[snd_GUS] = AHW_ULTRA_SOUND; + dmxCodes[snd_MPU] = AHW_MPU_401; + dmxCodes[snd_AWE] = AHW_AWE32; + + // inits sound library timer stuff + I_StartupTimer(); + + // pick the sound cards i'm going to use + // + I_sndArbitrateCards(); + + if (debugmode) + { + sprintf(tmp, " Music device #%d & dmxCode=%d", snd_MusicDevice, + dmxCodes[snd_MusicDevice]); + tprintf(tmp, 0); + sprintf(tmp, " Sfx device #%d & dmxCode=%d\n", snd_SfxDevice, + dmxCodes[snd_SfxDevice]); + tprintf(tmp, 0); + } + + // inits DMX sound library + tprintf(" calling DMX_Init", 0); + rc = DMX_Init(SND_TICRATE, SND_MAXSONGS, dmxCodes[snd_MusicDevice], + dmxCodes[snd_SfxDevice]); + + if (debugmode) + { + sprintf(tmp, " DMX_Init() returned %d", rc); + tprintf(tmp, 0); + } + +} + +// shuts down all sound stuff + +void I_ShutdownSound(void) +{ + DMX_DeInit(); + I_ShutdownTimer(); +} + +void I_SetChannels(int channels) +{ + WAV_PlayMode(channels, SND_SAMPLERATE); +} diff --git a/src/heretic/in_lude.c b/src/heretic/in_lude.c new file mode 100644 index 00000000..3c382814 --- /dev/null +++ b/src/heretic/in_lude.c @@ -0,0 +1,1077 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +/* +======================== += += IN_lude.c += +======================== +*/ + +#include "doomdef.h" +#include "deh_str.h" +#include "s_sound.h" +#include "i_system.h" +#include "i_video.h" +#include "v_video.h" + +typedef enum +{ + SINGLE, + COOPERATIVE, + DEATHMATCH +} gametype_t; + +// Public functions + +void IN_Start(void); +void IN_Ticker(void); +void IN_Drawer(void); + +boolean intermission; + +// Private functions + +void IN_WaitStop(void); +void IN_Stop(void); +void IN_LoadPics(void); +void IN_UnloadPics(void); +void IN_CheckForSkip(void); +void IN_InitStats(void); +void IN_InitDeathmatchStats(void); +void IN_InitNetgameStats(void); +void IN_DrawOldLevel(void); +void IN_DrawYAH(void); +void IN_DrawStatBack(void); +void IN_DrawSingleStats(void); +void IN_DrawCoopStats(void); +void IN_DrawDMStats(void); +void IN_DrawNumber(int val, int x, int y, int digits); +void IN_DrawTime(int x, int y, int h, int m, int s); +void IN_DrTextB(char *text, int x, int y); + +static boolean skipintermission; +static int interstate = 0; +static int intertime = -1; +static int oldintertime = 0; +static gametype_t gametype; + +static int cnt; + +static int hours; +static int minutes; +static int seconds; + +static int slaughterboy; // in DM, the player with the most kills + +static int killPercent[MAXPLAYERS]; +static int bonusPercent[MAXPLAYERS]; +static int secretPercent[MAXPLAYERS]; + +static patch_t *patchINTERPIC; +static patch_t *patchBEENTHERE; +static patch_t *patchGOINGTHERE; +static patch_t *FontBNumbers[10]; +static patch_t *FontBNegative; +static patch_t *FontBSlash; +static patch_t *FontBPercent; + +static int FontBLump; +static int FontBLumpBase; +static int patchFaceOkayBase; +static int patchFaceDeadBase; + +static signed int totalFrags[MAXPLAYERS]; +static fixed_t dSlideX[MAXPLAYERS]; +static fixed_t dSlideY[MAXPLAYERS]; + +static char *KillersText[] = { "K", "I", "L", "L", "E", "R", "S" }; + +extern char *LevelNames[]; + +typedef struct +{ + int x; + int y; +} yahpt_t; + +static yahpt_t YAHspot[3][9] = { + { + {172, 78}, + {86, 90}, + {73, 66}, + {159, 95}, + {148, 126}, + {132, 54}, + {131, 74}, + {208, 138}, + {52, 101} + }, + { + {218, 57}, + {137, 81}, + {155, 124}, + {171, 68}, + {250, 86}, + {136, 98}, + {203, 90}, + {220, 140}, + {279, 106} + }, + { + {86, 99}, + {124, 103}, + {154, 79}, + {202, 83}, + {178, 59}, + {142, 58}, + {219, 66}, + {247, 57}, + {107, 80} + } +}; + +//======================================================================== +// +// IN_Start +// +//======================================================================== + +extern void AM_Stop(void); + +void IN_Start(void) +{ + I_SetPalette(W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE)); + IN_LoadPics(); + IN_InitStats(); + intermission = true; + interstate = -1; + skipintermission = false; + intertime = 0; + oldintertime = 0; + AM_Stop(); + S_StartSong(mus_intr, true); +} + +//======================================================================== +// +// IN_WaitStop +// +//======================================================================== + +void IN_WaitStop(void) +{ + if (!--cnt) + { + IN_Stop(); + G_WorldDone(); + } +} + +//======================================================================== +// +// IN_Stop +// +//======================================================================== + +void IN_Stop(void) +{ + intermission = false; + IN_UnloadPics(); + SB_state = -1; + BorderNeedRefresh = true; +} + +//======================================================================== +// +// IN_InitStats +// +// Initializes the stats for single player mode +//======================================================================== + +void IN_InitStats(void) +{ + int i; + int j; + signed int slaughterfrags; + int posnum; + int slaughtercount; + int playercount; + int count; + + if (!netgame) + { + gametype = SINGLE; + count = leveltime / 35; + hours = count / 3600; + count -= hours * 3600; + minutes = count / 60; + count -= minutes * 60; + seconds = count; + } + else if (netgame && !deathmatch) + { + gametype = COOPERATIVE; + memset(killPercent, 0, MAXPLAYERS * sizeof(int)); + memset(bonusPercent, 0, MAXPLAYERS * sizeof(int)); + memset(secretPercent, 0, MAXPLAYERS * sizeof(int)); + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (totalkills) + { + killPercent[i] = players[i].killcount * 100 / totalkills; + } + if (totalitems) + { + bonusPercent[i] = players[i].itemcount * 100 / totalitems; + } + if (totalsecret) + { + secretPercent[i] = + players[i].secretcount * 100 / totalsecret; + } + } + } + } + else + { + gametype = DEATHMATCH; + slaughterboy = 0; + slaughterfrags = -9999; + posnum = 0; + playercount = 0; + slaughtercount = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + totalFrags[i] = 0; + if (playeringame[i]) + { + playercount++; + for (j = 0; j < MAXPLAYERS; j++) + { + if (playeringame[j]) + { + totalFrags[i] += players[i].frags[j]; + } + } + dSlideX[i] = (43 * posnum * FRACUNIT) / 20; + dSlideY[i] = (36 * posnum * FRACUNIT) / 20; + posnum++; + } + if (totalFrags[i] > slaughterfrags) + { + slaughterboy = 1 << i; + slaughterfrags = totalFrags[i]; + slaughtercount = 1; + } + else if (totalFrags[i] == slaughterfrags) + { + slaughterboy |= 1 << i; + slaughtercount++; + } + } + if (playercount == slaughtercount) + { // don't do the slaughter stuff if everyone is equal + slaughterboy = 0; + } + } +} + +static void IN_LoadUnloadPics(void (*callback)(char *lumpname, + int lumpnum, + patch_t **ptr)) +{ + int i; + + switch (gameepisode) + { + case 1: + callback(DEH_String("MAPE1"), 0, &patchINTERPIC); + break; + case 2: + callback(DEH_String("MAPE2"), 0, &patchINTERPIC); + break; + case 3: + callback(DEH_String("MAPE3"), 0, &patchINTERPIC); + break; + default: + break; + } + + callback(DEH_String("IN_X"), 0, &patchBEENTHERE); + callback(DEH_String("IN_YAH"), 0, &patchGOINGTHERE); + callback(DEH_String("FONTB13"), 0, &FontBNegative); + + callback(DEH_String("FONTB15"), 0, &FontBSlash); + callback(DEH_String("FONTB05"), 0, &FontBPercent); + + FontBLumpBase = W_GetNumForName(DEH_String("FONTB16")); + + for (i = 0; i < 10; i++) + { + callback(NULL, FontBLumpBase + i, &FontBNumbers[i]); + } +} + +//======================================================================== +// +// IN_LoadPics +// +//======================================================================== + +static void LoadLumpCallback(char *lumpname, int lumpnum, patch_t **ptr) +{ + if (lumpname != NULL) + { + lumpnum = W_GetNumForName(lumpname); + } + + // Cache the lump + + *ptr = W_CacheLumpNum(lumpnum, PU_STATIC); +} + +void IN_LoadPics(void) +{ + FontBLump = W_GetNumForName(DEH_String("FONTB_S")) + 1; + patchFaceOkayBase = W_GetNumForName(DEH_String("FACEA0")); + patchFaceDeadBase = W_GetNumForName(DEH_String("FACEB0")); + + IN_LoadUnloadPics(LoadLumpCallback); +} + +//======================================================================== +// +// IN_UnloadPics +// +//======================================================================== + +static void UnloadLumpCallback(char *lumpname, int lumpnum, patch_t **ptr) +{ + if (lumpname != NULL) + { + W_ReleaseLumpName(lumpname); + } + else + { + W_ReleaseLumpNum(lumpnum); + } +} + +void IN_UnloadPics(void) +{ + IN_LoadUnloadPics(UnloadLumpCallback); +} + +//======================================================================== +// +// IN_Ticker +// +//======================================================================== + +void IN_Ticker(void) +{ + if (!intermission) + { + return; + } + if (interstate == 3) + { + IN_WaitStop(); + return; + } + IN_CheckForSkip(); + intertime++; + if (oldintertime < intertime) + { + interstate++; + if (gameepisode > 3 && interstate >= 1) + { // Extended Wad levels: skip directly to the next level + interstate = 3; + } + switch (interstate) + { + case 0: + oldintertime = intertime + 300; + if (gameepisode > 3) + { + oldintertime = intertime + 1200; + } + break; + case 1: + oldintertime = intertime + 200; + break; + case 2: + oldintertime = INT_MAX; + break; + case 3: + cnt = 10; + break; + default: + break; + } + } + if (skipintermission) + { + if (interstate == 0 && intertime < 150) + { + intertime = 150; + skipintermission = false; + return; + } + else if (interstate < 2 && gameepisode < 4) + { + interstate = 2; + skipintermission = false; + S_StartSound(NULL, sfx_dorcls); + return; + } + interstate = 3; + cnt = 10; + skipintermission = false; + S_StartSound(NULL, sfx_dorcls); + } +} + +//======================================================================== +// +// IN_CheckForSkip +// +// Check to see if any player hit a key +//======================================================================== + +void IN_CheckForSkip(void) +{ + int i; + player_t *player; + + for (i = 0, player = players; i < MAXPLAYERS; i++, player++) + { + if (playeringame[i]) + { + if (player->cmd.buttons & BT_ATTACK) + { + if (!player->attackdown) + { + skipintermission = 1; + } + player->attackdown = true; + } + else + { + player->attackdown = false; + } + if (player->cmd.buttons & BT_USE) + { + if (!player->usedown) + { + skipintermission = 1; + } + player->usedown = true; + } + else + { + player->usedown = false; + } + } + } +} + +//======================================================================== +// +// IN_Drawer +// +//======================================================================== + +void IN_Drawer(void) +{ + static int oldinterstate; + + if (!intermission) + { + return; + } + if (interstate == 3) + { + return; + } + UpdateState |= I_FULLSCRN; + if (oldinterstate != 2 && interstate == 2) + { + S_StartSound(NULL, sfx_pstop); + } + oldinterstate = interstate; + switch (interstate) + { + case 0: // draw stats + IN_DrawStatBack(); + switch (gametype) + { + case SINGLE: + IN_DrawSingleStats(); + break; + case COOPERATIVE: + IN_DrawCoopStats(); + break; + case DEATHMATCH: + IN_DrawDMStats(); + break; + } + break; + case 1: // leaving old level + if (gameepisode < 4) + { + V_DrawPatch(0, 0, patchINTERPIC); + IN_DrawOldLevel(); + } + break; + case 2: // going to the next level + if (gameepisode < 4) + { + V_DrawPatch(0, 0, patchINTERPIC); + IN_DrawYAH(); + } + break; + case 3: // waiting before going to the next level + if (gameepisode < 4) + { + V_DrawPatch(0, 0, patchINTERPIC); + } + break; + default: + I_Error("IN_lude: Intermission state out of range.\n"); + break; + } +} + +//======================================================================== +// +// IN_DrawStatBack +// +//======================================================================== + +void IN_DrawStatBack(void) +{ + int x; + int y; + + byte *src; + byte *dest; + + src = W_CacheLumpName(DEH_String("FLOOR16"), PU_CACHE); + dest = I_VideoBuffer; + + for (y = 0; y < SCREENHEIGHT; y++) + { + for (x = 0; x < SCREENWIDTH / 64; x++) + { + memcpy(dest, src + ((y & 63) << 6), 64); + dest += 64; + } + if (SCREENWIDTH & 63) + { + memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63); + dest += (SCREENWIDTH & 63); + } + } +} + +//======================================================================== +// +// IN_DrawOldLevel +// +//======================================================================== + +void IN_DrawOldLevel(void) +{ + int i; + int x; + + x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + + 7) / 2; + IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3); + x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2; + MN_DrTextA(DEH_String("FINISHED"), x, 25); + + if (prevmap == 9) + { + for (i = 0; i < gamemap - 1; i++) + { + V_DrawPatch(YAHspot[gameepisode - 1][i].x, + YAHspot[gameepisode - 1][i].y, patchBEENTHERE); + } + if (!(intertime & 16)) + { + V_DrawPatch(YAHspot[gameepisode - 1][8].x, + YAHspot[gameepisode - 1][8].y, patchBEENTHERE); + } + } + else + { + for (i = 0; i < prevmap - 1; i++) + { + V_DrawPatch(YAHspot[gameepisode - 1][i].x, + YAHspot[gameepisode - 1][i].y, patchBEENTHERE); + } + if (players[consoleplayer].didsecret) + { + V_DrawPatch(YAHspot[gameepisode - 1][8].x, + YAHspot[gameepisode - 1][8].y, patchBEENTHERE); + } + if (!(intertime & 16)) + { + V_DrawPatch(YAHspot[gameepisode - 1][prevmap - 1].x, + YAHspot[gameepisode - 1][prevmap - 1].y, + patchBEENTHERE); + } + } +} + +//======================================================================== +// +// IN_DrawYAH +// +//======================================================================== + +void IN_DrawYAH(void) +{ + int i; + int x; + + x = 160 - MN_TextAWidth(DEH_String("NOW ENTERING:")) / 2; + MN_DrTextA(DEH_String("NOW ENTERING:"), x, 10); + x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] + + 7) / 2; + IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] + 7, x, 20); + + if (prevmap == 9) + { + prevmap = gamemap - 1; + } + for (i = 0; i < prevmap; i++) + { + V_DrawPatch(YAHspot[gameepisode - 1][i].x, + YAHspot[gameepisode - 1][i].y, patchBEENTHERE); + } + if (players[consoleplayer].didsecret) + { + V_DrawPatch(YAHspot[gameepisode - 1][8].x, + YAHspot[gameepisode - 1][8].y, patchBEENTHERE); + } + if (!(intertime & 16) || interstate == 3) + { // draw the destination 'X' + V_DrawPatch(YAHspot[gameepisode - 1][gamemap - 1].x, + YAHspot[gameepisode - 1][gamemap - 1].y, patchGOINGTHERE); + } +} + +//======================================================================== +// +// IN_DrawSingleStats +// +//======================================================================== + +void IN_DrawSingleStats(void) +{ + int x; + static int sounds; + + IN_DrTextB(DEH_String("KILLS"), 50, 65); + IN_DrTextB(DEH_String("ITEMS"), 50, 90); + IN_DrTextB(DEH_String("SECRETS"), 50, 115); + + x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + + 7) / 2; + IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3); + x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2; + MN_DrTextA(DEH_String("FINISHED"), x, 25); + + if (intertime < 30) + { + sounds = 0; + return; + } + if (sounds < 1 && intertime >= 30) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + IN_DrawNumber(players[consoleplayer].killcount, 200, 65, 3); + V_DrawShadowedPatch(237, 65, FontBSlash); + IN_DrawNumber(totalkills, 248, 65, 3); + if (intertime < 60) + { + return; + } + if (sounds < 2 && intertime >= 60) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + IN_DrawNumber(players[consoleplayer].itemcount, 200, 90, 3); + V_DrawShadowedPatch(237, 90, FontBSlash); + IN_DrawNumber(totalitems, 248, 90, 3); + if (intertime < 90) + { + return; + } + if (sounds < 3 && intertime >= 90) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + IN_DrawNumber(players[consoleplayer].secretcount, 200, 115, 3); + V_DrawShadowedPatch(237, 115, FontBSlash); + IN_DrawNumber(totalsecret, 248, 115, 3); + if (intertime < 150) + { + return; + } + if (sounds < 4 && intertime >= 150) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + + if (gamemode != retail || gameepisode <= 3) + { + IN_DrTextB(DEH_String("TIME"), 85, 160); + IN_DrawTime(155, 160, hours, minutes, seconds); + } + else + { + x = 160 - MN_TextAWidth(DEH_String("NOW ENTERING:")) / 2; + MN_DrTextA(DEH_String("NOW ENTERING:"), x, 160); + x = 160 - + MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] + + 7) / 2; + IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + gamemap - 1] + 7, x, + 170); + skipintermission = false; + } +} + +//======================================================================== +// +// IN_DrawCoopStats +// +//======================================================================== + +void IN_DrawCoopStats(void) +{ + int i; + int x; + int ypos; + + static int sounds; + + IN_DrTextB(DEH_String("KILLS"), 95, 35); + IN_DrTextB(DEH_String("BONUS"), 155, 35); + IN_DrTextB(DEH_String("SECRET"), 232, 35); + x = 160 - MN_TextBWidth(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + + 7) / 2; + IN_DrTextB(LevelNames[(gameepisode - 1) * 9 + prevmap - 1] + 7, x, 3); + x = 160 - MN_TextAWidth(DEH_String("FINISHED")) / 2; + MN_DrTextA(DEH_String("FINISHED"), x, 25); + + ypos = 50; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + V_DrawShadowedPatch(25, ypos, + W_CacheLumpNum(patchFaceOkayBase + i, + PU_CACHE)); + if (intertime < 40) + { + sounds = 0; + ypos += 37; + continue; + } + else if (intertime >= 40 && sounds < 1) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + IN_DrawNumber(killPercent[i], 85, ypos + 10, 3); + V_DrawShadowedPatch(121, ypos + 10, FontBPercent); + IN_DrawNumber(bonusPercent[i], 160, ypos + 10, 3); + V_DrawShadowedPatch(196, ypos + 10, FontBPercent); + IN_DrawNumber(secretPercent[i], 237, ypos + 10, 3); + V_DrawShadowedPatch(273, ypos + 10, FontBPercent); + ypos += 37; + } + } +} + +//======================================================================== +// +// IN_DrawDMStats +// +//======================================================================== + +void IN_DrawDMStats(void) +{ + int i; + int j; + int ypos; + int xpos; + int kpos; + + static int sounds; + + xpos = 90; + ypos = 55; + + IN_DrTextB(DEH_String("TOTAL"), 265, 30); + MN_DrTextA(DEH_String("VICTIMS"), 140, 8); + for (i = 0; i < 7; i++) + { + MN_DrTextA(DEH_String(KillersText[i]), 10, 80 + 9 * i); + } + if (intertime < 20) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + V_DrawShadowedPatch(40, + ((ypos << FRACBITS) + + dSlideY[i] * intertime) >> FRACBITS, + W_CacheLumpNum(patchFaceOkayBase + i, + PU_CACHE)); + V_DrawShadowedPatch(((xpos << FRACBITS) + + dSlideX[i] * intertime) >> FRACBITS, 18, + W_CacheLumpNum(patchFaceDeadBase + i, + PU_CACHE)); + } + } + sounds = 0; + return; + } + if (intertime >= 20 && sounds < 1) + { + S_StartSound(NULL, sfx_dorcls); + sounds++; + } + if (intertime >= 100 && slaughterboy && sounds < 2) + { + S_StartSound(NULL, sfx_wpnup); + sounds++; + } + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (intertime < 100 || i == consoleplayer) + { + V_DrawShadowedPatch(40, ypos, + W_CacheLumpNum(patchFaceOkayBase + i, + PU_CACHE)); + V_DrawShadowedPatch(xpos, 18, + W_CacheLumpNum(patchFaceDeadBase + i, + PU_CACHE)); + } + else + { + V_DrawTLPatch(40, ypos, + W_CacheLumpNum(patchFaceOkayBase + i, + PU_CACHE)); + V_DrawTLPatch(xpos, 18, + W_CacheLumpNum(patchFaceDeadBase + i, + PU_CACHE)); + } + kpos = 86; + for (j = 0; j < MAXPLAYERS; j++) + { + if (playeringame[j]) + { + IN_DrawNumber(players[i].frags[j], kpos, ypos + 10, 3); + kpos += 43; + } + } + if (slaughterboy & (1 << i)) + { + if (!(intertime & 16)) + { + IN_DrawNumber(totalFrags[i], 263, ypos + 10, 3); + } + } + else + { + IN_DrawNumber(totalFrags[i], 263, ypos + 10, 3); + } + ypos += 36; + xpos += 43; + } + } +} + +//======================================================================== +// +// IN_DrawTime +// +//======================================================================== + +void IN_DrawTime(int x, int y, int h, int m, int s) +{ + if (h) + { + IN_DrawNumber(h, x, y, 2); + IN_DrTextB(DEH_String(":"), x + 26, y); + } + x += 34; + if (m || h) + { + IN_DrawNumber(m, x, y, 2); + } + x += 34; + if (s) + { + IN_DrTextB(DEH_String(":"), x - 8, y); + IN_DrawNumber(s, x, y, 2); + } +} + +//======================================================================== +// +// IN_DrawNumber +// +//======================================================================== + +void IN_DrawNumber(int val, int x, int y, int digits) +{ + patch_t *patch; + int xpos; + int oldval; + int realdigits; + boolean neg; + + oldval = val; + xpos = x; + neg = false; + realdigits = 1; + + if (val < 0) + { //...this should reflect negative frags + val = -val; + neg = true; + if (val > 99) + { + val = 99; + } + } + if (val > 9) + { + realdigits++; + if (digits < realdigits) + { + realdigits = digits; + val = 9; + } + } + if (val > 99) + { + realdigits++; + if (digits < realdigits) + { + realdigits = digits; + val = 99; + } + } + if (val > 999) + { + realdigits++; + if (digits < realdigits) + { + realdigits = digits; + val = 999; + } + } + if (digits == 4) + { + patch = FontBNumbers[val / 1000]; + V_DrawShadowedPatch(xpos + 6 - patch->width / 2 - 12, y, patch); + } + if (digits > 2) + { + if (realdigits > 2) + { + patch = FontBNumbers[val / 100]; + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); + } + xpos += 12; + } + val = val % 100; + if (digits > 1) + { + if (val > 9) + { + patch = FontBNumbers[val / 10]; + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); + } + else if (digits == 2 || oldval > 99) + { + V_DrawShadowedPatch(xpos, y, FontBNumbers[0]); + } + xpos += 12; + } + val = val % 10; + patch = FontBNumbers[val]; + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); + if (neg) + { + patch = FontBNegative; + V_DrawShadowedPatch(xpos + 6 - patch->width / 2 - 12 * (realdigits), + y, patch); + } +} + +//======================================================================== +// +// IN_DrTextB +// +//======================================================================== + +void IN_DrTextB(char *text, int x, int y) +{ + char c; + patch_t *p; + + while ((c = *text++) != 0) + { + if (c < 33) + { + x += 8; + } + else + { + p = W_CacheLumpNum(FontBLump + c - 33, PU_CACHE); + V_DrawShadowedPatch(x, y, p); + x += p->width - 1; + } + } +} diff --git a/src/heretic/info.c b/src/heretic/info.c new file mode 100644 index 00000000..b6dd921f --- /dev/null +++ b/src/heretic/info.c @@ -0,0 +1,5608 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +#include "doomdef.h" +#include "p_action.h" + +char *sprnames[] = { + "IMPX","ACLO","PTN1","SHLD","SHD2","BAGH","SPMP","INVS","PTN2","SOAR", + "INVU","PWBK","EGGC","EGGM","FX01","SPHL","TRCH","FBMB","XPL1","ATLP", + "PPOD","AMG1","SPSH","LVAS","SLDG","SKH1","SKH2","SKH3","SKH4","CHDL", + "SRTC","SMPL","STGS","STGL","STCS","STCL","KFR1","BARL","BRPL","MOS1", + "MOS2","WTRH","HCOR","KGZ1","KGZB","KGZG","KGZY","VLCO","VFBL","VTFB", + "SFFI","TGLT","TELE","STFF","PUF3","PUF4","BEAK","WGNT","GAUN","PUF1", + "WBLS","BLSR","FX18","FX17","WMCE","MACE","FX02","WSKL","HROD","FX00", + "FX20","FX21","FX22","FX23","GWND","PUF2","WPHX","PHNX","FX04","FX08", + "FX09","WBOW","CRBW","FX03","BLOD","PLAY","FDTH","BSKL","CHKN","MUMM", + "FX15","BEAS","FRB1","SNKE","SNFX","HEAD","FX05","FX06","FX07","CLNK", + "WZRD","FX11","FX10","KNIG","SPAX","RAXE","SRCR","FX14","SOR2","SDTH", + "FX16","MNTR","FX12","FX13","AKYY","BKYY","CKYY","AMG2","AMM1","AMM2", + "AMC1","AMC2","AMS1","AMS2","AMP1","AMP2","AMB1","AMB2", + NULL +}; + +state_t states[NUMSTATES] = { + {SPR_IMPX, 0, -1, NULL, S_NULL, 0, 0}, // S_NULL + {SPR_ACLO, 4, 1050, A_FreeTargMobj, S_NULL, 0, 0}, // S_FREETARGMOBJ + {SPR_PTN1, 0, 3, NULL, S_ITEM_PTN1_2, 0, 0}, // S_ITEM_PTN1_1 + {SPR_PTN1, 1, 3, NULL, S_ITEM_PTN1_3, 0, 0}, // S_ITEM_PTN1_2 + {SPR_PTN1, 2, 3, NULL, S_ITEM_PTN1_1, 0, 0}, // S_ITEM_PTN1_3 + {SPR_SHLD, 0, -1, NULL, S_NULL, 0, 0}, // S_ITEM_SHLD1 + {SPR_SHD2, 0, -1, NULL, S_NULL, 0, 0}, // S_ITEM_SHD2_1 + {SPR_BAGH, 0, -1, NULL, S_NULL, 0, 0}, // S_ITEM_BAGH1 + {SPR_SPMP, 0, -1, NULL, S_NULL, 0, 0}, // S_ITEM_SPMP1 + {SPR_ACLO, 4, 1400, NULL, S_HIDESPECIAL2, 0, 0}, // S_HIDESPECIAL1 + {SPR_ACLO, 0, 4, A_RestoreSpecialThing1, S_HIDESPECIAL3, 0, 0}, // S_HIDESPECIAL2 + {SPR_ACLO, 1, 4, NULL, S_HIDESPECIAL4, 0, 0}, // S_HIDESPECIAL3 + {SPR_ACLO, 0, 4, NULL, S_HIDESPECIAL5, 0, 0}, // S_HIDESPECIAL4 + {SPR_ACLO, 1, 4, NULL, S_HIDESPECIAL6, 0, 0}, // S_HIDESPECIAL5 + {SPR_ACLO, 2, 4, NULL, S_HIDESPECIAL7, 0, 0}, // S_HIDESPECIAL6 + {SPR_ACLO, 1, 4, NULL, S_HIDESPECIAL8, 0, 0}, // S_HIDESPECIAL7 + {SPR_ACLO, 2, 4, NULL, S_HIDESPECIAL9, 0, 0}, // S_HIDESPECIAL8 + {SPR_ACLO, 3, 4, NULL, S_HIDESPECIAL10, 0, 0}, // S_HIDESPECIAL9 + {SPR_ACLO, 2, 4, NULL, S_HIDESPECIAL11, 0, 0}, // S_HIDESPECIAL10 + {SPR_ACLO, 3, 4, A_RestoreSpecialThing2, S_NULL, 0, 0}, // S_HIDESPECIAL11 + {SPR_ACLO, 3, 3, NULL, S_DORMANTARTI2, 0, 0}, // S_DORMANTARTI1 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI3, 0, 0}, // S_DORMANTARTI2 + {SPR_ACLO, 3, 3, NULL, S_DORMANTARTI4, 0, 0}, // S_DORMANTARTI3 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI5, 0, 0}, // S_DORMANTARTI4 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI6, 0, 0}, // S_DORMANTARTI5 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI7, 0, 0}, // S_DORMANTARTI6 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI8, 0, 0}, // S_DORMANTARTI7 + {SPR_ACLO, 0, 3, NULL, S_DORMANTARTI9, 0, 0}, // S_DORMANTARTI8 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI10, 0, 0}, // S_DORMANTARTI9 + {SPR_ACLO, 0, 3, NULL, S_DORMANTARTI11, 0, 0}, // S_DORMANTARTI10 + {SPR_ACLO, 0, 1400, A_HideThing, S_DORMANTARTI12, 0, 0}, // S_DORMANTARTI11 + {SPR_ACLO, 0, 3, A_UnHideThing, S_DORMANTARTI13, 0, 0}, // S_DORMANTARTI12 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI14, 0, 0}, // S_DORMANTARTI13 + {SPR_ACLO, 0, 3, NULL, S_DORMANTARTI15, 0, 0}, // S_DORMANTARTI14 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI16, 0, 0}, // S_DORMANTARTI15 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI17, 0, 0}, // S_DORMANTARTI16 + {SPR_ACLO, 1, 3, NULL, S_DORMANTARTI18, 0, 0}, // S_DORMANTARTI17 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI19, 0, 0}, // S_DORMANTARTI18 + {SPR_ACLO, 3, 3, NULL, S_DORMANTARTI20, 0, 0}, // S_DORMANTARTI19 + {SPR_ACLO, 2, 3, NULL, S_DORMANTARTI21, 0, 0}, // S_DORMANTARTI20 + {SPR_ACLO, 3, 3, A_RestoreArtifact, S_NULL, 0, 0}, // S_DORMANTARTI21 + {SPR_ACLO, 3, 3, NULL, S_DEADARTI2, 0, 0}, // S_DEADARTI1 + {SPR_ACLO, 2, 3, NULL, S_DEADARTI3, 0, 0}, // S_DEADARTI2 + {SPR_ACLO, 3, 3, NULL, S_DEADARTI4, 0, 0}, // S_DEADARTI3 + {SPR_ACLO, 2, 3, NULL, S_DEADARTI5, 0, 0}, // S_DEADARTI4 + {SPR_ACLO, 1, 3, NULL, S_DEADARTI6, 0, 0}, // S_DEADARTI5 + {SPR_ACLO, 2, 3, NULL, S_DEADARTI7, 0, 0}, // S_DEADARTI6 + {SPR_ACLO, 1, 3, NULL, S_DEADARTI8, 0, 0}, // S_DEADARTI7 + {SPR_ACLO, 0, 3, NULL, S_DEADARTI9, 0, 0}, // S_DEADARTI8 + {SPR_ACLO, 1, 3, NULL, S_DEADARTI10, 0, 0}, // S_DEADARTI9 + {SPR_ACLO, 0, 3, NULL, S_NULL, 0, 0}, // S_DEADARTI10 + {SPR_INVS, 32768, 350, NULL, S_ARTI_INVS1, 0, 0}, // S_ARTI_INVS1 + {SPR_PTN2, 0, 4, NULL, S_ARTI_PTN2_2, 0, 0}, // S_ARTI_PTN2_1 + {SPR_PTN2, 1, 4, NULL, S_ARTI_PTN2_3, 0, 0}, // S_ARTI_PTN2_2 + {SPR_PTN2, 2, 4, NULL, S_ARTI_PTN2_1, 0, 0}, // S_ARTI_PTN2_3 + {SPR_SOAR, 0, 5, NULL, S_ARTI_SOAR2, 0, 0}, // S_ARTI_SOAR1 + {SPR_SOAR, 1, 5, NULL, S_ARTI_SOAR3, 0, 0}, // S_ARTI_SOAR2 + {SPR_SOAR, 2, 5, NULL, S_ARTI_SOAR4, 0, 0}, // S_ARTI_SOAR3 + {SPR_SOAR, 1, 5, NULL, S_ARTI_SOAR1, 0, 0}, // S_ARTI_SOAR4 + {SPR_INVU, 0, 3, NULL, S_ARTI_INVU2, 0, 0}, // S_ARTI_INVU1 + {SPR_INVU, 1, 3, NULL, S_ARTI_INVU3, 0, 0}, // S_ARTI_INVU2 + {SPR_INVU, 2, 3, NULL, S_ARTI_INVU4, 0, 0}, // S_ARTI_INVU3 + {SPR_INVU, 3, 3, NULL, S_ARTI_INVU1, 0, 0}, // S_ARTI_INVU4 + {SPR_PWBK, 0, 350, NULL, S_ARTI_PWBK1, 0, 0}, // S_ARTI_PWBK1 + {SPR_EGGC, 0, 6, NULL, S_ARTI_EGGC2, 0, 0}, // S_ARTI_EGGC1 + {SPR_EGGC, 1, 6, NULL, S_ARTI_EGGC3, 0, 0}, // S_ARTI_EGGC2 + {SPR_EGGC, 2, 6, NULL, S_ARTI_EGGC4, 0, 0}, // S_ARTI_EGGC3 + {SPR_EGGC, 1, 6, NULL, S_ARTI_EGGC1, 0, 0}, // S_ARTI_EGGC4 + {SPR_EGGM, 0, 4, NULL, S_EGGFX2, 0, 0}, // S_EGGFX1 + {SPR_EGGM, 1, 4, NULL, S_EGGFX3, 0, 0}, // S_EGGFX2 + {SPR_EGGM, 2, 4, NULL, S_EGGFX4, 0, 0}, // S_EGGFX3 + {SPR_EGGM, 3, 4, NULL, S_EGGFX5, 0, 0}, // S_EGGFX4 + {SPR_EGGM, 4, 4, NULL, S_EGGFX1, 0, 0}, // S_EGGFX5 + {SPR_FX01, 32772, 3, NULL, S_EGGFXI1_2, 0, 0}, // S_EGGFXI1_1 + {SPR_FX01, 32773, 3, NULL, S_EGGFXI1_3, 0, 0}, // S_EGGFXI1_2 + {SPR_FX01, 32774, 3, NULL, S_EGGFXI1_4, 0, 0}, // S_EGGFXI1_3 + {SPR_FX01, 32775, 3, NULL, S_NULL, 0, 0}, // S_EGGFXI1_4 + {SPR_SPHL, 0, 350, NULL, S_ARTI_SPHL1, 0, 0}, // S_ARTI_SPHL1 + {SPR_TRCH, 32768, 3, NULL, S_ARTI_TRCH2, 0, 0}, // S_ARTI_TRCH1 + {SPR_TRCH, 32769, 3, NULL, S_ARTI_TRCH3, 0, 0}, // S_ARTI_TRCH2 + {SPR_TRCH, 32770, 3, NULL, S_ARTI_TRCH1, 0, 0}, // S_ARTI_TRCH3 + {SPR_FBMB, 4, 350, NULL, S_ARTI_FBMB1, 0, 0}, // S_ARTI_FBMB1 + {SPR_FBMB, 0, 10, NULL, S_FIREBOMB2, 0, 0}, // S_FIREBOMB1 + {SPR_FBMB, 1, 10, NULL, S_FIREBOMB3, 0, 0}, // S_FIREBOMB2 + {SPR_FBMB, 2, 10, NULL, S_FIREBOMB4, 0, 0}, // S_FIREBOMB3 + {SPR_FBMB, 3, 10, NULL, S_FIREBOMB5, 0, 0}, // S_FIREBOMB4 + {SPR_FBMB, 4, 6, A_Scream, S_FIREBOMB6, 0, 0}, // S_FIREBOMB5 + {SPR_XPL1, 32768, 4, A_Explode, S_FIREBOMB7, 0, 0}, // S_FIREBOMB6 + {SPR_XPL1, 32769, 4, NULL, S_FIREBOMB8, 0, 0}, // S_FIREBOMB7 + {SPR_XPL1, 32770, 4, NULL, S_FIREBOMB9, 0, 0}, // S_FIREBOMB8 + {SPR_XPL1, 32771, 4, NULL, S_FIREBOMB10, 0, 0}, // S_FIREBOMB9 + {SPR_XPL1, 32772, 4, NULL, S_FIREBOMB11, 0, 0}, // S_FIREBOMB10 + {SPR_XPL1, 32773, 4, NULL, S_NULL, 0, 0}, // S_FIREBOMB11 + {SPR_ATLP, 0, 4, NULL, S_ARTI_ATLP2, 0, 0}, // S_ARTI_ATLP1 + {SPR_ATLP, 1, 4, NULL, S_ARTI_ATLP3, 0, 0}, // S_ARTI_ATLP2 + {SPR_ATLP, 2, 4, NULL, S_ARTI_ATLP4, 0, 0}, // S_ARTI_ATLP3 + {SPR_ATLP, 1, 4, NULL, S_ARTI_ATLP1, 0, 0}, // S_ARTI_ATLP4 + {SPR_PPOD, 0, 10, NULL, S_POD_WAIT1, 0, 0}, // S_POD_WAIT1 + {SPR_PPOD, 1, 14, A_PodPain, S_POD_WAIT1, 0, 0}, // S_POD_PAIN1 + {SPR_PPOD, 32770, 5, A_RemovePod, S_POD_DIE2, 0, 0}, // S_POD_DIE1 + {SPR_PPOD, 32771, 5, A_Scream, S_POD_DIE3, 0, 0}, // S_POD_DIE2 + {SPR_PPOD, 32772, 5, A_Explode, S_POD_DIE4, 0, 0}, // S_POD_DIE3 + {SPR_PPOD, 32773, 10, NULL, S_FREETARGMOBJ, 0, 0}, // S_POD_DIE4 + {SPR_PPOD, 8, 3, NULL, S_POD_GROW2, 0, 0}, // S_POD_GROW1 + {SPR_PPOD, 9, 3, NULL, S_POD_GROW3, 0, 0}, // S_POD_GROW2 + {SPR_PPOD, 10, 3, NULL, S_POD_GROW4, 0, 0}, // S_POD_GROW3 + {SPR_PPOD, 11, 3, NULL, S_POD_GROW5, 0, 0}, // S_POD_GROW4 + {SPR_PPOD, 12, 3, NULL, S_POD_GROW6, 0, 0}, // S_POD_GROW5 + {SPR_PPOD, 13, 3, NULL, S_POD_GROW7, 0, 0}, // S_POD_GROW6 + {SPR_PPOD, 14, 3, NULL, S_POD_GROW8, 0, 0}, // S_POD_GROW7 + {SPR_PPOD, 15, 3, NULL, S_POD_WAIT1, 0, 0}, // S_POD_GROW8 + {SPR_PPOD, 6, 8, NULL, S_PODGOO2, 0, 0}, // S_PODGOO1 + {SPR_PPOD, 7, 8, NULL, S_PODGOO1, 0, 0}, // S_PODGOO2 + {SPR_PPOD, 6, 10, NULL, S_NULL, 0, 0}, // S_PODGOOX + {SPR_AMG1, 0, 35, A_MakePod, S_PODGENERATOR, 0, 0}, // S_PODGENERATOR + {SPR_SPSH, 0, 8, NULL, S_SPLASH2, 0, 0}, // S_SPLASH1 + {SPR_SPSH, 1, 8, NULL, S_SPLASH3, 0, 0}, // S_SPLASH2 + {SPR_SPSH, 2, 8, NULL, S_SPLASH4, 0, 0}, // S_SPLASH3 + {SPR_SPSH, 3, 16, NULL, S_NULL, 0, 0}, // S_SPLASH4 + {SPR_SPSH, 3, 10, NULL, S_NULL, 0, 0}, // S_SPLASHX + {SPR_SPSH, 4, 5, NULL, S_SPLASHBASE2, 0, 0}, // S_SPLASHBASE1 + {SPR_SPSH, 5, 5, NULL, S_SPLASHBASE3, 0, 0}, // S_SPLASHBASE2 + {SPR_SPSH, 6, 5, NULL, S_SPLASHBASE4, 0, 0}, // S_SPLASHBASE3 + {SPR_SPSH, 7, 5, NULL, S_SPLASHBASE5, 0, 0}, // S_SPLASHBASE4 + {SPR_SPSH, 8, 5, NULL, S_SPLASHBASE6, 0, 0}, // S_SPLASHBASE5 + {SPR_SPSH, 9, 5, NULL, S_SPLASHBASE7, 0, 0}, // S_SPLASHBASE6 + {SPR_SPSH, 10, 5, NULL, S_NULL, 0, 0}, // S_SPLASHBASE7 + {SPR_LVAS, 32768, 5, NULL, S_LAVASPLASH2, 0, 0}, // S_LAVASPLASH1 + {SPR_LVAS, 32769, 5, NULL, S_LAVASPLASH3, 0, 0}, // S_LAVASPLASH2 + {SPR_LVAS, 32770, 5, NULL, S_LAVASPLASH4, 0, 0}, // S_LAVASPLASH3 + {SPR_LVAS, 32771, 5, NULL, S_LAVASPLASH5, 0, 0}, // S_LAVASPLASH4 + {SPR_LVAS, 32772, 5, NULL, S_LAVASPLASH6, 0, 0}, // S_LAVASPLASH5 + {SPR_LVAS, 32773, 5, NULL, S_NULL, 0, 0}, // S_LAVASPLASH6 + {SPR_LVAS, 32774, 5, NULL, S_LAVASMOKE2, 0, 0}, // S_LAVASMOKE1 + {SPR_LVAS, 32775, 5, NULL, S_LAVASMOKE3, 0, 0}, // S_LAVASMOKE2 + {SPR_LVAS, 32776, 5, NULL, S_LAVASMOKE4, 0, 0}, // S_LAVASMOKE3 + {SPR_LVAS, 32777, 5, NULL, S_LAVASMOKE5, 0, 0}, // S_LAVASMOKE4 + {SPR_LVAS, 32778, 5, NULL, S_NULL, 0, 0}, // S_LAVASMOKE5 + {SPR_SLDG, 0, 8, NULL, S_SLUDGECHUNK2, 0, 0}, // S_SLUDGECHUNK1 + {SPR_SLDG, 1, 8, NULL, S_SLUDGECHUNK3, 0, 0}, // S_SLUDGECHUNK2 + {SPR_SLDG, 2, 8, NULL, S_SLUDGECHUNK4, 0, 0}, // S_SLUDGECHUNK3 + {SPR_SLDG, 3, 8, NULL, S_NULL, 0, 0}, // S_SLUDGECHUNK4 + {SPR_SLDG, 3, 6, NULL, S_NULL, 0, 0}, // S_SLUDGECHUNKX + {SPR_SLDG, 4, 5, NULL, S_SLUDGESPLASH2, 0, 0}, // S_SLUDGESPLASH1 + {SPR_SLDG, 5, 5, NULL, S_SLUDGESPLASH3, 0, 0}, // S_SLUDGESPLASH2 + {SPR_SLDG, 6, 5, NULL, S_SLUDGESPLASH4, 0, 0}, // S_SLUDGESPLASH3 + {SPR_SLDG, 7, 5, NULL, S_NULL, 0, 0}, // S_SLUDGESPLASH4 + {SPR_SKH1, 0, -1, NULL, S_NULL, 0, 0}, // S_SKULLHANG70_1 + {SPR_SKH2, 0, -1, NULL, S_NULL, 0, 0}, // S_SKULLHANG60_1 + {SPR_SKH3, 0, -1, NULL, S_NULL, 0, 0}, // S_SKULLHANG45_1 + {SPR_SKH4, 0, -1, NULL, S_NULL, 0, 0}, // S_SKULLHANG35_1 + {SPR_CHDL, 0, 4, NULL, S_CHANDELIER2, 0, 0}, // S_CHANDELIER1 + {SPR_CHDL, 1, 4, NULL, S_CHANDELIER3, 0, 0}, // S_CHANDELIER2 + {SPR_CHDL, 2, 4, NULL, S_CHANDELIER1, 0, 0}, // S_CHANDELIER3 + {SPR_SRTC, 0, 4, NULL, S_SERPTORCH2, 0, 0}, // S_SERPTORCH1 + {SPR_SRTC, 1, 4, NULL, S_SERPTORCH3, 0, 0}, // S_SERPTORCH2 + {SPR_SRTC, 2, 4, NULL, S_SERPTORCH1, 0, 0}, // S_SERPTORCH3 + {SPR_SMPL, 0, -1, NULL, S_NULL, 0, 0}, // S_SMALLPILLAR + {SPR_STGS, 0, -1, NULL, S_NULL, 0, 0}, // S_STALAGMITESMALL + {SPR_STGL, 0, -1, NULL, S_NULL, 0, 0}, // S_STALAGMITELARGE + {SPR_STCS, 0, -1, NULL, S_NULL, 0, 0}, // S_STALACTITESMALL + {SPR_STCL, 0, -1, NULL, S_NULL, 0, 0}, // S_STALACTITELARGE + {SPR_KFR1, 32768, 3, NULL, S_FIREBRAZIER2, 0, 0}, // S_FIREBRAZIER1 + {SPR_KFR1, 32769, 3, NULL, S_FIREBRAZIER3, 0, 0}, // S_FIREBRAZIER2 + {SPR_KFR1, 32770, 3, NULL, S_FIREBRAZIER4, 0, 0}, // S_FIREBRAZIER3 + {SPR_KFR1, 32771, 3, NULL, S_FIREBRAZIER5, 0, 0}, // S_FIREBRAZIER4 + {SPR_KFR1, 32772, 3, NULL, S_FIREBRAZIER6, 0, 0}, // S_FIREBRAZIER5 + {SPR_KFR1, 32773, 3, NULL, S_FIREBRAZIER7, 0, 0}, // S_FIREBRAZIER6 + {SPR_KFR1, 32774, 3, NULL, S_FIREBRAZIER8, 0, 0}, // S_FIREBRAZIER7 + {SPR_KFR1, 32775, 3, NULL, S_FIREBRAZIER1, 0, 0}, // S_FIREBRAZIER8 + {SPR_BARL, 0, -1, NULL, S_NULL, 0, 0}, // S_BARREL + {SPR_BRPL, 0, -1, NULL, S_NULL, 0, 0}, // S_BRPILLAR + {SPR_MOS1, 0, -1, NULL, S_NULL, 0, 0}, // S_MOSS1 + {SPR_MOS2, 0, -1, NULL, S_NULL, 0, 0}, // S_MOSS2 + {SPR_WTRH, 32768, 6, NULL, S_WALLTORCH2, 0, 0}, // S_WALLTORCH1 + {SPR_WTRH, 32769, 6, NULL, S_WALLTORCH3, 0, 0}, // S_WALLTORCH2 + {SPR_WTRH, 32770, 6, NULL, S_WALLTORCH1, 0, 0}, // S_WALLTORCH3 + {SPR_HCOR, 0, -1, NULL, S_NULL, 0, 0}, // S_HANGINGCORPSE + {SPR_KGZ1, 0, 1, NULL, S_KEYGIZMO2, 0, 0}, // S_KEYGIZMO1 + {SPR_KGZ1, 0, 1, A_InitKeyGizmo, S_KEYGIZMO3, 0, 0}, // S_KEYGIZMO2 + {SPR_KGZ1, 0, -1, NULL, S_NULL, 0, 0}, // S_KEYGIZMO3 + {SPR_KGZB, 0, 1, NULL, S_KGZ_START, 0, 0}, // S_KGZ_START + {SPR_KGZB, 32768, -1, NULL, S_NULL, 0, 0}, // S_KGZ_BLUEFLOAT1 + {SPR_KGZG, 32768, -1, NULL, S_NULL, 0, 0}, // S_KGZ_GREENFLOAT1 + {SPR_KGZY, 32768, -1, NULL, S_NULL, 0, 0}, // S_KGZ_YELLOWFLOAT1 + {SPR_VLCO, 0, 350, NULL, S_VOLCANO2, 0, 0}, // S_VOLCANO1 + {SPR_VLCO, 0, 35, A_VolcanoSet, S_VOLCANO3, 0, 0}, // S_VOLCANO2 + {SPR_VLCO, 1, 3, NULL, S_VOLCANO4, 0, 0}, // S_VOLCANO3 + {SPR_VLCO, 2, 3, NULL, S_VOLCANO5, 0, 0}, // S_VOLCANO4 + {SPR_VLCO, 3, 3, NULL, S_VOLCANO6, 0, 0}, // S_VOLCANO5 + {SPR_VLCO, 1, 3, NULL, S_VOLCANO7, 0, 0}, // S_VOLCANO6 + {SPR_VLCO, 2, 3, NULL, S_VOLCANO8, 0, 0}, // S_VOLCANO7 + {SPR_VLCO, 3, 3, NULL, S_VOLCANO9, 0, 0}, // S_VOLCANO8 + {SPR_VLCO, 4, 10, A_VolcanoBlast, S_VOLCANO2, 0, 0}, // S_VOLCANO9 + {SPR_VFBL, 0, 4, A_BeastPuff, S_VOLCANOBALL2, 0, 0}, // S_VOLCANOBALL1 + {SPR_VFBL, 1, 4, A_BeastPuff, S_VOLCANOBALL1, 0, 0}, // S_VOLCANOBALL2 + {SPR_XPL1, 0, 4, A_VolcBallImpact, S_VOLCANOBALLX2, 0, 0}, // S_VOLCANOBALLX1 + {SPR_XPL1, 1, 4, NULL, S_VOLCANOBALLX3, 0, 0}, // S_VOLCANOBALLX2 + {SPR_XPL1, 2, 4, NULL, S_VOLCANOBALLX4, 0, 0}, // S_VOLCANOBALLX3 + {SPR_XPL1, 3, 4, NULL, S_VOLCANOBALLX5, 0, 0}, // S_VOLCANOBALLX4 + {SPR_XPL1, 4, 4, NULL, S_VOLCANOBALLX6, 0, 0}, // S_VOLCANOBALLX5 + {SPR_XPL1, 5, 4, NULL, S_NULL, 0, 0}, // S_VOLCANOBALLX6 + {SPR_VTFB, 0, 4, NULL, S_VOLCANOTBALL2, 0, 0}, // S_VOLCANOTBALL1 + {SPR_VTFB, 1, 4, NULL, S_VOLCANOTBALL1, 0, 0}, // S_VOLCANOTBALL2 + {SPR_SFFI, 2, 4, NULL, S_VOLCANOTBALLX2, 0, 0}, // S_VOLCANOTBALLX1 + {SPR_SFFI, 1, 4, NULL, S_VOLCANOTBALLX3, 0, 0}, // S_VOLCANOTBALLX2 + {SPR_SFFI, 0, 4, NULL, S_VOLCANOTBALLX4, 0, 0}, // S_VOLCANOTBALLX3 + {SPR_SFFI, 1, 4, NULL, S_VOLCANOTBALLX5, 0, 0}, // S_VOLCANOTBALLX4 + {SPR_SFFI, 2, 4, NULL, S_VOLCANOTBALLX6, 0, 0}, // S_VOLCANOTBALLX5 + {SPR_SFFI, 3, 4, NULL, S_VOLCANOTBALLX7, 0, 0}, // S_VOLCANOTBALLX6 + {SPR_SFFI, 4, 4, NULL, S_NULL, 0, 0}, // S_VOLCANOTBALLX7 + {SPR_TGLT, 0, 8, A_SpawnTeleGlitter, S_TELEGLITGEN1, 0, 0}, // S_TELEGLITGEN1 + {SPR_TGLT, 5, 8, A_SpawnTeleGlitter2, S_TELEGLITGEN2, 0, 0}, // S_TELEGLITGEN2 + {SPR_TGLT, 32768, 2, NULL, S_TELEGLITTER1_2, 0, 0}, // S_TELEGLITTER1_1 + {SPR_TGLT, 32769, 2, A_AccTeleGlitter, S_TELEGLITTER1_3, 0, 0}, // S_TELEGLITTER1_2 + {SPR_TGLT, 32770, 2, NULL, S_TELEGLITTER1_4, 0, 0}, // S_TELEGLITTER1_3 + {SPR_TGLT, 32771, 2, A_AccTeleGlitter, S_TELEGLITTER1_5, 0, 0}, // S_TELEGLITTER1_4 + {SPR_TGLT, 32772, 2, NULL, S_TELEGLITTER1_1, 0, 0}, // S_TELEGLITTER1_5 + {SPR_TGLT, 32773, 2, NULL, S_TELEGLITTER2_2, 0, 0}, // S_TELEGLITTER2_1 + {SPR_TGLT, 32774, 2, A_AccTeleGlitter, S_TELEGLITTER2_3, 0, 0}, // S_TELEGLITTER2_2 + {SPR_TGLT, 32775, 2, NULL, S_TELEGLITTER2_4, 0, 0}, // S_TELEGLITTER2_3 + {SPR_TGLT, 32776, 2, A_AccTeleGlitter, S_TELEGLITTER2_5, 0, 0}, // S_TELEGLITTER2_4 + {SPR_TGLT, 32777, 2, NULL, S_TELEGLITTER2_1, 0, 0}, // S_TELEGLITTER2_5 + {SPR_TELE, 32768, 6, NULL, S_TFOG2, 0, 0}, // S_TFOG1 + {SPR_TELE, 32769, 6, NULL, S_TFOG3, 0, 0}, // S_TFOG2 + {SPR_TELE, 32770, 6, NULL, S_TFOG4, 0, 0}, // S_TFOG3 + {SPR_TELE, 32771, 6, NULL, S_TFOG5, 0, 0}, // S_TFOG4 + {SPR_TELE, 32772, 6, NULL, S_TFOG6, 0, 0}, // S_TFOG5 + {SPR_TELE, 32773, 6, NULL, S_TFOG7, 0, 0}, // S_TFOG6 + {SPR_TELE, 32774, 6, NULL, S_TFOG8, 0, 0}, // S_TFOG7 + {SPR_TELE, 32775, 6, NULL, S_TFOG9, 0, 0}, // S_TFOG8 + {SPR_TELE, 32774, 6, NULL, S_TFOG10, 0, 0}, // S_TFOG9 + {SPR_TELE, 32773, 6, NULL, S_TFOG11, 0, 0}, // S_TFOG10 + {SPR_TELE, 32772, 6, NULL, S_TFOG12, 0, 0}, // S_TFOG11 + {SPR_TELE, 32771, 6, NULL, S_TFOG13, 0, 0}, // S_TFOG12 + {SPR_TELE, 32770, 6, NULL, S_NULL, 0, 0}, // S_TFOG13 + {SPR_STFF, 0, 0, A_Light0, S_NULL, 0, 0}, // S_LIGHTDONE + {SPR_STFF, 0, 1, A_WeaponReady, S_STAFFREADY, 0, 0}, // S_STAFFREADY + {SPR_STFF, 0, 1, A_Lower, S_STAFFDOWN, 0, 0}, // S_STAFFDOWN + {SPR_STFF, 0, 1, A_Raise, S_STAFFUP, 0, 0}, // S_STAFFUP + {SPR_STFF, 3, 4, A_WeaponReady, S_STAFFREADY2_2, 0, 0}, // S_STAFFREADY2_1 + {SPR_STFF, 4, 4, A_WeaponReady, S_STAFFREADY2_3, 0, 0}, // S_STAFFREADY2_2 + {SPR_STFF, 5, 4, A_WeaponReady, S_STAFFREADY2_1, 0, 0}, // S_STAFFREADY2_3 + {SPR_STFF, 3, 1, A_Lower, S_STAFFDOWN2, 0, 0}, // S_STAFFDOWN2 + {SPR_STFF, 3, 1, A_Raise, S_STAFFUP2, 0, 0}, // S_STAFFUP2 + {SPR_STFF, 1, 6, NULL, S_STAFFATK1_2, 0, 0}, // S_STAFFATK1_1 + {SPR_STFF, 2, 8, A_StaffAttackPL1, S_STAFFATK1_3, 0, 0}, // S_STAFFATK1_2 + {SPR_STFF, 1, 8, A_ReFire, S_STAFFREADY, 0, 0}, // S_STAFFATK1_3 + {SPR_STFF, 6, 6, NULL, S_STAFFATK2_2, 0, 0}, // S_STAFFATK2_1 + {SPR_STFF, 7, 8, A_StaffAttackPL2, S_STAFFATK2_3, 0, 0}, // S_STAFFATK2_2 + {SPR_STFF, 6, 8, A_ReFire, S_STAFFREADY2_1, 0, 0}, // S_STAFFATK2_3 + {SPR_PUF3, 32768, 4, NULL, S_STAFFPUFF2, 0, 0}, // S_STAFFPUFF1 + {SPR_PUF3, 1, 4, NULL, S_STAFFPUFF3, 0, 0}, // S_STAFFPUFF2 + {SPR_PUF3, 2, 4, NULL, S_STAFFPUFF4, 0, 0}, // S_STAFFPUFF3 + {SPR_PUF3, 3, 4, NULL, S_NULL, 0, 0}, // S_STAFFPUFF4 + {SPR_PUF4, 32768, 4, NULL, S_STAFFPUFF2_2, 0, 0}, // S_STAFFPUFF2_1 + {SPR_PUF4, 32769, 4, NULL, S_STAFFPUFF2_3, 0, 0}, // S_STAFFPUFF2_2 + {SPR_PUF4, 32770, 4, NULL, S_STAFFPUFF2_4, 0, 0}, // S_STAFFPUFF2_3 + {SPR_PUF4, 32771, 4, NULL, S_STAFFPUFF2_5, 0, 0}, // S_STAFFPUFF2_4 + {SPR_PUF4, 32772, 4, NULL, S_STAFFPUFF2_6, 0, 0}, // S_STAFFPUFF2_5 + {SPR_PUF4, 32773, 4, NULL, S_NULL, 0, 0}, // S_STAFFPUFF2_6 + {SPR_BEAK, 0, 1, A_BeakReady, S_BEAKREADY, 0, 0}, // S_BEAKREADY + {SPR_BEAK, 0, 1, A_Lower, S_BEAKDOWN, 0, 0}, // S_BEAKDOWN + {SPR_BEAK, 0, 1, A_BeakRaise, S_BEAKUP, 0, 0}, // S_BEAKUP + {SPR_BEAK, 0, 18, A_BeakAttackPL1, S_BEAKREADY, 0, 0}, // S_BEAKATK1_1 + {SPR_BEAK, 0, 12, A_BeakAttackPL2, S_BEAKREADY, 0, 0}, // S_BEAKATK2_1 + {SPR_WGNT, 0, -1, NULL, S_NULL, 0, 0}, // S_WGNT + {SPR_GAUN, 0, 1, A_WeaponReady, S_GAUNTLETREADY, 0, 0}, // S_GAUNTLETREADY + {SPR_GAUN, 0, 1, A_Lower, S_GAUNTLETDOWN, 0, 0}, // S_GAUNTLETDOWN + {SPR_GAUN, 0, 1, A_Raise, S_GAUNTLETUP, 0, 0}, // S_GAUNTLETUP + {SPR_GAUN, 6, 4, A_WeaponReady, S_GAUNTLETREADY2_2, 0, 0}, // S_GAUNTLETREADY2_1 + {SPR_GAUN, 7, 4, A_WeaponReady, S_GAUNTLETREADY2_3, 0, 0}, // S_GAUNTLETREADY2_2 + {SPR_GAUN, 8, 4, A_WeaponReady, S_GAUNTLETREADY2_1, 0, 0}, // S_GAUNTLETREADY2_3 + {SPR_GAUN, 6, 1, A_Lower, S_GAUNTLETDOWN2, 0, 0}, // S_GAUNTLETDOWN2 + {SPR_GAUN, 6, 1, A_Raise, S_GAUNTLETUP2, 0, 0}, // S_GAUNTLETUP2 + {SPR_GAUN, 1, 4, NULL, S_GAUNTLETATK1_2, 0, 0}, // S_GAUNTLETATK1_1 + {SPR_GAUN, 2, 4, NULL, S_GAUNTLETATK1_3, 0, 0}, // S_GAUNTLETATK1_2 + {SPR_GAUN, 32771, 4, A_GauntletAttack, S_GAUNTLETATK1_4, 0, 0}, // S_GAUNTLETATK1_3 + {SPR_GAUN, 32772, 4, A_GauntletAttack, S_GAUNTLETATK1_5, 0, 0}, // S_GAUNTLETATK1_4 + {SPR_GAUN, 32773, 4, A_GauntletAttack, S_GAUNTLETATK1_6, 0, 0}, // S_GAUNTLETATK1_5 + {SPR_GAUN, 2, 4, A_ReFire, S_GAUNTLETATK1_7, 0, 0}, // S_GAUNTLETATK1_6 + {SPR_GAUN, 1, 4, A_Light0, S_GAUNTLETREADY, 0, 0}, // S_GAUNTLETATK1_7 + {SPR_GAUN, 9, 4, NULL, S_GAUNTLETATK2_2, 0, 0}, // S_GAUNTLETATK2_1 + {SPR_GAUN, 10, 4, NULL, S_GAUNTLETATK2_3, 0, 0}, // S_GAUNTLETATK2_2 + {SPR_GAUN, 32779, 4, A_GauntletAttack, S_GAUNTLETATK2_4, 0, 0}, // S_GAUNTLETATK2_3 + {SPR_GAUN, 32780, 4, A_GauntletAttack, S_GAUNTLETATK2_5, 0, 0}, // S_GAUNTLETATK2_4 + {SPR_GAUN, 32781, 4, A_GauntletAttack, S_GAUNTLETATK2_6, 0, 0}, // S_GAUNTLETATK2_5 + {SPR_GAUN, 10, 4, A_ReFire, S_GAUNTLETATK2_7, 0, 0}, // S_GAUNTLETATK2_6 + {SPR_GAUN, 9, 4, A_Light0, S_GAUNTLETREADY2_1, 0, 0}, // S_GAUNTLETATK2_7 + {SPR_PUF1, 32768, 4, NULL, S_GAUNTLETPUFF1_2, 0, 0}, // S_GAUNTLETPUFF1_1 + {SPR_PUF1, 32769, 4, NULL, S_GAUNTLETPUFF1_3, 0, 0}, // S_GAUNTLETPUFF1_2 + {SPR_PUF1, 32770, 4, NULL, S_GAUNTLETPUFF1_4, 0, 0}, // S_GAUNTLETPUFF1_3 + {SPR_PUF1, 32771, 4, NULL, S_NULL, 0, 0}, // S_GAUNTLETPUFF1_4 + {SPR_PUF1, 32772, 4, NULL, S_GAUNTLETPUFF2_2, 0, 0}, // S_GAUNTLETPUFF2_1 + {SPR_PUF1, 32773, 4, NULL, S_GAUNTLETPUFF2_3, 0, 0}, // S_GAUNTLETPUFF2_2 + {SPR_PUF1, 32774, 4, NULL, S_GAUNTLETPUFF2_4, 0, 0}, // S_GAUNTLETPUFF2_3 + {SPR_PUF1, 32775, 4, NULL, S_NULL, 0, 0}, // S_GAUNTLETPUFF2_4 + {SPR_WBLS, 0, -1, NULL, S_NULL, 0, 0}, // S_BLSR + {SPR_BLSR, 0, 1, A_WeaponReady, S_BLASTERREADY, 0, 0}, // S_BLASTERREADY + {SPR_BLSR, 0, 1, A_Lower, S_BLASTERDOWN, 0, 0}, // S_BLASTERDOWN + {SPR_BLSR, 0, 1, A_Raise, S_BLASTERUP, 0, 0}, // S_BLASTERUP + {SPR_BLSR, 1, 3, NULL, S_BLASTERATK1_2, 0, 0}, // S_BLASTERATK1_1 + {SPR_BLSR, 2, 3, NULL, S_BLASTERATK1_3, 0, 0}, // S_BLASTERATK1_2 + {SPR_BLSR, 3, 2, A_FireBlasterPL1, S_BLASTERATK1_4, 0, 0}, // S_BLASTERATK1_3 + {SPR_BLSR, 2, 2, NULL, S_BLASTERATK1_5, 0, 0}, // S_BLASTERATK1_4 + {SPR_BLSR, 1, 2, NULL, S_BLASTERATK1_6, 0, 0}, // S_BLASTERATK1_5 + {SPR_BLSR, 0, 0, A_ReFire, S_BLASTERREADY, 0, 0}, // S_BLASTERATK1_6 + {SPR_BLSR, 1, 0, NULL, S_BLASTERATK2_2, 0, 0}, // S_BLASTERATK2_1 + {SPR_BLSR, 2, 0, NULL, S_BLASTERATK2_3, 0, 0}, // S_BLASTERATK2_2 + {SPR_BLSR, 3, 3, A_FireBlasterPL2, S_BLASTERATK2_4, 0, 0}, // S_BLASTERATK2_3 + {SPR_BLSR, 2, 4, NULL, S_BLASTERATK2_5, 0, 0}, // S_BLASTERATK2_4 + {SPR_BLSR, 1, 4, NULL, S_BLASTERATK2_6, 0, 0}, // S_BLASTERATK2_5 + {SPR_BLSR, 0, 0, A_ReFire, S_BLASTERREADY, 0, 0}, // S_BLASTERATK2_6 + {SPR_ACLO, 4, 200, NULL, S_BLASTERFX1_1, 0, 0}, // S_BLASTERFX1_1 + {SPR_FX18, 32768, 3, A_SpawnRippers, S_BLASTERFXI1_2, 0, 0}, // S_BLASTERFXI1_1 + {SPR_FX18, 32769, 3, NULL, S_BLASTERFXI1_3, 0, 0}, // S_BLASTERFXI1_2 + {SPR_FX18, 32770, 4, NULL, S_BLASTERFXI1_4, 0, 0}, // S_BLASTERFXI1_3 + {SPR_FX18, 32771, 4, NULL, S_BLASTERFXI1_5, 0, 0}, // S_BLASTERFXI1_4 + {SPR_FX18, 32772, 4, NULL, S_BLASTERFXI1_6, 0, 0}, // S_BLASTERFXI1_5 + {SPR_FX18, 32773, 4, NULL, S_BLASTERFXI1_7, 0, 0}, // S_BLASTERFXI1_6 + {SPR_FX18, 32774, 4, NULL, S_NULL, 0, 0}, // S_BLASTERFXI1_7 + {SPR_FX18, 7, 4, NULL, S_BLASTERSMOKE2, 0, 0}, // S_BLASTERSMOKE1 + {SPR_FX18, 8, 4, NULL, S_BLASTERSMOKE3, 0, 0}, // S_BLASTERSMOKE2 + {SPR_FX18, 9, 4, NULL, S_BLASTERSMOKE4, 0, 0}, // S_BLASTERSMOKE3 + {SPR_FX18, 10, 4, NULL, S_BLASTERSMOKE5, 0, 0}, // S_BLASTERSMOKE4 + {SPR_FX18, 11, 4, NULL, S_NULL, 0, 0}, // S_BLASTERSMOKE5 + {SPR_FX18, 12, 4, NULL, S_RIPPER2, 0, 0}, // S_RIPPER1 + {SPR_FX18, 13, 5, NULL, S_RIPPER1, 0, 0}, // S_RIPPER2 + {SPR_FX18, 32782, 4, NULL, S_RIPPERX2, 0, 0}, // S_RIPPERX1 + {SPR_FX18, 32783, 4, NULL, S_RIPPERX3, 0, 0}, // S_RIPPERX2 + {SPR_FX18, 32784, 4, NULL, S_RIPPERX4, 0, 0}, // S_RIPPERX3 + {SPR_FX18, 32785, 4, NULL, S_RIPPERX5, 0, 0}, // S_RIPPERX4 + {SPR_FX18, 32786, 4, NULL, S_NULL, 0, 0}, // S_RIPPERX5 + {SPR_FX17, 32768, 4, NULL, S_BLASTERPUFF1_2, 0, 0}, // S_BLASTERPUFF1_1 + {SPR_FX17, 32769, 4, NULL, S_BLASTERPUFF1_3, 0, 0}, // S_BLASTERPUFF1_2 + {SPR_FX17, 32770, 4, NULL, S_BLASTERPUFF1_4, 0, 0}, // S_BLASTERPUFF1_3 + {SPR_FX17, 32771, 4, NULL, S_BLASTERPUFF1_5, 0, 0}, // S_BLASTERPUFF1_4 + {SPR_FX17, 32772, 4, NULL, S_NULL, 0, 0}, // S_BLASTERPUFF1_5 + {SPR_FX17, 32773, 3, NULL, S_BLASTERPUFF2_2, 0, 0}, // S_BLASTERPUFF2_1 + {SPR_FX17, 32774, 3, NULL, S_BLASTERPUFF2_3, 0, 0}, // S_BLASTERPUFF2_2 + {SPR_FX17, 32775, 4, NULL, S_BLASTERPUFF2_4, 0, 0}, // S_BLASTERPUFF2_3 + {SPR_FX17, 32776, 4, NULL, S_BLASTERPUFF2_5, 0, 0}, // S_BLASTERPUFF2_4 + {SPR_FX17, 32777, 4, NULL, S_BLASTERPUFF2_6, 0, 0}, // S_BLASTERPUFF2_5 + {SPR_FX17, 32778, 4, NULL, S_BLASTERPUFF2_7, 0, 0}, // S_BLASTERPUFF2_6 + {SPR_FX17, 32779, 4, NULL, S_NULL, 0, 0}, // S_BLASTERPUFF2_7 + {SPR_WMCE, 0, -1, NULL, S_NULL, 0, 0}, // S_WMCE + {SPR_MACE, 0, 1, A_WeaponReady, S_MACEREADY, 0, 0}, // S_MACEREADY + {SPR_MACE, 0, 1, A_Lower, S_MACEDOWN, 0, 0}, // S_MACEDOWN + {SPR_MACE, 0, 1, A_Raise, S_MACEUP, 0, 0}, // S_MACEUP + {SPR_MACE, 1, 4, NULL, S_MACEATK1_2, 0, 0}, // S_MACEATK1_1 + {SPR_MACE, 2, 3, A_FireMacePL1, S_MACEATK1_3, 0, 0}, // S_MACEATK1_2 + {SPR_MACE, 3, 3, A_FireMacePL1, S_MACEATK1_4, 0, 0}, // S_MACEATK1_3 + {SPR_MACE, 4, 3, A_FireMacePL1, S_MACEATK1_5, 0, 0}, // S_MACEATK1_4 + {SPR_MACE, 5, 3, A_FireMacePL1, S_MACEATK1_6, 0, 0}, // S_MACEATK1_5 + {SPR_MACE, 2, 4, A_ReFire, S_MACEATK1_7, 0, 0}, // S_MACEATK1_6 + {SPR_MACE, 3, 4, NULL, S_MACEATK1_8, 0, 0}, // S_MACEATK1_7 + {SPR_MACE, 4, 4, NULL, S_MACEATK1_9, 0, 0}, // S_MACEATK1_8 + {SPR_MACE, 5, 4, NULL, S_MACEATK1_10, 0, 0}, // S_MACEATK1_9 + {SPR_MACE, 1, 4, NULL, S_MACEREADY, 0, 0}, // S_MACEATK1_10 + {SPR_MACE, 1, 4, NULL, S_MACEATK2_2, 0, 0}, // S_MACEATK2_1 + {SPR_MACE, 3, 4, A_FireMacePL2, S_MACEATK2_3, 0, 0}, // S_MACEATK2_2 + {SPR_MACE, 1, 4, NULL, S_MACEATK2_4, 0, 0}, // S_MACEATK2_3 + {SPR_MACE, 0, 8, A_ReFire, S_MACEREADY, 0, 0}, // S_MACEATK2_4 + {SPR_FX02, 0, 4, A_MacePL1Check, S_MACEFX1_2, 0, 0}, // S_MACEFX1_1 + {SPR_FX02, 1, 4, A_MacePL1Check, S_MACEFX1_1, 0, 0}, // S_MACEFX1_2 + {SPR_FX02, 32773, 4, A_MaceBallImpact, S_MACEFXI1_2, 0, 0}, // S_MACEFXI1_1 + {SPR_FX02, 32774, 4, NULL, S_MACEFXI1_3, 0, 0}, // S_MACEFXI1_2 + {SPR_FX02, 32775, 4, NULL, S_MACEFXI1_4, 0, 0}, // S_MACEFXI1_3 + {SPR_FX02, 32776, 4, NULL, S_MACEFXI1_5, 0, 0}, // S_MACEFXI1_4 + {SPR_FX02, 32777, 4, NULL, S_NULL, 0, 0}, // S_MACEFXI1_5 + {SPR_FX02, 2, 4, NULL, S_MACEFX2_2, 0, 0}, // S_MACEFX2_1 + {SPR_FX02, 3, 4, NULL, S_MACEFX2_1, 0, 0}, // S_MACEFX2_2 + {SPR_FX02, 32773, 4, A_MaceBallImpact2, S_MACEFXI1_2, 0, 0}, // S_MACEFXI2_1 + {SPR_FX02, 0, 4, NULL, S_MACEFX3_2, 0, 0}, // S_MACEFX3_1 + {SPR_FX02, 1, 4, NULL, S_MACEFX3_1, 0, 0}, // S_MACEFX3_2 + {SPR_FX02, 4, 99, NULL, S_MACEFX4_1, 0, 0}, // S_MACEFX4_1 + {SPR_FX02, 32770, 4, A_DeathBallImpact, S_MACEFXI1_2, 0, 0}, // S_MACEFXI4_1 + {SPR_WSKL, 0, -1, NULL, S_NULL, 0, 0}, // S_WSKL + {SPR_HROD, 0, 1, A_WeaponReady, S_HORNRODREADY, 0, 0}, // S_HORNRODREADY + {SPR_HROD, 0, 1, A_Lower, S_HORNRODDOWN, 0, 0}, // S_HORNRODDOWN + {SPR_HROD, 0, 1, A_Raise, S_HORNRODUP, 0, 0}, // S_HORNRODUP + {SPR_HROD, 0, 4, A_FireSkullRodPL1, S_HORNRODATK1_2, 0, 0}, // S_HORNRODATK1_1 + {SPR_HROD, 1, 4, A_FireSkullRodPL1, S_HORNRODATK1_3, 0, 0}, // S_HORNRODATK1_2 + {SPR_HROD, 1, 0, A_ReFire, S_HORNRODREADY, 0, 0}, // S_HORNRODATK1_3 + {SPR_HROD, 2, 2, NULL, S_HORNRODATK2_2, 0, 0}, // S_HORNRODATK2_1 + {SPR_HROD, 3, 3, NULL, S_HORNRODATK2_3, 0, 0}, // S_HORNRODATK2_2 + {SPR_HROD, 4, 2, NULL, S_HORNRODATK2_4, 0, 0}, // S_HORNRODATK2_3 + {SPR_HROD, 5, 3, NULL, S_HORNRODATK2_5, 0, 0}, // S_HORNRODATK2_4 + {SPR_HROD, 6, 4, A_FireSkullRodPL2, S_HORNRODATK2_6, 0, 0}, // S_HORNRODATK2_5 + {SPR_HROD, 5, 2, NULL, S_HORNRODATK2_7, 0, 0}, // S_HORNRODATK2_6 + {SPR_HROD, 4, 3, NULL, S_HORNRODATK2_8, 0, 0}, // S_HORNRODATK2_7 + {SPR_HROD, 3, 2, NULL, S_HORNRODATK2_9, 0, 0}, // S_HORNRODATK2_8 + {SPR_HROD, 2, 2, A_ReFire, S_HORNRODREADY, 0, 0}, // S_HORNRODATK2_9 + {SPR_FX00, 32768, 6, NULL, S_HRODFX1_2, 0, 0}, // S_HRODFX1_1 + {SPR_FX00, 32769, 6, NULL, S_HRODFX1_1, 0, 0}, // S_HRODFX1_2 + {SPR_FX00, 32775, 5, NULL, S_HRODFXI1_2, 0, 0}, // S_HRODFXI1_1 + {SPR_FX00, 32776, 5, NULL, S_HRODFXI1_3, 0, 0}, // S_HRODFXI1_2 + {SPR_FX00, 32777, 4, NULL, S_HRODFXI1_4, 0, 0}, // S_HRODFXI1_3 + {SPR_FX00, 32778, 4, NULL, S_HRODFXI1_5, 0, 0}, // S_HRODFXI1_4 + {SPR_FX00, 32779, 3, NULL, S_HRODFXI1_6, 0, 0}, // S_HRODFXI1_5 + {SPR_FX00, 32780, 3, NULL, S_NULL, 0, 0}, // S_HRODFXI1_6 + {SPR_FX00, 32770, 3, NULL, S_HRODFX2_2, 0, 0}, // S_HRODFX2_1 + {SPR_FX00, 32771, 3, A_SkullRodPL2Seek, S_HRODFX2_3, 0, 0}, // S_HRODFX2_2 + {SPR_FX00, 32772, 3, NULL, S_HRODFX2_4, 0, 0}, // S_HRODFX2_3 + {SPR_FX00, 32773, 3, A_SkullRodPL2Seek, S_HRODFX2_1, 0, 0}, // S_HRODFX2_4 + {SPR_FX00, 32775, 5, A_AddPlayerRain, S_HRODFXI2_2, 0, 0}, // S_HRODFXI2_1 + {SPR_FX00, 32776, 5, NULL, S_HRODFXI2_3, 0, 0}, // S_HRODFXI2_2 + {SPR_FX00, 32777, 4, NULL, S_HRODFXI2_4, 0, 0}, // S_HRODFXI2_3 + {SPR_FX00, 32778, 3, NULL, S_HRODFXI2_5, 0, 0}, // S_HRODFXI2_4 + {SPR_FX00, 32779, 3, NULL, S_HRODFXI2_6, 0, 0}, // S_HRODFXI2_5 + {SPR_FX00, 32780, 3, NULL, S_HRODFXI2_7, 0, 0}, // S_HRODFXI2_6 + {SPR_FX00, 6, 1, A_HideInCeiling, S_HRODFXI2_8, 0, 0}, // S_HRODFXI2_7 + {SPR_FX00, 6, 1, A_SkullRodStorm, S_HRODFXI2_8, 0, 0}, // S_HRODFXI2_8 + {SPR_FX20, 32768, -1, NULL, S_NULL, 0, 0}, // S_RAINPLR1_1 + {SPR_FX21, 32768, -1, NULL, S_NULL, 0, 0}, // S_RAINPLR2_1 + {SPR_FX22, 32768, -1, NULL, S_NULL, 0, 0}, // S_RAINPLR3_1 + {SPR_FX23, 32768, -1, NULL, S_NULL, 0, 0}, // S_RAINPLR4_1 + {SPR_FX20, 32769, 4, A_RainImpact, S_RAINPLR1X_2, 0, 0}, // S_RAINPLR1X_1 + {SPR_FX20, 32770, 4, NULL, S_RAINPLR1X_3, 0, 0}, // S_RAINPLR1X_2 + {SPR_FX20, 32771, 4, NULL, S_RAINPLR1X_4, 0, 0}, // S_RAINPLR1X_3 + {SPR_FX20, 32772, 4, NULL, S_RAINPLR1X_5, 0, 0}, // S_RAINPLR1X_4 + {SPR_FX20, 32773, 4, NULL, S_NULL, 0, 0}, // S_RAINPLR1X_5 + {SPR_FX21, 32769, 4, A_RainImpact, S_RAINPLR2X_2, 0, 0}, // S_RAINPLR2X_1 + {SPR_FX21, 32770, 4, NULL, S_RAINPLR2X_3, 0, 0}, // S_RAINPLR2X_2 + {SPR_FX21, 32771, 4, NULL, S_RAINPLR2X_4, 0, 0}, // S_RAINPLR2X_3 + {SPR_FX21, 32772, 4, NULL, S_RAINPLR2X_5, 0, 0}, // S_RAINPLR2X_4 + {SPR_FX21, 32773, 4, NULL, S_NULL, 0, 0}, // S_RAINPLR2X_5 + {SPR_FX22, 32769, 4, A_RainImpact, S_RAINPLR3X_2, 0, 0}, // S_RAINPLR3X_1 + {SPR_FX22, 32770, 4, NULL, S_RAINPLR3X_3, 0, 0}, // S_RAINPLR3X_2 + {SPR_FX22, 32771, 4, NULL, S_RAINPLR3X_4, 0, 0}, // S_RAINPLR3X_3 + {SPR_FX22, 32772, 4, NULL, S_RAINPLR3X_5, 0, 0}, // S_RAINPLR3X_4 + {SPR_FX22, 32773, 4, NULL, S_NULL, 0, 0}, // S_RAINPLR3X_5 + {SPR_FX23, 32769, 4, A_RainImpact, S_RAINPLR4X_2, 0, 0}, // S_RAINPLR4X_1 + {SPR_FX23, 32770, 4, NULL, S_RAINPLR4X_3, 0, 0}, // S_RAINPLR4X_2 + {SPR_FX23, 32771, 4, NULL, S_RAINPLR4X_4, 0, 0}, // S_RAINPLR4X_3 + {SPR_FX23, 32772, 4, NULL, S_RAINPLR4X_5, 0, 0}, // S_RAINPLR4X_4 + {SPR_FX23, 32773, 4, NULL, S_NULL, 0, 0}, // S_RAINPLR4X_5 + {SPR_FX20, 32774, 4, NULL, S_RAINAIRXPLR1_2, 0, 0}, // S_RAINAIRXPLR1_1 + {SPR_FX21, 32774, 4, NULL, S_RAINAIRXPLR2_2, 0, 0}, // S_RAINAIRXPLR2_1 + {SPR_FX22, 32774, 4, NULL, S_RAINAIRXPLR3_2, 0, 0}, // S_RAINAIRXPLR3_1 + {SPR_FX23, 32774, 4, NULL, S_RAINAIRXPLR4_2, 0, 0}, // S_RAINAIRXPLR4_1 + {SPR_FX20, 32775, 4, NULL, S_RAINAIRXPLR1_3, 0, 0}, // S_RAINAIRXPLR1_2 + {SPR_FX21, 32775, 4, NULL, S_RAINAIRXPLR2_3, 0, 0}, // S_RAINAIRXPLR2_2 + {SPR_FX22, 32775, 4, NULL, S_RAINAIRXPLR3_3, 0, 0}, // S_RAINAIRXPLR3_2 + {SPR_FX23, 32775, 4, NULL, S_RAINAIRXPLR4_3, 0, 0}, // S_RAINAIRXPLR4_2 + {SPR_FX20, 32776, 4, NULL, S_NULL, 0, 0}, // S_RAINAIRXPLR1_3 + {SPR_FX21, 32776, 4, NULL, S_NULL, 0, 0}, // S_RAINAIRXPLR2_3 + {SPR_FX22, 32776, 4, NULL, S_NULL, 0, 0}, // S_RAINAIRXPLR3_3 + {SPR_FX23, 32776, 4, NULL, S_NULL, 0, 0}, // S_RAINAIRXPLR4_3 + {SPR_GWND, 0, 1, A_WeaponReady, S_GOLDWANDREADY, 0, 0}, // S_GOLDWANDREADY + {SPR_GWND, 0, 1, A_Lower, S_GOLDWANDDOWN, 0, 0}, // S_GOLDWANDDOWN + {SPR_GWND, 0, 1, A_Raise, S_GOLDWANDUP, 0, 0}, // S_GOLDWANDUP + {SPR_GWND, 1, 3, NULL, S_GOLDWANDATK1_2, 0, 0}, // S_GOLDWANDATK1_1 + {SPR_GWND, 2, 5, A_FireGoldWandPL1, S_GOLDWANDATK1_3, 0, 0}, // S_GOLDWANDATK1_2 + {SPR_GWND, 3, 3, NULL, S_GOLDWANDATK1_4, 0, 0}, // S_GOLDWANDATK1_3 + {SPR_GWND, 3, 0, A_ReFire, S_GOLDWANDREADY, 0, 0}, // S_GOLDWANDATK1_4 + {SPR_GWND, 1, 3, NULL, S_GOLDWANDATK2_2, 0, 0}, // S_GOLDWANDATK2_1 + {SPR_GWND, 2, 4, A_FireGoldWandPL2, S_GOLDWANDATK2_3, 0, 0}, // S_GOLDWANDATK2_2 + {SPR_GWND, 3, 3, NULL, S_GOLDWANDATK2_4, 0, 0}, // S_GOLDWANDATK2_3 + {SPR_GWND, 3, 0, A_ReFire, S_GOLDWANDREADY, 0, 0}, // S_GOLDWANDATK2_4 + {SPR_FX01, 32768, 6, NULL, S_GWANDFX1_2, 0, 0}, // S_GWANDFX1_1 + {SPR_FX01, 32769, 6, NULL, S_GWANDFX1_1, 0, 0}, // S_GWANDFX1_2 + {SPR_FX01, 32772, 3, NULL, S_GWANDFXI1_2, 0, 0}, // S_GWANDFXI1_1 + {SPR_FX01, 32773, 3, NULL, S_GWANDFXI1_3, 0, 0}, // S_GWANDFXI1_2 + {SPR_FX01, 32774, 3, NULL, S_GWANDFXI1_4, 0, 0}, // S_GWANDFXI1_3 + {SPR_FX01, 32775, 3, NULL, S_NULL, 0, 0}, // S_GWANDFXI1_4 + {SPR_FX01, 32770, 6, NULL, S_GWANDFX2_2, 0, 0}, // S_GWANDFX2_1 + {SPR_FX01, 32771, 6, NULL, S_GWANDFX2_1, 0, 0}, // S_GWANDFX2_2 + {SPR_PUF2, 32768, 3, NULL, S_GWANDPUFF1_2, 0, 0}, // S_GWANDPUFF1_1 + {SPR_PUF2, 32769, 3, NULL, S_GWANDPUFF1_3, 0, 0}, // S_GWANDPUFF1_2 + {SPR_PUF2, 32770, 3, NULL, S_GWANDPUFF1_4, 0, 0}, // S_GWANDPUFF1_3 + {SPR_PUF2, 32771, 3, NULL, S_GWANDPUFF1_5, 0, 0}, // S_GWANDPUFF1_4 + {SPR_PUF2, 32772, 3, NULL, S_NULL, 0, 0}, // S_GWANDPUFF1_5 + {SPR_WPHX, 0, -1, NULL, S_NULL, 0, 0}, // S_WPHX + {SPR_PHNX, 0, 1, A_WeaponReady, S_PHOENIXREADY, 0, 0}, // S_PHOENIXREADY + {SPR_PHNX, 0, 1, A_Lower, S_PHOENIXDOWN, 0, 0}, // S_PHOENIXDOWN + {SPR_PHNX, 0, 1, A_Raise, S_PHOENIXUP, 0, 0}, // S_PHOENIXUP + {SPR_PHNX, 1, 5, NULL, S_PHOENIXATK1_2, 0, 0}, // S_PHOENIXATK1_1 + {SPR_PHNX, 2, 7, A_FirePhoenixPL1, S_PHOENIXATK1_3, 0, 0}, // S_PHOENIXATK1_2 + {SPR_PHNX, 3, 4, NULL, S_PHOENIXATK1_4, 0, 0}, // S_PHOENIXATK1_3 + {SPR_PHNX, 1, 4, NULL, S_PHOENIXATK1_5, 0, 0}, // S_PHOENIXATK1_4 + {SPR_PHNX, 1, 0, A_ReFire, S_PHOENIXREADY, 0, 0}, // S_PHOENIXATK1_5 + {SPR_PHNX, 1, 3, A_InitPhoenixPL2, S_PHOENIXATK2_2, 0, 0}, // S_PHOENIXATK2_1 + {SPR_PHNX, 32770, 1, A_FirePhoenixPL2, S_PHOENIXATK2_3, 0, 0}, // S_PHOENIXATK2_2 + {SPR_PHNX, 1, 4, A_ReFire, S_PHOENIXATK2_4, 0, 0}, // S_PHOENIXATK2_3 + {SPR_PHNX, 1, 4, A_ShutdownPhoenixPL2, S_PHOENIXREADY, 0, 0}, // S_PHOENIXATK2_4 + {SPR_FX04, 32768, 4, A_PhoenixPuff, S_PHOENIXFX1_1, 0, 0}, // S_PHOENIXFX1_1 + {SPR_FX08, 32768, 6, A_Explode, S_PHOENIXFXI1_2, 0, 0}, // S_PHOENIXFXI1_1 + {SPR_FX08, 32769, 5, NULL, S_PHOENIXFXI1_3, 0, 0}, // S_PHOENIXFXI1_2 + {SPR_FX08, 32770, 5, NULL, S_PHOENIXFXI1_4, 0, 0}, // S_PHOENIXFXI1_3 + {SPR_FX08, 32771, 4, NULL, S_PHOENIXFXI1_5, 0, 0}, // S_PHOENIXFXI1_4 + {SPR_FX08, 32772, 4, NULL, S_PHOENIXFXI1_6, 0, 0}, // S_PHOENIXFXI1_5 + {SPR_FX08, 32773, 4, NULL, S_PHOENIXFXI1_7, 0, 0}, // S_PHOENIXFXI1_6 + {SPR_FX08, 32774, 4, NULL, S_PHOENIXFXI1_8, 0, 0}, // S_PHOENIXFXI1_7 + {SPR_FX08, 32775, 4, NULL, S_NULL, 0, 0}, // S_PHOENIXFXI1_8 + {SPR_FX08, 32776, 8, NULL, S_PHOENIXFXIX_1, 0, 0 }, // S_PHOENIXFXIX_1 + {SPR_FX08, 32777, 8, A_RemovedPhoenixFunc, S_PHOENIXFXIX_2, 0, 0 }, // S_PHOENIXFXIX_2 + {SPR_FX08, 32778, 8, NULL, S_NULL, 0, 0 }, // S_PHOENIXFXIX_3 + {SPR_FX04, 1, 4, NULL, S_PHOENIXPUFF2, 0, 0}, // S_PHOENIXPUFF1 + {SPR_FX04, 2, 4, NULL, S_PHOENIXPUFF3, 0, 0}, // S_PHOENIXPUFF2 + {SPR_FX04, 3, 4, NULL, S_PHOENIXPUFF4, 0, 0}, // S_PHOENIXPUFF3 + {SPR_FX04, 4, 4, NULL, S_PHOENIXPUFF5, 0, 0}, // S_PHOENIXPUFF4 + {SPR_FX04, 5, 4, NULL, S_NULL, 0, 0}, // S_PHOENIXPUFF5 + {SPR_FX09, 32768, 2, NULL, S_PHOENIXFX2_2, 0, 0}, // S_PHOENIXFX2_1 + {SPR_FX09, 32769, 2, NULL, S_PHOENIXFX2_3, 0, 0}, // S_PHOENIXFX2_2 + {SPR_FX09, 32768, 2, NULL, S_PHOENIXFX2_4, 0, 0}, // S_PHOENIXFX2_3 + {SPR_FX09, 32769, 2, NULL, S_PHOENIXFX2_5, 0, 0}, // S_PHOENIXFX2_4 + {SPR_FX09, 32768, 2, NULL, S_PHOENIXFX2_6, 0, 0}, // S_PHOENIXFX2_5 + {SPR_FX09, 32769, 2, A_FlameEnd, S_PHOENIXFX2_7, 0, 0}, // S_PHOENIXFX2_6 + {SPR_FX09, 32770, 2, NULL, S_PHOENIXFX2_8, 0, 0}, // S_PHOENIXFX2_7 + {SPR_FX09, 32771, 2, NULL, S_PHOENIXFX2_9, 0, 0}, // S_PHOENIXFX2_8 + {SPR_FX09, 32772, 2, NULL, S_PHOENIXFX2_10, 0, 0}, // S_PHOENIXFX2_9 + {SPR_FX09, 32773, 2, NULL, S_NULL, 0, 0}, // S_PHOENIXFX2_10 + {SPR_FX09, 32774, 3, NULL, S_PHOENIXFXI2_2, 0, 0}, // S_PHOENIXFXI2_1 + {SPR_FX09, 32775, 3, A_FloatPuff, S_PHOENIXFXI2_3, 0, 0}, // S_PHOENIXFXI2_2 + {SPR_FX09, 32776, 4, NULL, S_PHOENIXFXI2_4, 0, 0}, // S_PHOENIXFXI2_3 + {SPR_FX09, 32777, 5, NULL, S_PHOENIXFXI2_5, 0, 0}, // S_PHOENIXFXI2_4 + {SPR_FX09, 32778, 5, NULL, S_NULL, 0, 0}, // S_PHOENIXFXI2_5 + {SPR_WBOW, 0, -1, NULL, S_NULL, 0, 0}, // S_WBOW + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW2, 0, 0}, // S_CRBOW1 + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW3, 0, 0}, // S_CRBOW2 + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW4, 0, 0}, // S_CRBOW3 + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW5, 0, 0}, // S_CRBOW4 + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW6, 0, 0}, // S_CRBOW5 + {SPR_CRBW, 0, 1, A_WeaponReady, S_CRBOW7, 0, 0}, // S_CRBOW6 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW8, 0, 0}, // S_CRBOW7 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW9, 0, 0}, // S_CRBOW8 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW10, 0, 0}, // S_CRBOW9 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW11, 0, 0}, // S_CRBOW10 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW12, 0, 0}, // S_CRBOW11 + {SPR_CRBW, 1, 1, A_WeaponReady, S_CRBOW13, 0, 0}, // S_CRBOW12 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW14, 0, 0}, // S_CRBOW13 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW15, 0, 0}, // S_CRBOW14 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW16, 0, 0}, // S_CRBOW15 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW17, 0, 0}, // S_CRBOW16 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW18, 0, 0}, // S_CRBOW17 + {SPR_CRBW, 2, 1, A_WeaponReady, S_CRBOW1, 0, 0}, // S_CRBOW18 + {SPR_CRBW, 0, 1, A_Lower, S_CRBOWDOWN, 0, 0}, // S_CRBOWDOWN + {SPR_CRBW, 0, 1, A_Raise, S_CRBOWUP, 0, 0}, // S_CRBOWUP + {SPR_CRBW, 3, 6, A_FireCrossbowPL1, S_CRBOWATK1_2, 0, 0}, // S_CRBOWATK1_1 + {SPR_CRBW, 4, 3, NULL, S_CRBOWATK1_3, 0, 0}, // S_CRBOWATK1_2 + {SPR_CRBW, 5, 3, NULL, S_CRBOWATK1_4, 0, 0}, // S_CRBOWATK1_3 + {SPR_CRBW, 6, 3, NULL, S_CRBOWATK1_5, 0, 0}, // S_CRBOWATK1_4 + {SPR_CRBW, 7, 3, NULL, S_CRBOWATK1_6, 0, 0}, // S_CRBOWATK1_5 + {SPR_CRBW, 0, 4, NULL, S_CRBOWATK1_7, 0, 0}, // S_CRBOWATK1_6 + {SPR_CRBW, 1, 4, NULL, S_CRBOWATK1_8, 0, 0}, // S_CRBOWATK1_7 + {SPR_CRBW, 2, 5, A_ReFire, S_CRBOW1, 0, 0}, // S_CRBOWATK1_8 + {SPR_CRBW, 3, 5, A_FireCrossbowPL2, S_CRBOWATK2_2, 0, 0}, // S_CRBOWATK2_1 + {SPR_CRBW, 4, 3, NULL, S_CRBOWATK2_3, 0, 0}, // S_CRBOWATK2_2 + {SPR_CRBW, 5, 2, NULL, S_CRBOWATK2_4, 0, 0}, // S_CRBOWATK2_3 + {SPR_CRBW, 6, 3, NULL, S_CRBOWATK2_5, 0, 0}, // S_CRBOWATK2_4 + {SPR_CRBW, 7, 2, NULL, S_CRBOWATK2_6, 0, 0}, // S_CRBOWATK2_5 + {SPR_CRBW, 0, 3, NULL, S_CRBOWATK2_7, 0, 0}, // S_CRBOWATK2_6 + {SPR_CRBW, 1, 3, NULL, S_CRBOWATK2_8, 0, 0}, // S_CRBOWATK2_7 + {SPR_CRBW, 2, 4, A_ReFire, S_CRBOW1, 0, 0}, // S_CRBOWATK2_8 + {SPR_FX03, 32769, 1, NULL, S_CRBOWFX1, 0, 0}, // S_CRBOWFX1 + {SPR_FX03, 32775, 8, NULL, S_CRBOWFXI1_2, 0, 0}, // S_CRBOWFXI1_1 + {SPR_FX03, 32776, 8, NULL, S_CRBOWFXI1_3, 0, 0}, // S_CRBOWFXI1_2 + {SPR_FX03, 32777, 8, NULL, S_NULL, 0, 0}, // S_CRBOWFXI1_3 + {SPR_FX03, 32769, 1, A_BoltSpark, S_CRBOWFX2, 0, 0}, // S_CRBOWFX2 + {SPR_FX03, 32768, 1, NULL, S_CRBOWFX3, 0, 0}, // S_CRBOWFX3 + {SPR_FX03, 32770, 8, NULL, S_CRBOWFXI3_2, 0, 0}, // S_CRBOWFXI3_1 + {SPR_FX03, 32771, 8, NULL, S_CRBOWFXI3_3, 0, 0}, // S_CRBOWFXI3_2 + {SPR_FX03, 32772, 8, NULL, S_NULL, 0, 0}, // S_CRBOWFXI3_3 + {SPR_FX03, 32773, 8, NULL, S_CRBOWFX4_2, 0, 0}, // S_CRBOWFX4_1 + {SPR_FX03, 32774, 8, NULL, S_NULL, 0, 0}, // S_CRBOWFX4_2 + {SPR_BLOD, 2, 8, NULL, S_BLOOD2, 0, 0}, // S_BLOOD1 + {SPR_BLOD, 1, 8, NULL, S_BLOOD3, 0, 0}, // S_BLOOD2 + {SPR_BLOD, 0, 8, NULL, S_NULL, 0, 0}, // S_BLOOD3 + {SPR_BLOD, 2, 8, NULL, S_BLOODSPLATTER2, 0, 0}, // S_BLOODSPLATTER1 + {SPR_BLOD, 1, 8, NULL, S_BLOODSPLATTER3, 0, 0}, // S_BLOODSPLATTER2 + {SPR_BLOD, 0, 8, NULL, S_NULL, 0, 0}, // S_BLOODSPLATTER3 + {SPR_BLOD, 0, 6, NULL, S_NULL, 0, 0}, // S_BLOODSPLATTERX + {SPR_PLAY, 0, -1, NULL, S_NULL, 0, 0}, // S_PLAY + {SPR_PLAY, 0, 4, NULL, S_PLAY_RUN2, 0, 0}, // S_PLAY_RUN1 + {SPR_PLAY, 1, 4, NULL, S_PLAY_RUN3, 0, 0}, // S_PLAY_RUN2 + {SPR_PLAY, 2, 4, NULL, S_PLAY_RUN4, 0, 0}, // S_PLAY_RUN3 + {SPR_PLAY, 3, 4, NULL, S_PLAY_RUN1, 0, 0}, // S_PLAY_RUN4 + {SPR_PLAY, 4, 12, NULL, S_PLAY, 0, 0}, // S_PLAY_ATK1 + {SPR_PLAY, 32773, 6, NULL, S_PLAY_ATK1, 0, 0}, // S_PLAY_ATK2 + {SPR_PLAY, 6, 4, NULL, S_PLAY_PAIN2, 0, 0}, // S_PLAY_PAIN + {SPR_PLAY, 6, 4, A_Pain, S_PLAY, 0, 0}, // S_PLAY_PAIN2 + {SPR_PLAY, 7, 6, NULL, S_PLAY_DIE2, 0, 0}, // S_PLAY_DIE1 + {SPR_PLAY, 8, 6, A_Scream, S_PLAY_DIE3, 0, 0}, // S_PLAY_DIE2 + {SPR_PLAY, 9, 6, NULL, S_PLAY_DIE4, 0, 0}, // S_PLAY_DIE3 + {SPR_PLAY, 10, 6, NULL, S_PLAY_DIE5, 0, 0}, // S_PLAY_DIE4 + {SPR_PLAY, 11, 6, A_NoBlocking, S_PLAY_DIE6, 0, 0}, // S_PLAY_DIE5 + {SPR_PLAY, 12, 6, NULL, S_PLAY_DIE7, 0, 0}, // S_PLAY_DIE6 + {SPR_PLAY, 13, 6, NULL, S_PLAY_DIE8, 0, 0}, // S_PLAY_DIE7 + {SPR_PLAY, 14, 6, NULL, S_PLAY_DIE9, 0, 0}, // S_PLAY_DIE8 + {SPR_PLAY, 15, -1, A_AddPlayerCorpse, S_NULL, 0, 0}, // S_PLAY_DIE9 + {SPR_PLAY, 16, 5, A_Scream, S_PLAY_XDIE2, 0, 0}, // S_PLAY_XDIE1 + {SPR_PLAY, 17, 5, A_SkullPop, S_PLAY_XDIE3, 0, 0}, // S_PLAY_XDIE2 + {SPR_PLAY, 18, 5, A_NoBlocking, S_PLAY_XDIE4, 0, 0}, // S_PLAY_XDIE3 + {SPR_PLAY, 19, 5, NULL, S_PLAY_XDIE5, 0, 0}, // S_PLAY_XDIE4 + {SPR_PLAY, 20, 5, NULL, S_PLAY_XDIE6, 0, 0}, // S_PLAY_XDIE5 + {SPR_PLAY, 21, 5, NULL, S_PLAY_XDIE7, 0, 0}, // S_PLAY_XDIE6 + {SPR_PLAY, 22, 5, NULL, S_PLAY_XDIE8, 0, 0}, // S_PLAY_XDIE7 + {SPR_PLAY, 23, 5, NULL, S_PLAY_XDIE9, 0, 0}, // S_PLAY_XDIE8 + {SPR_PLAY, 24, -1, A_AddPlayerCorpse, S_NULL, 0, 0}, // S_PLAY_XDIE9 + {SPR_FDTH, 32768, 5, A_FlameSnd, S_PLAY_FDTH2, 0, 0}, // S_PLAY_FDTH1 + {SPR_FDTH, 32769, 4, NULL, S_PLAY_FDTH3, 0, 0}, // S_PLAY_FDTH2 + {SPR_FDTH, 32770, 5, NULL, S_PLAY_FDTH4, 0, 0}, // S_PLAY_FDTH3 + {SPR_FDTH, 32771, 4, A_Scream, S_PLAY_FDTH5, 0, 0}, // S_PLAY_FDTH4 + {SPR_FDTH, 32772, 5, NULL, S_PLAY_FDTH6, 0, 0}, // S_PLAY_FDTH5 + {SPR_FDTH, 32773, 4, NULL, S_PLAY_FDTH7, 0, 0}, // S_PLAY_FDTH6 + {SPR_FDTH, 32774, 5, A_FlameSnd, S_PLAY_FDTH8, 0, 0}, // S_PLAY_FDTH7 + {SPR_FDTH, 32775, 4, NULL, S_PLAY_FDTH9, 0, 0}, // S_PLAY_FDTH8 + {SPR_FDTH, 32776, 5, NULL, S_PLAY_FDTH10, 0, 0}, // S_PLAY_FDTH9 + {SPR_FDTH, 32777, 4, NULL, S_PLAY_FDTH11, 0, 0}, // S_PLAY_FDTH10 + {SPR_FDTH, 32778, 5, NULL, S_PLAY_FDTH12, 0, 0}, // S_PLAY_FDTH11 + {SPR_FDTH, 32779, 4, NULL, S_PLAY_FDTH13, 0, 0}, // S_PLAY_FDTH12 + {SPR_FDTH, 32780, 5, NULL, S_PLAY_FDTH14, 0, 0}, // S_PLAY_FDTH13 + {SPR_FDTH, 32781, 4, NULL, S_PLAY_FDTH15, 0, 0}, // S_PLAY_FDTH14 + {SPR_FDTH, 32782, 5, A_NoBlocking, S_PLAY_FDTH16, 0, 0}, // S_PLAY_FDTH15 + {SPR_FDTH, 32783, 4, NULL, S_PLAY_FDTH17, 0, 0}, // S_PLAY_FDTH16 + {SPR_FDTH, 32784, 5, NULL, S_PLAY_FDTH18, 0, 0}, // S_PLAY_FDTH17 + {SPR_FDTH, 32785, 4, NULL, S_PLAY_FDTH19, 0, 0}, // S_PLAY_FDTH18 + {SPR_ACLO, 4, 35, A_CheckBurnGone, S_PLAY_FDTH19, 0, 0}, // S_PLAY_FDTH19 + {SPR_ACLO, 4, 8, NULL, S_NULL, 0, 0}, // S_PLAY_FDTH20 + {SPR_BSKL, 0, 5, A_CheckSkullFloor, S_BLOODYSKULL2, 0, 0}, // S_BLOODYSKULL1 + {SPR_BSKL, 1, 5, A_CheckSkullFloor, S_BLOODYSKULL3, 0, 0}, // S_BLOODYSKULL2 + {SPR_BSKL, 2, 5, A_CheckSkullFloor, S_BLOODYSKULL4, 0, 0}, // S_BLOODYSKULL3 + {SPR_BSKL, 3, 5, A_CheckSkullFloor, S_BLOODYSKULL5, 0, 0}, // S_BLOODYSKULL4 + {SPR_BSKL, 4, 5, A_CheckSkullFloor, S_BLOODYSKULL1, 0, 0}, // S_BLOODYSKULL5 + {SPR_BSKL, 5, 16, A_CheckSkullDone, S_BLOODYSKULLX1, 0, 0}, // S_BLOODYSKULLX1 + {SPR_BSKL, 5, 1050, NULL, S_NULL, 0, 0}, // S_BLOODYSKULLX2 + {SPR_CHKN, 0, -1, NULL, S_NULL, 0, 0}, // S_CHICPLAY + {SPR_CHKN, 0, 3, NULL, S_CHICPLAY_RUN2, 0, 0}, // S_CHICPLAY_RUN1 + {SPR_CHKN, 1, 3, NULL, S_CHICPLAY_RUN3, 0, 0}, // S_CHICPLAY_RUN2 + {SPR_CHKN, 0, 3, NULL, S_CHICPLAY_RUN4, 0, 0}, // S_CHICPLAY_RUN3 + {SPR_CHKN, 1, 3, NULL, S_CHICPLAY_RUN1, 0, 0}, // S_CHICPLAY_RUN4 + {SPR_CHKN, 2, 12, NULL, S_CHICPLAY, 0, 0}, // S_CHICPLAY_ATK1 + {SPR_CHKN, 3, 4, A_Feathers, S_CHICPLAY_PAIN2, 0, 0}, // S_CHICPLAY_PAIN + {SPR_CHKN, 2, 4, A_Pain, S_CHICPLAY, 0, 0}, // S_CHICPLAY_PAIN2 + {SPR_CHKN, 0, 10, A_ChicLook, S_CHICKEN_LOOK2, 0, 0}, // S_CHICKEN_LOOK1 + {SPR_CHKN, 1, 10, A_ChicLook, S_CHICKEN_LOOK1, 0, 0}, // S_CHICKEN_LOOK2 + {SPR_CHKN, 0, 3, A_ChicChase, S_CHICKEN_WALK2, 0, 0}, // S_CHICKEN_WALK1 + {SPR_CHKN, 1, 3, A_ChicChase, S_CHICKEN_WALK1, 0, 0}, // S_CHICKEN_WALK2 + {SPR_CHKN, 3, 5, A_Feathers, S_CHICKEN_PAIN2, 0, 0}, // S_CHICKEN_PAIN1 + {SPR_CHKN, 2, 5, A_ChicPain, S_CHICKEN_WALK1, 0, 0}, // S_CHICKEN_PAIN2 + {SPR_CHKN, 0, 8, A_FaceTarget, S_CHICKEN_ATK2, 0, 0}, // S_CHICKEN_ATK1 + {SPR_CHKN, 2, 10, A_ChicAttack, S_CHICKEN_WALK1, 0, 0}, // S_CHICKEN_ATK2 + {SPR_CHKN, 4, 6, A_Scream, S_CHICKEN_DIE2, 0, 0}, // S_CHICKEN_DIE1 + {SPR_CHKN, 5, 6, A_Feathers, S_CHICKEN_DIE3, 0, 0}, // S_CHICKEN_DIE2 + {SPR_CHKN, 6, 6, NULL, S_CHICKEN_DIE4, 0, 0}, // S_CHICKEN_DIE3 + {SPR_CHKN, 7, 6, A_NoBlocking, S_CHICKEN_DIE5, 0, 0}, // S_CHICKEN_DIE4 + {SPR_CHKN, 8, 6, NULL, S_CHICKEN_DIE6, 0, 0}, // S_CHICKEN_DIE5 + {SPR_CHKN, 9, 6, NULL, S_CHICKEN_DIE7, 0, 0}, // S_CHICKEN_DIE6 + {SPR_CHKN, 10, 6, NULL, S_CHICKEN_DIE8, 0, 0}, // S_CHICKEN_DIE7 + {SPR_CHKN, 11, -1, NULL, S_NULL, 0, 0}, // S_CHICKEN_DIE8 + {SPR_CHKN, 12, 3, NULL, S_FEATHER2, 0, 0}, // S_FEATHER1 + {SPR_CHKN, 13, 3, NULL, S_FEATHER3, 0, 0}, // S_FEATHER2 + {SPR_CHKN, 14, 3, NULL, S_FEATHER4, 0, 0}, // S_FEATHER3 + {SPR_CHKN, 15, 3, NULL, S_FEATHER5, 0, 0}, // S_FEATHER4 + {SPR_CHKN, 16, 3, NULL, S_FEATHER6, 0, 0}, // S_FEATHER5 + {SPR_CHKN, 15, 3, NULL, S_FEATHER7, 0, 0}, // S_FEATHER6 + {SPR_CHKN, 14, 3, NULL, S_FEATHER8, 0, 0}, // S_FEATHER7 + {SPR_CHKN, 13, 3, NULL, S_FEATHER1, 0, 0}, // S_FEATHER8 + {SPR_CHKN, 13, 6, NULL, S_NULL, 0, 0}, // S_FEATHERX + {SPR_MUMM, 0, 10, A_Look, S_MUMMY_LOOK2, 0, 0}, // S_MUMMY_LOOK1 + {SPR_MUMM, 1, 10, A_Look, S_MUMMY_LOOK1, 0, 0}, // S_MUMMY_LOOK2 + {SPR_MUMM, 0, 4, A_Chase, S_MUMMY_WALK2, 0, 0}, // S_MUMMY_WALK1 + {SPR_MUMM, 1, 4, A_Chase, S_MUMMY_WALK3, 0, 0}, // S_MUMMY_WALK2 + {SPR_MUMM, 2, 4, A_Chase, S_MUMMY_WALK4, 0, 0}, // S_MUMMY_WALK3 + {SPR_MUMM, 3, 4, A_Chase, S_MUMMY_WALK1, 0, 0}, // S_MUMMY_WALK4 + {SPR_MUMM, 4, 6, A_FaceTarget, S_MUMMY_ATK2, 0, 0}, // S_MUMMY_ATK1 + {SPR_MUMM, 5, 6, A_MummyAttack, S_MUMMY_ATK3, 0, 0}, // S_MUMMY_ATK2 + {SPR_MUMM, 6, 6, A_FaceTarget, S_MUMMY_WALK1, 0, 0}, // S_MUMMY_ATK3 + {SPR_MUMM, 23, 5, A_FaceTarget, S_MUMMYL_ATK2, 0, 0}, // S_MUMMYL_ATK1 + {SPR_MUMM, 32792, 5, A_FaceTarget, S_MUMMYL_ATK3, 0, 0}, // S_MUMMYL_ATK2 + {SPR_MUMM, 23, 5, A_FaceTarget, S_MUMMYL_ATK4, 0, 0}, // S_MUMMYL_ATK3 + {SPR_MUMM, 32792, 5, A_FaceTarget, S_MUMMYL_ATK5, 0, 0}, // S_MUMMYL_ATK4 + {SPR_MUMM, 23, 5, A_FaceTarget, S_MUMMYL_ATK6, 0, 0}, // S_MUMMYL_ATK5 + {SPR_MUMM, 32792, 15, A_MummyAttack2, S_MUMMY_WALK1, 0, 0}, // S_MUMMYL_ATK6 + {SPR_MUMM, 7, 4, NULL, S_MUMMY_PAIN2, 0, 0}, // S_MUMMY_PAIN1 + {SPR_MUMM, 7, 4, A_Pain, S_MUMMY_WALK1, 0, 0}, // S_MUMMY_PAIN2 + {SPR_MUMM, 8, 5, NULL, S_MUMMY_DIE2, 0, 0}, // S_MUMMY_DIE1 + {SPR_MUMM, 9, 5, A_Scream, S_MUMMY_DIE3, 0, 0}, // S_MUMMY_DIE2 + {SPR_MUMM, 10, 5, A_MummySoul, S_MUMMY_DIE4, 0, 0}, // S_MUMMY_DIE3 + {SPR_MUMM, 11, 5, NULL, S_MUMMY_DIE5, 0, 0}, // S_MUMMY_DIE4 + {SPR_MUMM, 12, 5, A_NoBlocking, S_MUMMY_DIE6, 0, 0}, // S_MUMMY_DIE5 + {SPR_MUMM, 13, 5, NULL, S_MUMMY_DIE7, 0, 0}, // S_MUMMY_DIE6 + {SPR_MUMM, 14, 5, NULL, S_MUMMY_DIE8, 0, 0}, // S_MUMMY_DIE7 + {SPR_MUMM, 15, -1, NULL, S_NULL, 0, 0}, // S_MUMMY_DIE8 + {SPR_MUMM, 16, 5, NULL, S_MUMMY_SOUL2, 0, 0}, // S_MUMMY_SOUL1 + {SPR_MUMM, 17, 5, NULL, S_MUMMY_SOUL3, 0, 0}, // S_MUMMY_SOUL2 + {SPR_MUMM, 18, 5, NULL, S_MUMMY_SOUL4, 0, 0}, // S_MUMMY_SOUL3 + {SPR_MUMM, 19, 9, NULL, S_MUMMY_SOUL5, 0, 0}, // S_MUMMY_SOUL4 + {SPR_MUMM, 20, 5, NULL, S_MUMMY_SOUL6, 0, 0}, // S_MUMMY_SOUL5 + {SPR_MUMM, 21, 5, NULL, S_MUMMY_SOUL7, 0, 0}, // S_MUMMY_SOUL6 + {SPR_MUMM, 22, 5, NULL, S_NULL, 0, 0}, // S_MUMMY_SOUL7 + {SPR_FX15, 32768, 5, A_ContMobjSound, S_MUMMYFX1_2, 0, 0}, // S_MUMMYFX1_1 + {SPR_FX15, 32769, 5, A_MummyFX1Seek, S_MUMMYFX1_3, 0, 0}, // S_MUMMYFX1_2 + {SPR_FX15, 32770, 5, NULL, S_MUMMYFX1_4, 0, 0}, // S_MUMMYFX1_3 + {SPR_FX15, 32769, 5, A_MummyFX1Seek, S_MUMMYFX1_1, 0, 0}, // S_MUMMYFX1_4 + {SPR_FX15, 32771, 5, NULL, S_MUMMYFXI1_2, 0, 0}, // S_MUMMYFXI1_1 + {SPR_FX15, 32772, 5, NULL, S_MUMMYFXI1_3, 0, 0}, // S_MUMMYFXI1_2 + {SPR_FX15, 32773, 5, NULL, S_MUMMYFXI1_4, 0, 0}, // S_MUMMYFXI1_3 + {SPR_FX15, 32774, 5, NULL, S_NULL, 0, 0}, // S_MUMMYFXI1_4 + {SPR_BEAS, 0, 10, A_Look, S_BEAST_LOOK2, 0, 0}, // S_BEAST_LOOK1 + {SPR_BEAS, 1, 10, A_Look, S_BEAST_LOOK1, 0, 0}, // S_BEAST_LOOK2 + {SPR_BEAS, 0, 3, A_Chase, S_BEAST_WALK2, 0, 0}, // S_BEAST_WALK1 + {SPR_BEAS, 1, 3, A_Chase, S_BEAST_WALK3, 0, 0}, // S_BEAST_WALK2 + {SPR_BEAS, 2, 3, A_Chase, S_BEAST_WALK4, 0, 0}, // S_BEAST_WALK3 + {SPR_BEAS, 3, 3, A_Chase, S_BEAST_WALK5, 0, 0}, // S_BEAST_WALK4 + {SPR_BEAS, 4, 3, A_Chase, S_BEAST_WALK6, 0, 0}, // S_BEAST_WALK5 + {SPR_BEAS, 5, 3, A_Chase, S_BEAST_WALK1, 0, 0}, // S_BEAST_WALK6 + {SPR_BEAS, 7, 10, A_FaceTarget, S_BEAST_ATK2, 0, 0}, // S_BEAST_ATK1 + {SPR_BEAS, 8, 10, A_BeastAttack, S_BEAST_WALK1, 0, 0}, // S_BEAST_ATK2 + {SPR_BEAS, 6, 3, NULL, S_BEAST_PAIN2, 0, 0}, // S_BEAST_PAIN1 + {SPR_BEAS, 6, 3, A_Pain, S_BEAST_WALK1, 0, 0}, // S_BEAST_PAIN2 + {SPR_BEAS, 17, 6, NULL, S_BEAST_DIE2, 0, 0}, // S_BEAST_DIE1 + {SPR_BEAS, 18, 6, A_Scream, S_BEAST_DIE3, 0, 0}, // S_BEAST_DIE2 + {SPR_BEAS, 19, 6, NULL, S_BEAST_DIE4, 0, 0}, // S_BEAST_DIE3 + {SPR_BEAS, 20, 6, NULL, S_BEAST_DIE5, 0, 0}, // S_BEAST_DIE4 + {SPR_BEAS, 21, 6, NULL, S_BEAST_DIE6, 0, 0}, // S_BEAST_DIE5 + {SPR_BEAS, 22, 6, A_NoBlocking, S_BEAST_DIE7, 0, 0}, // S_BEAST_DIE6 + {SPR_BEAS, 23, 6, NULL, S_BEAST_DIE8, 0, 0}, // S_BEAST_DIE7 + {SPR_BEAS, 24, 6, NULL, S_BEAST_DIE9, 0, 0}, // S_BEAST_DIE8 + {SPR_BEAS, 25, -1, NULL, S_NULL, 0, 0}, // S_BEAST_DIE9 + {SPR_BEAS, 9, 5, NULL, S_BEAST_XDIE2, 0, 0}, // S_BEAST_XDIE1 + {SPR_BEAS, 10, 6, A_Scream, S_BEAST_XDIE3, 0, 0}, // S_BEAST_XDIE2 + {SPR_BEAS, 11, 5, NULL, S_BEAST_XDIE4, 0, 0}, // S_BEAST_XDIE3 + {SPR_BEAS, 12, 6, NULL, S_BEAST_XDIE5, 0, 0}, // S_BEAST_XDIE4 + {SPR_BEAS, 13, 5, NULL, S_BEAST_XDIE6, 0, 0}, // S_BEAST_XDIE5 + {SPR_BEAS, 14, 6, A_NoBlocking, S_BEAST_XDIE7, 0, 0}, // S_BEAST_XDIE6 + {SPR_BEAS, 15, 5, NULL, S_BEAST_XDIE8, 0, 0}, // S_BEAST_XDIE7 + {SPR_BEAS, 16, -1, NULL, S_NULL, 0, 0}, // S_BEAST_XDIE8 + {SPR_FRB1, 0, 2, A_BeastPuff, S_BEASTBALL2, 0, 0}, // S_BEASTBALL1 + {SPR_FRB1, 0, 2, A_BeastPuff, S_BEASTBALL3, 0, 0}, // S_BEASTBALL2 + {SPR_FRB1, 1, 2, A_BeastPuff, S_BEASTBALL4, 0, 0}, // S_BEASTBALL3 + {SPR_FRB1, 1, 2, A_BeastPuff, S_BEASTBALL5, 0, 0}, // S_BEASTBALL4 + {SPR_FRB1, 2, 2, A_BeastPuff, S_BEASTBALL6, 0, 0}, // S_BEASTBALL5 + {SPR_FRB1, 2, 2, A_BeastPuff, S_BEASTBALL1, 0, 0}, // S_BEASTBALL6 + {SPR_FRB1, 3, 4, NULL, S_BEASTBALLX2, 0, 0}, // S_BEASTBALLX1 + {SPR_FRB1, 4, 4, NULL, S_BEASTBALLX3, 0, 0}, // S_BEASTBALLX2 + {SPR_FRB1, 5, 4, NULL, S_BEASTBALLX4, 0, 0}, // S_BEASTBALLX3 + {SPR_FRB1, 6, 4, NULL, S_BEASTBALLX5, 0, 0}, // S_BEASTBALLX4 + {SPR_FRB1, 7, 4, NULL, S_NULL, 0, 0}, // S_BEASTBALLX5 + {SPR_FRB1, 0, 4, NULL, S_BURNBALL2, 0, 0}, // S_BURNBALL1 + {SPR_FRB1, 1, 4, NULL, S_BURNBALL3, 0, 0}, // S_BURNBALL2 + {SPR_FRB1, 2, 4, NULL, S_BURNBALL4, 0, 0}, // S_BURNBALL3 + {SPR_FRB1, 3, 4, NULL, S_BURNBALL5, 0, 0}, // S_BURNBALL4 + {SPR_FRB1, 4, 4, NULL, S_BURNBALL6, 0, 0}, // S_BURNBALL5 + {SPR_FRB1, 5, 4, NULL, S_BURNBALL7, 0, 0}, // S_BURNBALL6 + {SPR_FRB1, 6, 4, NULL, S_BURNBALL8, 0, 0}, // S_BURNBALL7 + {SPR_FRB1, 7, 4, NULL, S_NULL, 0, 0}, // S_BURNBALL8 + {SPR_FRB1, 32768, 4, NULL, S_BURNBALLFB2, 0, 0}, // S_BURNBALLFB1 + {SPR_FRB1, 32769, 4, NULL, S_BURNBALLFB3, 0, 0}, // S_BURNBALLFB2 + {SPR_FRB1, 32770, 4, NULL, S_BURNBALLFB4, 0, 0}, // S_BURNBALLFB3 + {SPR_FRB1, 32771, 4, NULL, S_BURNBALLFB5, 0, 0}, // S_BURNBALLFB4 + {SPR_FRB1, 32772, 4, NULL, S_BURNBALLFB6, 0, 0}, // S_BURNBALLFB5 + {SPR_FRB1, 32773, 4, NULL, S_BURNBALLFB7, 0, 0}, // S_BURNBALLFB6 + {SPR_FRB1, 32774, 4, NULL, S_BURNBALLFB8, 0, 0}, // S_BURNBALLFB7 + {SPR_FRB1, 32775, 4, NULL, S_NULL, 0, 0}, // S_BURNBALLFB8 + {SPR_FRB1, 3, 4, NULL, S_PUFFY2, 0, 0}, // S_PUFFY1 + {SPR_FRB1, 4, 4, NULL, S_PUFFY3, 0, 0}, // S_PUFFY2 + {SPR_FRB1, 5, 4, NULL, S_PUFFY4, 0, 0}, // S_PUFFY3 + {SPR_FRB1, 6, 4, NULL, S_PUFFY5, 0, 0}, // S_PUFFY4 + {SPR_FRB1, 7, 4, NULL, S_NULL, 0, 0}, // S_PUFFY5 + {SPR_SNKE, 0, 10, A_Look, S_SNAKE_LOOK2, 0, 0}, // S_SNAKE_LOOK1 + {SPR_SNKE, 1, 10, A_Look, S_SNAKE_LOOK1, 0, 0}, // S_SNAKE_LOOK2 + {SPR_SNKE, 0, 4, A_Chase, S_SNAKE_WALK2, 0, 0}, // S_SNAKE_WALK1 + {SPR_SNKE, 1, 4, A_Chase, S_SNAKE_WALK3, 0, 0}, // S_SNAKE_WALK2 + {SPR_SNKE, 2, 4, A_Chase, S_SNAKE_WALK4, 0, 0}, // S_SNAKE_WALK3 + {SPR_SNKE, 3, 4, A_Chase, S_SNAKE_WALK1, 0, 0}, // S_SNAKE_WALK4 + {SPR_SNKE, 5, 5, A_FaceTarget, S_SNAKE_ATK2, 0, 0}, // S_SNAKE_ATK1 + {SPR_SNKE, 5, 5, A_FaceTarget, S_SNAKE_ATK3, 0, 0}, // S_SNAKE_ATK2 + {SPR_SNKE, 5, 4, A_SnakeAttack, S_SNAKE_ATK4, 0, 0}, // S_SNAKE_ATK3 + {SPR_SNKE, 5, 4, A_SnakeAttack, S_SNAKE_ATK5, 0, 0}, // S_SNAKE_ATK4 + {SPR_SNKE, 5, 4, A_SnakeAttack, S_SNAKE_ATK6, 0, 0}, // S_SNAKE_ATK5 + {SPR_SNKE, 5, 5, A_FaceTarget, S_SNAKE_ATK7, 0, 0}, // S_SNAKE_ATK6 + {SPR_SNKE, 5, 5, A_FaceTarget, S_SNAKE_ATK8, 0, 0}, // S_SNAKE_ATK7 + {SPR_SNKE, 5, 5, A_FaceTarget, S_SNAKE_ATK9, 0, 0}, // S_SNAKE_ATK8 + {SPR_SNKE, 5, 4, A_SnakeAttack2, S_SNAKE_WALK1, 0, 0}, // S_SNAKE_ATK9 + {SPR_SNKE, 4, 3, NULL, S_SNAKE_PAIN2, 0, 0}, // S_SNAKE_PAIN1 + {SPR_SNKE, 4, 3, A_Pain, S_SNAKE_WALK1, 0, 0}, // S_SNAKE_PAIN2 + {SPR_SNKE, 6, 5, NULL, S_SNAKE_DIE2, 0, 0}, // S_SNAKE_DIE1 + {SPR_SNKE, 7, 5, A_Scream, S_SNAKE_DIE3, 0, 0}, // S_SNAKE_DIE2 + {SPR_SNKE, 8, 5, NULL, S_SNAKE_DIE4, 0, 0}, // S_SNAKE_DIE3 + {SPR_SNKE, 9, 5, NULL, S_SNAKE_DIE5, 0, 0}, // S_SNAKE_DIE4 + {SPR_SNKE, 10, 5, NULL, S_SNAKE_DIE6, 0, 0}, // S_SNAKE_DIE5 + {SPR_SNKE, 11, 5, NULL, S_SNAKE_DIE7, 0, 0}, // S_SNAKE_DIE6 + {SPR_SNKE, 12, 5, A_NoBlocking, S_SNAKE_DIE8, 0, 0}, // S_SNAKE_DIE7 + {SPR_SNKE, 13, 5, NULL, S_SNAKE_DIE9, 0, 0}, // S_SNAKE_DIE8 + {SPR_SNKE, 14, 5, NULL, S_SNAKE_DIE10, 0, 0}, // S_SNAKE_DIE9 + {SPR_SNKE, 15, -1, NULL, S_NULL, 0, 0}, // S_SNAKE_DIE10 + {SPR_SNFX, 32768, 5, NULL, S_SNAKEPRO_A2, 0, 0}, // S_SNAKEPRO_A1 + {SPR_SNFX, 32769, 5, NULL, S_SNAKEPRO_A3, 0, 0}, // S_SNAKEPRO_A2 + {SPR_SNFX, 32770, 5, NULL, S_SNAKEPRO_A4, 0, 0}, // S_SNAKEPRO_A3 + {SPR_SNFX, 32771, 5, NULL, S_SNAKEPRO_A1, 0, 0}, // S_SNAKEPRO_A4 + {SPR_SNFX, 32772, 5, NULL, S_SNAKEPRO_AX2, 0, 0}, // S_SNAKEPRO_AX1 + {SPR_SNFX, 32773, 5, NULL, S_SNAKEPRO_AX3, 0, 0}, // S_SNAKEPRO_AX2 + {SPR_SNFX, 32774, 4, NULL, S_SNAKEPRO_AX4, 0, 0}, // S_SNAKEPRO_AX3 + {SPR_SNFX, 32775, 3, NULL, S_SNAKEPRO_AX5, 0, 0}, // S_SNAKEPRO_AX4 + {SPR_SNFX, 32776, 3, NULL, S_NULL, 0, 0}, // S_SNAKEPRO_AX5 + {SPR_SNFX, 32777, 6, NULL, S_SNAKEPRO_B2, 0, 0}, // S_SNAKEPRO_B1 + {SPR_SNFX, 32778, 6, NULL, S_SNAKEPRO_B1, 0, 0}, // S_SNAKEPRO_B2 + {SPR_SNFX, 32779, 5, NULL, S_SNAKEPRO_BX2, 0, 0}, // S_SNAKEPRO_BX1 + {SPR_SNFX, 32780, 5, NULL, S_SNAKEPRO_BX3, 0, 0}, // S_SNAKEPRO_BX2 + {SPR_SNFX, 32781, 4, NULL, S_SNAKEPRO_BX4, 0, 0}, // S_SNAKEPRO_BX3 + {SPR_SNFX, 32782, 3, NULL, S_NULL, 0, 0}, // S_SNAKEPRO_BX4 + {SPR_HEAD, 0, 10, A_Look, S_HEAD_LOOK, 0, 0}, // S_HEAD_LOOK + {SPR_HEAD, 0, 4, A_Chase, S_HEAD_FLOAT, 0, 0}, // S_HEAD_FLOAT + {SPR_HEAD, 0, 5, A_FaceTarget, S_HEAD_ATK2, 0, 0}, // S_HEAD_ATK1 + {SPR_HEAD, 1, 20, A_HeadAttack, S_HEAD_FLOAT, 0, 0}, // S_HEAD_ATK2 + {SPR_HEAD, 0, 4, NULL, S_HEAD_PAIN2, 0, 0}, // S_HEAD_PAIN1 + {SPR_HEAD, 0, 4, A_Pain, S_HEAD_FLOAT, 0, 0}, // S_HEAD_PAIN2 + {SPR_HEAD, 2, 7, NULL, S_HEAD_DIE2, 0, 0}, // S_HEAD_DIE1 + {SPR_HEAD, 3, 7, A_Scream, S_HEAD_DIE3, 0, 0}, // S_HEAD_DIE2 + {SPR_HEAD, 4, 7, NULL, S_HEAD_DIE4, 0, 0}, // S_HEAD_DIE3 + {SPR_HEAD, 5, 7, NULL, S_HEAD_DIE5, 0, 0}, // S_HEAD_DIE4 + {SPR_HEAD, 6, 7, A_NoBlocking, S_HEAD_DIE6, 0, 0}, // S_HEAD_DIE5 + {SPR_HEAD, 7, 7, NULL, S_HEAD_DIE7, 0, 0}, // S_HEAD_DIE6 + {SPR_HEAD, 8, -1, A_BossDeath, S_NULL, 0, 0}, // S_HEAD_DIE7 + {SPR_FX05, 0, 6, NULL, S_HEADFX1_2, 0, 0}, // S_HEADFX1_1 + {SPR_FX05, 1, 6, NULL, S_HEADFX1_3, 0, 0}, // S_HEADFX1_2 + {SPR_FX05, 2, 6, NULL, S_HEADFX1_1, 0, 0}, // S_HEADFX1_3 + {SPR_FX05, 3, 5, A_HeadIceImpact, S_HEADFXI1_2, 0, 0}, // S_HEADFXI1_1 + {SPR_FX05, 4, 5, NULL, S_HEADFXI1_3, 0, 0}, // S_HEADFXI1_2 + {SPR_FX05, 5, 5, NULL, S_HEADFXI1_4, 0, 0}, // S_HEADFXI1_3 + {SPR_FX05, 6, 5, NULL, S_NULL, 0, 0}, // S_HEADFXI1_4 + {SPR_FX05, 7, 6, NULL, S_HEADFX2_2, 0, 0}, // S_HEADFX2_1 + {SPR_FX05, 8, 6, NULL, S_HEADFX2_3, 0, 0}, // S_HEADFX2_2 + {SPR_FX05, 9, 6, NULL, S_HEADFX2_1, 0, 0}, // S_HEADFX2_3 + {SPR_FX05, 3, 5, NULL, S_HEADFXI2_2, 0, 0}, // S_HEADFXI2_1 + {SPR_FX05, 4, 5, NULL, S_HEADFXI2_3, 0, 0}, // S_HEADFXI2_2 + {SPR_FX05, 5, 5, NULL, S_HEADFXI2_4, 0, 0}, // S_HEADFXI2_3 + {SPR_FX05, 6, 5, NULL, S_NULL, 0, 0}, // S_HEADFXI2_4 + {SPR_FX06, 0, 4, A_HeadFireGrow, S_HEADFX3_2, 0, 0}, // S_HEADFX3_1 + {SPR_FX06, 1, 4, A_HeadFireGrow, S_HEADFX3_3, 0, 0}, // S_HEADFX3_2 + {SPR_FX06, 2, 4, A_HeadFireGrow, S_HEADFX3_1, 0, 0}, // S_HEADFX3_3 + {SPR_FX06, 0, 5, NULL, S_HEADFX3_5, 0, 0}, // S_HEADFX3_4 + {SPR_FX06, 1, 5, NULL, S_HEADFX3_6, 0, 0}, // S_HEADFX3_5 + {SPR_FX06, 2, 5, NULL, S_HEADFX3_4, 0, 0}, // S_HEADFX3_6 + {SPR_FX06, 3, 5, NULL, S_HEADFXI3_2, 0, 0}, // S_HEADFXI3_1 + {SPR_FX06, 4, 5, NULL, S_HEADFXI3_3, 0, 0}, // S_HEADFXI3_2 + {SPR_FX06, 5, 5, NULL, S_HEADFXI3_4, 0, 0}, // S_HEADFXI3_3 + {SPR_FX06, 6, 5, NULL, S_NULL, 0, 0}, // S_HEADFXI3_4 + {SPR_FX07, 3, 3, NULL, S_HEADFX4_2, 0, 0}, // S_HEADFX4_1 + {SPR_FX07, 4, 3, NULL, S_HEADFX4_3, 0, 0}, // S_HEADFX4_2 + {SPR_FX07, 5, 3, NULL, S_HEADFX4_4, 0, 0}, // S_HEADFX4_3 + {SPR_FX07, 6, 3, NULL, S_HEADFX4_5, 0, 0}, // S_HEADFX4_4 + {SPR_FX07, 0, 3, A_WhirlwindSeek, S_HEADFX4_6, 0, 0}, // S_HEADFX4_5 + {SPR_FX07, 1, 3, A_WhirlwindSeek, S_HEADFX4_7, 0, 0}, // S_HEADFX4_6 + {SPR_FX07, 2, 3, A_WhirlwindSeek, S_HEADFX4_5, 0, 0}, // S_HEADFX4_7 + {SPR_FX07, 6, 4, NULL, S_HEADFXI4_2, 0, 0}, // S_HEADFXI4_1 + {SPR_FX07, 5, 4, NULL, S_HEADFXI4_3, 0, 0}, // S_HEADFXI4_2 + {SPR_FX07, 4, 4, NULL, S_HEADFXI4_4, 0, 0}, // S_HEADFXI4_3 + {SPR_FX07, 3, 4, NULL, S_NULL, 0, 0}, // S_HEADFXI4_4 + {SPR_CLNK, 0, 10, A_Look, S_CLINK_LOOK2, 0, 0}, // S_CLINK_LOOK1 + {SPR_CLNK, 1, 10, A_Look, S_CLINK_LOOK1, 0, 0}, // S_CLINK_LOOK2 + {SPR_CLNK, 0, 3, A_Chase, S_CLINK_WALK2, 0, 0}, // S_CLINK_WALK1 + {SPR_CLNK, 1, 3, A_Chase, S_CLINK_WALK3, 0, 0}, // S_CLINK_WALK2 + {SPR_CLNK, 2, 3, A_Chase, S_CLINK_WALK4, 0, 0}, // S_CLINK_WALK3 + {SPR_CLNK, 3, 3, A_Chase, S_CLINK_WALK1, 0, 0}, // S_CLINK_WALK4 + {SPR_CLNK, 4, 5, A_FaceTarget, S_CLINK_ATK2, 0, 0}, // S_CLINK_ATK1 + {SPR_CLNK, 5, 4, A_FaceTarget, S_CLINK_ATK3, 0, 0}, // S_CLINK_ATK2 + {SPR_CLNK, 6, 7, A_ClinkAttack, S_CLINK_WALK1, 0, 0}, // S_CLINK_ATK3 + {SPR_CLNK, 7, 3, NULL, S_CLINK_PAIN2, 0, 0}, // S_CLINK_PAIN1 + {SPR_CLNK, 7, 3, A_Pain, S_CLINK_WALK1, 0, 0}, // S_CLINK_PAIN2 + {SPR_CLNK, 8, 6, NULL, S_CLINK_DIE2, 0, 0}, // S_CLINK_DIE1 + {SPR_CLNK, 9, 6, NULL, S_CLINK_DIE3, 0, 0}, // S_CLINK_DIE2 + {SPR_CLNK, 10, 5, A_Scream, S_CLINK_DIE4, 0, 0}, // S_CLINK_DIE3 + {SPR_CLNK, 11, 5, A_NoBlocking, S_CLINK_DIE5, 0, 0}, // S_CLINK_DIE4 + {SPR_CLNK, 12, 5, NULL, S_CLINK_DIE6, 0, 0}, // S_CLINK_DIE5 + {SPR_CLNK, 13, 5, NULL, S_CLINK_DIE7, 0, 0}, // S_CLINK_DIE6 + {SPR_CLNK, 14, -1, NULL, S_NULL, 0, 0}, // S_CLINK_DIE7 + {SPR_WZRD, 0, 10, A_Look, S_WIZARD_LOOK2, 0, 0}, // S_WIZARD_LOOK1 + {SPR_WZRD, 1, 10, A_Look, S_WIZARD_LOOK1, 0, 0}, // S_WIZARD_LOOK2 + {SPR_WZRD, 0, 3, A_Chase, S_WIZARD_WALK2, 0, 0}, // S_WIZARD_WALK1 + {SPR_WZRD, 0, 4, A_Chase, S_WIZARD_WALK3, 0, 0}, // S_WIZARD_WALK2 + {SPR_WZRD, 0, 3, A_Chase, S_WIZARD_WALK4, 0, 0}, // S_WIZARD_WALK3 + {SPR_WZRD, 0, 4, A_Chase, S_WIZARD_WALK5, 0, 0}, // S_WIZARD_WALK4 + {SPR_WZRD, 1, 3, A_Chase, S_WIZARD_WALK6, 0, 0}, // S_WIZARD_WALK5 + {SPR_WZRD, 1, 4, A_Chase, S_WIZARD_WALK7, 0, 0}, // S_WIZARD_WALK6 + {SPR_WZRD, 1, 3, A_Chase, S_WIZARD_WALK8, 0, 0}, // S_WIZARD_WALK7 + {SPR_WZRD, 1, 4, A_Chase, S_WIZARD_WALK1, 0, 0}, // S_WIZARD_WALK8 + {SPR_WZRD, 2, 4, A_WizAtk1, S_WIZARD_ATK2, 0, 0}, // S_WIZARD_ATK1 + {SPR_WZRD, 2, 4, A_WizAtk2, S_WIZARD_ATK3, 0, 0}, // S_WIZARD_ATK2 + {SPR_WZRD, 2, 4, A_WizAtk1, S_WIZARD_ATK4, 0, 0}, // S_WIZARD_ATK3 + {SPR_WZRD, 2, 4, A_WizAtk2, S_WIZARD_ATK5, 0, 0}, // S_WIZARD_ATK4 + {SPR_WZRD, 2, 4, A_WizAtk1, S_WIZARD_ATK6, 0, 0}, // S_WIZARD_ATK5 + {SPR_WZRD, 2, 4, A_WizAtk2, S_WIZARD_ATK7, 0, 0}, // S_WIZARD_ATK6 + {SPR_WZRD, 2, 4, A_WizAtk1, S_WIZARD_ATK8, 0, 0}, // S_WIZARD_ATK7 + {SPR_WZRD, 2, 4, A_WizAtk2, S_WIZARD_ATK9, 0, 0}, // S_WIZARD_ATK8 + {SPR_WZRD, 3, 12, A_WizAtk3, S_WIZARD_WALK1, 0, 0}, // S_WIZARD_ATK9 + {SPR_WZRD, 4, 3, A_GhostOff, S_WIZARD_PAIN2, 0, 0}, // S_WIZARD_PAIN1 + {SPR_WZRD, 4, 3, A_Pain, S_WIZARD_WALK1, 0, 0}, // S_WIZARD_PAIN2 + {SPR_WZRD, 5, 6, A_GhostOff, S_WIZARD_DIE2, 0, 0}, // S_WIZARD_DIE1 + {SPR_WZRD, 6, 6, A_Scream, S_WIZARD_DIE3, 0, 0}, // S_WIZARD_DIE2 + {SPR_WZRD, 7, 6, NULL, S_WIZARD_DIE4, 0, 0}, // S_WIZARD_DIE3 + {SPR_WZRD, 8, 6, NULL, S_WIZARD_DIE5, 0, 0}, // S_WIZARD_DIE4 + {SPR_WZRD, 9, 6, A_NoBlocking, S_WIZARD_DIE6, 0, 0}, // S_WIZARD_DIE5 + {SPR_WZRD, 10, 6, NULL, S_WIZARD_DIE7, 0, 0}, // S_WIZARD_DIE6 + {SPR_WZRD, 11, 6, NULL, S_WIZARD_DIE8, 0, 0}, // S_WIZARD_DIE7 + {SPR_WZRD, 12, -1, NULL, S_NULL, 0, 0}, // S_WIZARD_DIE8 + {SPR_FX11, 32768, 6, NULL, S_WIZFX1_2, 0, 0}, // S_WIZFX1_1 + {SPR_FX11, 32769, 6, NULL, S_WIZFX1_1, 0, 0}, // S_WIZFX1_2 + {SPR_FX11, 32770, 5, NULL, S_WIZFXI1_2, 0, 0}, // S_WIZFXI1_1 + {SPR_FX11, 32771, 5, NULL, S_WIZFXI1_3, 0, 0}, // S_WIZFXI1_2 + {SPR_FX11, 32772, 5, NULL, S_WIZFXI1_4, 0, 0}, // S_WIZFXI1_3 + {SPR_FX11, 32773, 5, NULL, S_WIZFXI1_5, 0, 0}, // S_WIZFXI1_4 + {SPR_FX11, 32774, 5, NULL, S_NULL, 0, 0}, // S_WIZFXI1_5 + {SPR_IMPX, 0, 10, A_Look, S_IMP_LOOK2, 0, 0}, // S_IMP_LOOK1 + {SPR_IMPX, 1, 10, A_Look, S_IMP_LOOK3, 0, 0}, // S_IMP_LOOK2 + {SPR_IMPX, 2, 10, A_Look, S_IMP_LOOK4, 0, 0}, // S_IMP_LOOK3 + {SPR_IMPX, 1, 10, A_Look, S_IMP_LOOK1, 0, 0}, // S_IMP_LOOK4 + {SPR_IMPX, 0, 3, A_Chase, S_IMP_FLY2, 0, 0}, // S_IMP_FLY1 + {SPR_IMPX, 0, 3, A_Chase, S_IMP_FLY3, 0, 0}, // S_IMP_FLY2 + {SPR_IMPX, 1, 3, A_Chase, S_IMP_FLY4, 0, 0}, // S_IMP_FLY3 + {SPR_IMPX, 1, 3, A_Chase, S_IMP_FLY5, 0, 0}, // S_IMP_FLY4 + {SPR_IMPX, 2, 3, A_Chase, S_IMP_FLY6, 0, 0}, // S_IMP_FLY5 + {SPR_IMPX, 2, 3, A_Chase, S_IMP_FLY7, 0, 0}, // S_IMP_FLY6 + {SPR_IMPX, 1, 3, A_Chase, S_IMP_FLY8, 0, 0}, // S_IMP_FLY7 + {SPR_IMPX, 1, 3, A_Chase, S_IMP_FLY1, 0, 0}, // S_IMP_FLY8 + {SPR_IMPX, 3, 6, A_FaceTarget, S_IMP_MEATK2, 0, 0}, // S_IMP_MEATK1 + {SPR_IMPX, 4, 6, A_FaceTarget, S_IMP_MEATK3, 0, 0}, // S_IMP_MEATK2 + {SPR_IMPX, 5, 6, A_ImpMeAttack, S_IMP_FLY1, 0, 0}, // S_IMP_MEATK3 + {SPR_IMPX, 0, 10, A_FaceTarget, S_IMP_MSATK1_2, 0, 0}, // S_IMP_MSATK1_1 + {SPR_IMPX, 1, 6, A_ImpMsAttack, S_IMP_MSATK1_3, 0, 0}, // S_IMP_MSATK1_2 + {SPR_IMPX, 2, 6, NULL, S_IMP_MSATK1_4, 0, 0}, // S_IMP_MSATK1_3 + {SPR_IMPX, 1, 6, NULL, S_IMP_MSATK1_5, 0, 0}, // S_IMP_MSATK1_4 + {SPR_IMPX, 0, 6, NULL, S_IMP_MSATK1_6, 0, 0}, // S_IMP_MSATK1_5 + {SPR_IMPX, 1, 6, NULL, S_IMP_MSATK1_3, 0, 0}, // S_IMP_MSATK1_6 + {SPR_IMPX, 3, 6, A_FaceTarget, S_IMP_MSATK2_2, 0, 0}, // S_IMP_MSATK2_1 + {SPR_IMPX, 4, 6, A_FaceTarget, S_IMP_MSATK2_3, 0, 0}, // S_IMP_MSATK2_2 + {SPR_IMPX, 5, 6, A_ImpMsAttack2, S_IMP_FLY1, 0, 0}, // S_IMP_MSATK2_3 + {SPR_IMPX, 6, 3, NULL, S_IMP_PAIN2, 0, 0}, // S_IMP_PAIN1 + {SPR_IMPX, 6, 3, A_Pain, S_IMP_FLY1, 0, 0}, // S_IMP_PAIN2 + {SPR_IMPX, 6, 4, A_ImpDeath, S_IMP_DIE2, 0, 0}, // S_IMP_DIE1 + {SPR_IMPX, 7, 5, NULL, S_IMP_DIE2, 0, 0}, // S_IMP_DIE2 + {SPR_IMPX, 18, 5, A_ImpXDeath1, S_IMP_XDIE2, 0, 0}, // S_IMP_XDIE1 + {SPR_IMPX, 19, 5, NULL, S_IMP_XDIE3, 0, 0}, // S_IMP_XDIE2 + {SPR_IMPX, 20, 5, NULL, S_IMP_XDIE4, 0, 0}, // S_IMP_XDIE3 + {SPR_IMPX, 21, 5, A_ImpXDeath2, S_IMP_XDIE5, 0, 0}, // S_IMP_XDIE4 + {SPR_IMPX, 22, 5, NULL, S_IMP_XDIE5, 0, 0}, // S_IMP_XDIE5 + {SPR_IMPX, 8, 7, A_ImpExplode, S_IMP_CRASH2, 0, 0}, // S_IMP_CRASH1 + {SPR_IMPX, 9, 7, A_Scream, S_IMP_CRASH3, 0, 0}, // S_IMP_CRASH2 + {SPR_IMPX, 10, 7, NULL, S_IMP_CRASH4, 0, 0}, // S_IMP_CRASH3 + {SPR_IMPX, 11, -1, NULL, S_NULL, 0, 0}, // S_IMP_CRASH4 + {SPR_IMPX, 23, 7, NULL, S_IMP_XCRASH2, 0, 0}, // S_IMP_XCRASH1 + {SPR_IMPX, 24, 7, NULL, S_IMP_XCRASH3, 0, 0}, // S_IMP_XCRASH2 + {SPR_IMPX, 25, -1, NULL, S_NULL, 0, 0}, // S_IMP_XCRASH3 + {SPR_IMPX, 12, 5, NULL, S_IMP_CHUNKA2, 0, 0}, // S_IMP_CHUNKA1 + {SPR_IMPX, 13, 700, NULL, S_IMP_CHUNKA3, 0, 0}, // S_IMP_CHUNKA2 + {SPR_IMPX, 14, 700, NULL, S_NULL, 0, 0}, // S_IMP_CHUNKA3 + {SPR_IMPX, 15, 5, NULL, S_IMP_CHUNKB2, 0, 0}, // S_IMP_CHUNKB1 + {SPR_IMPX, 16, 700, NULL, S_IMP_CHUNKB3, 0, 0}, // S_IMP_CHUNKB2 + {SPR_IMPX, 17, 700, NULL, S_NULL, 0, 0}, // S_IMP_CHUNKB3 + {SPR_FX10, 32768, 6, NULL, S_IMPFX2, 0, 0}, // S_IMPFX1 + {SPR_FX10, 32769, 6, NULL, S_IMPFX3, 0, 0}, // S_IMPFX2 + {SPR_FX10, 32770, 6, NULL, S_IMPFX1, 0, 0}, // S_IMPFX3 + {SPR_FX10, 32771, 5, NULL, S_IMPFXI2, 0, 0}, // S_IMPFXI1 + {SPR_FX10, 32772, 5, NULL, S_IMPFXI3, 0, 0}, // S_IMPFXI2 + {SPR_FX10, 32773, 5, NULL, S_IMPFXI4, 0, 0}, // S_IMPFXI3 + {SPR_FX10, 32774, 5, NULL, S_NULL, 0, 0}, // S_IMPFXI4 + {SPR_KNIG, 0, 10, A_Look, S_KNIGHT_STND2, 0, 0}, // S_KNIGHT_STND1 + {SPR_KNIG, 1, 10, A_Look, S_KNIGHT_STND1, 0, 0}, // S_KNIGHT_STND2 + {SPR_KNIG, 0, 4, A_Chase, S_KNIGHT_WALK2, 0, 0}, // S_KNIGHT_WALK1 + {SPR_KNIG, 1, 4, A_Chase, S_KNIGHT_WALK3, 0, 0}, // S_KNIGHT_WALK2 + {SPR_KNIG, 2, 4, A_Chase, S_KNIGHT_WALK4, 0, 0}, // S_KNIGHT_WALK3 + {SPR_KNIG, 3, 4, A_Chase, S_KNIGHT_WALK1, 0, 0}, // S_KNIGHT_WALK4 + {SPR_KNIG, 4, 10, A_FaceTarget, S_KNIGHT_ATK2, 0, 0}, // S_KNIGHT_ATK1 + {SPR_KNIG, 5, 8, A_FaceTarget, S_KNIGHT_ATK3, 0, 0}, // S_KNIGHT_ATK2 + {SPR_KNIG, 6, 8, A_KnightAttack, S_KNIGHT_ATK4, 0, 0}, // S_KNIGHT_ATK3 + {SPR_KNIG, 4, 10, A_FaceTarget, S_KNIGHT_ATK5, 0, 0}, // S_KNIGHT_ATK4 + {SPR_KNIG, 5, 8, A_FaceTarget, S_KNIGHT_ATK6, 0, 0}, // S_KNIGHT_ATK5 + {SPR_KNIG, 6, 8, A_KnightAttack, S_KNIGHT_WALK1, 0, 0}, // S_KNIGHT_ATK6 + {SPR_KNIG, 7, 3, NULL, S_KNIGHT_PAIN2, 0, 0}, // S_KNIGHT_PAIN1 + {SPR_KNIG, 7, 3, A_Pain, S_KNIGHT_WALK1, 0, 0}, // S_KNIGHT_PAIN2 + {SPR_KNIG, 8, 6, NULL, S_KNIGHT_DIE2, 0, 0}, // S_KNIGHT_DIE1 + {SPR_KNIG, 9, 6, A_Scream, S_KNIGHT_DIE3, 0, 0}, // S_KNIGHT_DIE2 + {SPR_KNIG, 10, 6, NULL, S_KNIGHT_DIE4, 0, 0}, // S_KNIGHT_DIE3 + {SPR_KNIG, 11, 6, A_NoBlocking, S_KNIGHT_DIE5, 0, 0}, // S_KNIGHT_DIE4 + {SPR_KNIG, 12, 6, NULL, S_KNIGHT_DIE6, 0, 0}, // S_KNIGHT_DIE5 + {SPR_KNIG, 13, 6, NULL, S_KNIGHT_DIE7, 0, 0}, // S_KNIGHT_DIE6 + {SPR_KNIG, 14, -1, NULL, S_NULL, 0, 0}, // S_KNIGHT_DIE7 + {SPR_SPAX, 32768, 3, A_ContMobjSound, S_SPINAXE2, 0, 0}, // S_SPINAXE1 + {SPR_SPAX, 32769, 3, NULL, S_SPINAXE3, 0, 0}, // S_SPINAXE2 + {SPR_SPAX, 32770, 3, NULL, S_SPINAXE1, 0, 0}, // S_SPINAXE3 + {SPR_SPAX, 32771, 6, NULL, S_SPINAXEX2, 0, 0}, // S_SPINAXEX1 + {SPR_SPAX, 32772, 6, NULL, S_SPINAXEX3, 0, 0}, // S_SPINAXEX2 + {SPR_SPAX, 32773, 6, NULL, S_NULL, 0, 0}, // S_SPINAXEX3 + {SPR_RAXE, 32768, 5, A_DripBlood, S_REDAXE2, 0, 0}, // S_REDAXE1 + {SPR_RAXE, 32769, 5, A_DripBlood, S_REDAXE1, 0, 0}, // S_REDAXE2 + {SPR_RAXE, 32770, 6, NULL, S_REDAXEX2, 0, 0}, // S_REDAXEX1 + {SPR_RAXE, 32771, 6, NULL, S_REDAXEX3, 0, 0}, // S_REDAXEX2 + {SPR_RAXE, 32772, 6, NULL, S_NULL, 0, 0}, // S_REDAXEX3 + {SPR_SRCR, 0, 10, A_Look, S_SRCR1_LOOK2, 0, 0}, // S_SRCR1_LOOK1 + {SPR_SRCR, 1, 10, A_Look, S_SRCR1_LOOK1, 0, 0}, // S_SRCR1_LOOK2 + {SPR_SRCR, 0, 5, A_Sor1Chase, S_SRCR1_WALK2, 0, 0}, // S_SRCR1_WALK1 + {SPR_SRCR, 1, 5, A_Sor1Chase, S_SRCR1_WALK3, 0, 0}, // S_SRCR1_WALK2 + {SPR_SRCR, 2, 5, A_Sor1Chase, S_SRCR1_WALK4, 0, 0}, // S_SRCR1_WALK3 + {SPR_SRCR, 3, 5, A_Sor1Chase, S_SRCR1_WALK1, 0, 0}, // S_SRCR1_WALK4 + {SPR_SRCR, 16, 6, A_Sor1Pain, S_SRCR1_WALK1, 0, 0}, // S_SRCR1_PAIN1 + {SPR_SRCR, 16, 7, A_FaceTarget, S_SRCR1_ATK2, 0, 0}, // S_SRCR1_ATK1 + {SPR_SRCR, 17, 6, A_FaceTarget, S_SRCR1_ATK3, 0, 0}, // S_SRCR1_ATK2 + {SPR_SRCR, 18, 10, A_Srcr1Attack, S_SRCR1_WALK1, 0, 0}, // S_SRCR1_ATK3 + {SPR_SRCR, 18, 10, A_FaceTarget, S_SRCR1_ATK5, 0, 0}, // S_SRCR1_ATK4 + {SPR_SRCR, 16, 7, A_FaceTarget, S_SRCR1_ATK6, 0, 0}, // S_SRCR1_ATK5 + {SPR_SRCR, 17, 6, A_FaceTarget, S_SRCR1_ATK7, 0, 0}, // S_SRCR1_ATK6 + {SPR_SRCR, 18, 10, A_Srcr1Attack, S_SRCR1_WALK1, 0, 0}, // S_SRCR1_ATK7 + {SPR_SRCR, 4, 7, NULL, S_SRCR1_DIE2, 0, 0}, // S_SRCR1_DIE1 + {SPR_SRCR, 5, 7, A_Scream, S_SRCR1_DIE3, 0, 0}, // S_SRCR1_DIE2 + {SPR_SRCR, 6, 7, NULL, S_SRCR1_DIE4, 0, 0}, // S_SRCR1_DIE3 + {SPR_SRCR, 7, 6, NULL, S_SRCR1_DIE5, 0, 0}, // S_SRCR1_DIE4 + {SPR_SRCR, 8, 6, NULL, S_SRCR1_DIE6, 0, 0}, // S_SRCR1_DIE5 + {SPR_SRCR, 9, 6, NULL, S_SRCR1_DIE7, 0, 0}, // S_SRCR1_DIE6 + {SPR_SRCR, 10, 6, NULL, S_SRCR1_DIE8, 0, 0}, // S_SRCR1_DIE7 + {SPR_SRCR, 11, 25, A_SorZap, S_SRCR1_DIE9, 0, 0}, // S_SRCR1_DIE8 + {SPR_SRCR, 12, 5, NULL, S_SRCR1_DIE10, 0, 0}, // S_SRCR1_DIE9 + {SPR_SRCR, 13, 5, NULL, S_SRCR1_DIE11, 0, 0}, // S_SRCR1_DIE10 + {SPR_SRCR, 14, 4, NULL, S_SRCR1_DIE12, 0, 0}, // S_SRCR1_DIE11 + {SPR_SRCR, 11, 20, A_SorZap, S_SRCR1_DIE13, 0, 0}, // S_SRCR1_DIE12 + {SPR_SRCR, 12, 5, NULL, S_SRCR1_DIE14, 0, 0}, // S_SRCR1_DIE13 + {SPR_SRCR, 13, 5, NULL, S_SRCR1_DIE15, 0, 0}, // S_SRCR1_DIE14 + {SPR_SRCR, 14, 4, NULL, S_SRCR1_DIE16, 0, 0}, // S_SRCR1_DIE15 + {SPR_SRCR, 11, 12, NULL, S_SRCR1_DIE17, 0, 0}, // S_SRCR1_DIE16 + {SPR_SRCR, 15, -1, A_SorcererRise, S_NULL, 0, 0}, // S_SRCR1_DIE17 + {SPR_FX14, 32768, 6, NULL, S_SRCRFX1_2, 0, 0}, // S_SRCRFX1_1 + {SPR_FX14, 32769, 6, NULL, S_SRCRFX1_3, 0, 0}, // S_SRCRFX1_2 + {SPR_FX14, 32770, 6, NULL, S_SRCRFX1_1, 0, 0}, // S_SRCRFX1_3 + {SPR_FX14, 32771, 5, NULL, S_SRCRFXI1_2, 0, 0}, // S_SRCRFXI1_1 + {SPR_FX14, 32772, 5, NULL, S_SRCRFXI1_3, 0, 0}, // S_SRCRFXI1_2 + {SPR_FX14, 32773, 5, NULL, S_SRCRFXI1_4, 0, 0}, // S_SRCRFXI1_3 + {SPR_FX14, 32774, 5, NULL, S_SRCRFXI1_5, 0, 0}, // S_SRCRFXI1_4 + {SPR_FX14, 32775, 5, NULL, S_NULL, 0, 0}, // S_SRCRFXI1_5 + {SPR_SOR2, 0, 4, NULL, S_SOR2_RISE2, 0, 0}, // S_SOR2_RISE1 + {SPR_SOR2, 1, 4, NULL, S_SOR2_RISE3, 0, 0}, // S_SOR2_RISE2 + {SPR_SOR2, 2, 4, A_SorRise, S_SOR2_RISE4, 0, 0}, // S_SOR2_RISE3 + {SPR_SOR2, 3, 4, NULL, S_SOR2_RISE5, 0, 0}, // S_SOR2_RISE4 + {SPR_SOR2, 4, 4, NULL, S_SOR2_RISE6, 0, 0}, // S_SOR2_RISE5 + {SPR_SOR2, 5, 4, NULL, S_SOR2_RISE7, 0, 0}, // S_SOR2_RISE6 + {SPR_SOR2, 6, 12, A_SorSightSnd, S_SOR2_WALK1, 0, 0}, // S_SOR2_RISE7 + {SPR_SOR2, 12, 10, A_Look, S_SOR2_LOOK2, 0, 0}, // S_SOR2_LOOK1 + {SPR_SOR2, 13, 10, A_Look, S_SOR2_LOOK1, 0, 0}, // S_SOR2_LOOK2 + {SPR_SOR2, 12, 4, A_Chase, S_SOR2_WALK2, 0, 0}, // S_SOR2_WALK1 + {SPR_SOR2, 13, 4, A_Chase, S_SOR2_WALK3, 0, 0}, // S_SOR2_WALK2 + {SPR_SOR2, 14, 4, A_Chase, S_SOR2_WALK4, 0, 0}, // S_SOR2_WALK3 + {SPR_SOR2, 15, 4, A_Chase, S_SOR2_WALK1, 0, 0}, // S_SOR2_WALK4 + {SPR_SOR2, 16, 3, NULL, S_SOR2_PAIN2, 0, 0}, // S_SOR2_PAIN1 + {SPR_SOR2, 16, 6, A_Pain, S_SOR2_WALK1, 0, 0}, // S_SOR2_PAIN2 + {SPR_SOR2, 17, 9, A_Srcr2Decide, S_SOR2_ATK2, 0, 0}, // S_SOR2_ATK1 + {SPR_SOR2, 18, 9, A_FaceTarget, S_SOR2_ATK3, 0, 0}, // S_SOR2_ATK2 + {SPR_SOR2, 19, 20, A_Srcr2Attack, S_SOR2_WALK1, 0, 0}, // S_SOR2_ATK3 + {SPR_SOR2, 11, 6, NULL, S_SOR2_TELE2, 0, 0}, // S_SOR2_TELE1 + {SPR_SOR2, 10, 6, NULL, S_SOR2_TELE3, 0, 0}, // S_SOR2_TELE2 + {SPR_SOR2, 9, 6, NULL, S_SOR2_TELE4, 0, 0}, // S_SOR2_TELE3 + {SPR_SOR2, 8, 6, NULL, S_SOR2_TELE5, 0, 0}, // S_SOR2_TELE4 + {SPR_SOR2, 7, 6, NULL, S_SOR2_TELE6, 0, 0}, // S_SOR2_TELE5 + {SPR_SOR2, 6, 6, NULL, S_SOR2_WALK1, 0, 0}, // S_SOR2_TELE6 + {SPR_SDTH, 0, 8, A_Sor2DthInit, S_SOR2_DIE2, 0, 0}, // S_SOR2_DIE1 + {SPR_SDTH, 1, 8, NULL, S_SOR2_DIE3, 0, 0}, // S_SOR2_DIE2 + {SPR_SDTH, 2, 8, A_SorDSph, S_SOR2_DIE4, 0, 0}, // S_SOR2_DIE3 + {SPR_SDTH, 3, 7, NULL, S_SOR2_DIE5, 0, 0}, // S_SOR2_DIE4 + {SPR_SDTH, 4, 7, NULL, S_SOR2_DIE6, 0, 0}, // S_SOR2_DIE5 + {SPR_SDTH, 5, 7, A_Sor2DthLoop, S_SOR2_DIE7, 0, 0}, // S_SOR2_DIE6 + {SPR_SDTH, 6, 6, A_SorDExp, S_SOR2_DIE8, 0, 0}, // S_SOR2_DIE7 + {SPR_SDTH, 7, 6, NULL, S_SOR2_DIE9, 0, 0}, // S_SOR2_DIE8 + {SPR_SDTH, 8, 18, NULL, S_SOR2_DIE10, 0, 0}, // S_SOR2_DIE9 + {SPR_SDTH, 9, 6, A_NoBlocking, S_SOR2_DIE11, 0, 0}, // S_SOR2_DIE10 + {SPR_SDTH, 10, 6, A_SorDBon, S_SOR2_DIE12, 0, 0}, // S_SOR2_DIE11 + {SPR_SDTH, 11, 6, NULL, S_SOR2_DIE13, 0, 0}, // S_SOR2_DIE12 + {SPR_SDTH, 12, 6, NULL, S_SOR2_DIE14, 0, 0}, // S_SOR2_DIE13 + {SPR_SDTH, 13, 6, NULL, S_SOR2_DIE15, 0, 0}, // S_SOR2_DIE14 + {SPR_SDTH, 14, -1, A_BossDeath, S_NULL, 0, 0}, // S_SOR2_DIE15 + {SPR_FX16, 32768, 3, A_BlueSpark, S_SOR2FX1_2, 0, 0}, // S_SOR2FX1_1 + {SPR_FX16, 32769, 3, A_BlueSpark, S_SOR2FX1_3, 0, 0}, // S_SOR2FX1_2 + {SPR_FX16, 32770, 3, A_BlueSpark, S_SOR2FX1_1, 0, 0}, // S_SOR2FX1_3 + {SPR_FX16, 32774, 5, A_Explode, S_SOR2FXI1_2, 0, 0}, // S_SOR2FXI1_1 + {SPR_FX16, 32775, 5, NULL, S_SOR2FXI1_3, 0, 0}, // S_SOR2FXI1_2 + {SPR_FX16, 32776, 5, NULL, S_SOR2FXI1_4, 0, 0}, // S_SOR2FXI1_3 + {SPR_FX16, 32777, 5, NULL, S_SOR2FXI1_5, 0, 0}, // S_SOR2FXI1_4 + {SPR_FX16, 32778, 5, NULL, S_SOR2FXI1_6, 0, 0}, // S_SOR2FXI1_5 + {SPR_FX16, 32779, 5, NULL, S_NULL, 0, 0}, // S_SOR2FXI1_6 + {SPR_FX16, 32771, 12, NULL, S_SOR2FXSPARK2, 0, 0}, // S_SOR2FXSPARK1 + {SPR_FX16, 32772, 12, NULL, S_SOR2FXSPARK3, 0, 0}, // S_SOR2FXSPARK2 + {SPR_FX16, 32773, 12, NULL, S_NULL, 0, 0}, // S_SOR2FXSPARK3 + {SPR_FX11, 32768, 35, NULL, S_SOR2FX2_2, 0, 0}, // S_SOR2FX2_1 + {SPR_FX11, 32768, 5, A_GenWizard, S_SOR2FX2_3, 0, 0}, // S_SOR2FX2_2 + {SPR_FX11, 32769, 5, NULL, S_SOR2FX2_2, 0, 0}, // S_SOR2FX2_3 + {SPR_FX11, 32770, 5, NULL, S_SOR2FXI2_2, 0, 0}, // S_SOR2FXI2_1 + {SPR_FX11, 32771, 5, NULL, S_SOR2FXI2_3, 0, 0}, // S_SOR2FXI2_2 + {SPR_FX11, 32772, 5, NULL, S_SOR2FXI2_4, 0, 0}, // S_SOR2FXI2_3 + {SPR_FX11, 32773, 5, NULL, S_SOR2FXI2_5, 0, 0}, // S_SOR2FXI2_4 + {SPR_FX11, 32774, 5, NULL, S_NULL, 0, 0}, // S_SOR2FXI2_5 + {SPR_SOR2, 6, 8, NULL, S_SOR2TELEFADE2, 0, 0}, // S_SOR2TELEFADE1 + {SPR_SOR2, 7, 6, NULL, S_SOR2TELEFADE3, 0, 0}, // S_SOR2TELEFADE2 + {SPR_SOR2, 8, 6, NULL, S_SOR2TELEFADE4, 0, 0}, // S_SOR2TELEFADE3 + {SPR_SOR2, 9, 6, NULL, S_SOR2TELEFADE5, 0, 0}, // S_SOR2TELEFADE4 + {SPR_SOR2, 10, 6, NULL, S_SOR2TELEFADE6, 0, 0}, // S_SOR2TELEFADE5 + {SPR_SOR2, 11, 6, NULL, S_NULL, 0, 0}, // S_SOR2TELEFADE6 + {SPR_MNTR, 0, 10, A_Look, S_MNTR_LOOK2, 0, 0}, // S_MNTR_LOOK1 + {SPR_MNTR, 1, 10, A_Look, S_MNTR_LOOK1, 0, 0}, // S_MNTR_LOOK2 + {SPR_MNTR, 0, 5, A_Chase, S_MNTR_WALK2, 0, 0}, // S_MNTR_WALK1 + {SPR_MNTR, 1, 5, A_Chase, S_MNTR_WALK3, 0, 0}, // S_MNTR_WALK2 + {SPR_MNTR, 2, 5, A_Chase, S_MNTR_WALK4, 0, 0}, // S_MNTR_WALK3 + {SPR_MNTR, 3, 5, A_Chase, S_MNTR_WALK1, 0, 0}, // S_MNTR_WALK4 + {SPR_MNTR, 21, 10, A_FaceTarget, S_MNTR_ATK1_2, 0, 0}, // S_MNTR_ATK1_1 + {SPR_MNTR, 22, 7, A_FaceTarget, S_MNTR_ATK1_3, 0, 0}, // S_MNTR_ATK1_2 + {SPR_MNTR, 23, 12, A_MinotaurAtk1, S_MNTR_WALK1, 0, 0}, // S_MNTR_ATK1_3 + {SPR_MNTR, 21, 10, A_MinotaurDecide, S_MNTR_ATK2_2, 0, 0}, // S_MNTR_ATK2_1 + {SPR_MNTR, 24, 4, A_FaceTarget, S_MNTR_ATK2_3, 0, 0}, // S_MNTR_ATK2_2 + {SPR_MNTR, 25, 9, A_MinotaurAtk2, S_MNTR_WALK1, 0, 0}, // S_MNTR_ATK2_3 + {SPR_MNTR, 21, 10, A_FaceTarget, S_MNTR_ATK3_2, 0, 0}, // S_MNTR_ATK3_1 + {SPR_MNTR, 22, 7, A_FaceTarget, S_MNTR_ATK3_3, 0, 0}, // S_MNTR_ATK3_2 + {SPR_MNTR, 23, 12, A_MinotaurAtk3, S_MNTR_WALK1, 0, 0}, // S_MNTR_ATK3_3 + {SPR_MNTR, 23, 12, NULL, S_MNTR_ATK3_1, 0, 0}, // S_MNTR_ATK3_4 + {SPR_MNTR, 20, 2, A_MinotaurCharge, S_MNTR_ATK4_1, 0, 0}, // S_MNTR_ATK4_1 + {SPR_MNTR, 4, 3, NULL, S_MNTR_PAIN2, 0, 0}, // S_MNTR_PAIN1 + {SPR_MNTR, 4, 6, A_Pain, S_MNTR_WALK1, 0, 0}, // S_MNTR_PAIN2 + {SPR_MNTR, 5, 6, NULL, S_MNTR_DIE2, 0, 0}, // S_MNTR_DIE1 + {SPR_MNTR, 6, 5, NULL, S_MNTR_DIE3, 0, 0}, // S_MNTR_DIE2 + {SPR_MNTR, 7, 6, A_Scream, S_MNTR_DIE4, 0, 0}, // S_MNTR_DIE3 + {SPR_MNTR, 8, 5, NULL, S_MNTR_DIE5, 0, 0}, // S_MNTR_DIE4 + {SPR_MNTR, 9, 6, NULL, S_MNTR_DIE6, 0, 0}, // S_MNTR_DIE5 + {SPR_MNTR, 10, 5, NULL, S_MNTR_DIE7, 0, 0}, // S_MNTR_DIE6 + {SPR_MNTR, 11, 6, NULL, S_MNTR_DIE8, 0, 0}, // S_MNTR_DIE7 + {SPR_MNTR, 12, 5, A_NoBlocking, S_MNTR_DIE9, 0, 0}, // S_MNTR_DIE8 + {SPR_MNTR, 13, 6, NULL, S_MNTR_DIE10, 0, 0}, // S_MNTR_DIE9 + {SPR_MNTR, 14, 5, NULL, S_MNTR_DIE11, 0, 0}, // S_MNTR_DIE10 + {SPR_MNTR, 15, 6, NULL, S_MNTR_DIE12, 0, 0}, // S_MNTR_DIE11 + {SPR_MNTR, 16, 5, NULL, S_MNTR_DIE13, 0, 0}, // S_MNTR_DIE12 + {SPR_MNTR, 17, 6, NULL, S_MNTR_DIE14, 0, 0}, // S_MNTR_DIE13 + {SPR_MNTR, 18, 5, NULL, S_MNTR_DIE15, 0, 0}, // S_MNTR_DIE14 + {SPR_MNTR, 19, -1, A_BossDeath, S_NULL, 0, 0}, // S_MNTR_DIE15 + {SPR_FX12, 32768, 6, NULL, S_MNTRFX1_2, 0, 0}, // S_MNTRFX1_1 + {SPR_FX12, 32769, 6, NULL, S_MNTRFX1_1, 0, 0}, // S_MNTRFX1_2 + {SPR_FX12, 32770, 5, NULL, S_MNTRFXI1_2, 0, 0}, // S_MNTRFXI1_1 + {SPR_FX12, 32771, 5, NULL, S_MNTRFXI1_3, 0, 0}, // S_MNTRFXI1_2 + {SPR_FX12, 32772, 5, NULL, S_MNTRFXI1_4, 0, 0}, // S_MNTRFXI1_3 + {SPR_FX12, 32773, 5, NULL, S_MNTRFXI1_5, 0, 0}, // S_MNTRFXI1_4 + {SPR_FX12, 32774, 5, NULL, S_MNTRFXI1_6, 0, 0}, // S_MNTRFXI1_5 + {SPR_FX12, 32775, 5, NULL, S_NULL, 0, 0}, // S_MNTRFXI1_6 + {SPR_FX13, 0, 2, A_MntrFloorFire, S_MNTRFX2_1, 0, 0}, // S_MNTRFX2_1 + {SPR_FX13, 32776, 4, A_Explode, S_MNTRFXI2_2, 0, 0}, // S_MNTRFXI2_1 + {SPR_FX13, 32777, 4, NULL, S_MNTRFXI2_3, 0, 0}, // S_MNTRFXI2_2 + {SPR_FX13, 32778, 4, NULL, S_MNTRFXI2_4, 0, 0}, // S_MNTRFXI2_3 + {SPR_FX13, 32779, 4, NULL, S_MNTRFXI2_5, 0, 0}, // S_MNTRFXI2_4 + {SPR_FX13, 32780, 4, NULL, S_NULL, 0, 0}, // S_MNTRFXI2_5 + {SPR_FX13, 32771, 4, NULL, S_MNTRFX3_2, 0, 0}, // S_MNTRFX3_1 + {SPR_FX13, 32770, 4, NULL, S_MNTRFX3_3, 0, 0}, // S_MNTRFX3_2 + {SPR_FX13, 32769, 5, NULL, S_MNTRFX3_4, 0, 0}, // S_MNTRFX3_3 + {SPR_FX13, 32770, 5, NULL, S_MNTRFX3_5, 0, 0}, // S_MNTRFX3_4 + {SPR_FX13, 32771, 5, NULL, S_MNTRFX3_6, 0, 0}, // S_MNTRFX3_5 + {SPR_FX13, 32772, 5, NULL, S_MNTRFX3_7, 0, 0}, // S_MNTRFX3_6 + {SPR_FX13, 32773, 4, NULL, S_MNTRFX3_8, 0, 0}, // S_MNTRFX3_7 + {SPR_FX13, 32774, 4, NULL, S_MNTRFX3_9, 0, 0}, // S_MNTRFX3_8 + {SPR_FX13, 32775, 4, NULL, S_NULL, 0, 0}, // S_MNTRFX3_9 + {SPR_AKYY, 32768, 3, NULL, S_AKYY2, 0, 0}, // S_AKYY1 + {SPR_AKYY, 32769, 3, NULL, S_AKYY3, 0, 0}, // S_AKYY2 + {SPR_AKYY, 32770, 3, NULL, S_AKYY4, 0, 0}, // S_AKYY3 + {SPR_AKYY, 32771, 3, NULL, S_AKYY5, 0, 0}, // S_AKYY4 + {SPR_AKYY, 32772, 3, NULL, S_AKYY6, 0, 0}, // S_AKYY5 + {SPR_AKYY, 32773, 3, NULL, S_AKYY7, 0, 0}, // S_AKYY6 + {SPR_AKYY, 32774, 3, NULL, S_AKYY8, 0, 0}, // S_AKYY7 + {SPR_AKYY, 32775, 3, NULL, S_AKYY9, 0, 0}, // S_AKYY8 + {SPR_AKYY, 32776, 3, NULL, S_AKYY10, 0, 0}, // S_AKYY9 + {SPR_AKYY, 32777, 3, NULL, S_AKYY1, 0, 0}, // S_AKYY10 + {SPR_BKYY, 32768, 3, NULL, S_BKYY2, 0, 0}, // S_BKYY1 + {SPR_BKYY, 32769, 3, NULL, S_BKYY3, 0, 0}, // S_BKYY2 + {SPR_BKYY, 32770, 3, NULL, S_BKYY4, 0, 0}, // S_BKYY3 + {SPR_BKYY, 32771, 3, NULL, S_BKYY5, 0, 0}, // S_BKYY4 + {SPR_BKYY, 32772, 3, NULL, S_BKYY6, 0, 0}, // S_BKYY5 + {SPR_BKYY, 32773, 3, NULL, S_BKYY7, 0, 0}, // S_BKYY6 + {SPR_BKYY, 32774, 3, NULL, S_BKYY8, 0, 0}, // S_BKYY7 + {SPR_BKYY, 32775, 3, NULL, S_BKYY9, 0, 0}, // S_BKYY8 + {SPR_BKYY, 32776, 3, NULL, S_BKYY10, 0, 0}, // S_BKYY9 + {SPR_BKYY, 32777, 3, NULL, S_BKYY1, 0, 0}, // S_BKYY10 + {SPR_CKYY, 32768, 3, NULL, S_CKYY2, 0, 0}, // S_CKYY1 + {SPR_CKYY, 32769, 3, NULL, S_CKYY3, 0, 0}, // S_CKYY2 + {SPR_CKYY, 32770, 3, NULL, S_CKYY4, 0, 0}, // S_CKYY3 + {SPR_CKYY, 32771, 3, NULL, S_CKYY5, 0, 0}, // S_CKYY4 + {SPR_CKYY, 32772, 3, NULL, S_CKYY6, 0, 0}, // S_CKYY5 + {SPR_CKYY, 32773, 3, NULL, S_CKYY7, 0, 0}, // S_CKYY6 + {SPR_CKYY, 32774, 3, NULL, S_CKYY8, 0, 0}, // S_CKYY7 + {SPR_CKYY, 32775, 3, NULL, S_CKYY9, 0, 0}, // S_CKYY8 + {SPR_CKYY, 32776, 3, NULL, S_CKYY1, 0, 0}, // S_CKYY9 + {SPR_AMG1, 0, -1, NULL, S_NULL, 0, 0}, // S_AMG1 + {SPR_AMG2, 0, 4, NULL, S_AMG2_2, 0, 0}, // S_AMG2_1 + {SPR_AMG2, 1, 4, NULL, S_AMG2_3, 0, 0}, // S_AMG2_2 + {SPR_AMG2, 2, 4, NULL, S_AMG2_1, 0, 0}, // S_AMG2_3 + {SPR_AMM1, 0, -1, NULL, S_NULL, 0, 0}, // S_AMM1 + {SPR_AMM2, 0, -1, NULL, S_NULL, 0, 0}, // S_AMM2 + {SPR_AMC1, 0, -1, NULL, S_NULL, 0, 0}, // S_AMC1 + {SPR_AMC2, 0, 5, NULL, S_AMC2_2, 0, 0}, // S_AMC2_1 + {SPR_AMC2, 1, 5, NULL, S_AMC2_3, 0, 0}, // S_AMC2_2 + {SPR_AMC2, 2, 5, NULL, S_AMC2_1, 0, 0}, // S_AMC2_3 + {SPR_AMS1, 0, 5, NULL, S_AMS1_2, 0, 0}, // S_AMS1_1 + {SPR_AMS1, 1, 5, NULL, S_AMS1_1, 0, 0}, // S_AMS1_2 + {SPR_AMS2, 0, 5, NULL, S_AMS2_2, 0, 0}, // S_AMS2_1 + {SPR_AMS2, 1, 5, NULL, S_AMS2_1, 0, 0}, // S_AMS2_2 + {SPR_AMP1, 0, 4, NULL, S_AMP1_2, 0, 0}, // S_AMP1_1 + {SPR_AMP1, 1, 4, NULL, S_AMP1_3, 0, 0}, // S_AMP1_2 + {SPR_AMP1, 2, 4, NULL, S_AMP1_1, 0, 0}, // S_AMP1_3 + {SPR_AMP2, 0, 4, NULL, S_AMP2_2, 0, 0}, // S_AMP2_1 + {SPR_AMP2, 1, 4, NULL, S_AMP2_3, 0, 0}, // S_AMP2_2 + {SPR_AMP2, 2, 4, NULL, S_AMP2_1, 0, 0}, // S_AMP2_3 + {SPR_AMB1, 0, 4, NULL, S_AMB1_2, 0, 0}, // S_AMB1_1 + {SPR_AMB1, 1, 4, NULL, S_AMB1_3, 0, 0}, // S_AMB1_2 + {SPR_AMB1, 2, 4, NULL, S_AMB1_1, 0, 0}, // S_AMB1_3 + {SPR_AMB2, 0, 4, NULL, S_AMB2_2, 0, 0}, // S_AMB2_1 + {SPR_AMB2, 1, 4, NULL, S_AMB2_3, 0, 0}, // S_AMB2_2 + {SPR_AMB2, 2, 4, NULL, S_AMB2_1, 0, 0}, // S_AMB2_3 + {SPR_AMG1, 0, 100, A_ESound, S_SND_WIND, 0, 0}, // S_SND_WIND + {SPR_AMG1, 0, 85, A_ESound, S_SND_WATERFALL, 0, 0} // S_SND_WATERFALL +}; + + +mobjinfo_t mobjinfo[NUMMOBJTYPES] = { + + { // MT_MISC0 + 81, // doomednum + S_ITEM_PTN1_1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ITEMSHIELD1 + 85, // doomednum + S_ITEM_SHLD1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ITEMSHIELD2 + 31, // doomednum + S_ITEM_SHD2_1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_MISC1 + 8, // doomednum + S_ITEM_BAGH1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_MISC2 + 35, // doomednum + S_ITEM_SPMP1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ARTIINVISIBILITY + 75, // doomednum + S_ARTI_INVS1, // 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, // crashstate + 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_SHADOW | MF_COUNTITEM, // flags + MF2_FLOATBOB // flags2 + }, + + { // MT_MISC3 + 82, // doomednum + S_ARTI_PTN2_1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ARTIFLY + 83, // doomednum + S_ARTI_SOAR1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ARTIINVULNERABILITY + 84, // doomednum + S_ARTI_INVU1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ARTITOMEOFPOWER + 86, // doomednum + S_ARTI_PWBK1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_ARTIEGG + 30, // doomednum + S_ARTI_EGGC1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_EGGFX + -1, // doomednum + S_EGGFX1, // 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_NULL, // crashstate + S_EGGFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 18 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_ARTISUPERHEAL + 32, // doomednum + S_ARTI_SPHL1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_MISC4 + 33, // doomednum + S_ARTI_TRCH1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_MISC5 + 34, // doomednum + S_ARTI_FBMB1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_FIREBOMB + -1, // doomednum + S_FIREBOMB1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_phohit, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY | MF_SHADOW, // flags + 0 // flags2 + }, + + { // MT_ARTITELEPORT + 36, // doomednum + S_ARTI_ATLP1, // 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, // crashstate + 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 + MF2_FLOATBOB // flags2 + }, + + { // MT_POD + 2035, // doomednum + S_POD_WAIT1, // spawnstate + 45, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_POD_PAIN1, // painstate + 255, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_POD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_podexp, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 54 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_NOBLOOD | MF_SHOOTABLE | MF_DROPOFF, // flags + MF2_WINDTHRUST | MF2_PUSHABLE | MF2_SLIDE | MF2_PASSMOBJ | MF2_TELESTOMP // flags2 + }, + + { // MT_PODGOO + -1, // doomednum + S_PODGOO1, // 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, // crashstate + S_PODGOOX, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_NOTELEPORT | MF2_LOGRAV | MF2_CANNOTPUSH // flags2 + }, + + { // MT_PODGENERATOR + 43, // doomednum + S_PODGENERATOR, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_SPLASH + -1, // doomednum + S_SPLASH1, // 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, // crashstate + S_SPLASHX, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_NOTELEPORT | MF2_LOGRAV | MF2_CANNOTPUSH // flags2 + }, + + { // MT_SPLASHBASE + -1, // doomednum + S_SPLASHBASE1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_LAVASPLASH + -1, // doomednum + S_LAVASPLASH1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_LAVASMOKE + -1, // doomednum + S_LAVASMOKE1, // 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, // crashstate + 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 | MF_SHADOW, // flags + 0 // flags2 + }, + + { // MT_SLUDGECHUNK + -1, // doomednum + S_SLUDGECHUNK1, // 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, // crashstate + S_SLUDGECHUNKX, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_NOTELEPORT | MF2_LOGRAV | MF2_CANNOTPUSH // flags2 + }, + + { // MT_SLUDGESPLASH + -1, // doomednum + S_SLUDGESPLASH1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_SKULLHANG70 + 17, // doomednum + S_SKULLHANG70_1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 70 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_SKULLHANG60 + 24, // doomednum + S_SKULLHANG60_1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 60 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_SKULLHANG45 + 25, // doomednum + S_SKULLHANG45_1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 45 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_SKULLHANG35 + 26, // doomednum + S_SKULLHANG35_1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 35 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_CHANDELIER + 28, // doomednum + S_CHANDELIER1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 60 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_SERPTORCH + 27, // doomednum + S_SERPTORCH1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12 * FRACUNIT, // radius + 54 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_SMALLPILLAR + 29, // doomednum + S_SMALLPILLAR, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 34 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_STALAGMITESMALL + 37, // doomednum + S_STALAGMITESMALL, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8 * FRACUNIT, // radius + 32 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_STALAGMITELARGE + 38, // doomednum + S_STALAGMITELARGE, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_STALACTITESMALL + 39, // doomednum + S_STALACTITESMALL, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8 * FRACUNIT, // radius + 36 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_STALACTITELARGE + 40, // doomednum + S_STALACTITELARGE, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_MISC6 + 76, // doomednum + S_FIREBRAZIER1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 44 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_BARREL + 44, // doomednum + S_BARREL, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12 * FRACUNIT, // radius + 32 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_MISC7 + 47, // doomednum + S_BRPILLAR, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 14 * FRACUNIT, // radius + 128 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_MISC8 + 48, // doomednum + S_MOSS1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 23 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_MISC9 + 49, // doomednum + S_MOSS2, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20 * FRACUNIT, // radius + 27 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_MISC10 + 50, // doomednum + S_WALLTORCH1, // 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, // crashstate + 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_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_MISC11 + 51, // doomednum + S_HANGINGCORPSE, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8 * FRACUNIT, // radius + 104 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SPAWNCEILING | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_KEYGIZMOBLUE + 94, // doomednum + S_KEYGIZMO1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 50 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_KEYGIZMOGREEN + 95, // doomednum + S_KEYGIZMO1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 50 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_KEYGIZMOYELLOW + 96, // doomednum + S_KEYGIZMO1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 50 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_KEYGIZMOFLOAT + -1, // doomednum + S_KGZ_START, // 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, // crashstate + 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 | MF_NOGRAVITY, // flags + 0 // flags2 + }, + + { // MT_MISC12 + 87, // doomednum + S_VOLCANO1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 12 * FRACUNIT, // radius + 20 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + 0 // flags2 + }, + + { // MT_VOLCANOBLAST + -1, // doomednum + S_VOLCANOBALL1, // 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, // crashstate + S_VOLCANOBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_volhit, // deathsound + 2 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_VOLCANOTBLAST + -1, // doomednum + S_VOLCANOTBALL1, // 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, // crashstate + S_VOLCANOTBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 2 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_TELEGLITGEN + 74, // doomednum + S_TELEGLITGEN1, // 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, // crashstate + 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 | MF_NOSECTOR, // flags + 0 // flags2 + }, + + { // MT_TELEGLITGEN2 + 52, // doomednum + S_TELEGLITGEN2, // 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, // crashstate + 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 | MF_NOSECTOR, // flags + 0 // flags2 + }, + + { // MT_TELEGLITTER + -1, // doomednum + S_TELEGLITTER1_1, // 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, // crashstate + 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 | MF_MISSILE, // flags + 0 // flags2 + }, + + { // MT_TELEGLITTER2 + -1, // doomednum + S_TELEGLITTER2_1, // 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, // crashstate + 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 | MF_MISSILE, // flags + 0 // flags2 + }, + + { // MT_TFOG + -1, // doomednum + S_TFOG1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_STAFFPUFF + -1, // doomednum + S_STAFFPUFF1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_stfhit, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + 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 + 0 // flags2 + }, + + { // MT_STAFFPUFF2 + -1, // doomednum + S_STAFFPUFF2_1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_stfpow, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + 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 + 0 // flags2 + }, + + { // MT_BEAKPUFF + -1, // doomednum + S_STAFFPUFF1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_chicatk, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + 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 + 0 // flags2 + }, + + { // MT_MISC13 + 2005, // doomednum + S_WGNT, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_GAUNTLETPUFF1 + -1, // doomednum + S_GAUNTLETPUFF1_1, // 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, // crashstate + 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 | MF_SHADOW, // flags + 0 // flags2 + }, + + { // MT_GAUNTLETPUFF2 + -1, // doomednum + S_GAUNTLETPUFF2_1, // 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, // crashstate + 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 | MF_SHADOW, // flags + 0 // flags2 + }, + + { // MT_MISC14 + 53, // doomednum + S_BLSR, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_BLASTERFX1 + -1, // doomednum + S_BLASTERFX1_1, // 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, // crashstate + S_BLASTERFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_blshit, // deathsound + 184 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_BLASTERSMOKE + -1, // doomednum + S_BLASTERSMOKE1, // 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, // crashstate + 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 | MF_SHADOW, // flags + MF2_NOTELEPORT | MF2_CANNOTPUSH // flags2 + }, + + { // MT_RIPPER + -1, // doomednum + S_RIPPER1, // 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_NULL, // crashstate + S_RIPPERX1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 14 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_RIP // flags2 + }, + + { // MT_BLASTERPUFF1 + -1, // doomednum + S_BLASTERPUFF1_1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_BLASTERPUFF2 + -1, // doomednum + S_BLASTERPUFF2_1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_WMACE + 2002, // doomednum + S_WMCE, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_MACEFX1 + -1, // doomednum + S_MACEFX1_1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_lobsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_MACEFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 20 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_FLOORBOUNCE | MF2_THRUGHOST | MF2_NOTELEPORT // flags2 + }, + + { // MT_MACEFX2 + -1, // doomednum + S_MACEFX2_1, // 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_NULL, // crashstate + S_MACEFXI2_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 6, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_FLOORBOUNCE | MF2_THRUGHOST | MF2_NOTELEPORT // flags2 + }, + + { // MT_MACEFX3 + -1, // doomednum + S_MACEFX3_1, // 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_NULL, // crashstate + S_MACEFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 7 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 4, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_FLOORBOUNCE | MF2_THRUGHOST | MF2_NOTELEPORT // flags2 + }, + + { // MT_MACEFX4 + -1, // doomednum + S_MACEFX4_1, // 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_NULL, // crashstate + S_MACEFXI4_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 7 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 18, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_FLOORBOUNCE | MF2_THRUGHOST | MF2_TELESTOMP // flags2 + }, + + { // MT_WSKULLROD + 2004, // doomednum + S_WSKL, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_HORNRODFX1 + -1, // doomednum + S_HRODFX1_1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_hrnsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_HRODFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 22 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT // flags2 + }, + + { // MT_HORNRODFX2 + -1, // doomednum + S_HRODFX2_1, // spawnstate + 4 * 35, // spawnhealth + S_NULL, // seestate + sfx_hrnsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_HRODFXI2_1, // deathstate + S_NULL, // xdeathstate + sfx_ramphit, // deathsound + 22 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_RAINPLR1 + -1, // doomednum + S_RAINPLR1_1, // 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_NULL, // crashstate + S_RAINPLR1X_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 12 * FRACUNIT, // speed + 5 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_RAINPLR2 + -1, // doomednum + S_RAINPLR2_1, // 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_NULL, // crashstate + S_RAINPLR2X_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 12 * FRACUNIT, // speed + 5 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_RAINPLR3 + -1, // doomednum + S_RAINPLR3_1, // 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_NULL, // crashstate + S_RAINPLR3X_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 12 * FRACUNIT, // speed + 5 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_RAINPLR4 + -1, // doomednum + S_RAINPLR4_1, // 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_NULL, // crashstate + S_RAINPLR4X_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 12 * FRACUNIT, // speed + 5 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_GOLDWANDFX1 + -1, // doomednum + S_GWANDFX1_1, // 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_NULL, // crashstate + S_GWANDFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_gldhit, // deathsound + 22 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_GOLDWANDFX2 + -1, // doomednum + S_GWANDFX2_1, // 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_NULL, // crashstate + S_GWANDFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 18 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_GOLDWANDPUFF1 + -1, // doomednum + S_GWANDPUFF1_1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_GOLDWANDPUFF2 + -1, // doomednum + S_GWANDFXI1_1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_WPHOENIXROD + 2003, // doomednum + S_WPHX, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_PHOENIXFX1 + -1, // doomednum + S_PHOENIXFX1_1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_phosht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_PHOENIXFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_phohit, // 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 + MF2_THRUGHOST | MF2_NOTELEPORT // flags2 + }, + + // The following thing is present in the mobjinfo table from Heretic 1.0, + // but not in Heretic 1.3 (ie. it was removed). It has been re-inserted + // here to support HHE patches. + + { // MT_PHOENIXFX_REMOVED + -1, // doomednum + S_PHOENIXFXIX_1, // 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, // crashstate + S_PHOENIXFXIX_3, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_PHOENIXPUFF + -1, // doomednum + S_PHOENIXPUFF1, // 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, // crashstate + 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 | MF_SHADOW, // flags + MF2_NOTELEPORT | MF2_CANNOTPUSH // flags2 + }, + + { // MT_PHOENIXFX2 + -1, // doomednum + S_PHOENIXFX2_1, // 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_NULL, // crashstate + S_PHOENIXFXI2_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_MISC15 + 2001, // doomednum + S_WBOW, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_CRBOWFX1 + -1, // doomednum + S_CRBOWFX1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_bowsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_CRBOWFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 30 * FRACUNIT, // speed + 11 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_CRBOWFX2 + -1, // doomednum + S_CRBOWFX2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_bowsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // crashstate + S_CRBOWFXI1_1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 32 * FRACUNIT, // speed + 11 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 6, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_CRBOWFX3 + -1, // doomednum + S_CRBOWFX3, // 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_NULL, // crashstate + S_CRBOWFXI3_1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 20 * FRACUNIT, // speed + 11 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_THRUGHOST | MF2_NOTELEPORT // flags2 + }, + + { // MT_CRBOWFX4 + -1, // doomednum + S_CRBOWFX4_1, // 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, // crashstate + 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 + MF2_LOGRAV // flags2 + }, + + { // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_BLOODSPLATTER + -1, // doomednum + S_BLOODSPLATTER1, // 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, // crashstate + S_BLOODSPLATTERX, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_NOTELEPORT | MF2_CANNOTPUSH // flags2 + }, + + { // 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_plrpai, // painsound + S_NULL, // meleestate + S_PLAY_ATK1, // missilestate + S_NULL, // crashstate + S_PLAY_DIE1, // deathstate + S_PLAY_XDIE1, // xdeathstate + sfx_plrdth, // 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 + MF2_WINDTHRUST | MF2_FOOTCLIP | MF2_SLIDE | MF2_PASSMOBJ | MF2_TELESTOMP // flags2 + }, + + { // MT_BLOODYSKULL + -1, // doomednum + S_BLOODYSKULL1, // 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, // crashstate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 4 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_DROPOFF, // flags + MF2_LOGRAV | MF2_CANNOTPUSH // flags2 + }, + + { // MT_CHICPLAYER + -1, // doomednum + S_CHICPLAY, // spawnstate + 100, // spawnhealth + S_CHICPLAY_RUN1, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_CHICPLAY_PAIN, // painstate + 255, // painchance + sfx_chicpai, // painsound + S_NULL, // meleestate + S_CHICPLAY_ATK1, // missilestate + S_NULL, // crashstate + S_CHICKEN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_chicdth, // deathsound + 0, // speed + 16 * FRACUNIT, // radius + 24 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID | MF_SHOOTABLE | MF_DROPOFF | MF_NOTDMATCH, // flags + MF2_WINDTHRUST | MF2_SLIDE | MF2_PASSMOBJ | MF2_FOOTCLIP | MF2_LOGRAV | MF2_TELESTOMP // flags2 + }, + + { // MT_CHICKEN + -1, // doomednum + S_CHICKEN_LOOK1, // spawnstate + 10, // spawnhealth + S_CHICKEN_WALK1, // seestate + sfx_chicpai, // seesound + 8, // reactiontime + sfx_chicatk, // attacksound + S_CHICKEN_PAIN1, // painstate + 200, // painchance + sfx_chicpai, // painsound + S_CHICKEN_ATK1, // meleestate + 0, // missilestate + S_NULL, // crashstate + S_CHICKEN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_chicdth, // deathsound + 4, // speed + 9 * FRACUNIT, // radius + 22 * FRACUNIT, // height + 40, // mass + 0, // damage + sfx_chicact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_DROPOFF, // flags + MF2_WINDTHRUST | MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_FEATHER + -1, // doomednum + S_FEATHER1, // 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, // crashstate + S_FEATHERX, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 2 * FRACUNIT, // radius + 4 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF, // flags + MF2_NOTELEPORT | MF2_LOGRAV | MF2_CANNOTPUSH | MF2_WINDTHRUST // flags2 + }, + + { // MT_MUMMY + 68, // doomednum + S_MUMMY_LOOK1, // spawnstate + 80, // spawnhealth + S_MUMMY_WALK1, // seestate + sfx_mumsit, // seesound + 8, // reactiontime + sfx_mumat1, // attacksound + S_MUMMY_PAIN1, // painstate + 128, // painchance + sfx_mumpai, // painsound + S_MUMMY_ATK1, // meleestate + 0, // missilestate + S_NULL, // crashstate + S_MUMMY_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mumdth, // deathsound + 12, // speed + 22 * FRACUNIT, // radius + 62 * FRACUNIT, // height + 75, // mass + 0, // damage + sfx_mumact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_MUMMYLEADER + 45, // doomednum + S_MUMMY_LOOK1, // spawnstate + 100, // spawnhealth + S_MUMMY_WALK1, // seestate + sfx_mumsit, // seesound + 8, // reactiontime + sfx_mumat1, // attacksound + S_MUMMY_PAIN1, // painstate + 64, // painchance + sfx_mumpai, // painsound + S_MUMMY_ATK1, // meleestate + S_MUMMYL_ATK1, // missilestate + S_NULL, // crashstate + S_MUMMY_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mumdth, // deathsound + 12, // speed + 22 * FRACUNIT, // radius + 62 * FRACUNIT, // height + 75, // mass + 0, // damage + sfx_mumact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_MUMMYGHOST + 69, // doomednum + S_MUMMY_LOOK1, // spawnstate + 80, // spawnhealth + S_MUMMY_WALK1, // seestate + sfx_mumsit, // seesound + 8, // reactiontime + sfx_mumat1, // attacksound + S_MUMMY_PAIN1, // painstate + 128, // painchance + sfx_mumpai, // painsound + S_MUMMY_ATK1, // meleestate + 0, // missilestate + S_NULL, // crashstate + S_MUMMY_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mumdth, // deathsound + 12, // speed + 22 * FRACUNIT, // radius + 62 * FRACUNIT, // height + 75, // mass + 0, // damage + sfx_mumact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_SHADOW, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_MUMMYLEADERGHOST + 46, // doomednum + S_MUMMY_LOOK1, // spawnstate + 100, // spawnhealth + S_MUMMY_WALK1, // seestate + sfx_mumsit, // seesound + 8, // reactiontime + sfx_mumat1, // attacksound + S_MUMMY_PAIN1, // painstate + 64, // painchance + sfx_mumpai, // painsound + S_MUMMY_ATK1, // meleestate + S_MUMMYL_ATK1, // missilestate + S_NULL, // crashstate + S_MUMMY_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mumdth, // deathsound + 12, // speed + 22 * FRACUNIT, // radius + 62 * FRACUNIT, // height + 75, // mass + 0, // damage + sfx_mumact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_SHADOW, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_MUMMYSOUL + -1, // doomednum + S_MUMMY_SOUL1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_MUMMYFX1 + -1, // doomednum + S_MUMMYFX1_1, // 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_NULL, // crashstate + S_MUMMYFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 9 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 14 * FRACUNIT, // height + 100, // mass + 4, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_BEAST + 70, // doomednum + S_BEAST_LOOK1, // spawnstate + 220, // spawnhealth + S_BEAST_WALK1, // seestate + sfx_bstsit, // seesound + 8, // reactiontime + sfx_bstatk, // attacksound + S_BEAST_PAIN1, // painstate + 100, // painchance + sfx_bstpai, // painsound + 0, // meleestate + S_BEAST_ATK1, // missilestate + S_NULL, // crashstate + S_BEAST_DIE1, // deathstate + S_BEAST_XDIE1, // xdeathstate + sfx_bstdth, // deathsound + 14, // speed + 32 * FRACUNIT, // radius + 74 * FRACUNIT, // height + 200, // mass + 0, // damage + sfx_bstact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_BEASTBALL + -1, // doomednum + S_BEASTBALL1, // 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_NULL, // crashstate + S_BEASTBALLX1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 12 * FRACUNIT, // speed + 9 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 4, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT // flags2 + }, + + { // MT_BURNBALL + -1, // doomednum + S_BURNBALL1, // 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_NULL, // crashstate + S_BEASTBALLX1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY | MF_MISSILE, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_BURNBALLFB + -1, // doomednum + S_BURNBALLFB1, // 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_NULL, // crashstate + S_BEASTBALLX1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY | MF_MISSILE, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_PUFFY + -1, // doomednum + S_PUFFY1, // 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_NULL, // crashstate + S_PUFFY1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 6 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_NOGRAVITY | MF_MISSILE, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_SNAKE + 92, // doomednum + S_SNAKE_LOOK1, // spawnstate + 280, // spawnhealth + S_SNAKE_WALK1, // seestate + sfx_snksit, // seesound + 8, // reactiontime + sfx_snkatk, // attacksound + S_SNAKE_PAIN1, // painstate + 48, // painchance + sfx_snkpai, // painsound + 0, // meleestate + S_SNAKE_ATK1, // missilestate + S_NULL, // crashstate + S_SNAKE_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_snkdth, // deathsound + 10, // speed + 22 * FRACUNIT, // radius + 70 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_snkact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_SNAKEPRO_A + -1, // doomednum + S_SNAKEPRO_A1, // 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_NULL, // crashstate + S_SNAKEPRO_AX1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 14 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT // flags2 + }, + + { // MT_SNAKEPRO_B + -1, // doomednum + S_SNAKEPRO_B1, // 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_NULL, // crashstate + S_SNAKEPRO_BX1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 14 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_HEAD + 6, // doomednum + S_HEAD_LOOK, // spawnstate + 700, // spawnhealth + S_HEAD_FLOAT, // seestate + sfx_hedsit, // seesound + 8, // reactiontime + sfx_hedat1, // attacksound + S_HEAD_PAIN1, // painstate + 32, // painchance + sfx_hedpai, // painsound + 0, // meleestate + S_HEAD_ATK1, // missilestate + S_NULL, // crashstate + S_HEAD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_heddth, // deathsound + 6, // speed + 40 * FRACUNIT, // radius + 72 * FRACUNIT, // height + 325, // mass + 0, // damage + sfx_hedact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_NOBLOOD, // flags + MF2_PASSMOBJ // flags2 + }, + + { // MT_HEADFX1 + -1, // doomednum + S_HEADFX1_1, // 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_NULL, // crashstate + S_HEADFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 13 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_THRUGHOST // flags2 + }, + + { // MT_HEADFX2 + -1, // doomednum + S_HEADFX2_1, // 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_NULL, // crashstate + S_HEADFXI2_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 8 * FRACUNIT, // speed + 12 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_HEADFX3 + -1, // doomednum + S_HEADFX3_1, // 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_NULL, // crashstate + S_HEADFXI3_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 14 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT // flags2 + }, + + { // MT_WHIRLWIND + -1, // doomednum + S_HEADFX4_1, // 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_NULL, // crashstate + S_HEADFXI4_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 16 * FRACUNIT, // radius + 74 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY | MF_SHADOW, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_CLINK + 90, // doomednum + S_CLINK_LOOK1, // spawnstate + 150, // spawnhealth + S_CLINK_WALK1, // seestate + sfx_clksit, // seesound + 8, // reactiontime + sfx_clkatk, // attacksound + S_CLINK_PAIN1, // painstate + 32, // painchance + sfx_clkpai, // painsound + S_CLINK_ATK1, // meleestate + 0, // missilestate + S_NULL, // crashstate + S_CLINK_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_clkdth, // deathsound + 14, // speed + 20 * FRACUNIT, // radius + 64 * FRACUNIT, // height + 75, // mass + 0, // damage + sfx_clkact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_NOBLOOD, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_WIZARD + 15, // doomednum + S_WIZARD_LOOK1, // spawnstate + 180, // spawnhealth + S_WIZARD_WALK1, // seestate + sfx_wizsit, // seesound + 8, // reactiontime + sfx_wizatk, // attacksound + S_WIZARD_PAIN1, // painstate + 64, // painchance + sfx_wizpai, // painsound + 0, // meleestate + S_WIZARD_ATK1, // missilestate + S_NULL, // crashstate + S_WIZARD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_wizdth, // deathsound + 12, // speed + 16 * FRACUNIT, // radius + 68 * FRACUNIT, // height + 100, // mass + 0, // damage + sfx_wizact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_FLOAT | MF_NOGRAVITY, // flags + MF2_PASSMOBJ // flags2 + }, + + { // MT_WIZFX1 + -1, // doomednum + S_WIZFX1_1, // 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_NULL, // crashstate + S_WIZFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 18 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_IMP + 66, // doomednum + S_IMP_LOOK1, // spawnstate + 40, // spawnhealth + S_IMP_FLY1, // seestate + sfx_impsit, // seesound + 8, // reactiontime + sfx_impat1, // attacksound + S_IMP_PAIN1, // painstate + 200, // painchance + sfx_imppai, // painsound + S_IMP_MEATK1, // meleestate + S_IMP_MSATK1_1, // missilestate + S_IMP_CRASH1, // crashstate + S_IMP_DIE1, // deathstate + S_IMP_XDIE1, // xdeathstate + sfx_impdth, // deathsound + 10, // speed + 16 * FRACUNIT, // radius + 36 * FRACUNIT, // height + 50, // mass + 0, // damage + sfx_impact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | MF_COUNTKILL, // flags + MF2_SPAWNFLOAT | MF2_PASSMOBJ // flags2 + }, + + { // MT_IMPLEADER + 5, // doomednum + S_IMP_LOOK1, // spawnstate + 80, // spawnhealth + S_IMP_FLY1, // seestate + sfx_impsit, // seesound + 8, // reactiontime + sfx_impat2, // attacksound + S_IMP_PAIN1, // painstate + 200, // painchance + sfx_imppai, // painsound + 0, // meleestate + S_IMP_MSATK2_1, // missilestate + S_IMP_CRASH1, // crashstate + S_IMP_DIE1, // deathstate + S_IMP_XDIE1, // xdeathstate + sfx_impdth, // deathsound + 10, // speed + 16 * FRACUNIT, // radius + 36 * FRACUNIT, // height + 50, // mass + 0, // damage + sfx_impact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_FLOAT | MF_NOGRAVITY | MF_COUNTKILL, // flags + MF2_SPAWNFLOAT | MF2_PASSMOBJ // flags2 + }, + + { // MT_IMPCHUNK1 + -1, // doomednum + S_IMP_CHUNKA1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_IMPCHUNK2 + -1, // doomednum + S_IMP_CHUNKB1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_IMPBALL + -1, // doomednum + S_IMPFX1, // 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_NULL, // crashstate + S_IMPFXI1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 10 * FRACUNIT, // speed + 8 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT // flags2 + }, + + { // MT_KNIGHT + 64, // doomednum + S_KNIGHT_STND1, // spawnstate + 200, // spawnhealth + S_KNIGHT_WALK1, // seestate + sfx_kgtsit, // seesound + 8, // reactiontime + sfx_kgtatk, // attacksound + S_KNIGHT_PAIN1, // painstate + 100, // painchance + sfx_kgtpai, // painsound + S_KNIGHT_ATK1, // meleestate + S_KNIGHT_ATK1, // missilestate + S_NULL, // crashstate + S_KNIGHT_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_kgtdth, // deathsound + 12, // speed + 24 * FRACUNIT, // radius + 78 * FRACUNIT, // height + 150, // mass + 0, // damage + sfx_kgtact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_KNIGHTGHOST + 65, // doomednum + S_KNIGHT_STND1, // spawnstate + 200, // spawnhealth + S_KNIGHT_WALK1, // seestate + sfx_kgtsit, // seesound + 8, // reactiontime + sfx_kgtatk, // attacksound + S_KNIGHT_PAIN1, // painstate + 100, // painchance + sfx_kgtpai, // painsound + S_KNIGHT_ATK1, // meleestate + S_KNIGHT_ATK1, // missilestate + S_NULL, // crashstate + S_KNIGHT_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_kgtdth, // deathsound + 12, // speed + 24 * FRACUNIT, // radius + 78 * FRACUNIT, // height + 150, // mass + 0, // damage + sfx_kgtact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_SHADOW, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ // flags2 + }, + + { // MT_KNIGHTAXE + -1, // doomednum + S_SPINAXE1, // 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_NULL, // crashstate + S_SPINAXEX1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 9 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 2, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_WINDTHRUST | MF2_NOTELEPORT | MF2_THRUGHOST // flags2 + }, + + { // MT_REDAXE + -1, // doomednum + S_REDAXE1, // 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_NULL, // crashstate + S_REDAXEX1, // deathstate + S_NULL, // xdeathstate + sfx_hrnhit, // deathsound + 9 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 8 * FRACUNIT, // height + 100, // mass + 7, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_THRUGHOST // flags2 + }, + + { // MT_SORCERER1 + 7, // doomednum + S_SRCR1_LOOK1, // spawnstate + 2000, // spawnhealth + S_SRCR1_WALK1, // seestate + sfx_sbtsit, // seesound + 8, // reactiontime + sfx_sbtatk, // attacksound + S_SRCR1_PAIN1, // painstate + 56, // painchance + sfx_sbtpai, // painsound + 0, // meleestate + S_SRCR1_ATK1, // missilestate + S_NULL, // crashstate + S_SRCR1_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sbtdth, // deathsound + 16, // speed + 28 * FRACUNIT, // radius + 100 * FRACUNIT, // height + 800, // mass + 0, // damage + sfx_sbtact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ | MF2_BOSS // flags2 + }, + + { // MT_SRCRFX1 + -1, // doomednum + S_SRCRFX1_1, // 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_NULL, // crashstate + S_SRCRFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 20 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 10 * FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_SORCERER2 + -1, // doomednum + S_SOR2_LOOK1, // spawnstate + 3500, // spawnhealth + S_SOR2_WALK1, // seestate + sfx_sorsit, // seesound + 8, // reactiontime + sfx_soratk, // attacksound + S_SOR2_PAIN1, // painstate + 32, // painchance + sfx_sorpai, // painsound + 0, // meleestate + S_SOR2_ATK1, // missilestate + S_NULL, // crashstate + S_SOR2_DIE1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 14, // speed + 16 * FRACUNIT, // radius + 70 * FRACUNIT, // height + 300, // mass + 0, // damage + sfx_soract, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_DROPOFF, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ | MF2_BOSS // flags2 + }, + + { // MT_SOR2FX1 + -1, // doomednum + S_SOR2FX1_1, // 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_NULL, // crashstate + S_SOR2FXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 20 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_SOR2FXSPARK + -1, // doomednum + S_SOR2FXSPARK1, // 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, // crashstate + 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 + MF2_NOTELEPORT | MF2_CANNOTPUSH // flags2 + }, + + { // MT_SOR2FX2 + -1, // doomednum + S_SOR2FX2_1, // 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_NULL, // crashstate + S_SOR2FXI2_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 6 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT // flags2 + }, + + { // MT_SOR2TELEFADE + -1, // doomednum + S_SOR2TELEFADE1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_MINOTAUR + 9, // doomednum + S_MNTR_LOOK1, // spawnstate + 3000, // spawnhealth + S_MNTR_WALK1, // seestate + sfx_minsit, // seesound + 8, // reactiontime + sfx_minat1, // attacksound + S_MNTR_PAIN1, // painstate + 25, // painchance + sfx_minpai, // painsound + S_MNTR_ATK1_1, // meleestate + S_MNTR_ATK2_1, // missilestate + S_NULL, // crashstate + S_MNTR_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mindth, // deathsound + 16, // speed + 28 * FRACUNIT, // radius + 100 * FRACUNIT, // height + 800, // mass + 7, // damage + sfx_minact, // activesound + MF_SOLID | MF_SHOOTABLE | MF_COUNTKILL | MF_DROPOFF, // flags + MF2_FOOTCLIP | MF2_PASSMOBJ | MF2_BOSS // flags2 + }, + + { // MT_MNTRFX1 + -1, // doomednum + S_MNTRFX1_1, // 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_NULL, // crashstate + S_MNTRFXI1_1, // deathstate + S_NULL, // xdeathstate + 0, // deathsound + 20 * FRACUNIT, // speed + 10 * FRACUNIT, // radius + 6 * FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_MNTRFX2 + -1, // doomednum + S_MNTRFX2_1, // 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_NULL, // crashstate + S_MNTRFXI2_1, // deathstate + S_NULL, // xdeathstate + sfx_phohit, // deathsound + 14 * FRACUNIT, // speed + 5 * FRACUNIT, // radius + 12 * FRACUNIT, // height + 100, // mass + 4, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_MNTRFX3 + -1, // doomednum + S_MNTRFX3_1, // 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_NULL, // crashstate + S_MNTRFXI2_1, // deathstate + S_NULL, // xdeathstate + sfx_phohit, // deathsound + 0, // speed + 8 * FRACUNIT, // radius + 16 * FRACUNIT, // height + 100, // mass + 4, // damage + sfx_None, // activesound + MF_NOBLOCKMAP | MF_MISSILE | MF_DROPOFF | MF_NOGRAVITY, // flags + MF2_NOTELEPORT | MF2_FIREDAMAGE // flags2 + }, + + { // MT_AKYY + 73, // doomednum + S_AKYY1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_BKYY + 79, // doomednum + S_BKYY1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_CKEY + 80, // doomednum + S_CKYY1, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMGWNDWIMPY + 10, // doomednum + S_AMG1, // spawnstate + AMMO_GWND_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMGWNDHEFTY + 12, // doomednum + S_AMG2_1, // spawnstate + AMMO_GWND_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMMACEWIMPY + 13, // doomednum + S_AMM1, // spawnstate + AMMO_MACE_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMMACEHEFTY + 16, // doomednum + S_AMM2, // spawnstate + AMMO_MACE_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMCBOWWIMPY + 18, // doomednum + S_AMC1, // spawnstate + AMMO_CBOW_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMCBOWHEFTY + 19, // doomednum + S_AMC2_1, // spawnstate + AMMO_CBOW_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMSKRDWIMPY + 20, // doomednum + S_AMS1_1, // spawnstate + AMMO_SKRD_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMSKRDHEFTY + 21, // doomednum + S_AMS2_1, // spawnstate + AMMO_SKRD_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMPHRDWIMPY + 22, // doomednum + S_AMP1_1, // spawnstate + AMMO_PHRD_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMPHRDHEFTY + 23, // doomednum + S_AMP2_1, // spawnstate + AMMO_PHRD_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMBLSRWIMPY + 54, // doomednum + S_AMB1_1, // spawnstate + AMMO_BLSR_WIMPY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_AMBLSRHEFTY + 55, // doomednum + S_AMB2_1, // spawnstate + AMMO_BLSR_HEFTY, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_SOUNDWIND + 42, // doomednum + S_SND_WIND, // 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, // crashstate + 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 + 0 // flags2 + }, + + { // MT_SOUNDWATERFALL + 41, // doomednum + S_SND_WATERFALL, // 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, // crashstate + 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 + 0 // flags2 + } +}; diff --git a/src/heretic/info.h b/src/heretic/info.h new file mode 100644 index 00000000..236826cb --- /dev/null +++ b/src/heretic/info.h @@ -0,0 +1,1586 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#ifndef HERETIC_INFO_H +#define HERETIC_INFO_H + +typedef enum +{ + SPR_IMPX, + SPR_ACLO, + SPR_PTN1, + SPR_SHLD, + SPR_SHD2, + SPR_BAGH, + SPR_SPMP, + SPR_INVS, + SPR_PTN2, + SPR_SOAR, + SPR_INVU, + SPR_PWBK, + SPR_EGGC, + SPR_EGGM, + SPR_FX01, + SPR_SPHL, + SPR_TRCH, + SPR_FBMB, + SPR_XPL1, + SPR_ATLP, + SPR_PPOD, + SPR_AMG1, + SPR_SPSH, + SPR_LVAS, + SPR_SLDG, + SPR_SKH1, + SPR_SKH2, + SPR_SKH3, + SPR_SKH4, + SPR_CHDL, + SPR_SRTC, + SPR_SMPL, + SPR_STGS, + SPR_STGL, + SPR_STCS, + SPR_STCL, + SPR_KFR1, + SPR_BARL, + SPR_BRPL, + SPR_MOS1, + SPR_MOS2, + SPR_WTRH, + SPR_HCOR, + SPR_KGZ1, + SPR_KGZB, + SPR_KGZG, + SPR_KGZY, + SPR_VLCO, + SPR_VFBL, + SPR_VTFB, + SPR_SFFI, + SPR_TGLT, + SPR_TELE, + SPR_STFF, + SPR_PUF3, + SPR_PUF4, + SPR_BEAK, + SPR_WGNT, + SPR_GAUN, + SPR_PUF1, + SPR_WBLS, + SPR_BLSR, + SPR_FX18, + SPR_FX17, + SPR_WMCE, + SPR_MACE, + SPR_FX02, + SPR_WSKL, + SPR_HROD, + SPR_FX00, + SPR_FX20, + SPR_FX21, + SPR_FX22, + SPR_FX23, + SPR_GWND, + SPR_PUF2, + SPR_WPHX, + SPR_PHNX, + SPR_FX04, + SPR_FX08, + SPR_FX09, + SPR_WBOW, + SPR_CRBW, + SPR_FX03, + SPR_BLOD, + SPR_PLAY, + SPR_FDTH, + SPR_BSKL, + SPR_CHKN, + SPR_MUMM, + SPR_FX15, + SPR_BEAS, + SPR_FRB1, + SPR_SNKE, + SPR_SNFX, + SPR_HEAD, + SPR_FX05, + SPR_FX06, + SPR_FX07, + SPR_CLNK, + SPR_WZRD, + SPR_FX11, + SPR_FX10, + SPR_KNIG, + SPR_SPAX, + SPR_RAXE, + SPR_SRCR, + SPR_FX14, + SPR_SOR2, + SPR_SDTH, + SPR_FX16, + SPR_MNTR, + SPR_FX12, + SPR_FX13, + SPR_AKYY, + SPR_BKYY, + SPR_CKYY, + SPR_AMG2, + SPR_AMM1, + SPR_AMM2, + SPR_AMC1, + SPR_AMC2, + SPR_AMS1, + SPR_AMS2, + SPR_AMP1, + SPR_AMP2, + SPR_AMB1, + SPR_AMB2, + NUMSPRITES +} spritenum_t; + +typedef enum +{ + S_NULL, + S_FREETARGMOBJ, + S_ITEM_PTN1_1, + S_ITEM_PTN1_2, + S_ITEM_PTN1_3, + S_ITEM_SHLD1, + S_ITEM_SHD2_1, + S_ITEM_BAGH1, + S_ITEM_SPMP1, + S_HIDESPECIAL1, + S_HIDESPECIAL2, + S_HIDESPECIAL3, + S_HIDESPECIAL4, + S_HIDESPECIAL5, + S_HIDESPECIAL6, + S_HIDESPECIAL7, + S_HIDESPECIAL8, + S_HIDESPECIAL9, + S_HIDESPECIAL10, + S_HIDESPECIAL11, + S_DORMANTARTI1, + S_DORMANTARTI2, + S_DORMANTARTI3, + S_DORMANTARTI4, + S_DORMANTARTI5, + S_DORMANTARTI6, + S_DORMANTARTI7, + S_DORMANTARTI8, + S_DORMANTARTI9, + S_DORMANTARTI10, + S_DORMANTARTI11, + S_DORMANTARTI12, + S_DORMANTARTI13, + S_DORMANTARTI14, + S_DORMANTARTI15, + S_DORMANTARTI16, + S_DORMANTARTI17, + S_DORMANTARTI18, + S_DORMANTARTI19, + S_DORMANTARTI20, + S_DORMANTARTI21, + S_DEADARTI1, + S_DEADARTI2, + S_DEADARTI3, + S_DEADARTI4, + S_DEADARTI5, + S_DEADARTI6, + S_DEADARTI7, + S_DEADARTI8, + S_DEADARTI9, + S_DEADARTI10, + S_ARTI_INVS1, + S_ARTI_PTN2_1, + S_ARTI_PTN2_2, + S_ARTI_PTN2_3, + S_ARTI_SOAR1, + S_ARTI_SOAR2, + S_ARTI_SOAR3, + S_ARTI_SOAR4, + S_ARTI_INVU1, + S_ARTI_INVU2, + S_ARTI_INVU3, + S_ARTI_INVU4, + S_ARTI_PWBK1, + S_ARTI_EGGC1, + S_ARTI_EGGC2, + S_ARTI_EGGC3, + S_ARTI_EGGC4, + S_EGGFX1, + S_EGGFX2, + S_EGGFX3, + S_EGGFX4, + S_EGGFX5, + S_EGGFXI1_1, + S_EGGFXI1_2, + S_EGGFXI1_3, + S_EGGFXI1_4, + S_ARTI_SPHL1, + S_ARTI_TRCH1, + S_ARTI_TRCH2, + S_ARTI_TRCH3, + S_ARTI_FBMB1, + S_FIREBOMB1, + S_FIREBOMB2, + S_FIREBOMB3, + S_FIREBOMB4, + S_FIREBOMB5, + S_FIREBOMB6, + S_FIREBOMB7, + S_FIREBOMB8, + S_FIREBOMB9, + S_FIREBOMB10, + S_FIREBOMB11, + S_ARTI_ATLP1, + S_ARTI_ATLP2, + S_ARTI_ATLP3, + S_ARTI_ATLP4, + S_POD_WAIT1, + S_POD_PAIN1, + S_POD_DIE1, + S_POD_DIE2, + S_POD_DIE3, + S_POD_DIE4, + S_POD_GROW1, + S_POD_GROW2, + S_POD_GROW3, + S_POD_GROW4, + S_POD_GROW5, + S_POD_GROW6, + S_POD_GROW7, + S_POD_GROW8, + S_PODGOO1, + S_PODGOO2, + S_PODGOOX, + S_PODGENERATOR, + S_SPLASH1, + S_SPLASH2, + S_SPLASH3, + S_SPLASH4, + S_SPLASHX, + S_SPLASHBASE1, + S_SPLASHBASE2, + S_SPLASHBASE3, + S_SPLASHBASE4, + S_SPLASHBASE5, + S_SPLASHBASE6, + S_SPLASHBASE7, + S_LAVASPLASH1, + S_LAVASPLASH2, + S_LAVASPLASH3, + S_LAVASPLASH4, + S_LAVASPLASH5, + S_LAVASPLASH6, + S_LAVASMOKE1, + S_LAVASMOKE2, + S_LAVASMOKE3, + S_LAVASMOKE4, + S_LAVASMOKE5, + S_SLUDGECHUNK1, + S_SLUDGECHUNK2, + S_SLUDGECHUNK3, + S_SLUDGECHUNK4, + S_SLUDGECHUNKX, + S_SLUDGESPLASH1, + S_SLUDGESPLASH2, + S_SLUDGESPLASH3, + S_SLUDGESPLASH4, + S_SKULLHANG70_1, + S_SKULLHANG60_1, + S_SKULLHANG45_1, + S_SKULLHANG35_1, + S_CHANDELIER1, + S_CHANDELIER2, + S_CHANDELIER3, + S_SERPTORCH1, + S_SERPTORCH2, + S_SERPTORCH3, + S_SMALLPILLAR, + S_STALAGMITESMALL, + S_STALAGMITELARGE, + S_STALACTITESMALL, + S_STALACTITELARGE, + S_FIREBRAZIER1, + S_FIREBRAZIER2, + S_FIREBRAZIER3, + S_FIREBRAZIER4, + S_FIREBRAZIER5, + S_FIREBRAZIER6, + S_FIREBRAZIER7, + S_FIREBRAZIER8, + S_BARREL, + S_BRPILLAR, + S_MOSS1, + S_MOSS2, + S_WALLTORCH1, + S_WALLTORCH2, + S_WALLTORCH3, + S_HANGINGCORPSE, + S_KEYGIZMO1, + S_KEYGIZMO2, + S_KEYGIZMO3, + S_KGZ_START, + S_KGZ_BLUEFLOAT1, + S_KGZ_GREENFLOAT1, + S_KGZ_YELLOWFLOAT1, + S_VOLCANO1, + S_VOLCANO2, + S_VOLCANO3, + S_VOLCANO4, + S_VOLCANO5, + S_VOLCANO6, + S_VOLCANO7, + S_VOLCANO8, + S_VOLCANO9, + S_VOLCANOBALL1, + S_VOLCANOBALL2, + S_VOLCANOBALLX1, + S_VOLCANOBALLX2, + S_VOLCANOBALLX3, + S_VOLCANOBALLX4, + S_VOLCANOBALLX5, + S_VOLCANOBALLX6, + S_VOLCANOTBALL1, + S_VOLCANOTBALL2, + S_VOLCANOTBALLX1, + S_VOLCANOTBALLX2, + S_VOLCANOTBALLX3, + S_VOLCANOTBALLX4, + S_VOLCANOTBALLX5, + S_VOLCANOTBALLX6, + S_VOLCANOTBALLX7, + S_TELEGLITGEN1, + S_TELEGLITGEN2, + S_TELEGLITTER1_1, + S_TELEGLITTER1_2, + S_TELEGLITTER1_3, + S_TELEGLITTER1_4, + S_TELEGLITTER1_5, + S_TELEGLITTER2_1, + S_TELEGLITTER2_2, + S_TELEGLITTER2_3, + S_TELEGLITTER2_4, + S_TELEGLITTER2_5, + S_TFOG1, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_TFOG11, + S_TFOG12, + S_TFOG13, + S_LIGHTDONE, + S_STAFFREADY, + S_STAFFDOWN, + S_STAFFUP, + S_STAFFREADY2_1, + S_STAFFREADY2_2, + S_STAFFREADY2_3, + S_STAFFDOWN2, + S_STAFFUP2, + S_STAFFATK1_1, + S_STAFFATK1_2, + S_STAFFATK1_3, + S_STAFFATK2_1, + S_STAFFATK2_2, + S_STAFFATK2_3, + S_STAFFPUFF1, + S_STAFFPUFF2, + S_STAFFPUFF3, + S_STAFFPUFF4, + S_STAFFPUFF2_1, + S_STAFFPUFF2_2, + S_STAFFPUFF2_3, + S_STAFFPUFF2_4, + S_STAFFPUFF2_5, + S_STAFFPUFF2_6, + S_BEAKREADY, + S_BEAKDOWN, + S_BEAKUP, + S_BEAKATK1_1, + S_BEAKATK2_1, + S_WGNT, + S_GAUNTLETREADY, + S_GAUNTLETDOWN, + S_GAUNTLETUP, + S_GAUNTLETREADY2_1, + S_GAUNTLETREADY2_2, + S_GAUNTLETREADY2_3, + S_GAUNTLETDOWN2, + S_GAUNTLETUP2, + S_GAUNTLETATK1_1, + S_GAUNTLETATK1_2, + S_GAUNTLETATK1_3, + S_GAUNTLETATK1_4, + S_GAUNTLETATK1_5, + S_GAUNTLETATK1_6, + S_GAUNTLETATK1_7, + S_GAUNTLETATK2_1, + S_GAUNTLETATK2_2, + S_GAUNTLETATK2_3, + S_GAUNTLETATK2_4, + S_GAUNTLETATK2_5, + S_GAUNTLETATK2_6, + S_GAUNTLETATK2_7, + S_GAUNTLETPUFF1_1, + S_GAUNTLETPUFF1_2, + S_GAUNTLETPUFF1_3, + S_GAUNTLETPUFF1_4, + S_GAUNTLETPUFF2_1, + S_GAUNTLETPUFF2_2, + S_GAUNTLETPUFF2_3, + S_GAUNTLETPUFF2_4, + S_BLSR, + S_BLASTERREADY, + S_BLASTERDOWN, + S_BLASTERUP, + S_BLASTERATK1_1, + S_BLASTERATK1_2, + S_BLASTERATK1_3, + S_BLASTERATK1_4, + S_BLASTERATK1_5, + S_BLASTERATK1_6, + S_BLASTERATK2_1, + S_BLASTERATK2_2, + S_BLASTERATK2_3, + S_BLASTERATK2_4, + S_BLASTERATK2_5, + S_BLASTERATK2_6, + S_BLASTERFX1_1, + S_BLASTERFXI1_1, + S_BLASTERFXI1_2, + S_BLASTERFXI1_3, + S_BLASTERFXI1_4, + S_BLASTERFXI1_5, + S_BLASTERFXI1_6, + S_BLASTERFXI1_7, + S_BLASTERSMOKE1, + S_BLASTERSMOKE2, + S_BLASTERSMOKE3, + S_BLASTERSMOKE4, + S_BLASTERSMOKE5, + S_RIPPER1, + S_RIPPER2, + S_RIPPERX1, + S_RIPPERX2, + S_RIPPERX3, + S_RIPPERX4, + S_RIPPERX5, + S_BLASTERPUFF1_1, + S_BLASTERPUFF1_2, + S_BLASTERPUFF1_3, + S_BLASTERPUFF1_4, + S_BLASTERPUFF1_5, + S_BLASTERPUFF2_1, + S_BLASTERPUFF2_2, + S_BLASTERPUFF2_3, + S_BLASTERPUFF2_4, + S_BLASTERPUFF2_5, + S_BLASTERPUFF2_6, + S_BLASTERPUFF2_7, + S_WMCE, + S_MACEREADY, + S_MACEDOWN, + S_MACEUP, + S_MACEATK1_1, + S_MACEATK1_2, + S_MACEATK1_3, + S_MACEATK1_4, + S_MACEATK1_5, + S_MACEATK1_6, + S_MACEATK1_7, + S_MACEATK1_8, + S_MACEATK1_9, + S_MACEATK1_10, + S_MACEATK2_1, + S_MACEATK2_2, + S_MACEATK2_3, + S_MACEATK2_4, + S_MACEFX1_1, + S_MACEFX1_2, + S_MACEFXI1_1, + S_MACEFXI1_2, + S_MACEFXI1_3, + S_MACEFXI1_4, + S_MACEFXI1_5, + S_MACEFX2_1, + S_MACEFX2_2, + S_MACEFXI2_1, + S_MACEFX3_1, + S_MACEFX3_2, + S_MACEFX4_1, + S_MACEFXI4_1, + S_WSKL, + S_HORNRODREADY, + S_HORNRODDOWN, + S_HORNRODUP, + S_HORNRODATK1_1, + S_HORNRODATK1_2, + S_HORNRODATK1_3, + S_HORNRODATK2_1, + S_HORNRODATK2_2, + S_HORNRODATK2_3, + S_HORNRODATK2_4, + S_HORNRODATK2_5, + S_HORNRODATK2_6, + S_HORNRODATK2_7, + S_HORNRODATK2_8, + S_HORNRODATK2_9, + S_HRODFX1_1, + S_HRODFX1_2, + S_HRODFXI1_1, + S_HRODFXI1_2, + S_HRODFXI1_3, + S_HRODFXI1_4, + S_HRODFXI1_5, + S_HRODFXI1_6, + S_HRODFX2_1, + S_HRODFX2_2, + S_HRODFX2_3, + S_HRODFX2_4, + S_HRODFXI2_1, + S_HRODFXI2_2, + S_HRODFXI2_3, + S_HRODFXI2_4, + S_HRODFXI2_5, + S_HRODFXI2_6, + S_HRODFXI2_7, + S_HRODFXI2_8, + S_RAINPLR1_1, + S_RAINPLR2_1, + S_RAINPLR3_1, + S_RAINPLR4_1, + S_RAINPLR1X_1, + S_RAINPLR1X_2, + S_RAINPLR1X_3, + S_RAINPLR1X_4, + S_RAINPLR1X_5, + S_RAINPLR2X_1, + S_RAINPLR2X_2, + S_RAINPLR2X_3, + S_RAINPLR2X_4, + S_RAINPLR2X_5, + S_RAINPLR3X_1, + S_RAINPLR3X_2, + S_RAINPLR3X_3, + S_RAINPLR3X_4, + S_RAINPLR3X_5, + S_RAINPLR4X_1, + S_RAINPLR4X_2, + S_RAINPLR4X_3, + S_RAINPLR4X_4, + S_RAINPLR4X_5, + S_RAINAIRXPLR1_1, + S_RAINAIRXPLR2_1, + S_RAINAIRXPLR3_1, + S_RAINAIRXPLR4_1, + S_RAINAIRXPLR1_2, + S_RAINAIRXPLR2_2, + S_RAINAIRXPLR3_2, + S_RAINAIRXPLR4_2, + S_RAINAIRXPLR1_3, + S_RAINAIRXPLR2_3, + S_RAINAIRXPLR3_3, + S_RAINAIRXPLR4_3, + S_GOLDWANDREADY, + S_GOLDWANDDOWN, + S_GOLDWANDUP, + S_GOLDWANDATK1_1, + S_GOLDWANDATK1_2, + S_GOLDWANDATK1_3, + S_GOLDWANDATK1_4, + S_GOLDWANDATK2_1, + S_GOLDWANDATK2_2, + S_GOLDWANDATK2_3, + S_GOLDWANDATK2_4, + S_GWANDFX1_1, + S_GWANDFX1_2, + S_GWANDFXI1_1, + S_GWANDFXI1_2, + S_GWANDFXI1_3, + S_GWANDFXI1_4, + S_GWANDFX2_1, + S_GWANDFX2_2, + S_GWANDPUFF1_1, + S_GWANDPUFF1_2, + S_GWANDPUFF1_3, + S_GWANDPUFF1_4, + S_GWANDPUFF1_5, + S_WPHX, + S_PHOENIXREADY, + S_PHOENIXDOWN, + S_PHOENIXUP, + S_PHOENIXATK1_1, + S_PHOENIXATK1_2, + S_PHOENIXATK1_3, + S_PHOENIXATK1_4, + S_PHOENIXATK1_5, + S_PHOENIXATK2_1, + S_PHOENIXATK2_2, + S_PHOENIXATK2_3, + S_PHOENIXATK2_4, + S_PHOENIXFX1_1, + S_PHOENIXFXI1_1, + S_PHOENIXFXI1_2, + S_PHOENIXFXI1_3, + S_PHOENIXFXI1_4, + S_PHOENIXFXI1_5, + S_PHOENIXFXI1_6, + S_PHOENIXFXI1_7, + S_PHOENIXFXI1_8, + S_PHOENIXFXIX_1, // [ States in Heretic 1.0 that were removed + S_PHOENIXFXIX_2, + S_PHOENIXFXIX_3, // ] + S_PHOENIXPUFF1, + S_PHOENIXPUFF2, + S_PHOENIXPUFF3, + S_PHOENIXPUFF4, + S_PHOENIXPUFF5, + S_PHOENIXFX2_1, + S_PHOENIXFX2_2, + S_PHOENIXFX2_3, + S_PHOENIXFX2_4, + S_PHOENIXFX2_5, + S_PHOENIXFX2_6, + S_PHOENIXFX2_7, + S_PHOENIXFX2_8, + S_PHOENIXFX2_9, + S_PHOENIXFX2_10, + S_PHOENIXFXI2_1, + S_PHOENIXFXI2_2, + S_PHOENIXFXI2_3, + S_PHOENIXFXI2_4, + S_PHOENIXFXI2_5, + S_WBOW, + S_CRBOW1, + S_CRBOW2, + S_CRBOW3, + S_CRBOW4, + S_CRBOW5, + S_CRBOW6, + S_CRBOW7, + S_CRBOW8, + S_CRBOW9, + S_CRBOW10, + S_CRBOW11, + S_CRBOW12, + S_CRBOW13, + S_CRBOW14, + S_CRBOW15, + S_CRBOW16, + S_CRBOW17, + S_CRBOW18, + S_CRBOWDOWN, + S_CRBOWUP, + S_CRBOWATK1_1, + S_CRBOWATK1_2, + S_CRBOWATK1_3, + S_CRBOWATK1_4, + S_CRBOWATK1_5, + S_CRBOWATK1_6, + S_CRBOWATK1_7, + S_CRBOWATK1_8, + S_CRBOWATK2_1, + S_CRBOWATK2_2, + S_CRBOWATK2_3, + S_CRBOWATK2_4, + S_CRBOWATK2_5, + S_CRBOWATK2_6, + S_CRBOWATK2_7, + S_CRBOWATK2_8, + S_CRBOWFX1, + S_CRBOWFXI1_1, + S_CRBOWFXI1_2, + S_CRBOWFXI1_3, + S_CRBOWFX2, + S_CRBOWFX3, + S_CRBOWFXI3_1, + S_CRBOWFXI3_2, + S_CRBOWFXI3_3, + S_CRBOWFX4_1, + S_CRBOWFX4_2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_BLOODSPLATTER1, + S_BLOODSPLATTER2, + S_BLOODSPLATTER3, + S_BLOODSPLATTERX, + 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_DIE8, + S_PLAY_DIE9, + 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_PLAY_FDTH1, + S_PLAY_FDTH2, + S_PLAY_FDTH3, + S_PLAY_FDTH4, + S_PLAY_FDTH5, + S_PLAY_FDTH6, + S_PLAY_FDTH7, + S_PLAY_FDTH8, + S_PLAY_FDTH9, + S_PLAY_FDTH10, + S_PLAY_FDTH11, + S_PLAY_FDTH12, + S_PLAY_FDTH13, + S_PLAY_FDTH14, + S_PLAY_FDTH15, + S_PLAY_FDTH16, + S_PLAY_FDTH17, + S_PLAY_FDTH18, + S_PLAY_FDTH19, // < These two frames were not present in the Heretic + S_PLAY_FDTH20, // < 1.0 executable (fire death animation was extended) + S_BLOODYSKULL1, + S_BLOODYSKULL2, + S_BLOODYSKULL3, + S_BLOODYSKULL4, + S_BLOODYSKULL5, + S_BLOODYSKULLX1, + S_BLOODYSKULLX2, + S_CHICPLAY, + S_CHICPLAY_RUN1, + S_CHICPLAY_RUN2, + S_CHICPLAY_RUN3, + S_CHICPLAY_RUN4, + S_CHICPLAY_ATK1, + S_CHICPLAY_PAIN, + S_CHICPLAY_PAIN2, + S_CHICKEN_LOOK1, + S_CHICKEN_LOOK2, + S_CHICKEN_WALK1, + S_CHICKEN_WALK2, + S_CHICKEN_PAIN1, + S_CHICKEN_PAIN2, + S_CHICKEN_ATK1, + S_CHICKEN_ATK2, + S_CHICKEN_DIE1, + S_CHICKEN_DIE2, + S_CHICKEN_DIE3, + S_CHICKEN_DIE4, + S_CHICKEN_DIE5, + S_CHICKEN_DIE6, + S_CHICKEN_DIE7, + S_CHICKEN_DIE8, + S_FEATHER1, + S_FEATHER2, + S_FEATHER3, + S_FEATHER4, + S_FEATHER5, + S_FEATHER6, + S_FEATHER7, + S_FEATHER8, + S_FEATHERX, + S_MUMMY_LOOK1, + S_MUMMY_LOOK2, + S_MUMMY_WALK1, + S_MUMMY_WALK2, + S_MUMMY_WALK3, + S_MUMMY_WALK4, + S_MUMMY_ATK1, + S_MUMMY_ATK2, + S_MUMMY_ATK3, + S_MUMMYL_ATK1, + S_MUMMYL_ATK2, + S_MUMMYL_ATK3, + S_MUMMYL_ATK4, + S_MUMMYL_ATK5, + S_MUMMYL_ATK6, + S_MUMMY_PAIN1, + S_MUMMY_PAIN2, + S_MUMMY_DIE1, + S_MUMMY_DIE2, + S_MUMMY_DIE3, + S_MUMMY_DIE4, + S_MUMMY_DIE5, + S_MUMMY_DIE6, + S_MUMMY_DIE7, + S_MUMMY_DIE8, + S_MUMMY_SOUL1, + S_MUMMY_SOUL2, + S_MUMMY_SOUL3, + S_MUMMY_SOUL4, + S_MUMMY_SOUL5, + S_MUMMY_SOUL6, + S_MUMMY_SOUL7, + S_MUMMYFX1_1, + S_MUMMYFX1_2, + S_MUMMYFX1_3, + S_MUMMYFX1_4, + S_MUMMYFXI1_1, + S_MUMMYFXI1_2, + S_MUMMYFXI1_3, + S_MUMMYFXI1_4, + S_BEAST_LOOK1, + S_BEAST_LOOK2, + S_BEAST_WALK1, + S_BEAST_WALK2, + S_BEAST_WALK3, + S_BEAST_WALK4, + S_BEAST_WALK5, + S_BEAST_WALK6, + S_BEAST_ATK1, + S_BEAST_ATK2, + S_BEAST_PAIN1, + S_BEAST_PAIN2, + S_BEAST_DIE1, + S_BEAST_DIE2, + S_BEAST_DIE3, + S_BEAST_DIE4, + S_BEAST_DIE5, + S_BEAST_DIE6, + S_BEAST_DIE7, + S_BEAST_DIE8, + S_BEAST_DIE9, + S_BEAST_XDIE1, + S_BEAST_XDIE2, + S_BEAST_XDIE3, + S_BEAST_XDIE4, + S_BEAST_XDIE5, + S_BEAST_XDIE6, + S_BEAST_XDIE7, + S_BEAST_XDIE8, + S_BEASTBALL1, + S_BEASTBALL2, + S_BEASTBALL3, + S_BEASTBALL4, + S_BEASTBALL5, + S_BEASTBALL6, + S_BEASTBALLX1, + S_BEASTBALLX2, + S_BEASTBALLX3, + S_BEASTBALLX4, + S_BEASTBALLX5, + S_BURNBALL1, + S_BURNBALL2, + S_BURNBALL3, + S_BURNBALL4, + S_BURNBALL5, + S_BURNBALL6, + S_BURNBALL7, + S_BURNBALL8, + S_BURNBALLFB1, + S_BURNBALLFB2, + S_BURNBALLFB3, + S_BURNBALLFB4, + S_BURNBALLFB5, + S_BURNBALLFB6, + S_BURNBALLFB7, + S_BURNBALLFB8, + S_PUFFY1, + S_PUFFY2, + S_PUFFY3, + S_PUFFY4, + S_PUFFY5, + S_SNAKE_LOOK1, + S_SNAKE_LOOK2, + S_SNAKE_WALK1, + S_SNAKE_WALK2, + S_SNAKE_WALK3, + S_SNAKE_WALK4, + S_SNAKE_ATK1, + S_SNAKE_ATK2, + S_SNAKE_ATK3, + S_SNAKE_ATK4, + S_SNAKE_ATK5, + S_SNAKE_ATK6, + S_SNAKE_ATK7, + S_SNAKE_ATK8, + S_SNAKE_ATK9, + S_SNAKE_PAIN1, + S_SNAKE_PAIN2, + S_SNAKE_DIE1, + S_SNAKE_DIE2, + S_SNAKE_DIE3, + S_SNAKE_DIE4, + S_SNAKE_DIE5, + S_SNAKE_DIE6, + S_SNAKE_DIE7, + S_SNAKE_DIE8, + S_SNAKE_DIE9, + S_SNAKE_DIE10, + S_SNAKEPRO_A1, + S_SNAKEPRO_A2, + S_SNAKEPRO_A3, + S_SNAKEPRO_A4, + S_SNAKEPRO_AX1, + S_SNAKEPRO_AX2, + S_SNAKEPRO_AX3, + S_SNAKEPRO_AX4, + S_SNAKEPRO_AX5, + S_SNAKEPRO_B1, + S_SNAKEPRO_B2, + S_SNAKEPRO_BX1, + S_SNAKEPRO_BX2, + S_SNAKEPRO_BX3, + S_SNAKEPRO_BX4, + S_HEAD_LOOK, + S_HEAD_FLOAT, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_PAIN1, + S_HEAD_PAIN2, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_DIE7, + S_HEADFX1_1, + S_HEADFX1_2, + S_HEADFX1_3, + S_HEADFXI1_1, + S_HEADFXI1_2, + S_HEADFXI1_3, + S_HEADFXI1_4, + S_HEADFX2_1, + S_HEADFX2_2, + S_HEADFX2_3, + S_HEADFXI2_1, + S_HEADFXI2_2, + S_HEADFXI2_3, + S_HEADFXI2_4, + S_HEADFX3_1, + S_HEADFX3_2, + S_HEADFX3_3, + S_HEADFX3_4, + S_HEADFX3_5, + S_HEADFX3_6, + S_HEADFXI3_1, + S_HEADFXI3_2, + S_HEADFXI3_3, + S_HEADFXI3_4, + S_HEADFX4_1, + S_HEADFX4_2, + S_HEADFX4_3, + S_HEADFX4_4, + S_HEADFX4_5, + S_HEADFX4_6, + S_HEADFX4_7, + S_HEADFXI4_1, + S_HEADFXI4_2, + S_HEADFXI4_3, + S_HEADFXI4_4, + S_CLINK_LOOK1, + S_CLINK_LOOK2, + S_CLINK_WALK1, + S_CLINK_WALK2, + S_CLINK_WALK3, + S_CLINK_WALK4, + S_CLINK_ATK1, + S_CLINK_ATK2, + S_CLINK_ATK3, + S_CLINK_PAIN1, + S_CLINK_PAIN2, + S_CLINK_DIE1, + S_CLINK_DIE2, + S_CLINK_DIE3, + S_CLINK_DIE4, + S_CLINK_DIE5, + S_CLINK_DIE6, + S_CLINK_DIE7, + S_WIZARD_LOOK1, + S_WIZARD_LOOK2, + S_WIZARD_WALK1, + S_WIZARD_WALK2, + S_WIZARD_WALK3, + S_WIZARD_WALK4, + S_WIZARD_WALK5, + S_WIZARD_WALK6, + S_WIZARD_WALK7, + S_WIZARD_WALK8, + S_WIZARD_ATK1, + S_WIZARD_ATK2, + S_WIZARD_ATK3, + S_WIZARD_ATK4, + S_WIZARD_ATK5, + S_WIZARD_ATK6, + S_WIZARD_ATK7, + S_WIZARD_ATK8, + S_WIZARD_ATK9, + S_WIZARD_PAIN1, + S_WIZARD_PAIN2, + S_WIZARD_DIE1, + S_WIZARD_DIE2, + S_WIZARD_DIE3, + S_WIZARD_DIE4, + S_WIZARD_DIE5, + S_WIZARD_DIE6, + S_WIZARD_DIE7, + S_WIZARD_DIE8, + S_WIZFX1_1, + S_WIZFX1_2, + S_WIZFXI1_1, + S_WIZFXI1_2, + S_WIZFXI1_3, + S_WIZFXI1_4, + S_WIZFXI1_5, + S_IMP_LOOK1, + S_IMP_LOOK2, + S_IMP_LOOK3, + S_IMP_LOOK4, + S_IMP_FLY1, + S_IMP_FLY2, + S_IMP_FLY3, + S_IMP_FLY4, + S_IMP_FLY5, + S_IMP_FLY6, + S_IMP_FLY7, + S_IMP_FLY8, + S_IMP_MEATK1, + S_IMP_MEATK2, + S_IMP_MEATK3, + S_IMP_MSATK1_1, + S_IMP_MSATK1_2, + S_IMP_MSATK1_3, + S_IMP_MSATK1_4, + S_IMP_MSATK1_5, + S_IMP_MSATK1_6, + S_IMP_MSATK2_1, + S_IMP_MSATK2_2, + S_IMP_MSATK2_3, + S_IMP_PAIN1, + S_IMP_PAIN2, + S_IMP_DIE1, + S_IMP_DIE2, + S_IMP_XDIE1, + S_IMP_XDIE2, + S_IMP_XDIE3, + S_IMP_XDIE4, + S_IMP_XDIE5, + S_IMP_CRASH1, + S_IMP_CRASH2, + S_IMP_CRASH3, + S_IMP_CRASH4, + S_IMP_XCRASH1, + S_IMP_XCRASH2, + S_IMP_XCRASH3, + S_IMP_CHUNKA1, + S_IMP_CHUNKA2, + S_IMP_CHUNKA3, + S_IMP_CHUNKB1, + S_IMP_CHUNKB2, + S_IMP_CHUNKB3, + S_IMPFX1, + S_IMPFX2, + S_IMPFX3, + S_IMPFXI1, + S_IMPFXI2, + S_IMPFXI3, + S_IMPFXI4, + S_KNIGHT_STND1, + S_KNIGHT_STND2, + S_KNIGHT_WALK1, + S_KNIGHT_WALK2, + S_KNIGHT_WALK3, + S_KNIGHT_WALK4, + S_KNIGHT_ATK1, + S_KNIGHT_ATK2, + S_KNIGHT_ATK3, + S_KNIGHT_ATK4, + S_KNIGHT_ATK5, + S_KNIGHT_ATK6, + S_KNIGHT_PAIN1, + S_KNIGHT_PAIN2, + S_KNIGHT_DIE1, + S_KNIGHT_DIE2, + S_KNIGHT_DIE3, + S_KNIGHT_DIE4, + S_KNIGHT_DIE5, + S_KNIGHT_DIE6, + S_KNIGHT_DIE7, + S_SPINAXE1, + S_SPINAXE2, + S_SPINAXE3, + S_SPINAXEX1, + S_SPINAXEX2, + S_SPINAXEX3, + S_REDAXE1, + S_REDAXE2, + S_REDAXEX1, + S_REDAXEX2, + S_REDAXEX3, + S_SRCR1_LOOK1, + S_SRCR1_LOOK2, + S_SRCR1_WALK1, + S_SRCR1_WALK2, + S_SRCR1_WALK3, + S_SRCR1_WALK4, + S_SRCR1_PAIN1, + S_SRCR1_ATK1, + S_SRCR1_ATK2, + S_SRCR1_ATK3, + S_SRCR1_ATK4, + S_SRCR1_ATK5, + S_SRCR1_ATK6, + S_SRCR1_ATK7, + S_SRCR1_DIE1, + S_SRCR1_DIE2, + S_SRCR1_DIE3, + S_SRCR1_DIE4, + S_SRCR1_DIE5, + S_SRCR1_DIE6, + S_SRCR1_DIE7, + S_SRCR1_DIE8, + S_SRCR1_DIE9, + S_SRCR1_DIE10, + S_SRCR1_DIE11, + S_SRCR1_DIE12, + S_SRCR1_DIE13, + S_SRCR1_DIE14, + S_SRCR1_DIE15, + S_SRCR1_DIE16, + S_SRCR1_DIE17, + S_SRCRFX1_1, + S_SRCRFX1_2, + S_SRCRFX1_3, + S_SRCRFXI1_1, + S_SRCRFXI1_2, + S_SRCRFXI1_3, + S_SRCRFXI1_4, + S_SRCRFXI1_5, + S_SOR2_RISE1, + S_SOR2_RISE2, + S_SOR2_RISE3, + S_SOR2_RISE4, + S_SOR2_RISE5, + S_SOR2_RISE6, + S_SOR2_RISE7, + S_SOR2_LOOK1, + S_SOR2_LOOK2, + S_SOR2_WALK1, + S_SOR2_WALK2, + S_SOR2_WALK3, + S_SOR2_WALK4, + S_SOR2_PAIN1, + S_SOR2_PAIN2, + S_SOR2_ATK1, + S_SOR2_ATK2, + S_SOR2_ATK3, + S_SOR2_TELE1, + S_SOR2_TELE2, + S_SOR2_TELE3, + S_SOR2_TELE4, + S_SOR2_TELE5, + S_SOR2_TELE6, + S_SOR2_DIE1, + S_SOR2_DIE2, + S_SOR2_DIE3, + S_SOR2_DIE4, + S_SOR2_DIE5, + S_SOR2_DIE6, + S_SOR2_DIE7, + S_SOR2_DIE8, + S_SOR2_DIE9, + S_SOR2_DIE10, + S_SOR2_DIE11, + S_SOR2_DIE12, + S_SOR2_DIE13, + S_SOR2_DIE14, + S_SOR2_DIE15, + S_SOR2FX1_1, + S_SOR2FX1_2, + S_SOR2FX1_3, + S_SOR2FXI1_1, + S_SOR2FXI1_2, + S_SOR2FXI1_3, + S_SOR2FXI1_4, + S_SOR2FXI1_5, + S_SOR2FXI1_6, + S_SOR2FXSPARK1, + S_SOR2FXSPARK2, + S_SOR2FXSPARK3, + S_SOR2FX2_1, + S_SOR2FX2_2, + S_SOR2FX2_3, + S_SOR2FXI2_1, + S_SOR2FXI2_2, + S_SOR2FXI2_3, + S_SOR2FXI2_4, + S_SOR2FXI2_5, + S_SOR2TELEFADE1, + S_SOR2TELEFADE2, + S_SOR2TELEFADE3, + S_SOR2TELEFADE4, + S_SOR2TELEFADE5, + S_SOR2TELEFADE6, + S_MNTR_LOOK1, + S_MNTR_LOOK2, + S_MNTR_WALK1, + S_MNTR_WALK2, + S_MNTR_WALK3, + S_MNTR_WALK4, + S_MNTR_ATK1_1, + S_MNTR_ATK1_2, + S_MNTR_ATK1_3, + S_MNTR_ATK2_1, + S_MNTR_ATK2_2, + S_MNTR_ATK2_3, + S_MNTR_ATK3_1, + S_MNTR_ATK3_2, + S_MNTR_ATK3_3, + S_MNTR_ATK3_4, + S_MNTR_ATK4_1, + S_MNTR_PAIN1, + S_MNTR_PAIN2, + S_MNTR_DIE1, + S_MNTR_DIE2, + S_MNTR_DIE3, + S_MNTR_DIE4, + S_MNTR_DIE5, + S_MNTR_DIE6, + S_MNTR_DIE7, + S_MNTR_DIE8, + S_MNTR_DIE9, + S_MNTR_DIE10, + S_MNTR_DIE11, + S_MNTR_DIE12, + S_MNTR_DIE13, + S_MNTR_DIE14, + S_MNTR_DIE15, + S_MNTRFX1_1, + S_MNTRFX1_2, + S_MNTRFXI1_1, + S_MNTRFXI1_2, + S_MNTRFXI1_3, + S_MNTRFXI1_4, + S_MNTRFXI1_5, + S_MNTRFXI1_6, + S_MNTRFX2_1, + S_MNTRFXI2_1, + S_MNTRFXI2_2, + S_MNTRFXI2_3, + S_MNTRFXI2_4, + S_MNTRFXI2_5, + S_MNTRFX3_1, + S_MNTRFX3_2, + S_MNTRFX3_3, + S_MNTRFX3_4, + S_MNTRFX3_5, + S_MNTRFX3_6, + S_MNTRFX3_7, + S_MNTRFX3_8, + S_MNTRFX3_9, + S_AKYY1, + S_AKYY2, + S_AKYY3, + S_AKYY4, + S_AKYY5, + S_AKYY6, + S_AKYY7, + S_AKYY8, + S_AKYY9, + S_AKYY10, + S_BKYY1, + S_BKYY2, + S_BKYY3, + S_BKYY4, + S_BKYY5, + S_BKYY6, + S_BKYY7, + S_BKYY8, + S_BKYY9, + S_BKYY10, + S_CKYY1, + S_CKYY2, + S_CKYY3, + S_CKYY4, + S_CKYY5, + S_CKYY6, + S_CKYY7, + S_CKYY8, + S_CKYY9, + S_AMG1, + S_AMG2_1, + S_AMG2_2, + S_AMG2_3, + S_AMM1, + S_AMM2, + S_AMC1, + S_AMC2_1, + S_AMC2_2, + S_AMC2_3, + S_AMS1_1, + S_AMS1_2, + S_AMS2_1, + S_AMS2_2, + S_AMP1_1, + S_AMP1_2, + S_AMP1_3, + S_AMP2_1, + S_AMP2_2, + S_AMP2_3, + S_AMB1_1, + S_AMB1_2, + S_AMB1_3, + S_AMB2_1, + S_AMB2_2, + S_AMB2_3, + S_SND_WIND, + S_SND_WATERFALL, + NUMSTATES +} statenum_t; + +typedef struct +{ + spritenum_t sprite; + int frame; + int tics; + void (*action) (); + statenum_t nextstate; + int misc1, misc2; +} state_t; + +extern state_t states[NUMSTATES]; +extern char *sprnames[]; + + + +typedef enum +{ + MT_MISC0, + MT_ITEMSHIELD1, + MT_ITEMSHIELD2, + MT_MISC1, + MT_MISC2, + MT_ARTIINVISIBILITY, + MT_MISC3, + MT_ARTIFLY, + MT_ARTIINVULNERABILITY, + MT_ARTITOMEOFPOWER, + MT_ARTIEGG, + MT_EGGFX, + MT_ARTISUPERHEAL, + MT_MISC4, + MT_MISC5, + MT_FIREBOMB, + MT_ARTITELEPORT, + MT_POD, + MT_PODGOO, + MT_PODGENERATOR, + MT_SPLASH, + MT_SPLASHBASE, + MT_LAVASPLASH, + MT_LAVASMOKE, + MT_SLUDGECHUNK, + MT_SLUDGESPLASH, + MT_SKULLHANG70, + MT_SKULLHANG60, + MT_SKULLHANG45, + MT_SKULLHANG35, + MT_CHANDELIER, + MT_SERPTORCH, + MT_SMALLPILLAR, + MT_STALAGMITESMALL, + MT_STALAGMITELARGE, + MT_STALACTITESMALL, + MT_STALACTITELARGE, + MT_MISC6, + MT_BARREL, + MT_MISC7, + MT_MISC8, + MT_MISC9, + MT_MISC10, + MT_MISC11, + MT_KEYGIZMOBLUE, + MT_KEYGIZMOGREEN, + MT_KEYGIZMOYELLOW, + MT_KEYGIZMOFLOAT, + MT_MISC12, + MT_VOLCANOBLAST, + MT_VOLCANOTBLAST, + MT_TELEGLITGEN, + MT_TELEGLITGEN2, + MT_TELEGLITTER, + MT_TELEGLITTER2, + MT_TFOG, + MT_TELEPORTMAN, + MT_STAFFPUFF, + MT_STAFFPUFF2, + MT_BEAKPUFF, + MT_MISC13, + MT_GAUNTLETPUFF1, + MT_GAUNTLETPUFF2, + MT_MISC14, + MT_BLASTERFX1, + MT_BLASTERSMOKE, + MT_RIPPER, + MT_BLASTERPUFF1, + MT_BLASTERPUFF2, + MT_WMACE, + MT_MACEFX1, + MT_MACEFX2, + MT_MACEFX3, + MT_MACEFX4, + MT_WSKULLROD, + MT_HORNRODFX1, + MT_HORNRODFX2, + MT_RAINPLR1, + MT_RAINPLR2, + MT_RAINPLR3, + MT_RAINPLR4, + MT_GOLDWANDFX1, + MT_GOLDWANDFX2, + MT_GOLDWANDPUFF1, + MT_GOLDWANDPUFF2, + MT_WPHOENIXROD, + MT_PHOENIXFX1, + MT_PHOENIXFX_REMOVED, // In Heretic 1.0, but removed. + MT_PHOENIXPUFF, + MT_PHOENIXFX2, + MT_MISC15, + MT_CRBOWFX1, + MT_CRBOWFX2, + MT_CRBOWFX3, + MT_CRBOWFX4, + MT_BLOOD, + MT_BLOODSPLATTER, + MT_PLAYER, + MT_BLOODYSKULL, + MT_CHICPLAYER, + MT_CHICKEN, + MT_FEATHER, + MT_MUMMY, + MT_MUMMYLEADER, + MT_MUMMYGHOST, + MT_MUMMYLEADERGHOST, + MT_MUMMYSOUL, + MT_MUMMYFX1, + MT_BEAST, + MT_BEASTBALL, + MT_BURNBALL, + MT_BURNBALLFB, + MT_PUFFY, + MT_SNAKE, + MT_SNAKEPRO_A, + MT_SNAKEPRO_B, + MT_HEAD, + MT_HEADFX1, + MT_HEADFX2, + MT_HEADFX3, + MT_WHIRLWIND, + MT_CLINK, + MT_WIZARD, + MT_WIZFX1, + MT_IMP, + MT_IMPLEADER, + MT_IMPCHUNK1, + MT_IMPCHUNK2, + MT_IMPBALL, + MT_KNIGHT, + MT_KNIGHTGHOST, + MT_KNIGHTAXE, + MT_REDAXE, + MT_SORCERER1, + MT_SRCRFX1, + MT_SORCERER2, + MT_SOR2FX1, + MT_SOR2FXSPARK, + MT_SOR2FX2, + MT_SOR2TELEFADE, + MT_MINOTAUR, + MT_MNTRFX1, + MT_MNTRFX2, + MT_MNTRFX3, + MT_AKYY, + MT_BKYY, + MT_CKEY, + MT_AMGWNDWIMPY, + MT_AMGWNDHEFTY, + MT_AMMACEWIMPY, + MT_AMMACEHEFTY, + MT_AMCBOWWIMPY, + MT_AMCBOWHEFTY, + MT_AMSKRDWIMPY, + MT_AMSKRDHEFTY, + MT_AMPHRDWIMPY, + MT_AMPHRDHEFTY, + MT_AMBLSRWIMPY, + MT_AMBLSRHEFTY, + MT_SOUNDWIND, + MT_SOUNDWATERFALL, + 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 crashstate; + int deathstate; + int xdeathstate; + int deathsound; + int speed; + int radius; + int height; + int mass; + int damage; + int activesound; + int flags; + int flags2; +} mobjinfo_t; + +extern mobjinfo_t mobjinfo[NUMMOBJTYPES]; + +#endif /* #ifndef HERETIC_INFO_H */ + diff --git a/src/heretic/m_random.c b/src/heretic/m_random.c new file mode 100644 index 00000000..383d906c --- /dev/null +++ b/src/heretic/m_random.c @@ -0,0 +1,78 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#include "m_random.h" + +/* +=============== += += M_Random += += Returns a 0-255 number += +=============== +*/ + +const unsigned int 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; + +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; +} + diff --git a/src/heretic/m_random.h b/src/heretic/m_random.h new file mode 100644 index 00000000..b1d2ca7d --- /dev/null +++ b/src/heretic/m_random.h @@ -0,0 +1,42 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#ifndef HERETIC_M_RANDOM_H +#define HERETIC_M_RANDOM_H + +// Most damage defined using HITDICE +#define HITDICE(a) ((1+(P_Random()&7))*a) + +int M_Random(void); +// returns a number from 0 to 255 +int P_Random(void); +// as M_Random, but used only by the play simulation + +void M_ClearRandom(void); +// fix randoms for demos + +extern int rndindex; + +#endif // HERETIC_M_RANDOM_H + diff --git a/src/heretic/mn_menu.c b/src/heretic/mn_menu.c new file mode 100644 index 00000000..955e0124 --- /dev/null +++ b/src/heretic/mn_menu.c @@ -0,0 +1,1652 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// MN_menu.c + +#include <ctype.h> + +#include "deh_str.h" +#include "doomdef.h" +#include "doomkeys.h" +#include "i_system.h" +#include "i_swap.h" +#include "m_controls.h" +#include "p_local.h" +#include "r_local.h" +#include "s_sound.h" +#include "v_video.h" + +// Macros + +#define LEFT_DIR 0 +#define RIGHT_DIR 1 +#define ITEM_HEIGHT 20 +#define SELECTOR_XOFFSET (-28) +#define SELECTOR_YOFFSET (-1) +#define SLOTTEXTLEN 16 +#define ASCII_CURSOR '[' + +// Types + +typedef enum +{ + ITT_EMPTY, + ITT_EFUNC, + ITT_LRFUNC, + ITT_SETMENU, + ITT_INERT +} ItemType_t; + +typedef enum +{ + MENU_MAIN, + MENU_EPISODE, + MENU_SKILL, + MENU_OPTIONS, + MENU_OPTIONS2, + MENU_FILES, + MENU_LOAD, + MENU_SAVE, + MENU_NONE +} MenuType_t; + +typedef struct +{ + ItemType_t type; + char *text; + boolean(*func) (int option); + int option; + MenuType_t menu; +} MenuItem_t; + +typedef struct +{ + int x; + int y; + void (*drawFunc) (void); + int itemCount; + MenuItem_t *items; + int oldItPos; + MenuType_t prevMenu; +} Menu_t; + +// Private Functions + +static void InitFonts(void); +static void SetMenu(MenuType_t menu); +static boolean SCNetCheck(int option); +static boolean SCQuitGame(int option); +static boolean SCEpisode(int option); +static boolean SCSkill(int option); +static boolean SCMouseSensi(int option); +static boolean SCSfxVolume(int option); +static boolean SCMusicVolume(int option); +static boolean SCScreenSize(int option); +static boolean SCLoadGame(int option); +static boolean SCSaveGame(int option); +static boolean SCMessages(int option); +static boolean SCEndGame(int option); +static boolean SCInfo(int option); +static void DrawMainMenu(void); +static void DrawEpisodeMenu(void); +static void DrawSkillMenu(void); +static void DrawOptionsMenu(void); +static void DrawOptions2Menu(void); +static void DrawFileSlots(Menu_t * menu); +static void DrawFilesMenu(void); +static void MN_DrawInfo(void); +static void DrawLoadMenu(void); +static void DrawSaveMenu(void); +static void DrawSlider(Menu_t * menu, int item, int width, int slot); +void MN_LoadSlotText(void); + +// External Data + +extern int usegamma; +extern int detailLevel; +extern int screenblocks; + +// Public Data + +boolean MenuActive; +int InfoType; +boolean messageson; + +// Private Data + +static int FontABaseLump; +static int FontBBaseLump; +static int SkullBaseLump; +static Menu_t *CurrentMenu; +static int CurrentItPos; +static int MenuEpisode; +static int MenuTime; +static boolean soundchanged; + +boolean askforquit; +boolean typeofask; +static boolean FileMenuKeySteal; +static boolean slottextloaded; +static char SlotText[6][SLOTTEXTLEN + 2]; +static char oldSlotText[SLOTTEXTLEN + 2]; +static int SlotStatus[6]; +static int slotptr; +static int currentSlot; +static int quicksave; +static int quickload; + +static MenuItem_t MainItems[] = { + {ITT_EFUNC, "NEW GAME", SCNetCheck, 1, MENU_EPISODE}, + {ITT_SETMENU, "OPTIONS", NULL, 0, MENU_OPTIONS}, + {ITT_SETMENU, "GAME FILES", NULL, 0, MENU_FILES}, + {ITT_EFUNC, "INFO", SCInfo, 0, MENU_NONE}, + {ITT_EFUNC, "QUIT GAME", SCQuitGame, 0, MENU_NONE} +}; + +static Menu_t MainMenu = { + 110, 56, + DrawMainMenu, + 5, MainItems, + 0, + MENU_NONE +}; + +static MenuItem_t EpisodeItems[] = { + {ITT_EFUNC, "CITY OF THE DAMNED", SCEpisode, 1, MENU_NONE}, + {ITT_EFUNC, "HELL'S MAW", SCEpisode, 2, MENU_NONE}, + {ITT_EFUNC, "THE DOME OF D'SPARIL", SCEpisode, 3, MENU_NONE}, + {ITT_EFUNC, "THE OSSUARY", SCEpisode, 4, MENU_NONE}, + {ITT_EFUNC, "THE STAGNANT DEMESNE", SCEpisode, 5, MENU_NONE} +}; + +static Menu_t EpisodeMenu = { + 80, 50, + DrawEpisodeMenu, + 3, EpisodeItems, + 0, + MENU_MAIN +}; + +static MenuItem_t FilesItems[] = { + {ITT_EFUNC, "LOAD GAME", SCNetCheck, 2, MENU_LOAD}, + {ITT_SETMENU, "SAVE GAME", NULL, 0, MENU_SAVE} +}; + +static Menu_t FilesMenu = { + 110, 60, + DrawFilesMenu, + 2, FilesItems, + 0, + MENU_MAIN +}; + +static MenuItem_t LoadItems[] = { + {ITT_EFUNC, NULL, SCLoadGame, 0, MENU_NONE}, + {ITT_EFUNC, NULL, SCLoadGame, 1, MENU_NONE}, + {ITT_EFUNC, NULL, SCLoadGame, 2, MENU_NONE}, + {ITT_EFUNC, NULL, SCLoadGame, 3, MENU_NONE}, + {ITT_EFUNC, NULL, SCLoadGame, 4, MENU_NONE}, + {ITT_EFUNC, NULL, SCLoadGame, 5, MENU_NONE} +}; + +static Menu_t LoadMenu = { + 70, 30, + DrawLoadMenu, + 6, LoadItems, + 0, + MENU_FILES +}; + +static MenuItem_t SaveItems[] = { + {ITT_EFUNC, NULL, SCSaveGame, 0, MENU_NONE}, + {ITT_EFUNC, NULL, SCSaveGame, 1, MENU_NONE}, + {ITT_EFUNC, NULL, SCSaveGame, 2, MENU_NONE}, + {ITT_EFUNC, NULL, SCSaveGame, 3, MENU_NONE}, + {ITT_EFUNC, NULL, SCSaveGame, 4, MENU_NONE}, + {ITT_EFUNC, NULL, SCSaveGame, 5, MENU_NONE} +}; + +static Menu_t SaveMenu = { + 70, 30, + DrawSaveMenu, + 6, SaveItems, + 0, + MENU_FILES +}; + +static MenuItem_t SkillItems[] = { + {ITT_EFUNC, "THOU NEEDETH A WET-NURSE", SCSkill, sk_baby, MENU_NONE}, + {ITT_EFUNC, "YELLOWBELLIES-R-US", SCSkill, sk_easy, MENU_NONE}, + {ITT_EFUNC, "BRINGEST THEM ONETH", SCSkill, sk_medium, MENU_NONE}, + {ITT_EFUNC, "THOU ART A SMITE-MEISTER", SCSkill, sk_hard, MENU_NONE}, + {ITT_EFUNC, "BLACK PLAGUE POSSESSES THEE", + SCSkill, sk_nightmare, MENU_NONE} +}; + +static Menu_t SkillMenu = { + 38, 30, + DrawSkillMenu, + 5, SkillItems, + 2, + MENU_EPISODE +}; + +static MenuItem_t OptionsItems[] = { + {ITT_EFUNC, "END GAME", SCEndGame, 0, MENU_NONE}, + {ITT_EFUNC, "MESSAGES : ", SCMessages, 0, MENU_NONE}, + {ITT_LRFUNC, "MOUSE SENSITIVITY", SCMouseSensi, 0, MENU_NONE}, + {ITT_EMPTY, NULL, NULL, 0, MENU_NONE}, + {ITT_SETMENU, "MORE...", NULL, 0, MENU_OPTIONS2} +}; + +static Menu_t OptionsMenu = { + 88, 30, + DrawOptionsMenu, + 5, OptionsItems, + 0, + MENU_MAIN +}; + +static MenuItem_t Options2Items[] = { + {ITT_LRFUNC, "SCREEN SIZE", SCScreenSize, 0, MENU_NONE}, + {ITT_EMPTY, NULL, NULL, 0, MENU_NONE}, + {ITT_LRFUNC, "SFX VOLUME", SCSfxVolume, 0, MENU_NONE}, + {ITT_EMPTY, NULL, NULL, 0, MENU_NONE}, + {ITT_LRFUNC, "MUSIC VOLUME", SCMusicVolume, 0, MENU_NONE}, + {ITT_EMPTY, NULL, NULL, 0, MENU_NONE} +}; + +static Menu_t Options2Menu = { + 90, 20, + DrawOptions2Menu, + 6, Options2Items, + 0, + MENU_OPTIONS +}; + +static Menu_t *Menus[] = { + &MainMenu, + &EpisodeMenu, + &SkillMenu, + &OptionsMenu, + &Options2Menu, + &FilesMenu, + &LoadMenu, + &SaveMenu +}; + +//--------------------------------------------------------------------------- +// +// PROC MN_Init +// +//--------------------------------------------------------------------------- + +void MN_Init(void) +{ + InitFonts(); + MenuActive = false; + messageson = true; + SkullBaseLump = W_GetNumForName(DEH_String("M_SKL00")); + + if (gamemode == retail) + { // Add episodes 4 and 5 to the menu + EpisodeMenu.itemCount = 5; + EpisodeMenu.y -= ITEM_HEIGHT; + } +} + +//--------------------------------------------------------------------------- +// +// PROC InitFonts +// +//--------------------------------------------------------------------------- + +static void InitFonts(void) +{ + FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1; + FontBBaseLump = W_GetNumForName(DEH_String("FONTB_S")) + 1; +} + +//--------------------------------------------------------------------------- +// +// PROC MN_DrTextA +// +// Draw text using font A. +// +//--------------------------------------------------------------------------- + +void MN_DrTextA(char *text, int x, int y) +{ + char c; + patch_t *p; + + while ((c = *text++) != 0) + { + if (c < 33) + { + x += 5; + } + else + { + p = W_CacheLumpNum(FontABaseLump + c - 33, PU_CACHE); + V_DrawPatch(x, y, p); + x += SHORT(p->width) - 1; + } + } +} + +//--------------------------------------------------------------------------- +// +// FUNC MN_TextAWidth +// +// Returns the pixel width of a string using font A. +// +//--------------------------------------------------------------------------- + +int MN_TextAWidth(char *text) +{ + char c; + int width; + patch_t *p; + + width = 0; + while ((c = *text++) != 0) + { + if (c < 33) + { + width += 5; + } + else + { + p = W_CacheLumpNum(FontABaseLump + c - 33, PU_CACHE); + width += SHORT(p->width) - 1; + } + } + return (width); +} + +//--------------------------------------------------------------------------- +// +// PROC MN_DrTextB +// +// Draw text using font B. +// +//--------------------------------------------------------------------------- + +void MN_DrTextB(char *text, int x, int y) +{ + char c; + patch_t *p; + + while ((c = *text++) != 0) + { + if (c < 33) + { + x += 8; + } + else + { + p = W_CacheLumpNum(FontBBaseLump + c - 33, PU_CACHE); + V_DrawPatch(x, y, p); + x += SHORT(p->width) - 1; + } + } +} + +//--------------------------------------------------------------------------- +// +// FUNC MN_TextBWidth +// +// Returns the pixel width of a string using font B. +// +//--------------------------------------------------------------------------- + +int MN_TextBWidth(char *text) +{ + char c; + int width; + patch_t *p; + + width = 0; + while ((c = *text++) != 0) + { + if (c < 33) + { + width += 5; + } + else + { + p = W_CacheLumpNum(FontBBaseLump + c - 33, PU_CACHE); + width += SHORT(p->width) - 1; + } + } + return (width); +} + +//--------------------------------------------------------------------------- +// +// PROC MN_Ticker +// +//--------------------------------------------------------------------------- + +void MN_Ticker(void) +{ + if (MenuActive == false) + { + return; + } + MenuTime++; +} + +//--------------------------------------------------------------------------- +// +// PROC MN_Drawer +// +//--------------------------------------------------------------------------- + +char *QuitEndMsg[] = { + "ARE YOU SURE YOU WANT TO QUIT?", + "ARE YOU SURE YOU WANT TO END THE GAME?", + "DO YOU WANT TO QUICKSAVE THE GAME NAMED", + "DO YOU WANT TO QUICKLOAD THE GAME NAMED" +}; + +void MN_Drawer(void) +{ + int i; + int x; + int y; + MenuItem_t *item; + char *message; + char *selName; + + if (MenuActive == false) + { + if (askforquit) + { + message = DEH_String(QuitEndMsg[typeofask - 1]); + + MN_DrTextA(message, 160 - MN_TextAWidth(message) / 2, 80); + if (typeofask == 3) + { + MN_DrTextA(SlotText[quicksave - 1], 160 - + MN_TextAWidth(SlotText[quicksave - 1]) / 2, 90); + MN_DrTextA(DEH_String("?"), 160 + + MN_TextAWidth(SlotText[quicksave - 1]) / 2, 90); + } + if (typeofask == 4) + { + MN_DrTextA(SlotText[quickload - 1], 160 - + MN_TextAWidth(SlotText[quickload - 1]) / 2, 90); + MN_DrTextA(DEH_String("?"), 160 + + MN_TextAWidth(SlotText[quickload - 1]) / 2, 90); + } + UpdateState |= I_FULLSCRN; + } + return; + } + else + { + UpdateState |= I_FULLSCRN; + if (InfoType) + { + MN_DrawInfo(); + return; + } + if (screenblocks < 10) + { + BorderNeedRefresh = true; + } + if (CurrentMenu->drawFunc != NULL) + { + CurrentMenu->drawFunc(); + } + x = CurrentMenu->x; + y = CurrentMenu->y; + item = CurrentMenu->items; + for (i = 0; i < CurrentMenu->itemCount; i++) + { + if (item->type != ITT_EMPTY && item->text) + { + MN_DrTextB(DEH_String(item->text), x, y); + } + y += ITEM_HEIGHT; + item++; + } + y = CurrentMenu->y + (CurrentItPos * ITEM_HEIGHT) + SELECTOR_YOFFSET; + selName = DEH_String(MenuTime & 16 ? "M_SLCTR1" : "M_SLCTR2"); + V_DrawPatch(x + SELECTOR_XOFFSET, y, + W_CacheLumpName(selName, PU_CACHE)); + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawMainMenu +// +//--------------------------------------------------------------------------- + +static void DrawMainMenu(void) +{ + int frame; + + frame = (MenuTime / 3) % 18; + V_DrawPatch(88, 0, W_CacheLumpName(DEH_String("M_HTIC"), PU_CACHE)); + V_DrawPatch(40, 10, W_CacheLumpNum(SkullBaseLump + (17 - frame), + PU_CACHE)); + V_DrawPatch(232, 10, W_CacheLumpNum(SkullBaseLump + frame, PU_CACHE)); +} + +//--------------------------------------------------------------------------- +// +// PROC DrawEpisodeMenu +// +//--------------------------------------------------------------------------- + +static void DrawEpisodeMenu(void) +{ +} + +//--------------------------------------------------------------------------- +// +// PROC DrawSkillMenu +// +//--------------------------------------------------------------------------- + +static void DrawSkillMenu(void) +{ +} + +//--------------------------------------------------------------------------- +// +// PROC DrawFilesMenu +// +//--------------------------------------------------------------------------- + +static void DrawFilesMenu(void) +{ +// clear out the quicksave/quickload stuff + quicksave = 0; + quickload = 0; + players[consoleplayer].message = NULL; + players[consoleplayer].messageTics = 1; +} + +//--------------------------------------------------------------------------- +// +// PROC DrawLoadMenu +// +//--------------------------------------------------------------------------- + +static void DrawLoadMenu(void) +{ + char *title; + + title = DEH_String("LOAD GAME"); + + MN_DrTextB(title, 160 - MN_TextBWidth(title) / 2, 10); + if (!slottextloaded) + { + MN_LoadSlotText(); + } + DrawFileSlots(&LoadMenu); +} + +//--------------------------------------------------------------------------- +// +// PROC DrawSaveMenu +// +//--------------------------------------------------------------------------- + +static void DrawSaveMenu(void) +{ + char *title; + + title = DEH_String("SAVE GAME"); + + MN_DrTextB(title, 160 - MN_TextBWidth(title) / 2, 10); + if (!slottextloaded) + { + MN_LoadSlotText(); + } + DrawFileSlots(&SaveMenu); +} + +//=========================================================================== +// +// MN_LoadSlotText +// +// Loads in the text message for each slot +//=========================================================================== + +void MN_LoadSlotText(void) +{ + FILE *fp; + int count; + int i; + char name[256]; + + for (i = 0; i < 6; i++) + { + if (cdrom) + { + sprintf(name, SAVEGAMENAMECD "%d.hsg", i); + } + else + { + sprintf(name, SAVEGAMENAME "%d.hsg", i); + } + fp = fopen(name, "rb+"); + if (!fp) + { + SlotText[i][0] = 0; // empty the string + SlotStatus[i] = 0; + continue; + } + count = fread(&SlotText[i], SLOTTEXTLEN, 1, fp); + fclose(fp); + SlotStatus[i] = 1; + } + slottextloaded = true; +} + +//--------------------------------------------------------------------------- +// +// PROC DrawFileSlots +// +//--------------------------------------------------------------------------- + +static void DrawFileSlots(Menu_t * menu) +{ + int i; + int x; + int y; + + x = menu->x; + y = menu->y; + for (i = 0; i < 6; i++) + { + V_DrawPatch(x, y, W_CacheLumpName(DEH_String("M_FSLOT"), PU_CACHE)); + if (SlotStatus[i]) + { + MN_DrTextA(SlotText[i], x + 5, y + 5); + } + y += ITEM_HEIGHT; + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawOptionsMenu +// +//--------------------------------------------------------------------------- + +static void DrawOptionsMenu(void) +{ + if (messageson) + { + MN_DrTextB(DEH_String("ON"), 196, 50); + } + else + { + MN_DrTextB(DEH_String("OFF"), 196, 50); + } + DrawSlider(&OptionsMenu, 3, 10, mouseSensitivity); +} + +//--------------------------------------------------------------------------- +// +// PROC DrawOptions2Menu +// +//--------------------------------------------------------------------------- + +static void DrawOptions2Menu(void) +{ + DrawSlider(&Options2Menu, 1, 9, screenblocks - 3); + DrawSlider(&Options2Menu, 3, 16, snd_MaxVolume); + DrawSlider(&Options2Menu, 5, 16, snd_MusicVolume); +} + +//--------------------------------------------------------------------------- +// +// PROC SCNetCheck +// +//--------------------------------------------------------------------------- + +static boolean SCNetCheck(int option) +{ + if (!netgame) + { // okay to go into the menu + return true; + } + switch (option) + { + case 1: + P_SetMessage(&players[consoleplayer], + "YOU CAN'T START A NEW GAME IN NETPLAY!", true); + break; + case 2: + P_SetMessage(&players[consoleplayer], + "YOU CAN'T LOAD A GAME IN NETPLAY!", true); + break; + default: + break; + } + MenuActive = false; + return false; +} + +//--------------------------------------------------------------------------- +// +// PROC SCQuitGame +// +//--------------------------------------------------------------------------- + +static boolean SCQuitGame(int option) +{ + MenuActive = false; + askforquit = true; + typeofask = 1; //quit game + if (!netgame && !demoplayback) + { + paused = true; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCEndGame +// +//--------------------------------------------------------------------------- + +static boolean SCEndGame(int option) +{ + if (demoplayback || netgame) + { + return false; + } + MenuActive = false; + askforquit = true; + typeofask = 2; //endgame + if (!netgame && !demoplayback) + { + paused = true; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCMessages +// +//--------------------------------------------------------------------------- + +static boolean SCMessages(int option) +{ + messageson ^= 1; + if (messageson) + { + P_SetMessage(&players[consoleplayer], DEH_String("MESSAGES ON"), true); + } + else + { + P_SetMessage(&players[consoleplayer], DEH_String("MESSAGES OFF"), true); + } + S_StartSound(NULL, sfx_chat); + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCLoadGame +// +//--------------------------------------------------------------------------- + +static boolean SCLoadGame(int option) +{ + char name[256]; + + if (!SlotStatus[option]) + { // slot's empty...don't try and load + return false; + } + if (cdrom) + { + sprintf(name, SAVEGAMENAMECD "%d.hsg", option); + } + else + { + sprintf(name, SAVEGAMENAME "%d.hsg", option); + } + G_LoadGame(name); + MN_DeactivateMenu(); + BorderNeedRefresh = true; + if (quickload == -1) + { + quickload = option + 1; + players[consoleplayer].message = NULL; + players[consoleplayer].messageTics = 1; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCSaveGame +// +//--------------------------------------------------------------------------- + +static boolean SCSaveGame(int option) +{ + char *ptr; + + if (!FileMenuKeySteal) + { + FileMenuKeySteal = true; + strcpy(oldSlotText, SlotText[option]); + ptr = SlotText[option]; + while (*ptr) + { + ptr++; + } + *ptr = '['; + *(ptr + 1) = 0; + SlotStatus[option]++; + currentSlot = option; + slotptr = ptr - SlotText[option]; + return false; + } + else + { + G_SaveGame(option, SlotText[option]); + FileMenuKeySteal = false; + MN_DeactivateMenu(); + } + BorderNeedRefresh = true; + if (quicksave == -1) + { + quicksave = option + 1; + players[consoleplayer].message = NULL; + players[consoleplayer].messageTics = 1; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCEpisode +// +//--------------------------------------------------------------------------- + +static boolean SCEpisode(int option) +{ + if (gamemode == shareware && option > 1) + { + P_SetMessage(&players[consoleplayer], + "ONLY AVAILABLE IN THE REGISTERED VERSION", true); + } + else + { + MenuEpisode = option; + SetMenu(MENU_SKILL); + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCSkill +// +//--------------------------------------------------------------------------- + +static boolean SCSkill(int option) +{ + G_DeferedInitNew(option, MenuEpisode, 1); + MN_DeactivateMenu(); + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCMouseSensi +// +//--------------------------------------------------------------------------- + +static boolean SCMouseSensi(int option) +{ + if (option == RIGHT_DIR) + { + if (mouseSensitivity < 9) + { + mouseSensitivity++; + } + } + else if (mouseSensitivity) + { + mouseSensitivity--; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCSfxVolume +// +//--------------------------------------------------------------------------- + +static boolean SCSfxVolume(int option) +{ + if (option == RIGHT_DIR) + { + if (snd_MaxVolume < 15) + { + snd_MaxVolume++; + } + } + else if (snd_MaxVolume) + { + snd_MaxVolume--; + } + S_SetMaxVolume(false); // don't recalc the sound curve, yet + soundchanged = true; // we'll set it when we leave the menu + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCMusicVolume +// +//--------------------------------------------------------------------------- + +static boolean SCMusicVolume(int option) +{ + if (option == RIGHT_DIR) + { + if (snd_MusicVolume < 15) + { + snd_MusicVolume++; + } + } + else if (snd_MusicVolume) + { + snd_MusicVolume--; + } + S_SetMusicVolume(); + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCScreenSize +// +//--------------------------------------------------------------------------- + +static boolean SCScreenSize(int option) +{ + if (option == RIGHT_DIR) + { + if (screenblocks < 11) + { + screenblocks++; + } + } + else if (screenblocks > 3) + { + screenblocks--; + } + R_SetViewSize(screenblocks, detailLevel); + return true; +} + +//--------------------------------------------------------------------------- +// +// PROC SCInfo +// +//--------------------------------------------------------------------------- + +static boolean SCInfo(int option) +{ + InfoType = 1; + S_StartSound(NULL, sfx_dorcls); + if (!netgame && !demoplayback) + { + paused = true; + } + return true; +} + +//--------------------------------------------------------------------------- +// +// FUNC MN_Responder +// +//--------------------------------------------------------------------------- + +boolean MN_Responder(event_t * event) +{ + int charTyped; + int key; + int i; + MenuItem_t *item; + extern boolean automapactive; + static boolean shiftdown; + extern void D_StartTitle(void); + extern void G_CheckDemoStatus(void); + char *textBuffer; + + // "close" button pressed on window? + if (event->type == ev_quit) + { + SCQuitGame(0); + S_StartSound(NULL, sfx_chat); + return true; + } + + if (event->data1 == KEY_RSHIFT) + { + shiftdown = (event->type == ev_keydown); + } + if (event->type != ev_keydown) + { + return (false); + } + key = event->data1; + charTyped = event->data2; + + if (InfoType) + { + if (gamemode == shareware) + { + InfoType = (InfoType + 1) % 5; + } + else + { + InfoType = (InfoType + 1) % 4; + } + if (key == KEY_ESCAPE) + { + InfoType = 0; + } + if (!InfoType) + { + paused = false; + MN_DeactivateMenu(); + SB_state = -1; //refresh the statbar + BorderNeedRefresh = true; + } + S_StartSound(NULL, sfx_dorcls); + return (true); //make the info screen eat the keypress + } + + if (ravpic && key == KEY_F1) + { + G_ScreenShot(); + return (true); + } + + if (askforquit) + { + if (key == key_menu_confirm) + { + switch (typeofask) + { + case 1: + G_CheckDemoStatus(); + I_Quit(); + return false; + + case 2: + players[consoleplayer].messageTics = 0; + //set the msg to be cleared + players[consoleplayer].message = NULL; + paused = false; + I_SetPalette(W_CacheLumpName + ("PLAYPAL", PU_CACHE)); + D_StartTitle(); // go to intro/demo mode. + break; + + case 3: + P_SetMessage(&players[consoleplayer], + "QUICKSAVING....", false); + FileMenuKeySteal = true; + SCSaveGame(quicksave - 1); + BorderNeedRefresh = true; + break; + + case 4: + P_SetMessage(&players[consoleplayer], + "QUICKLOADING....", false); + SCLoadGame(quickload - 1); + BorderNeedRefresh = true; + break; + + default: + break; + } + + askforquit = false; + typeofask = 0; + + return true; + } + else if (key == key_menu_abort || key == KEY_ESCAPE) + { + players[consoleplayer].messageTics = 1; //set the msg to be cleared + askforquit = false; + typeofask = 0; + paused = false; + UpdateState |= I_FULLSCRN; + BorderNeedRefresh = true; + return true; + } + + return false; // don't let the keys filter thru + } + + if (!MenuActive && !chatmodeon) + { + if (key == key_menu_decscreen) + { + if (automapactive) + { // Don't screen size in automap + return (false); + } + SCScreenSize(LEFT_DIR); + S_StartSound(NULL, sfx_keyup); + BorderNeedRefresh = true; + UpdateState |= I_FULLSCRN; + return (true); + } + else if (key == key_menu_incscreen) + { + if (automapactive) + { // Don't screen size in automap + return (false); + } + SCScreenSize(RIGHT_DIR); + S_StartSound(NULL, sfx_keyup); + BorderNeedRefresh = true; + UpdateState |= I_FULLSCRN; + return (true); + } + else if (key == key_menu_help) // F1 + { + SCInfo(0); // start up info screens + MenuActive = true; + return (true); + } + else if (key == key_menu_save) // F2 (save game) + { + if (gamestate == GS_LEVEL && !demoplayback) + { + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &SaveMenu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed + } + return true; + } + else if (key == key_menu_load) // F3 (load game) + { + if (SCNetCheck(2)) + { + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &LoadMenu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed + } + return true; + } + else if (key == key_menu_volume) // F4 (volume) + { + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &Options2Menu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed + return true; + } + else if (key == key_menu_detail) // F5 (detail) + { + // F5 isn't used in Heretic. (detail level) + return true; + } + else if (key == key_menu_qsave) // F6 (quicksave) + { + if (gamestate == GS_LEVEL && !demoplayback) + { + if (!quicksave || quicksave == -1) + { + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &SaveMenu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed + quicksave = -1; + P_SetMessage(&players[consoleplayer], + "CHOOSE A QUICKSAVE SLOT", true); + } + else + { + askforquit = true; + typeofask = 3; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_chat); + } + } + return true; + } + else if (key == key_menu_endgame) // F7 (end game) + { + if (gamestate == GS_LEVEL && !demoplayback) + { + S_StartSound(NULL, sfx_chat); + SCEndGame(0); + } + return true; + } + else if (key == key_menu_messages) // F8 (toggle messages) + { + SCMessages(0); + return true; + } + else if (key == key_menu_qload) // F9 (quickload) + { + if (!quickload || quickload == -1) + { + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &LoadMenu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed + quickload = -1; + P_SetMessage(&players[consoleplayer], + "CHOOSE A QUICKLOAD SLOT", true); + } + else + { + askforquit = true; + if (!netgame && !demoplayback) + { + paused = true; + } + typeofask = 4; + S_StartSound(NULL, sfx_chat); + } + return true; + } + else if (key == key_menu_quit) // F10 (quit) + { + if (gamestate == GS_LEVEL) + { + SCQuitGame(0); + S_StartSound(NULL, sfx_chat); + } + return true; + } + else if (key == key_menu_gamma) // F11 (gamma correction) + { + usegamma++; + if (usegamma > 4) + { + usegamma = 0; + } + I_SetPalette((byte *) W_CacheLumpName("PLAYPAL", PU_CACHE)); + return true; + } + + } + + if (!MenuActive) + { + if (key == key_menu_activate || gamestate == GS_DEMOSCREEN || demoplayback) + { + MN_ActivateMenu(); + return (true); + } + return (false); + } + if (!FileMenuKeySteal) + { + item = &CurrentMenu->items[CurrentItPos]; + + if (key == key_menu_down) // Next menu item + { + do + { + if (CurrentItPos + 1 > CurrentMenu->itemCount - 1) + { + CurrentItPos = 0; + } + else + { + CurrentItPos++; + } + } + while (CurrentMenu->items[CurrentItPos].type == ITT_EMPTY); + S_StartSound(NULL, sfx_switch); + return (true); + } + else if (key == key_menu_up) // Previous menu item + { + do + { + if (CurrentItPos == 0) + { + CurrentItPos = CurrentMenu->itemCount - 1; + } + else + { + CurrentItPos--; + } + } + while (CurrentMenu->items[CurrentItPos].type == ITT_EMPTY); + S_StartSound(NULL, sfx_switch); + return (true); + } + else if (key == key_menu_left) // Slider left + { + if (item->type == ITT_LRFUNC && item->func != NULL) + { + item->func(LEFT_DIR); + S_StartSound(NULL, sfx_keyup); + } + return (true); + } + else if (key == key_menu_right) // Slider right + { + if (item->type == ITT_LRFUNC && item->func != NULL) + { + item->func(RIGHT_DIR); + S_StartSound(NULL, sfx_keyup); + } + return (true); + } + else if (key == key_menu_forward) // Activate item (enter) + { + if (item->type == ITT_SETMENU) + { + SetMenu(item->menu); + } + else if (item->func != NULL) + { + CurrentMenu->oldItPos = CurrentItPos; + if (item->type == ITT_LRFUNC) + { + item->func(RIGHT_DIR); + } + else if (item->type == ITT_EFUNC) + { + if (item->func(item->option)) + { + if (item->menu != MENU_NONE) + { + SetMenu(item->menu); + } + } + } + } + S_StartSound(NULL, sfx_dorcls); + return (true); + } + else if (key == key_menu_activate) // Toggle menu + { + MN_DeactivateMenu(); + return (true); + } + else if (key == key_menu_back) // Go back to previous menu + { + S_StartSound(NULL, sfx_switch); + if (CurrentMenu->prevMenu == MENU_NONE) + { + MN_DeactivateMenu(); + } + else + { + SetMenu(CurrentMenu->prevMenu); + } + return (true); + } + else if (charTyped != 0) + { + // Jump to menu item based on first letter: + + for (i = 0; i < CurrentMenu->itemCount; i++) + { + if (CurrentMenu->items[i].text) + { + if (toupper(charTyped) + == toupper(DEH_String(CurrentMenu->items[i].text)[0])) + { + CurrentItPos = i; + return (true); + } + } + } + } + + return (false); + } + else + { // Editing file names + textBuffer = &SlotText[currentSlot][slotptr]; + if (key == KEY_BACKSPACE) + { + if (slotptr) + { + *textBuffer-- = 0; + *textBuffer = ASCII_CURSOR; + slotptr--; + } + return (true); + } + if (key == KEY_ESCAPE) + { + memset(SlotText[currentSlot], 0, SLOTTEXTLEN + 2); + strcpy(SlotText[currentSlot], oldSlotText); + SlotStatus[currentSlot]--; + MN_DeactivateMenu(); + return (true); + } + if (key == KEY_ENTER) + { + SlotText[currentSlot][slotptr] = 0; // clear the cursor + item = &CurrentMenu->items[CurrentItPos]; + CurrentMenu->oldItPos = CurrentItPos; + if (item->type == ITT_EFUNC) + { + item->func(item->option); + if (item->menu != MENU_NONE) + { + SetMenu(item->menu); + } + } + return (true); + } + if (slotptr < SLOTTEXTLEN && key != KEY_BACKSPACE) + { + if (isalpha(charTyped)) + { + *textBuffer++ = toupper(charTyped); + *textBuffer = ASCII_CURSOR; + slotptr++; + return (true); + } + if (isdigit(charTyped) || charTyped == ' ' + || charTyped == ',' || charTyped == '.' || charTyped == '-' + || charTyped == '!') + { + *textBuffer++ = charTyped; + *textBuffer = ASCII_CURSOR; + slotptr++; + return (true); + } + } + return (true); + } + return (false); +} + +//--------------------------------------------------------------------------- +// +// PROC MN_ActivateMenu +// +//--------------------------------------------------------------------------- + +void MN_ActivateMenu(void) +{ + if (MenuActive) + { + return; + } + if (paused) + { + S_ResumeSound(); + } + MenuActive = true; + FileMenuKeySteal = false; + MenuTime = 0; + CurrentMenu = &MainMenu; + CurrentItPos = CurrentMenu->oldItPos; + if (!netgame && !demoplayback) + { + paused = true; + } + S_StartSound(NULL, sfx_dorcls); + slottextloaded = false; //reload the slot text, when needed +} + +//--------------------------------------------------------------------------- +// +// PROC MN_DeactivateMenu +// +//--------------------------------------------------------------------------- + +void MN_DeactivateMenu(void) +{ + CurrentMenu->oldItPos = CurrentItPos; + MenuActive = false; + if (!netgame) + { + paused = false; + } + S_StartSound(NULL, sfx_dorcls); + if (soundchanged) + { + S_SetMaxVolume(true); //recalc the sound curve + soundchanged = false; + } + players[consoleplayer].message = NULL; + players[consoleplayer].messageTics = 1; +} + +//--------------------------------------------------------------------------- +// +// PROC MN_DrawInfo +// +//--------------------------------------------------------------------------- + +void MN_DrawInfo(void) +{ + I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE)); + V_DrawRawScreen(W_CacheLumpNum(W_GetNumForName("TITLE") + InfoType, + PU_CACHE)); +// V_DrawPatch(0, 0, W_CacheLumpNum(W_GetNumForName("TITLE")+InfoType, +// PU_CACHE)); +} + + +//--------------------------------------------------------------------------- +// +// PROC SetMenu +// +//--------------------------------------------------------------------------- + +static void SetMenu(MenuType_t menu) +{ + CurrentMenu->oldItPos = CurrentItPos; + CurrentMenu = Menus[menu]; + CurrentItPos = CurrentMenu->oldItPos; +} + +//--------------------------------------------------------------------------- +// +// PROC DrawSlider +// +//--------------------------------------------------------------------------- + +static void DrawSlider(Menu_t * menu, int item, int width, int slot) +{ + int x; + int y; + int x2; + int count; + + x = menu->x + 24; + y = menu->y + 2 + (item * ITEM_HEIGHT); + V_DrawPatch(x - 32, y, W_CacheLumpName(DEH_String("M_SLDLT"), PU_CACHE)); + for (x2 = x, count = width; count--; x2 += 8) + { + V_DrawPatch(x2, y, W_CacheLumpName(DEH_String(count & 1 ? "M_SLDMD1" + : "M_SLDMD2"), PU_CACHE)); + } + V_DrawPatch(x2, y, W_CacheLumpName(DEH_String("M_SLDRT"), PU_CACHE)); + V_DrawPatch(x + 4 + slot * 8, y + 7, + W_CacheLumpName(DEH_String("M_SLDKB"), PU_CACHE)); +} diff --git a/src/heretic/p_action.h b/src/heretic/p_action.h new file mode 100644 index 00000000..8d8e383c --- /dev/null +++ b/src/heretic/p_action.h @@ -0,0 +1,160 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// External definitions for action pointer functions. +// +//----------------------------------------------------------------------------- + +#ifndef HERETIC_P_ACTION_H +#define HERETIC_P_ACTION_H + +void A_FreeTargMobj(); +void A_RestoreSpecialThing1(); +void A_RestoreSpecialThing2(); +void A_HideThing(); +void A_UnHideThing(); +void A_RestoreArtifact(); +void A_Scream(); +void A_Explode(); +void A_PodPain(); +void A_RemovePod(); +void A_MakePod(); +void A_InitKeyGizmo(); +void A_VolcanoSet(); +void A_VolcanoBlast(); +void A_BeastPuff(); +void A_VolcBallImpact(); +void A_SpawnTeleGlitter(); +void A_SpawnTeleGlitter2(); +void A_AccTeleGlitter(); +void A_Light0(); +void A_WeaponReady(); +void A_Lower(); +void A_Raise(); +void A_StaffAttackPL1(); +void A_ReFire(); +void A_StaffAttackPL2(); +void A_BeakReady(); +void A_BeakRaise(); +void A_BeakAttackPL1(); +void A_BeakAttackPL2(); +void A_GauntletAttack(); +void A_FireBlasterPL1(); +void A_FireBlasterPL2(); +void A_SpawnRippers(); +void A_FireMacePL1(); +void A_FireMacePL2(); +void A_MacePL1Check(); +void A_MaceBallImpact(); +void A_MaceBallImpact2(); +void A_DeathBallImpact(); +void A_FireSkullRodPL1(); +void A_FireSkullRodPL2(); +void A_SkullRodPL2Seek(); +void A_AddPlayerRain(); +void A_HideInCeiling(); +void A_SkullRodStorm(); +void A_RainImpact(); +void A_FireGoldWandPL1(); +void A_FireGoldWandPL2(); +void A_FirePhoenixPL1(); +void A_InitPhoenixPL2(); +void A_FirePhoenixPL2(); +void A_ShutdownPhoenixPL2(); +void A_PhoenixPuff(); +void A_RemovedPhoenixFunc(); +void A_FlameEnd(); +void A_FloatPuff(); +void A_FireCrossbowPL1(); +void A_FireCrossbowPL2(); +void A_BoltSpark(); +void A_Pain(); +void A_NoBlocking(); +void A_AddPlayerCorpse(); +void A_SkullPop(); +void A_FlameSnd(); +void A_CheckBurnGone(); +void A_CheckSkullFloor(); +void A_CheckSkullDone(); +void A_Feathers(); +void A_ChicLook(); +void A_ChicChase(); +void A_ChicPain(); +void A_FaceTarget(); +void A_ChicAttack(); +void A_Look(); +void A_Chase(); +void A_MummyAttack(); +void A_MummyAttack2(); +void A_MummySoul(); +void A_ContMobjSound(); +void A_MummyFX1Seek(); +void A_BeastAttack(); +void A_SnakeAttack(); +void A_SnakeAttack2(); +void A_HeadAttack(); +void A_BossDeath(); +void A_HeadIceImpact(); +void A_HeadFireGrow(); +void A_WhirlwindSeek(); +void A_ClinkAttack(); +void A_WizAtk1(); +void A_WizAtk2(); +void A_WizAtk3(); +void A_GhostOff(); +void A_ImpMeAttack(); +void A_ImpMsAttack(); +void A_ImpMsAttack2(); +void A_ImpDeath(); +void A_ImpXDeath1(); +void A_ImpXDeath2(); +void A_ImpExplode(); +void A_KnightAttack(); +void A_DripBlood(); +void A_Sor1Chase(); +void A_Sor1Pain(); +void A_Srcr1Attack(); +void A_SorZap(); +void A_SorcererRise(); +void A_SorRise(); +void A_SorSightSnd(); +void A_Srcr2Decide(); +void A_Srcr2Attack(); +void A_Sor2DthInit(); +void A_SorDSph(); +void A_Sor2DthLoop(); +void A_SorDExp(); +void A_SorDBon(); +void A_BlueSpark(); +void A_GenWizard(); +void A_MinotaurAtk1(); +void A_MinotaurDecide(); +void A_MinotaurAtk2(); +void A_MinotaurAtk3(); +void A_MinotaurCharge(); +void A_MntrFloorFire(); +void A_ESound(); + +#endif /* #ifndef HERETIC_P_ACTION_H */ + diff --git a/src/heretic/p_ceilng.c b/src/heretic/p_ceilng.c new file mode 100644 index 00000000..bf7ef676 --- /dev/null +++ b/src/heretic/p_ceilng.c @@ -0,0 +1,263 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#include "doomdef.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +//================================================================== +//================================================================== +// +// CEILINGS +// +//================================================================== +//================================================================== + +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)) + S_StartSound(&ceiling->sector->soundorg, + sfx_dormov); + if (res == pastdest) + switch (ceiling->type) + { + case raiseToHighest: + P_RemoveActiveCeiling(ceiling); + break; + 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)) + S_StartSound(&ceiling->sector->soundorg, + sfx_dormov); + if (res == pastdest) + switch (ceiling->type) + { + case crushAndRaise: + ceiling->speed = CEILSPEED; + case fastCrushAndRaise: + ceiling->direction = 1; + break; + case lowerAndCrush: + case lowerToFloor: + P_RemoveActiveCeiling(ceiling); + break; + default: + break; + } + else if (res == crushed) + switch (ceiling->type) + { + 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, rtn; + sector_t *sec; + ceiling_t *ceiling; + + secnum = -1; + rtn = 0; + + // + // Reactivate in-stasis ceilings...for certain types. + // + switch (type) + { + case fastCrushAndRaise: + case crushAndRaise: + P_ActivateInStasisCeiling(line); + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) + { + sec = §ors[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 = 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 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] == NULL) + { + 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 = NULL; + P_RemoveThinker(&activeceilings[i]->thinker); + activeceilings[i] = NULL; + 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 = 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 = NULL; + activeceilings[i]->direction = 0; // in-stasis + rtn = 1; + } + + return rtn; +} diff --git a/src/heretic/p_doors.c b/src/heretic/p_doors.c new file mode 100644 index 00000000..56e37119 --- /dev/null +++ b/src/heretic/p_doors.c @@ -0,0 +1,389 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_doors.c + +#include "doomdef.h" +#include "deh_str.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +//================================================================== +//================================================================== +// +// VERTICAL DOORS +// +//================================================================== +//================================================================== + +//================================================================== +// +// T_VerticalDoor +// +//================================================================== +void T_VerticalDoor(vldoor_t * door) +{ + result_e res; + + switch (door->direction) + { + case 0: // WAITING + if (!--door->topcountdown) + switch (door->type) + { + case normal: + door->direction = -1; // time to go back down + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + case close30ThenOpen: + door->direction = 1; + S_StartSound(&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 = normal; + S_StartSound(&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 normal: + case close: + door->sector->specialdata = NULL; + P_RemoveThinker(&door->thinker); // unlink and free + S_StartSound(&door->sector->soundorg, sfx_dorcls); + break; + case close30ThenOpen: + door->direction = 0; + door->topcountdown = 35 * 30; + break; + default: + break; + } + } + else if (res == crushed) + { + switch (door->type) + { + case close: // DON'T GO BACK UP! + break; + default: + door->direction = 1; + S_StartSound(&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 normal: + door->direction = 0; // wait at top + door->topcountdown = door->topwait; + break; + case close30ThenOpen: + case open: + door->sector->specialdata = NULL; + P_RemoveThinker(&door->thinker); // unlink and free + S_StopSound(&door->sector->soundorg); + break; + default: + break; + } + } + break; + } +} + +//---------------------------------------------------------------------------- +// +// EV_DoDoor +// +// Move a door up/down +// +//---------------------------------------------------------------------------- + +int EV_DoDoor(line_t * line, vldoor_e type, fixed_t speed) +{ + int secnum; + int retcode; + sector_t *sec; + vldoor_t *door; + + secnum = -1; + retcode = 0; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) + { + sec = §ors[secnum]; + if (sec->specialdata) + { + continue; + } + // Add new door thinker + retcode = 1; + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker(&door->thinker); + sec->specialdata = door; + door->thinker.function = T_VerticalDoor; + door->sector = sec; + switch (type) + { + case close: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + case close30ThenOpen: + door->topheight = sec->ceilingheight; + door->direction = -1; + S_StartSound(&door->sector->soundorg, sfx_doropn); + break; + case normal: + case open: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4 * FRACUNIT; + if (door->topheight != sec->ceilingheight) + { + S_StartSound(&door->sector->soundorg, + sfx_doropn); + } + break; + default: + break; + } + door->type = type; + door->speed = speed; + door->topwait = VDOORWAIT; + } + return (retcode); +} + +//================================================================== +// +// 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->keys[key_blue]) + { + P_SetMessage(player, DEH_String(TXT_NEEDBLUEKEY), false); + S_StartSound(NULL, sfx_plroof); + return; + } + break; + case 27: // Yellow Lock + case 34: + if (!player) + { + return; + } + if (!player->keys[key_yellow]) + { + P_SetMessage(player, DEH_String(TXT_NEEDYELLOWKEY), false); + S_StartSound(NULL, sfx_plroof); + return; + } + break; + case 28: // Green Lock + case 33: + if (!player) + { + return; + } + if (!player->keys[key_green]) + { + P_SetMessage(player, DEH_String(TXT_NEEDGREENKEY), false); + S_StartSound(NULL, sfx_plroof); + return; + } + break; + } + + // if the sector has an active thinker, use it + sec = sides[line->sidenum[side ^ 1]].sector; + secnum = 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: + if (door->direction == -1) + { + door->direction = 1; // go back up + } + else + { + if (!thing->player) + { // Monsters don't close doors + return; + } + door->direction = -1; // start going down immediately + } + return; + } + } + + // for proper sound + switch (line->special) + { + case 1: // NORMAL DOOR SOUND + case 31: + S_StartSound(&sec->soundorg, sfx_doropn); + //S_StartSound(&sec->soundorg, sfx_dormov); + break; + default: // LOCKED DOOR SOUND + S_StartSound(&sec->soundorg, sfx_doropn); + //S_StartSound(&sec->soundorg, sfx_dormov); + break; + } + + // + // new door thinker + // + door = Z_Malloc(sizeof(*door), PU_LEVSPEC, 0); + P_AddThinker(&door->thinker); + sec->specialdata = door; + door->thinker.function = T_VerticalDoor; + door->sector = sec; + door->direction = 1; + switch (line->special) + { + case 1: + case 26: + case 27: + case 28: + door->type = normal; + break; + case 31: + case 32: + case 33: + case 34: + door->type = open; + line->special = 0; + break; + } + door->speed = VDOORSPEED; + door->topwait = VDOORWAIT; + + // + // 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 = T_VerticalDoor; + door->sector = sec; + door->direction = 0; + door->type = 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 = 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; +} diff --git a/src/heretic/p_enemy.c b/src/heretic/p_enemy.c new file mode 100644 index 00000000..6ad8cff0 --- /dev/null +++ b/src/heretic/p_enemy.c @@ -0,0 +1,2690 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_enemy.c + +#include <stdlib.h> +#include "doomdef.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +// Macros + +#define MAX_BOSS_SPOTS 8 + +// Types + +typedef struct +{ + fixed_t x; + fixed_t y; + angle_t angle; +} BossSpot_t; + +// Private Data + +static int BossSpotCount; +static BossSpot_t BossSpots[MAX_BOSS_SPOTS]; + +//---------------------------------------------------------------------------- +// +// PROC P_InitMonsters +// +// Called at level load. +// +//---------------------------------------------------------------------------- + +void P_InitMonsters(void) +{ + BossSpotCount = 0; +} + +//---------------------------------------------------------------------------- +// +// PROC P_AddBossSpot +// +//---------------------------------------------------------------------------- + +void P_AddBossSpot(fixed_t x, fixed_t y, angle_t angle) +{ + if (BossSpotCount == MAX_BOSS_SPOTS) + { + I_Error("Too many boss spots."); + } + BossSpots[BossSpotCount].x = x; + BossSpots[BossSpotCount].y = y; + BossSpots[BossSpotCount].angle = angle; + BossSpotCount++; +} + +//---------------------------------------------------------------------------- +// +// PROC P_RecursiveSound +// +//---------------------------------------------------------------------------- + +mobj_t *soundtarget; + +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) + { // Already flooded + return; + } + 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) + { // Closed door + continue; + } + 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); + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC 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); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_CheckMeleeRange +// +//---------------------------------------------------------------------------- + +boolean P_CheckMeleeRange(mobj_t * actor) +{ + mobj_t *mo; + fixed_t dist; + + if (!actor->target) + { + return (false); + } + mo = actor->target; + dist = P_AproxDistance(mo->x - actor->x, mo->y - actor->y); + if (dist >= MELEERANGE) + { + return (false); + } + if (!P_CheckSight(actor, mo)) + { + return (false); + } + if (mo->z > actor->z + actor->height) + { // Target is higher than the attacker + return (false); + } + else if (actor->z > mo->z + mo->height) + { // Attacker is higher + return (false); + } + return (true); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_CheckMissileRange +// +//---------------------------------------------------------------------------- + +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) + { // Don't attack yet + return (false); + } + dist = (P_AproxDistance(actor->x - actor->target->x, + actor->y - actor->target->y) >> FRACBITS) - 64; + if (!actor->info->meleestate) + { // No melee attack, so fire more frequently + dist -= 128; + } + if (actor->type == MT_IMP) + { // Imp's fly attack from far away + dist >>= 1; + } + if (dist > 200) + { + dist = 200; + } + if (P_Random() < dist) + { + return (false); + } + return (true); +} + +/* +================ += += P_Move += += Move in the current direction += returns false if the move is blocked +================ +*/ + +fixed_t xspeed[8] = + { FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000, 0, 47000 }; +fixed_t yspeed[8] = + { 0, 47000, FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000 }; + +#define MAXSPECIALCROSS 8 +extern line_t *spechit[MAXSPECIALCROSS]; +extern int numspechit; + +boolean P_Move(mobj_t * actor) +{ + fixed_t tryx, tryy; + line_t *ld; + boolean good; + + if (actor->movedir == DI_NODIR) + { + return (false); + } + tryx = actor->x + actor->info->speed * xspeed[actor->movedir]; + tryy = actor->y + actor->info->speed * yspeed[actor->movedir]; + if (!P_TryMove(actor, tryx, tryy)) + { // 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 isn't a door that can be opened, return false + if (P_UseSpecialLine(actor, ld)) + { + good = true; + } + } + return (good); + } + else + { + actor->flags &= ~MF_INFLOAT; + } + if (!(actor->flags & MF_FLOAT)) + { + if (actor->z > actor->floorz) + { + P_HitFloor(actor); + } + actor->z = actor->floorz; + } + return (true); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_TryWalk +// +// Attempts to move actor in its current (ob->moveangle) direction. +// If blocked by either a wall or an actor returns FALSE. +// If move is either clear of block only by a door, returns TRUE and sets. +// If a door is in the way, an OpenDoor call is made to start it opening. +// +//---------------------------------------------------------------------------- + +boolean P_TryWalk(mobj_t * actor) +{ + if (!P_Move(actor)) + { + return (false); + } + actor->movecount = P_Random() & 15; + return (true); +} + +/* +================ += += P_NewChaseDir += +================ +*/ + +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 }; + +void P_NewChaseDir(mobj_t * actor) +{ + fixed_t deltax, deltay; + dirtype_t d[3]; + dirtype_t tdir, olddir, turnaround; + + if (!actor->target) + I_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 || abs(deltay) > 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)) + return; /*either moved forward or attacked */ + } + + 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; + } + + if (P_Random() & 1) /*randomly determine direction of search */ + { + 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't move +} + +//--------------------------------------------------------------------------- +// +// FUNC P_LookForMonsters +// +//--------------------------------------------------------------------------- + +#define MONS_LOOK_RANGE (20*64*FRACUNIT) +#define MONS_LOOK_LIMIT 64 + +boolean P_LookForMonsters(mobj_t * actor) +{ + int count; + mobj_t *mo; + thinker_t *think; + + if (!P_CheckSight(players[0].mo, actor)) + { // Player can't see monster + return (false); + } + count = 0; + for (think = thinkercap.next; think != &thinkercap; think = think->next) + { + if (think->function != P_MobjThinker) + { // Not a mobj thinker + continue; + } + mo = (mobj_t *) think; + if (!(mo->flags & MF_COUNTKILL) || (mo == actor) || (mo->health <= 0)) + { // Not a valid monster + continue; + } + if (P_AproxDistance(actor->x - mo->x, actor->y - mo->y) + > MONS_LOOK_RANGE) + { // Out of range + continue; + } + if (P_Random() < 16) + { // Skip + continue; + } + if (count++ > MONS_LOOK_LIMIT) + { // Stop searching + return (false); + } + if (!P_CheckSight(actor, mo)) + { // Out of sight + continue; + } + // Found a target monster + actor->target = mo; + return (true); + } + return (false); +} + +/* +================ += += P_LookForPlayers += += If allaround is false, only look 180 degrees in front += returns true if a player is targeted +================ +*/ + +boolean P_LookForPlayers(mobj_t * actor, boolean allaround) +{ + int c; + int stop; + player_t *player; + sector_t *sector; + angle_t an; + fixed_t dist; + + if (!netgame && players[0].health <= 0) + { // Single player game and player is dead, look for monsters + return (P_LookForMonsters(actor)); + } + 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) + return false; // done looking + + 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 + } + } + if (player->mo->flags & MF_SHADOW) + { // Player is invisible + if ((P_AproxDistance(player->mo->x - actor->x, + player->mo->y - actor->y) > 2 * MELEERANGE) + && P_AproxDistance(player->mo->momx, player->mo->momy) + < 5 * FRACUNIT) + { // Player is sneaking - can't detect + return (false); + } + if (P_Random() < 225) + { // Player isn't sneaking, but still didn't detect + return (false); + } + } + actor->target = player->mo; + return (true); + } + return (false); +} + +/* +=============================================================================== + + 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; + } +*/ + sound = actor->info->seesound; + if (actor->flags2 & MF2_BOSS) + { // Full volume + S_StartSound(NULL, 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) + { + actor->threshold--; + } + + if (gameskill == sk_nightmare) + { // Monsters move faster in nightmare mode + actor->tics -= actor->tics / 2; + if (actor->tics < 3) + { + actor->tics = 3; + } + } + +// +// 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)) + { // got a new target + return; + } + P_SetMobjState(actor, actor->info->spawnstate); + return; + } + +// +// don't attack twice in a row +// + if (actor->flags & MF_JUSTATTACKED) + { + actor->flags &= ~MF_JUSTATTACKED; + if (gameskill != sk_nightmare) + 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 && 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) + { + if (actor->type == MT_WIZARD && P_Random() < 128) + { + S_StartSound(actor, actor->info->seesound); + } + else if (actor->type == MT_SORCERER2) + { + S_StartSound(NULL, actor->info->activesound); + } + else + { + S_StartSound(actor, actor->info->activesound); + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC 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) + { // Target is a ghost + actor->angle += (P_Random() - P_Random()) << 21; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_Pain +// +//---------------------------------------------------------------------------- + +void A_Pain(mobj_t * actor) +{ + if (actor->info->painsound) + { + S_StartSound(actor, actor->info->painsound); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_DripBlood +// +//---------------------------------------------------------------------------- + +void A_DripBlood(mobj_t * actor) +{ + mobj_t *mo; + + mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 11), + actor->y + ((P_Random() - P_Random()) << 11), actor->z, + MT_BLOOD); + mo->momx = (P_Random() - P_Random()) << 10; + mo->momy = (P_Random() - P_Random()) << 10; + mo->flags2 |= MF2_LOGRAV; +} + +//---------------------------------------------------------------------------- +// +// PROC A_KnightAttack +// +//---------------------------------------------------------------------------- + +void A_KnightAttack(mobj_t * actor) +{ + if (!actor->target) + { + return; + } + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(3)); + S_StartSound(actor, sfx_kgtat2); + return; + } + // Throw axe + S_StartSound(actor, actor->info->attacksound); + if (actor->type == MT_KNIGHTGHOST || P_Random() < 40) + { // Red axe + P_SpawnMissile(actor, actor->target, MT_REDAXE); + return; + } + // Green axe + P_SpawnMissile(actor, actor->target, MT_KNIGHTAXE); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpExplode +// +//---------------------------------------------------------------------------- + +void A_ImpExplode(mobj_t * actor) +{ + mobj_t *mo; + + mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK1); + mo->momx = (P_Random() - P_Random()) << 10; + mo->momy = (P_Random() - P_Random()) << 10; + mo->momz = 9 * FRACUNIT; + mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK2); + mo->momx = (P_Random() - P_Random()) << 10; + mo->momy = (P_Random() - P_Random()) << 10; + mo->momz = 9 * FRACUNIT; + if (actor->special1 == 666) + { // Extreme death crash + P_SetMobjState(actor, S_IMP_XCRASH1); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_BeastPuff +// +//---------------------------------------------------------------------------- + +void A_BeastPuff(mobj_t * actor) +{ + if (P_Random() > 64) + { + P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10), + actor->y + ((P_Random() - P_Random()) << 10), + actor->z + ((P_Random() - P_Random()) << 10), MT_PUFFY); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpMeAttack +// +//---------------------------------------------------------------------------- + +void A_ImpMeAttack(mobj_t * actor) +{ + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7)); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpMsAttack +// +//---------------------------------------------------------------------------- + +void A_ImpMsAttack(mobj_t * actor) +{ + mobj_t *dest; + angle_t an; + int dist; + + if (!actor->target || P_Random() > 64) + { + P_SetMobjState(actor, actor->info->seestate); + return; + } + dest = actor->target; + actor->flags |= MF_SKULLFLY; + S_StartSound(actor, actor->info->attacksound); + A_FaceTarget(actor); + an = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul(12 * FRACUNIT, finecosine[an]); + actor->momy = FixedMul(12 * FRACUNIT, finesine[an]); + dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); + dist = dist / (12 * FRACUNIT); + if (dist < 1) + { + dist = 1; + } + actor->momz = (dest->z + (dest->height >> 1) - actor->z) / dist; +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpMsAttack2 +// +// Fireball attack of the imp leader. +// +//---------------------------------------------------------------------------- + +void A_ImpMsAttack2(mobj_t * actor) +{ + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7)); + return; + } + P_SpawnMissile(actor, actor->target, MT_IMPBALL); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpDeath +// +//---------------------------------------------------------------------------- + +void A_ImpDeath(mobj_t * actor) +{ + actor->flags &= ~MF_SOLID; + actor->flags2 |= MF2_FOOTCLIP; + if (actor->z <= actor->floorz) + { + P_SetMobjState(actor, S_IMP_CRASH1); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpXDeath1 +// +//---------------------------------------------------------------------------- + +void A_ImpXDeath1(mobj_t * actor) +{ + actor->flags &= ~MF_SOLID; + actor->flags |= MF_NOGRAVITY; + actor->flags2 |= MF2_FOOTCLIP; + actor->special1 = 666; // Flag the crash routine +} + +//---------------------------------------------------------------------------- +// +// PROC A_ImpXDeath2 +// +//---------------------------------------------------------------------------- + +void A_ImpXDeath2(mobj_t * actor) +{ + actor->flags &= ~MF_NOGRAVITY; + if (actor->z <= actor->floorz) + { + P_SetMobjState(actor, S_IMP_CRASH1); + } +} + +//---------------------------------------------------------------------------- +// +// FUNC P_UpdateChicken +// +// Returns true if the chicken morphs. +// +//---------------------------------------------------------------------------- + +boolean P_UpdateChicken(mobj_t * actor, int tics) +{ + mobj_t *fog; + fixed_t x; + fixed_t y; + fixed_t z; + mobjtype_t moType; + mobj_t *mo; + mobj_t oldChicken; + + actor->special1 -= tics; + if (actor->special1 > 0) + { + return (false); + } + moType = actor->special2; + x = actor->x; + y = actor->y; + z = actor->z; + oldChicken = *actor; + P_SetMobjState(actor, S_FREETARGMOBJ); + mo = P_SpawnMobj(x, y, z, moType); + if (P_TestMobjLocation(mo) == false) + { // Didn't fit + P_RemoveMobj(mo); + mo = P_SpawnMobj(x, y, z, MT_CHICKEN); + mo->angle = oldChicken.angle; + mo->flags = oldChicken.flags; + mo->health = oldChicken.health; + mo->target = oldChicken.target; + mo->special1 = 5 * 35; // Next try in 5 seconds + mo->special2 = moType; + return (false); + } + mo->angle = oldChicken.angle; + mo->target = oldChicken.target; + fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG); + S_StartSound(fog, sfx_telept); + return (true); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ChicAttack +// +//---------------------------------------------------------------------------- + +void A_ChicAttack(mobj_t * actor) +{ + if (P_UpdateChicken(actor, 18)) + { + return; + } + if (!actor->target) + { + return; + } + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, 1 + (P_Random() & 1)); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_ChicLook +// +//---------------------------------------------------------------------------- + +void A_ChicLook(mobj_t * actor) +{ + if (P_UpdateChicken(actor, 10)) + { + return; + } + A_Look(actor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ChicChase +// +//---------------------------------------------------------------------------- + +void A_ChicChase(mobj_t * actor) +{ + if (P_UpdateChicken(actor, 3)) + { + return; + } + A_Chase(actor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ChicPain +// +//---------------------------------------------------------------------------- + +void A_ChicPain(mobj_t * actor) +{ + if (P_UpdateChicken(actor, 10)) + { + return; + } + S_StartSound(actor, actor->info->painsound); +} + +//---------------------------------------------------------------------------- +// +// PROC A_Feathers +// +//---------------------------------------------------------------------------- + +void A_Feathers(mobj_t * actor) +{ + int i; + int count; + mobj_t *mo; + + if (actor->health > 0) + { // Pain + count = P_Random() < 32 ? 2 : 1; + } + else + { // Death + count = 5 + (P_Random() & 3); + } + for (i = 0; i < count; i++) + { + mo = P_SpawnMobj(actor->x, actor->y, actor->z + 20 * FRACUNIT, + MT_FEATHER); + mo->target = actor; + mo->momx = (P_Random() - P_Random()) << 8; + mo->momy = (P_Random() - P_Random()) << 8; + mo->momz = FRACUNIT + (P_Random() << 9); + P_SetMobjState(mo, S_FEATHER1 + (P_Random() & 7)); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MummyAttack +// +//---------------------------------------------------------------------------- + +void A_MummyAttack(mobj_t * actor) +{ + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(2)); + S_StartSound(actor, sfx_mumat2); + return; + } + S_StartSound(actor, sfx_mumat1); +} + +//---------------------------------------------------------------------------- +// +// PROC A_MummyAttack2 +// +// Mummy leader missile attack. +// +//---------------------------------------------------------------------------- + +void A_MummyAttack2(mobj_t * actor) +{ + mobj_t *mo; + + if (!actor->target) + { + return; + } + //S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(2)); + return; + } + mo = P_SpawnMissile(actor, actor->target, MT_MUMMYFX1); + //mo = P_SpawnMissile(actor, actor->target, MT_EGGFX); + if (mo != NULL) + { + mo->special1 = (int) actor->target; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MummyFX1Seek +// +//---------------------------------------------------------------------------- + +void A_MummyFX1Seek(mobj_t * actor) +{ + P_SeekerMissile(actor, ANG1_X * 10, ANG1_X * 20); +} + +//---------------------------------------------------------------------------- +// +// PROC A_MummySoul +// +//---------------------------------------------------------------------------- + +void A_MummySoul(mobj_t * mummy) +{ + mobj_t *mo; + + mo = P_SpawnMobj(mummy->x, mummy->y, mummy->z + 10 * FRACUNIT, + MT_MUMMYSOUL); + mo->momz = FRACUNIT; +} + +//---------------------------------------------------------------------------- +// +// PROC A_Sor1Pain +// +//---------------------------------------------------------------------------- + +void A_Sor1Pain(mobj_t * actor) +{ + actor->special1 = 20; // Number of steps to walk fast + A_Pain(actor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_Sor1Chase +// +//---------------------------------------------------------------------------- + +void A_Sor1Chase(mobj_t * actor) +{ + if (actor->special1) + { + actor->special1--; + actor->tics -= 3; + } + A_Chase(actor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_Srcr1Attack +// +// Sorcerer demon attack. +// +//---------------------------------------------------------------------------- + +void A_Srcr1Attack(mobj_t * actor) +{ + mobj_t *mo; + fixed_t momz; + angle_t angle; + + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(8)); + return; + } + if (actor->health > (actor->info->spawnhealth / 3) * 2) + { // Spit one fireball + P_SpawnMissile(actor, actor->target, MT_SRCRFX1); + } + else + { // Spit three fireballs + mo = P_SpawnMissile(actor, actor->target, MT_SRCRFX1); + if (mo) + { + momz = mo->momz; + angle = mo->angle; + P_SpawnMissileAngle(actor, MT_SRCRFX1, angle - ANG1_X * 3, momz); + P_SpawnMissileAngle(actor, MT_SRCRFX1, angle + ANG1_X * 3, momz); + } + if (actor->health < actor->info->spawnhealth / 3) + { // Maybe attack again + if (actor->special1) + { // Just attacked, so don't attack again + actor->special1 = 0; + } + else + { // Set state to attack again + actor->special1 = 1; + P_SetMobjState(actor, S_SRCR1_ATK4); + } + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_SorcererRise +// +//---------------------------------------------------------------------------- + +void A_SorcererRise(mobj_t * actor) +{ + mobj_t *mo; + + actor->flags &= ~MF_SOLID; + mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCERER2); + P_SetMobjState(mo, S_SOR2_RISE1); + mo->angle = actor->angle; + mo->target = actor->target; +} + +//---------------------------------------------------------------------------- +// +// PROC P_DSparilTeleport +// +//---------------------------------------------------------------------------- + +void P_DSparilTeleport(mobj_t * actor) +{ + int i; + fixed_t x; + fixed_t y; + fixed_t prevX; + fixed_t prevY; + fixed_t prevZ; + mobj_t *mo; + + if (!BossSpotCount) + { // No spots + return; + } + i = P_Random(); + do + { + i++; + x = BossSpots[i % BossSpotCount].x; + y = BossSpots[i % BossSpotCount].y; + } + while (P_AproxDistance(actor->x - x, actor->y - y) < 128 * FRACUNIT); + prevX = actor->x; + prevY = actor->y; + prevZ = actor->z; + if (P_TeleportMove(actor, x, y)) + { + mo = P_SpawnMobj(prevX, prevY, prevZ, MT_SOR2TELEFADE); + S_StartSound(mo, sfx_telept); + P_SetMobjState(actor, S_SOR2_TELE1); + S_StartSound(actor, sfx_telept); + actor->z = actor->floorz; + actor->angle = BossSpots[i % BossSpotCount].angle; + actor->momx = actor->momy = actor->momz = 0; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_Srcr2Decide +// +//---------------------------------------------------------------------------- + +void A_Srcr2Decide(mobj_t * actor) +{ + static int chance[] = { + 192, 120, 120, 120, 64, 64, 32, 16, 0 + }; + + if (!BossSpotCount) + { // No spots + return; + } + if (P_Random() < chance[actor->health / (actor->info->spawnhealth / 8)]) + { + P_DSparilTeleport(actor); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_Srcr2Attack +// +//---------------------------------------------------------------------------- + +void A_Srcr2Attack(mobj_t * actor) +{ + int chance; + + if (!actor->target) + { + return; + } + S_StartSound(NULL, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(20)); + return; + } + chance = actor->health < actor->info->spawnhealth / 2 ? 96 : 48; + if (P_Random() < chance) + { // Wizard spawners + P_SpawnMissileAngle(actor, MT_SOR2FX2, + actor->angle - ANG45, FRACUNIT / 2); + P_SpawnMissileAngle(actor, MT_SOR2FX2, + actor->angle + ANG45, FRACUNIT / 2); + } + else + { // Blue bolt + P_SpawnMissile(actor, actor->target, MT_SOR2FX1); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_BlueSpark +// +//---------------------------------------------------------------------------- + +void A_BlueSpark(mobj_t * actor) +{ + int i; + mobj_t *mo; + + for (i = 0; i < 2; i++) + { + mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SOR2FXSPARK); + mo->momx = (P_Random() - P_Random()) << 9; + mo->momy = (P_Random() - P_Random()) << 9; + mo->momz = FRACUNIT + (P_Random() << 8); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_GenWizard +// +//---------------------------------------------------------------------------- + +void A_GenWizard(mobj_t * actor) +{ + mobj_t *mo; + mobj_t *fog; + + mo = P_SpawnMobj(actor->x, actor->y, + actor->z - mobjinfo[MT_WIZARD].height / 2, MT_WIZARD); + if (P_TestMobjLocation(mo) == false) + { // Didn't fit + P_RemoveMobj(mo); + return; + } + actor->momx = actor->momy = actor->momz = 0; + P_SetMobjState(actor, mobjinfo[actor->type].deathstate); + actor->flags &= ~MF_MISSILE; + fog = P_SpawnMobj(actor->x, actor->y, actor->z, MT_TFOG); + S_StartSound(fog, sfx_telept); +} + +//---------------------------------------------------------------------------- +// +// PROC A_Sor2DthInit +// +//---------------------------------------------------------------------------- + +void A_Sor2DthInit(mobj_t * actor) +{ + actor->special1 = 7; // Animation loop counter + P_Massacre(); // Kill monsters early +} + +//---------------------------------------------------------------------------- +// +// PROC A_Sor2DthLoop +// +//---------------------------------------------------------------------------- + +void A_Sor2DthLoop(mobj_t * actor) +{ + if (--actor->special1) + { // Need to loop + P_SetMobjState(actor, S_SOR2_DIE4); + } +} + +//---------------------------------------------------------------------------- +// +// D'Sparil Sound Routines +// +//---------------------------------------------------------------------------- + +void A_SorZap(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sorzap); +} + +void A_SorRise(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sorrise); +} + +void A_SorDSph(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sordsph); +} + +void A_SorDExp(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sordexp); +} + +void A_SorDBon(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sordbon); +} + +void A_SorSightSnd(mobj_t * actor) +{ + S_StartSound(NULL, sfx_sorsit); +} + +//---------------------------------------------------------------------------- +// +// PROC A_MinotaurAtk1 +// +// Melee attack. +// +//---------------------------------------------------------------------------- + +void A_MinotaurAtk1(mobj_t * actor) +{ + player_t *player; + + if (!actor->target) + { + return; + } + S_StartSound(actor, sfx_stfpow); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(4)); + if ((player = actor->target->player) != NULL) + { // Squish the player + player->deltaviewheight = -16 * FRACUNIT; + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MinotaurDecide +// +// Choose a missile attack. +// +//---------------------------------------------------------------------------- + +#define MNTR_CHARGE_SPEED (13*FRACUNIT) + +void A_MinotaurDecide(mobj_t * actor) +{ + angle_t angle; + mobj_t *target; + int dist; + + target = actor->target; + if (!target) + { + return; + } + S_StartSound(actor, sfx_minsit); + dist = P_AproxDistance(actor->x - target->x, actor->y - target->y); + if (target->z + target->height > actor->z + && target->z + target->height < actor->z + actor->height + && dist < 8 * 64 * FRACUNIT + && dist > 1 * 64 * FRACUNIT && P_Random() < 150) + { // Charge attack + // Don't call the state function right away + P_SetMobjStateNF(actor, S_MNTR_ATK4_1); + actor->flags |= MF_SKULLFLY; + A_FaceTarget(actor); + angle = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine[angle]); + actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine[angle]); + actor->special1 = 35 / 2; // Charge duration + } + else if (target->z == target->floorz + && dist < 9 * 64 * FRACUNIT && P_Random() < 220) + { // Floor fire attack + P_SetMobjState(actor, S_MNTR_ATK3_1); + actor->special2 = 0; + } + else + { // Swing attack + A_FaceTarget(actor); + // Don't need to call P_SetMobjState because the current state + // falls through to the swing attack + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MinotaurCharge +// +//---------------------------------------------------------------------------- + +void A_MinotaurCharge(mobj_t * actor) +{ + mobj_t *puff; + + if (actor->special1) + { + puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF); + puff->momz = 2 * FRACUNIT; + actor->special1--; + } + else + { + actor->flags &= ~MF_SKULLFLY; + P_SetMobjState(actor, actor->info->seestate); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MinotaurAtk2 +// +// Swing attack. +// +//---------------------------------------------------------------------------- + +void A_MinotaurAtk2(mobj_t * actor) +{ + mobj_t *mo; + angle_t angle; + fixed_t momz; + + if (!actor->target) + { + return; + } + S_StartSound(actor, sfx_minat2); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(5)); + return; + } + mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX1); + if (mo) + { + S_StartSound(mo, sfx_minat2); + momz = mo->momz; + angle = mo->angle; + P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 / 8), momz); + P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 / 8), momz); + P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 / 16), momz); + P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 / 16), momz); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MinotaurAtk3 +// +// Floor fire attack. +// +//---------------------------------------------------------------------------- + +void A_MinotaurAtk3(mobj_t * actor) +{ + mobj_t *mo; + player_t *player; + + if (!actor->target) + { + return; + } + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(5)); + if ((player = actor->target->player) != NULL) + { // Squish the player + player->deltaviewheight = -16 * FRACUNIT; + } + } + else + { + mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX2); + if (mo != NULL) + { + S_StartSound(mo, sfx_minat1); + } + } + if (P_Random() < 192 && actor->special2 == 0) + { + P_SetMobjState(actor, S_MNTR_ATK3_4); + actor->special2 = 1; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MntrFloorFire +// +//---------------------------------------------------------------------------- + +void A_MntrFloorFire(mobj_t * actor) +{ + mobj_t *mo; + + actor->z = actor->floorz; + mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10), + actor->y + ((P_Random() - P_Random()) << 10), ONFLOORZ, + MT_MNTRFX3); + mo->target = actor->target; + mo->momx = 1; // Force block checking + P_CheckMissileSpawn(mo); +} + +//---------------------------------------------------------------------------- +// +// PROC A_BeastAttack +// +//---------------------------------------------------------------------------- + +void A_BeastAttack(mobj_t * actor) +{ + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(3)); + return; + } + P_SpawnMissile(actor, actor->target, MT_BEASTBALL); +} + +//---------------------------------------------------------------------------- +// +// PROC A_HeadAttack +// +//---------------------------------------------------------------------------- + +void A_HeadAttack(mobj_t * actor) +{ + int i; + mobj_t *fire; + mobj_t *baseFire; + mobj_t *mo; + mobj_t *target; + int randAttack; + static int atkResolve1[] = { 50, 150 }; + static int atkResolve2[] = { 150, 200 }; + int dist; + + // Ice ball (close 20% : far 60%) + // Fire column (close 40% : far 20%) + // Whirlwind (close 40% : far 20%) + // Distance threshold = 8 cells + + target = actor->target; + if (target == NULL) + { + return; + } + A_FaceTarget(actor); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(target, actor, actor, HITDICE(6)); + return; + } + dist = P_AproxDistance(actor->x - target->x, actor->y - target->y) + > 8 * 64 * FRACUNIT; + randAttack = P_Random(); + if (randAttack < atkResolve1[dist]) + { // Ice ball + P_SpawnMissile(actor, target, MT_HEADFX1); + S_StartSound(actor, sfx_hedat2); + } + else if (randAttack < atkResolve2[dist]) + { // Fire column + baseFire = P_SpawnMissile(actor, target, MT_HEADFX3); + if (baseFire != NULL) + { + P_SetMobjState(baseFire, S_HEADFX3_4); // Don't grow + for (i = 0; i < 5; i++) + { + fire = P_SpawnMobj(baseFire->x, baseFire->y, + baseFire->z, MT_HEADFX3); + if (i == 0) + { + S_StartSound(actor, sfx_hedat1); + } + fire->target = baseFire->target; + fire->angle = baseFire->angle; + fire->momx = baseFire->momx; + fire->momy = baseFire->momy; + fire->momz = baseFire->momz; + fire->damage = 0; + fire->health = (i + 1) * 2; + P_CheckMissileSpawn(fire); + } + } + } + else + { // Whirlwind + mo = P_SpawnMissile(actor, target, MT_WHIRLWIND); + if (mo != NULL) + { + mo->z -= 32 * FRACUNIT; + mo->special1 = (int) target; + mo->special2 = 50; // Timer for active sound + mo->health = 20 * TICRATE; // Duration + S_StartSound(actor, sfx_hedat3); + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_WhirlwindSeek +// +//---------------------------------------------------------------------------- + +void A_WhirlwindSeek(mobj_t * actor) +{ + actor->health -= 3; + if (actor->health < 0) + { + actor->momx = actor->momy = actor->momz = 0; + P_SetMobjState(actor, mobjinfo[actor->type].deathstate); + actor->flags &= ~MF_MISSILE; + return; + } + if ((actor->special2 -= 3) < 0) + { + actor->special2 = 58 + (P_Random() & 31); + S_StartSound(actor, sfx_hedat3); + } + if (actor->special1 + && (((mobj_t *) (actor->special1))->flags & MF_SHADOW)) + { + return; + } + P_SeekerMissile(actor, ANG1_X * 10, ANG1_X * 30); +} + +//---------------------------------------------------------------------------- +// +// PROC A_HeadIceImpact +// +//---------------------------------------------------------------------------- + +void A_HeadIceImpact(mobj_t * ice) +{ + int i; + angle_t angle; + mobj_t *shard; + + for (i = 0; i < 8; i++) + { + shard = P_SpawnMobj(ice->x, ice->y, ice->z, MT_HEADFX2); + angle = i * ANG45; + shard->target = ice->target; + shard->angle = angle; + angle >>= ANGLETOFINESHIFT; + shard->momx = FixedMul(shard->info->speed, finecosine[angle]); + shard->momy = FixedMul(shard->info->speed, finesine[angle]); + shard->momz = (fixed_t)(-.6 * FRACUNIT); + P_CheckMissileSpawn(shard); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_HeadFireGrow +// +//---------------------------------------------------------------------------- + +void A_HeadFireGrow(mobj_t * fire) +{ + fire->health--; + fire->z += 9 * FRACUNIT; + if (fire->health == 0) + { + fire->damage = fire->info->damage; + P_SetMobjState(fire, S_HEADFX3_4); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_SnakeAttack +// +//---------------------------------------------------------------------------- + +void A_SnakeAttack(mobj_t * actor) +{ + if (!actor->target) + { + P_SetMobjState(actor, S_SNAKE_WALK1); + return; + } + S_StartSound(actor, actor->info->attacksound); + A_FaceTarget(actor); + P_SpawnMissile(actor, actor->target, MT_SNAKEPRO_A); +} + +//---------------------------------------------------------------------------- +// +// PROC A_SnakeAttack2 +// +//---------------------------------------------------------------------------- + +void A_SnakeAttack2(mobj_t * actor) +{ + if (!actor->target) + { + P_SetMobjState(actor, S_SNAKE_WALK1); + return; + } + S_StartSound(actor, actor->info->attacksound); + A_FaceTarget(actor); + P_SpawnMissile(actor, actor->target, MT_SNAKEPRO_B); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ClinkAttack +// +//---------------------------------------------------------------------------- + +void A_ClinkAttack(mobj_t * actor) +{ + int damage; + + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + damage = ((P_Random() % 7) + 3); + P_DamageMobj(actor->target, actor, actor, damage); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_GhostOff +// +//---------------------------------------------------------------------------- + +void A_GhostOff(mobj_t * actor) +{ + actor->flags &= ~MF_SHADOW; +} + +//---------------------------------------------------------------------------- +// +// PROC A_WizAtk1 +// +//---------------------------------------------------------------------------- + +void A_WizAtk1(mobj_t * actor) +{ + A_FaceTarget(actor); + actor->flags &= ~MF_SHADOW; +} + +//---------------------------------------------------------------------------- +// +// PROC A_WizAtk2 +// +//---------------------------------------------------------------------------- + +void A_WizAtk2(mobj_t * actor) +{ + A_FaceTarget(actor); + actor->flags |= MF_SHADOW; +} + +//---------------------------------------------------------------------------- +// +// PROC A_WizAtk3 +// +//---------------------------------------------------------------------------- + +void A_WizAtk3(mobj_t * actor) +{ + mobj_t *mo; + angle_t angle; + fixed_t momz; + + actor->flags &= ~MF_SHADOW; + if (!actor->target) + { + return; + } + S_StartSound(actor, actor->info->attacksound); + if (P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(4)); + return; + } + mo = P_SpawnMissile(actor, actor->target, MT_WIZFX1); + if (mo) + { + momz = mo->momz; + angle = mo->angle; + P_SpawnMissileAngle(actor, MT_WIZFX1, angle - (ANG45 / 8), momz); + P_SpawnMissileAngle(actor, MT_WIZFX1, angle + (ANG45 / 8), momz); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_Scream +// +//---------------------------------------------------------------------------- + +void A_Scream(mobj_t * actor) +{ + switch (actor->type) + { + case MT_CHICPLAYER: + case MT_SORCERER1: + case MT_MINOTAUR: + // Make boss death sounds full volume + S_StartSound(NULL, actor->info->deathsound); + break; + case MT_PLAYER: + // Handle the different player death screams + if (actor->special1 < 10) + { // Wimpy death sound + S_StartSound(actor, sfx_plrwdth); + } + else if (actor->health > -50) + { // Normal death sound + S_StartSound(actor, actor->info->deathsound); + } + else if (actor->health > -100) + { // Crazy death sound + S_StartSound(actor, sfx_plrcdth); + } + else + { // Extreme death sound + S_StartSound(actor, sfx_gibdth); + } + break; + default: + S_StartSound(actor, actor->info->deathsound); + break; + } +} + +//--------------------------------------------------------------------------- +// +// PROC P_DropItem +// +//--------------------------------------------------------------------------- + +void P_DropItem(mobj_t * source, mobjtype_t type, int special, int chance) +{ + mobj_t *mo; + + if (P_Random() > chance) + { + return; + } + mo = P_SpawnMobj(source->x, source->y, + source->z + (source->height >> 1), type); + mo->momx = (P_Random() - P_Random()) << 8; + mo->momy = (P_Random() - P_Random()) << 8; + mo->momz = FRACUNIT * 5 + (P_Random() << 10); + mo->flags |= MF_DROPPED; + mo->health = special; +} + +//---------------------------------------------------------------------------- +// +// PROC A_NoBlocking +// +//---------------------------------------------------------------------------- + +void A_NoBlocking(mobj_t * actor) +{ + actor->flags &= ~MF_SOLID; + // Check for monsters dropping things + switch (actor->type) + { + case MT_MUMMY: + case MT_MUMMYLEADER: + case MT_MUMMYGHOST: + case MT_MUMMYLEADERGHOST: + P_DropItem(actor, MT_AMGWNDWIMPY, 3, 84); + break; + case MT_KNIGHT: + case MT_KNIGHTGHOST: + P_DropItem(actor, MT_AMCBOWWIMPY, 5, 84); + break; + case MT_WIZARD: + P_DropItem(actor, MT_AMBLSRWIMPY, 10, 84); + P_DropItem(actor, MT_ARTITOMEOFPOWER, 0, 4); + break; + case MT_HEAD: + P_DropItem(actor, MT_AMBLSRWIMPY, 10, 84); + P_DropItem(actor, MT_ARTIEGG, 0, 51); + break; + case MT_BEAST: + P_DropItem(actor, MT_AMCBOWWIMPY, 10, 84); + break; + case MT_CLINK: + P_DropItem(actor, MT_AMSKRDWIMPY, 20, 84); + break; + case MT_SNAKE: + P_DropItem(actor, MT_AMPHRDWIMPY, 5, 84); + break; + case MT_MINOTAUR: + P_DropItem(actor, MT_ARTISUPERHEAL, 0, 51); + P_DropItem(actor, MT_AMPHRDWIMPY, 10, 84); + break; + default: + break; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_Explode +// +// Handles a bunch of exploding things. +// +//---------------------------------------------------------------------------- + +void A_Explode(mobj_t * actor) +{ + int damage; + + damage = 128; + switch (actor->type) + { + case MT_FIREBOMB: // Time Bombs + actor->z += 32 * FRACUNIT; + actor->flags &= ~MF_SHADOW; + break; + case MT_MNTRFX2: // Minotaur floor fire + damage = 24; + break; + case MT_SOR2FX1: // D'Sparil missile + damage = 80 + (P_Random() & 31); + break; + default: + break; + } + P_RadiusAttack(actor, actor->target, damage); + P_HitFloor(actor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_PodPain +// +//---------------------------------------------------------------------------- + +void A_PodPain(mobj_t * actor) +{ + int i; + int count; + int chance; + mobj_t *goo; + + chance = P_Random(); + if (chance < 128) + { + return; + } + count = chance > 240 ? 2 : 1; + for (i = 0; i < count; i++) + { + goo = P_SpawnMobj(actor->x, actor->y, + actor->z + 48 * FRACUNIT, MT_PODGOO); + goo->target = actor; + goo->momx = (P_Random() - P_Random()) << 9; + goo->momy = (P_Random() - P_Random()) << 9; + goo->momz = FRACUNIT / 2 + (P_Random() << 9); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_RemovePod +// +//---------------------------------------------------------------------------- + +void A_RemovePod(mobj_t * actor) +{ + mobj_t *mo; + + if (actor->special2) + { + mo = (mobj_t *) actor->special2; + if (mo->special1 > 0) + { + mo->special1--; + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MakePod +// +//---------------------------------------------------------------------------- + +#define MAX_GEN_PODS 16 + +void A_MakePod(mobj_t * actor) +{ + mobj_t *mo; + fixed_t x; + fixed_t y; + fixed_t z; + + if (actor->special1 == MAX_GEN_PODS) + { // Too many generated pods + return; + } + x = actor->x; + y = actor->y; + z = actor->z; + mo = P_SpawnMobj(x, y, ONFLOORZ, MT_POD); + if (P_CheckPosition(mo, x, y) == false) + { // Didn't fit + P_RemoveMobj(mo); + return; + } + P_SetMobjState(mo, S_POD_GROW1); + P_ThrustMobj(mo, P_Random() << 24, (fixed_t) (4.5 * FRACUNIT)); + S_StartSound(mo, sfx_newpod); + actor->special1++; // Increment generated pod count + mo->special2 = (int) actor; // Link the generator to the pod + return; +} + +//---------------------------------------------------------------------------- +// +// PROC P_Massacre +// +// Kills all monsters. +// +//---------------------------------------------------------------------------- + +void P_Massacre(void) +{ + mobj_t *mo; + thinker_t *think; + + for (think = thinkercap.next; think != &thinkercap; think = think->next) + { + if (think->function != P_MobjThinker) + { // Not a mobj thinker + continue; + } + mo = (mobj_t *) think; + if ((mo->flags & MF_COUNTKILL) && (mo->health > 0)) + { + P_DamageMobj(mo, NULL, NULL, 10000); + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_BossDeath +// +// Trigger special effects if all bosses are dead. +// +//---------------------------------------------------------------------------- + +void A_BossDeath(mobj_t * actor) +{ + mobj_t *mo; + thinker_t *think; + line_t dummyLine; + static mobjtype_t bossType[6] = { + MT_HEAD, + MT_MINOTAUR, + MT_SORCERER2, + MT_HEAD, + MT_MINOTAUR, + -1 + }; + + if (gamemap != 8) + { // Not a boss level + return; + } + if (actor->type != bossType[gameepisode - 1]) + { // Not considered a boss in this episode + return; + } + // Make sure all bosses are dead + for (think = thinkercap.next; think != &thinkercap; think = think->next) + { + if (think->function != P_MobjThinker) + { // Not a mobj thinker + continue; + } + mo = (mobj_t *) think; + if ((mo != actor) && (mo->type == actor->type) && (mo->health > 0)) + { // Found a living boss + return; + } + } + if (gameepisode > 1) + { // Kill any remaining monsters + P_Massacre(); + } + dummyLine.tag = 666; + EV_DoFloor(&dummyLine, lowerFloor); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ESound +// +//---------------------------------------------------------------------------- + +void A_ESound(mobj_t * mo) +{ + int sound = sfx_None; + + switch (mo->type) + { + case MT_SOUNDWATERFALL: + sound = sfx_waterfl; + break; + case MT_SOUNDWIND: + sound = sfx_wind; + break; + default: + break; + } + S_StartSound(mo, sound); +} + +//---------------------------------------------------------------------------- +// +// PROC A_SpawnTeleGlitter +// +//---------------------------------------------------------------------------- + +void A_SpawnTeleGlitter(mobj_t * actor) +{ + mobj_t *mo; + + mo = P_SpawnMobj(actor->x + ((P_Random() & 31) - 16) * FRACUNIT, + actor->y + ((P_Random() & 31) - 16) * FRACUNIT, + actor->subsector->sector->floorheight, MT_TELEGLITTER); + mo->momz = FRACUNIT / 4; +} + +//---------------------------------------------------------------------------- +// +// PROC A_SpawnTeleGlitter2 +// +//---------------------------------------------------------------------------- + +void A_SpawnTeleGlitter2(mobj_t * actor) +{ + mobj_t *mo; + + mo = P_SpawnMobj(actor->x + ((P_Random() & 31) - 16) * FRACUNIT, + actor->y + ((P_Random() & 31) - 16) * FRACUNIT, + actor->subsector->sector->floorheight, MT_TELEGLITTER2); + mo->momz = FRACUNIT / 4; +} + +//---------------------------------------------------------------------------- +// +// PROC A_AccTeleGlitter +// +//---------------------------------------------------------------------------- + +void A_AccTeleGlitter(mobj_t * actor) +{ + if (++actor->health > 35) + { + actor->momz += actor->momz / 2; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_InitKeyGizmo +// +//---------------------------------------------------------------------------- + +void A_InitKeyGizmo(mobj_t * gizmo) +{ + mobj_t *mo; + statenum_t state = S_NULL; + + switch (gizmo->type) + { + case MT_KEYGIZMOBLUE: + state = S_KGZ_BLUEFLOAT1; + break; + case MT_KEYGIZMOGREEN: + state = S_KGZ_GREENFLOAT1; + break; + case MT_KEYGIZMOYELLOW: + state = S_KGZ_YELLOWFLOAT1; + break; + default: + break; + } + mo = P_SpawnMobj(gizmo->x, gizmo->y, gizmo->z + 60 * FRACUNIT, + MT_KEYGIZMOFLOAT); + P_SetMobjState(mo, state); +} + +//---------------------------------------------------------------------------- +// +// PROC A_VolcanoSet +// +//---------------------------------------------------------------------------- + +void A_VolcanoSet(mobj_t * volcano) +{ + volcano->tics = 105 + (P_Random() & 127); +} + +//---------------------------------------------------------------------------- +// +// PROC A_VolcanoBlast +// +//---------------------------------------------------------------------------- + +void A_VolcanoBlast(mobj_t * volcano) +{ + int i; + int count; + mobj_t *blast; + angle_t angle; + + count = 1 + (P_Random() % 3); + for (i = 0; i < count; i++) + { + blast = P_SpawnMobj(volcano->x, volcano->y, volcano->z + 44 * FRACUNIT, MT_VOLCANOBLAST); // MT_VOLCANOBLAST + blast->target = volcano; + angle = P_Random() << 24; + blast->angle = angle; + angle >>= ANGLETOFINESHIFT; + blast->momx = FixedMul(1 * FRACUNIT, finecosine[angle]); + blast->momy = FixedMul(1 * FRACUNIT, finesine[angle]); + blast->momz = (fixed_t)(2.5 * FRACUNIT) + (P_Random() << 10); + S_StartSound(blast, sfx_volsht); + P_CheckMissileSpawn(blast); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_VolcBallImpact +// +//---------------------------------------------------------------------------- + +void A_VolcBallImpact(mobj_t * ball) +{ + int i; + mobj_t *tiny; + angle_t angle; + + if (ball->z <= ball->floorz) + { + ball->flags |= MF_NOGRAVITY; + ball->flags2 &= ~MF2_LOGRAV; + ball->z += 28 * FRACUNIT; + //ball->momz = 3*FRACUNIT; + } + P_RadiusAttack(ball, ball->target, 25); + for (i = 0; i < 4; i++) + { + tiny = P_SpawnMobj(ball->x, ball->y, ball->z, MT_VOLCANOTBLAST); + tiny->target = ball; + angle = i * ANG90; + tiny->angle = angle; + angle >>= ANGLETOFINESHIFT; + tiny->momx = FixedMul((fixed_t)(FRACUNIT * .7), finecosine[angle]); + tiny->momy = FixedMul((fixed_t)(FRACUNIT * .7), finesine[angle]); + tiny->momz = FRACUNIT + (P_Random() << 9); + P_CheckMissileSpawn(tiny); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_SkullPop +// +//---------------------------------------------------------------------------- + +void A_SkullPop(mobj_t * actor) +{ + mobj_t *mo; + player_t *player; + + actor->flags &= ~MF_SOLID; + mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48 * FRACUNIT, + MT_BLOODYSKULL); + //mo->target = actor; + mo->momx = (P_Random() - P_Random()) << 9; + mo->momy = (P_Random() - P_Random()) << 9; + mo->momz = FRACUNIT * 2 + (P_Random() << 6); + // Attach player mobj to bloody skull + player = actor->player; + actor->player = NULL; + mo->player = player; + mo->health = actor->health; + mo->angle = actor->angle; + player->mo = mo; + player->lookdir = 0; + player->damagecount = 32; +} + +//---------------------------------------------------------------------------- +// +// PROC A_CheckSkullFloor +// +//---------------------------------------------------------------------------- + +void A_CheckSkullFloor(mobj_t * actor) +{ + if (actor->z <= actor->floorz) + { + P_SetMobjState(actor, S_BLOODYSKULLX1); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_CheckSkullDone +// +//---------------------------------------------------------------------------- + +void A_CheckSkullDone(mobj_t * actor) +{ + if (actor->special2 == 666) + { + P_SetMobjState(actor, S_BLOODYSKULLX2); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_CheckBurnGone +// +//---------------------------------------------------------------------------- + +void A_CheckBurnGone(mobj_t * actor) +{ + if (actor->special2 == 666) + { + P_SetMobjState(actor, S_PLAY_FDTH20); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FreeTargMobj +// +//---------------------------------------------------------------------------- + +void A_FreeTargMobj(mobj_t * mo) +{ + mo->momx = mo->momy = mo->momz = 0; + mo->z = mo->ceilingz + 4 * FRACUNIT; + mo->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY | MF_SOLID); + mo->flags |= MF_CORPSE | MF_DROPOFF | MF_NOGRAVITY; + mo->flags2 &= ~(MF2_PASSMOBJ | MF2_LOGRAV); + mo->player = NULL; +} + +//---------------------------------------------------------------------------- +// +// PROC A_AddPlayerCorpse +// +//---------------------------------------------------------------------------- + +#define BODYQUESIZE 32 +mobj_t *bodyque[BODYQUESIZE]; +int bodyqueslot; + +void A_AddPlayerCorpse(mobj_t * actor) +{ + if (bodyqueslot >= BODYQUESIZE) + { // Too many player corpses - remove an old one + P_RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]); + } + bodyque[bodyqueslot % BODYQUESIZE] = actor; + bodyqueslot++; +} + +//---------------------------------------------------------------------------- +// +// PROC A_FlameSnd +// +//---------------------------------------------------------------------------- + +void A_FlameSnd(mobj_t * actor) +{ + S_StartSound(actor, sfx_hedat1); // Burn sound +} + +//---------------------------------------------------------------------------- +// +// PROC A_HideThing +// +//---------------------------------------------------------------------------- + +void A_HideThing(mobj_t * actor) +{ + //P_UnsetThingPosition(actor); + actor->flags2 |= MF2_DONTDRAW; +} + +//---------------------------------------------------------------------------- +// +// PROC A_UnHideThing +// +//---------------------------------------------------------------------------- + +void A_UnHideThing(mobj_t * actor) +{ + //P_SetThingPosition(actor); + actor->flags2 &= ~MF2_DONTDRAW; +} diff --git a/src/heretic/p_floor.c b/src/heretic/p_floor.c new file mode 100644 index 00000000..24c7ffa2 --- /dev/null +++ b/src/heretic/p_floor.c @@ -0,0 +1,468 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +#include "doomdef.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +//================================================================== +//================================================================== +// +// FLOORS +// +//================================================================== +//================================================================== + + + +//================================================================== +// +// Move a plane (floor or ceiling) and check for crushing +// +//================================================================== +result_e T_MovePlane(sector_t * sector, fixed_t speed, + fixed_t dest, boolean crush, int floorOrCeiling, + int direction) +{ + 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); +#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(&floor->sector->soundorg, sfx_dormov); + } + + if (res == pastdest) + { + floor->sector->specialdata = NULL; + if (floor->type == raiseBuildStep) + { + S_StartSound(&floor->sector->soundorg, sfx_pstop); + } + 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); + } + +} + +//================================================================== +// +// 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 = §ors[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 = 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 = (8 * FRACUNIT) + + P_FindHighestFloorSurrounding(sec); + 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 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 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 = INT_MAX; + 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); + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + else + { + sec = getSector(secnum, i, 0); + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + default: + break; + } + } + return rtn; +} + +//================================================================== +// +// BUILD A STAIRCASE! +// +//================================================================== +int EV_BuildStairs(line_t * line, fixed_t stepDelta) +{ + int secnum; + int height; + int i; + int newsecnum; + int texture; + int ok; + int rtn; + sector_t *sec, *tsec; + floormove_t *floor; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0) + { + sec = §ors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // + // new floor thinker + // + rtn = 1; + height = sec->floorheight + stepDelta; + floor = Z_Malloc(sizeof(*floor), PU_LEVSPEC, 0); + P_AddThinker(&floor->thinker); + sec->specialdata = floor; + floor->thinker.function = T_MoveFloor; + floor->type = raiseBuildStep; + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + 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 = tsec - sectors; + if (secnum != newsecnum) + continue; + tsec = (sec->lines[i])->backsector; + newsecnum = tsec - sectors; + if (tsec->floorpic != texture) + continue; + + height += stepDelta; + 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 = T_MoveFloor; + floor->type = raiseBuildStep; + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = height; + ok = 1; + break; + } + } + while (ok); + } + return (rtn); +} diff --git a/src/heretic/p_inter.c b/src/heretic/p_inter.c new file mode 100644 index 00000000..afdb37f2 --- /dev/null +++ b/src/heretic/p_inter.c @@ -0,0 +1,1493 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_inter.c + +#include "doomdef.h" +#include "deh_str.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +#define BONUSADD 6 + +int WeaponValue[] = { + 1, // staff + 3, // goldwand + 4, // crossbow + 5, // blaster + 6, // skullrod + 7, // phoenixrod + 8, // mace + 2, // gauntlets + 0 // beak +}; + +int maxammo[NUMAMMO] = { + 100, // gold wand + 50, // crossbow + 200, // blaster + 200, // skull rod + 20, // phoenix rod + 150 // mace +}; + +int GetWeaponAmmo[NUMWEAPONS] = { + 0, // staff + 25, // gold wand + 10, // crossbow + 30, // blaster + 50, // skull rod + 2, // phoenix rod + 50, // mace + 0, // gauntlets + 0 // beak +}; + +static weapontype_t GetAmmoChange[] = { + wp_goldwand, + wp_crossbow, + wp_blaster, + wp_skullrod, + wp_phoenixrod, + wp_mace +}; + +/* +static boolean GetAmmoChangePL1[NUMWEAPONS][NUMAMMO] = +{ + // staff + {wp_goldwand, wp_crossbow, wp_blaster, wp_skullrod, -1, wp_mace}, + // gold wand + {-1, wp_crossbow, wp_blaster, wp_skullrod, -1, wp_mace}, + // crossbow + {-1, -1, wp_blaster, wp_skullrod, -1, -1}, + // blaster + {-1, -1, -1, -1, -1, -1}, + // skull rod + {-1, -1, -1, -1, -1, -1}, + // phoenix rod + {-1, -1, -1, -1, -1, -1}, + // mace + {-1, wp_crossbow, wp_blaster, wp_skullrod, -1, -1}, + // gauntlets + {-1, wp_crossbow, wp_blaster, wp_skullrod, -1, wp_mace} +}; +*/ + +/* +static boolean GetAmmoChangePL2[NUMWEAPONS][NUMAMMO] = +{ + // staff + {wp_goldwand, wp_crossbow, wp_blaster, wp_skullrod, wp_phoenixrod, + wp_mace}, + // gold wand + {-1, wp_crossbow, wp_blaster, wp_skullrod, wp_phoenixrod, wp_mace}, + // crossbow + {-1, -1, wp_blaster, wp_skullrod, wp_phoenixrod, -1}, + // blaster + {-1, -1, -1, wp_skullrod, wp_phoenixrod, -1}, + // skull rod + {-1, -1, -1, -1, -1, -1}, + // phoenix rod + {-1, -1, -1, -1, -1, -1}, + // mace + {-1, wp_crossbow, wp_blaster, wp_skullrod, -1, -1}, + // gauntlets + {-1, -1, -1, wp_skullrod, wp_phoenixrod, wp_mace} +}; +*/ + +//-------------------------------------------------------------------------- +// +// PROC P_SetMessage +// +//-------------------------------------------------------------------------- + +boolean ultimatemsg; + +void P_SetMessage(player_t * player, char *message, boolean ultmsg) +{ + extern boolean messageson; + + if ((ultimatemsg || !messageson) && !ultmsg) + { + return; + } + player->message = message; + player->messageTics = MESSAGETICS; + BorderTopRefresh = true; + if (ultmsg) + { + ultimatemsg = true; + } +} + +//-------------------------------------------------------------------------- +// +// FUNC P_GiveAmmo +// +// Returns true if the player accepted the ammo, false if it was +// refused (player has maxammo[ammo]). +// +//-------------------------------------------------------------------------- + +boolean P_GiveAmmo(player_t * player, ammotype_t ammo, int count) +{ + int prevAmmo; + //weapontype_t changeWeapon; + + if (ammo == am_noammo) + { + return (false); + } + if (ammo < 0 || ammo > NUMAMMO) + { + I_Error("P_GiveAmmo: bad type %i", ammo); + } + if (player->ammo[ammo] == player->maxammo[ammo]) + { + return (false); + } + if (gameskill == sk_baby || gameskill == sk_nightmare) + { // extra ammo in baby mode and nightmare mode + count += count >> 1; + } + prevAmmo = player->ammo[ammo]; + + player->ammo[ammo] += count; + if (player->ammo[ammo] > player->maxammo[ammo]) + { + player->ammo[ammo] = player->maxammo[ammo]; + } + if (prevAmmo) + { + // Don't attempt to change weapons if the player already had + // ammo of the type just given + return (true); + } + if (player->readyweapon == wp_staff + || player->readyweapon == wp_gauntlets) + { + if (player->weaponowned[GetAmmoChange[ammo]]) + { + player->pendingweapon = GetAmmoChange[ammo]; + } + } +/* + if(player->powers[pw_weaponlevel2]) + { + changeWeapon = GetAmmoChangePL2[player->readyweapon][ammo]; + } + else + { + changeWeapon = GetAmmoChangePL1[player->readyweapon][ammo]; + } + if(changeWeapon != -1) + { + if(player->weaponowned[changeWeapon]) + { + player->pendingweapon = changeWeapon; + } + } +*/ + return (true); +} + +//-------------------------------------------------------------------------- +// +// FUNC P_GiveWeapon +// +// Returns true if the weapon or its ammo was accepted. +// +//-------------------------------------------------------------------------- + +boolean P_GiveWeapon(player_t * player, weapontype_t weapon) +{ + boolean gaveAmmo; + boolean gaveWeapon; + + if (netgame && !deathmatch) + { // Cooperative net-game + if (player->weaponowned[weapon]) + { + return (false); + } + player->bonuscount += BONUSADD; + player->weaponowned[weapon] = true; + P_GiveAmmo(player, wpnlev1info[weapon].ammo, GetWeaponAmmo[weapon]); + player->pendingweapon = weapon; + if (player == &players[consoleplayer]) + { + S_StartSound(NULL, sfx_wpnup); + } + return (false); + } + gaveAmmo = P_GiveAmmo(player, wpnlev1info[weapon].ammo, + GetWeaponAmmo[weapon]); + if (player->weaponowned[weapon]) + { + gaveWeapon = false; + } + else + { + gaveWeapon = true; + player->weaponowned[weapon] = true; + if (WeaponValue[weapon] > WeaponValue[player->readyweapon]) + { // Only switch to more powerful weapons + player->pendingweapon = weapon; + } + } + return (gaveWeapon || gaveAmmo); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_GiveBody +// +// Returns false if the body isn't needed at all. +// +//--------------------------------------------------------------------------- + +boolean P_GiveBody(player_t * player, int num) +{ + int max; + + max = MAXHEALTH; + if (player->chickenTics) + { + max = MAXCHICKENHEALTH; + } + if (player->health >= max) + { + return (false); + } + player->health += num; + if (player->health > max) + { + player->health = max; + } + player->mo->health = player->health; + return (true); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_GiveArmor +// +// Returns false if the armor is worse than the current armor. +// +//--------------------------------------------------------------------------- + +boolean P_GiveArmor(player_t * player, int armortype) +{ + int hits; + + hits = armortype * 100; + if (player->armorpoints >= hits) + { + return (false); + } + player->armortype = armortype; + player->armorpoints = hits; + return (true); +} + +//--------------------------------------------------------------------------- +// +// PROC P_GiveKey +// +//--------------------------------------------------------------------------- + +void P_GiveKey(player_t * player, keytype_t key) +{ + extern int playerkeys; + extern vertex_t KeyPoints[]; + + if (player->keys[key]) + { + return; + } + if (player == &players[consoleplayer]) + { + playerkeys |= 1 << key; + KeyPoints[key].x = 0; + KeyPoints[key].y = 0; + } + player->bonuscount = BONUSADD; + player->keys[key] = true; +} + +//--------------------------------------------------------------------------- +// +// FUNC P_GivePower +// +// Returns true if power accepted. +// +//--------------------------------------------------------------------------- + +boolean P_GivePower(player_t * player, powertype_t power) +{ + if (power == pw_invulnerability) + { + if (player->powers[power] > BLINKTHRESHOLD) + { // Already have it + return (false); + } + player->powers[power] = INVULNTICS; + return (true); + } + if (power == pw_weaponlevel2) + { + if (player->powers[power] > BLINKTHRESHOLD) + { // Already have it + return (false); + } + player->powers[power] = WPNLEV2TICS; + return (true); + } + if (power == pw_invisibility) + { + if (player->powers[power] > BLINKTHRESHOLD) + { // Already have it + return (false); + } + player->powers[power] = INVISTICS; + player->mo->flags |= MF_SHADOW; + return (true); + } + if (power == pw_flight) + { + if (player->powers[power] > BLINKTHRESHOLD) + { // Already have it + return (false); + } + player->powers[power] = FLIGHTTICS; + player->mo->flags2 |= MF2_FLY; + player->mo->flags |= MF_NOGRAVITY; + if (player->mo->z <= player->mo->floorz) + { + player->flyheight = 10; // thrust the player in the air a bit + } + return (true); + } + if (power == pw_infrared) + { + if (player->powers[power] > BLINKTHRESHOLD) + { // Already have it + return (false); + } + 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); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_GiveArtifact +// +// Returns true if artifact accepted. +// +//--------------------------------------------------------------------------- + +boolean P_GiveArtifact(player_t * player, artitype_t arti, mobj_t * mo) +{ + int i; + + i = 0; + while (player->inventory[i].type != arti && i < player->inventorySlotNum) + { + i++; + } + if (i == player->inventorySlotNum) + { + player->inventory[i].count = 1; + player->inventory[i].type = arti; + player->inventorySlotNum++; + } + else + { + if (player->inventory[i].count >= 16) + { // Player already has 16 of this item + return (false); + } + player->inventory[i].count++; + } + if (player->artifactCount == 0) + { + player->readyArtifact = arti; + } + player->artifactCount++; + if (mo && (mo->flags & MF_COUNTITEM)) + { + player->itemcount++; + } + return (true); +} + +//--------------------------------------------------------------------------- +// +// PROC P_SetDormantArtifact +// +// Removes the MF_SPECIAL flag, and initiates the artifact pickup +// animation. +// +//--------------------------------------------------------------------------- + +void P_SetDormantArtifact(mobj_t * arti) +{ + arti->flags &= ~MF_SPECIAL; + if (deathmatch && (arti->type != MT_ARTIINVULNERABILITY) + && (arti->type != MT_ARTIINVISIBILITY)) + { + P_SetMobjState(arti, S_DORMANTARTI1); + } + else + { // Don't respawn + P_SetMobjState(arti, S_DEADARTI1); + } + S_StartSound(arti, sfx_artiup); +} + +//--------------------------------------------------------------------------- +// +// PROC A_RestoreArtifact +// +//--------------------------------------------------------------------------- + +void A_RestoreArtifact(mobj_t * arti) +{ + arti->flags |= MF_SPECIAL; + P_SetMobjState(arti, arti->info->spawnstate); + S_StartSound(arti, sfx_respawn); +} + +//---------------------------------------------------------------------------- +// +// PROC P_HideSpecialThing +// +//---------------------------------------------------------------------------- + +void P_HideSpecialThing(mobj_t * thing) +{ + thing->flags &= ~MF_SPECIAL; + thing->flags2 |= MF2_DONTDRAW; + P_SetMobjState(thing, S_HIDESPECIAL1); +} + +//--------------------------------------------------------------------------- +// +// PROC A_RestoreSpecialThing1 +// +// Make a special thing visible again. +// +//--------------------------------------------------------------------------- + +void A_RestoreSpecialThing1(mobj_t * thing) +{ + if (thing->type == MT_WMACE) + { // Do random mace placement + P_RepositionMace(thing); + } + thing->flags2 &= ~MF2_DONTDRAW; + S_StartSound(thing, sfx_respawn); +} + +//--------------------------------------------------------------------------- +// +// PROC A_RestoreSpecialThing2 +// +//--------------------------------------------------------------------------- + +void A_RestoreSpecialThing2(mobj_t * thing) +{ + thing->flags |= MF_SPECIAL; + P_SetMobjState(thing, thing->info->spawnstate); +} + +//--------------------------------------------------------------------------- +// +// PROC P_TouchSpecialThing +// +//--------------------------------------------------------------------------- + +void P_TouchSpecialThing(mobj_t * special, mobj_t * toucher) +{ + int i; + player_t *player; + fixed_t delta; + int sound; + boolean respawn; + + delta = special->z - toucher->z; + if (delta > toucher->height || delta < -32 * FRACUNIT) + { // Out of reach + return; + } + if (toucher->health <= 0) + { // Toucher is dead + return; + } + sound = sfx_itemup; + player = toucher->player; + respawn = true; + switch (special->sprite) + { + // Items + case SPR_PTN1: // Item_HealingPotion + if (!P_GiveBody(player, 10)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_ITEMHEALTH), false); + break; + case SPR_SHLD: // Item_Shield1 + if (!P_GiveArmor(player, 1)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_ITEMSHIELD1), false); + break; + case SPR_SHD2: // Item_Shield2 + if (!P_GiveArmor(player, 2)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_ITEMSHIELD2), false); + break; + case SPR_BAGH: // Item_BagOfHolding + if (!player->backpack) + { + for (i = 0; i < NUMAMMO; i++) + { + player->maxammo[i] *= 2; + } + player->backpack = true; + } + P_GiveAmmo(player, am_goldwand, AMMO_GWND_WIMPY); + P_GiveAmmo(player, am_blaster, AMMO_BLSR_WIMPY); + P_GiveAmmo(player, am_crossbow, AMMO_CBOW_WIMPY); + P_GiveAmmo(player, am_skullrod, AMMO_SKRD_WIMPY); + P_GiveAmmo(player, am_phoenixrod, AMMO_PHRD_WIMPY); + P_SetMessage(player, DEH_String(TXT_ITEMBAGOFHOLDING), false); + break; + case SPR_SPMP: // Item_SuperMap + if (!P_GivePower(player, pw_allmap)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_ITEMSUPERMAP), false); + break; + + // Keys + case SPR_BKYY: // Key_Blue + if (!player->keys[key_blue]) + { + P_SetMessage(player, DEH_String(TXT_GOTBLUEKEY), false); + } + P_GiveKey(player, key_blue); + sound = sfx_keyup; + if (!netgame) + { + break; + } + return; + case SPR_CKYY: // Key_Yellow + if (!player->keys[key_yellow]) + { + P_SetMessage(player, DEH_String(TXT_GOTYELLOWKEY), false); + } + sound = sfx_keyup; + P_GiveKey(player, key_yellow); + if (!netgame) + { + break; + } + return; + case SPR_AKYY: // Key_Green + if (!player->keys[key_green]) + { + P_SetMessage(player, DEH_String(TXT_GOTGREENKEY), false); + } + sound = sfx_keyup; + P_GiveKey(player, key_green); + if (!netgame) + { + break; + } + return; + + // Artifacts + case SPR_PTN2: // Arti_HealingPotion + if (P_GiveArtifact(player, arti_health, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIHEALTH), false); + P_SetDormantArtifact(special); + } + return; + case SPR_SOAR: // Arti_Fly + if (P_GiveArtifact(player, arti_fly, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIFLY), false); + P_SetDormantArtifact(special); + } + return; + case SPR_INVU: // Arti_Invulnerability + if (P_GiveArtifact(player, arti_invulnerability, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIINVULNERABILITY), false); + P_SetDormantArtifact(special); + } + return; + case SPR_PWBK: // Arti_TomeOfPower + if (P_GiveArtifact(player, arti_tomeofpower, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTITOMEOFPOWER), false); + P_SetDormantArtifact(special); + } + return; + case SPR_INVS: // Arti_Invisibility + if (P_GiveArtifact(player, arti_invisibility, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIINVISIBILITY), false); + P_SetDormantArtifact(special); + } + return; + case SPR_EGGC: // Arti_Egg + if (P_GiveArtifact(player, arti_egg, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIEGG), false); + P_SetDormantArtifact(special); + } + return; + case SPR_SPHL: // Arti_SuperHealth + if (P_GiveArtifact(player, arti_superhealth, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTISUPERHEALTH), false); + P_SetDormantArtifact(special); + } + return; + case SPR_TRCH: // Arti_Torch + if (P_GiveArtifact(player, arti_torch, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTITORCH), false); + P_SetDormantArtifact(special); + } + return; + case SPR_FBMB: // Arti_FireBomb + if (P_GiveArtifact(player, arti_firebomb, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTIFIREBOMB), false); + P_SetDormantArtifact(special); + } + return; + case SPR_ATLP: // Arti_Teleport + if (P_GiveArtifact(player, arti_teleport, special)) + { + P_SetMessage(player, DEH_String(TXT_ARTITELEPORT), false); + P_SetDormantArtifact(special); + } + return; + + // Ammo + case SPR_AMG1: // Ammo_GoldWandWimpy + if (!P_GiveAmmo(player, am_goldwand, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOGOLDWAND1), false); + break; + case SPR_AMG2: // Ammo_GoldWandHefty + if (!P_GiveAmmo(player, am_goldwand, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOGOLDWAND2), false); + break; + case SPR_AMM1: // Ammo_MaceWimpy + if (!P_GiveAmmo(player, am_mace, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOMACE1), false); + break; + case SPR_AMM2: // Ammo_MaceHefty + if (!P_GiveAmmo(player, am_mace, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOMACE2), false); + break; + case SPR_AMC1: // Ammo_CrossbowWimpy + if (!P_GiveAmmo(player, am_crossbow, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOCROSSBOW1), false); + break; + case SPR_AMC2: // Ammo_CrossbowHefty + if (!P_GiveAmmo(player, am_crossbow, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOCROSSBOW2), false); + break; + case SPR_AMB1: // Ammo_BlasterWimpy + if (!P_GiveAmmo(player, am_blaster, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOBLASTER1), false); + break; + case SPR_AMB2: // Ammo_BlasterHefty + if (!P_GiveAmmo(player, am_blaster, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOBLASTER2), false); + break; + case SPR_AMS1: // Ammo_SkullRodWimpy + if (!P_GiveAmmo(player, am_skullrod, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOSKULLROD1), false); + break; + case SPR_AMS2: // Ammo_SkullRodHefty + if (!P_GiveAmmo(player, am_skullrod, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOSKULLROD2), false); + break; + case SPR_AMP1: // Ammo_PhoenixRodWimpy + if (!P_GiveAmmo(player, am_phoenixrod, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOPHOENIXROD1), false); + break; + case SPR_AMP2: // Ammo_PhoenixRodHefty + if (!P_GiveAmmo(player, am_phoenixrod, special->health)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_AMMOPHOENIXROD2), false); + break; + + // Weapons + case SPR_WMCE: // Weapon_Mace + if (!P_GiveWeapon(player, wp_mace)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNMACE), false); + sound = sfx_wpnup; + break; + case SPR_WBOW: // Weapon_Crossbow + if (!P_GiveWeapon(player, wp_crossbow)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNCROSSBOW), false); + sound = sfx_wpnup; + break; + case SPR_WBLS: // Weapon_Blaster + if (!P_GiveWeapon(player, wp_blaster)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNBLASTER), false); + sound = sfx_wpnup; + break; + case SPR_WSKL: // Weapon_SkullRod + if (!P_GiveWeapon(player, wp_skullrod)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNSKULLROD), false); + sound = sfx_wpnup; + break; + case SPR_WPHX: // Weapon_PhoenixRod + if (!P_GiveWeapon(player, wp_phoenixrod)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNPHOENIXROD), false); + sound = sfx_wpnup; + break; + case SPR_WGNT: // Weapon_Gauntlets + if (!P_GiveWeapon(player, wp_gauntlets)) + { + return; + } + P_SetMessage(player, DEH_String(TXT_WPNGAUNTLETS), false); + sound = sfx_wpnup; + break; + default: + I_Error("P_SpecialThing: Unknown gettable thing"); + } + if (special->flags & MF_COUNTITEM) + { + player->itemcount++; + } + if (deathmatch && respawn && !(special->flags & MF_DROPPED)) + { + P_HideSpecialThing(special); + } + else + { + P_RemoveMobj(special); + } + player->bonuscount += BONUSADD; + if (player == &players[consoleplayer]) + { + S_StartSound(NULL, sound); + SB_PaletteFlash(); + } +} + +//--------------------------------------------------------------------------- +// +// PROC P_KillMobj +// +//--------------------------------------------------------------------------- + +void P_KillMobj(mobj_t * source, mobj_t * target) +{ + target->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY | MF_NOGRAVITY); + target->flags |= MF_CORPSE | MF_DROPOFF; + target->flags2 &= ~MF2_PASSMOBJ; + target->height >>= 2; + if (source && source->player) + { + if (target->flags & MF_COUNTKILL) + { // Count for intermission + source->player->killcount++; + } + if (target->player) + { // Frag stuff + if (target == source) + { // Self-frag + target->player->frags[target->player - players]--; + } + else + { + source->player->frags[target->player - players]++; + if (source->player == &players[consoleplayer]) + { + S_StartSound(NULL, sfx_gfrag); + } + if (source->player->chickenTics) + { // Make a super chicken + P_GivePower(source->player, pw_weaponlevel2); + } + } + } + } + else if (!netgame && (target->flags & MF_COUNTKILL)) + { // Count all monster deaths + players[0].killcount++; + } + if (target->player) + { + if (!source) + { // Self-frag + target->player->frags[target->player - players]--; + } + target->flags &= ~MF_SOLID; + target->flags2 &= ~MF2_FLY; + target->player->powers[pw_flight] = 0; + target->player->powers[pw_weaponlevel2] = 0; + target->player->playerstate = PST_DEAD; + P_DropWeapon(target->player); + if (target->flags2 & MF2_FIREDAMAGE) + { // Player flame death + P_SetMobjState(target, S_PLAY_FDTH1); + //S_StartSound(target, sfx_hedat1); // Burn sound + return; + } + } + if (target->health < -(target->info->spawnhealth >> 1) + && target->info->xdeathstate) + { // Extreme death + P_SetMobjState(target, target->info->xdeathstate); + } + else + { // Normal death + P_SetMobjState(target, target->info->deathstate); + } + target->tics -= P_Random() & 3; +// I_StartSound(&actor->r, actor->info->deathsound); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_MinotaurSlam +// +//--------------------------------------------------------------------------- + +void P_MinotaurSlam(mobj_t * source, mobj_t * target) +{ + angle_t angle; + fixed_t thrust; + + angle = R_PointToAngle2(source->x, source->y, target->x, target->y); + angle >>= ANGLETOFINESHIFT; + thrust = 16 * FRACUNIT + (P_Random() << 10); + target->momx += FixedMul(thrust, finecosine[angle]); + target->momy += FixedMul(thrust, finesine[angle]); + P_DamageMobj(target, NULL, NULL, HITDICE(6)); + if (target->player) + { + target->reactiontime = 14 + (P_Random() & 7); + } +} + +//--------------------------------------------------------------------------- +// +// FUNC P_TouchWhirlwind +// +//--------------------------------------------------------------------------- + +void P_TouchWhirlwind(mobj_t * target) +{ + int randVal; + + target->angle += (P_Random() - P_Random()) << 20; + target->momx += (P_Random() - P_Random()) << 10; + target->momy += (P_Random() - P_Random()) << 10; + if (leveltime & 16 && !(target->flags2 & MF2_BOSS)) + { + randVal = P_Random(); + if (randVal > 160) + { + randVal = 160; + } + target->momz += randVal << 10; + if (target->momz > 12 * FRACUNIT) + { + target->momz = 12 * FRACUNIT; + } + } + if (!(leveltime & 7)) + { + P_DamageMobj(target, NULL, NULL, 3); + } +} + +//--------------------------------------------------------------------------- +// +// FUNC P_ChickenMorphPlayer +// +// Returns true if the player gets turned into a chicken. +// +//--------------------------------------------------------------------------- + +boolean P_ChickenMorphPlayer(player_t * player) +{ + mobj_t *pmo; + mobj_t *fog; + mobj_t *chicken; + fixed_t x; + fixed_t y; + fixed_t z; + angle_t angle; + int oldFlags2; + + if (player->chickenTics) + { + if ((player->chickenTics < CHICKENTICS - TICRATE) + && !player->powers[pw_weaponlevel2]) + { // Make a super chicken + P_GivePower(player, pw_weaponlevel2); + } + return (false); + } + if (player->powers[pw_invulnerability]) + { // Immune when invulnerable + return (false); + } + pmo = player->mo; + x = pmo->x; + y = pmo->y; + z = pmo->z; + angle = pmo->angle; + oldFlags2 = pmo->flags2; + P_SetMobjState(pmo, S_FREETARGMOBJ); + fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG); + S_StartSound(fog, sfx_telept); + chicken = P_SpawnMobj(x, y, z, MT_CHICPLAYER); + chicken->special1 = player->readyweapon; + chicken->angle = angle; + chicken->player = player; + player->health = chicken->health = MAXCHICKENHEALTH; + player->mo = chicken; + player->armorpoints = player->armortype = 0; + player->powers[pw_invisibility] = 0; + player->powers[pw_weaponlevel2] = 0; + if (oldFlags2 & MF2_FLY) + { + chicken->flags2 |= MF2_FLY; + } + player->chickenTics = CHICKENTICS; + P_ActivateBeak(player); + return (true); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_ChickenMorph +// +//--------------------------------------------------------------------------- + +boolean P_ChickenMorph(mobj_t * actor) +{ + mobj_t *fog; + mobj_t *chicken; + mobj_t *target; + mobjtype_t moType; + fixed_t x; + fixed_t y; + fixed_t z; + angle_t angle; + int ghost; + + if (actor->player) + { + return (false); + } + moType = actor->type; + switch (moType) + { + case MT_POD: + case MT_CHICKEN: + case MT_HEAD: + case MT_MINOTAUR: + case MT_SORCERER1: + case MT_SORCERER2: + return (false); + default: + break; + } + x = actor->x; + y = actor->y; + z = actor->z; + angle = actor->angle; + ghost = actor->flags & MF_SHADOW; + target = actor->target; + P_SetMobjState(actor, S_FREETARGMOBJ); + fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG); + S_StartSound(fog, sfx_telept); + chicken = P_SpawnMobj(x, y, z, MT_CHICKEN); + chicken->special2 = moType; + chicken->special1 = CHICKENTICS + P_Random(); + chicken->flags |= ghost; + chicken->target = target; + chicken->angle = angle; + return (true); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_AutoUseChaosDevice +// +//--------------------------------------------------------------------------- + +boolean P_AutoUseChaosDevice(player_t * player) +{ + int i; + + for (i = 0; i < player->inventorySlotNum; i++) + { + if (player->inventory[i].type == arti_teleport) + { + P_PlayerUseArtifact(player, arti_teleport); + player->health = player->mo->health = (player->health + 1) / 2; + return (true); + } + } + return (false); +} + +//--------------------------------------------------------------------------- +// +// PROC P_AutoUseHealth +// +//--------------------------------------------------------------------------- + +void P_AutoUseHealth(player_t * player, int saveHealth) +{ + int i; + int count; + int normalCount; + int normalSlot; + int superCount; + int superSlot; + + normalCount = 0; + superCount = 0; + normalSlot = 0; + superSlot = 0; + + for (i = 0; i < player->inventorySlotNum; i++) + { + if (player->inventory[i].type == arti_health) + { + normalSlot = i; + normalCount = player->inventory[i].count; + } + else if (player->inventory[i].type == arti_superhealth) + { + superSlot = i; + superCount = player->inventory[i].count; + } + } + if ((gameskill == sk_baby) && (normalCount * 25 >= saveHealth)) + { // Use quartz flasks + count = (saveHealth + 24) / 25; + for (i = 0; i < count; i++) + { + player->health += 25; + P_PlayerRemoveArtifact(player, normalSlot); + } + } + else if (superCount * 100 >= saveHealth) + { // Use mystic urns + count = (saveHealth + 99) / 100; + for (i = 0; i < count; i++) + { + player->health += 100; + P_PlayerRemoveArtifact(player, superSlot); + } + } + else if ((gameskill == sk_baby) + && (superCount * 100 + normalCount * 25 >= saveHealth)) + { // Use mystic urns and quartz flasks + count = (saveHealth + 24) / 25; + saveHealth -= count * 25; + for (i = 0; i < count; i++) + { + player->health += 25; + P_PlayerRemoveArtifact(player, normalSlot); + } + count = (saveHealth + 99) / 100; + for (i = 0; i < count; i++) + { + player->health += 100; + P_PlayerRemoveArtifact(player, normalSlot); + } + } + player->mo->health = player->health; +} + +/* +================= += += P_DamageMobj += += Damages both enemies and players += inflictor is the thing that caused the damage += creature or missile, can be NULL (slime, etc) += source is the thing to target after taking damage += creature or NULL += Source and inflictor are the same for melee attacks += source can be null for 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)) + { + // Shouldn't happen + return; + } + if (target->health <= 0) + { + return; + } + if (target->flags & MF_SKULLFLY) + { + if (target->type == MT_MINOTAUR) + { // Minotaur is invulnerable during charge attack + return; + } + target->momx = target->momy = target->momz = 0; + } + player = target->player; + if (player && gameskill == sk_baby) + { + // Take half damage in trainer mode + damage >>= 1; + } + // Special damage types + if (inflictor) + { + switch (inflictor->type) + { + case MT_EGGFX: + if (player) + { + P_ChickenMorphPlayer(player); + } + else + { + P_ChickenMorph(target); + } + return; // Always return + case MT_WHIRLWIND: + P_TouchWhirlwind(target); + return; + case MT_MINOTAUR: + if (inflictor->flags & MF_SKULLFLY) + { // Slam only when in charge mode + P_MinotaurSlam(inflictor, target); + return; + } + break; + case MT_MACEFX4: // Death ball + if ((target->flags2 & MF2_BOSS) || target->type == MT_HEAD) + { // Don't allow cheap boss kills + break; + } + else if (target->player) + { // Player specific checks + if (target->player->powers[pw_invulnerability]) + { // Can't hurt invulnerable players + break; + } + if (P_AutoUseChaosDevice(target->player)) + { // Player was saved using chaos device + return; + } + } + damage = 10000; // Something's gonna die + break; + case MT_PHOENIXFX2: // Flame thrower + if (target->player && P_Random() < 128) + { // Freeze player for a bit + target->reactiontime += 4; + } + break; + case MT_RAINPLR1: // Rain missiles + case MT_RAINPLR2: + case MT_RAINPLR3: + case MT_RAINPLR4: + if (target->flags2 & MF2_BOSS) + { // Decrease damage for bosses + damage = (P_Random() & 7) + 1; + } + break; + case MT_HORNRODFX2: + case MT_PHOENIXFX1: + if (target->type == MT_SORCERER2 && P_Random() < 96) + { // D'Sparil teleports away + P_DSparilTeleport(target); + return; + } + break; + case MT_BLASTERFX1: + case MT_RIPPER: + if (target->type == MT_HEAD) + { // Less damage to Ironlich bosses + damage = P_Random() & 1; + if (!damage) + { + return; + } + } + break; + default: + break; + } + } + // Push the target unless source is using the gauntlets + if (inflictor && (!source || !source->player + || source->player->readyweapon != wp_gauntlets) + && !(inflictor->flags2 & MF2_NODMGTHRUST)) + { + ang = R_PointToAngle2(inflictor->x, inflictor->y, + target->x, target->y); + //thrust = damage*(FRACUNIT>>3)*100/target->info->mass; + thrust = damage * (FRACUNIT >> 3) * 150 / 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; + if (source && source->player && (source == inflictor) + && source->player->powers[pw_weaponlevel2] + && source->player->readyweapon == wp_staff) + { + // Staff power level 2 + target->momx += FixedMul(10 * FRACUNIT, finecosine[ang]); + target->momy += FixedMul(10 * FRACUNIT, finesine[ang]); + if (!(target->flags & MF_NOGRAVITY)) + { + target->momz += 5 * FRACUNIT; + } + } + else + { + 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; + //} + + if (damage < 1000 && ((player->cheats & CF_GODMODE) + || player->powers[pw_invulnerability])) + { + return; + } + if (player->armortype) + { + if (player->armortype == 1) + { + saved = damage >> 1; + } + else + { + saved = (damage >> 1) + (damage >> 2); + } + if (player->armorpoints <= saved) + { + // armor is used up + saved = player->armorpoints; + player->armortype = 0; + } + player->armorpoints -= saved; + damage -= saved; + } + if (damage >= player->health + && ((gameskill == sk_baby) || deathmatch) && !player->chickenTics) + { // Try to use some inventory health + P_AutoUseHealth(player, damage - player->health + 1); + } + 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); + SB_PaletteFlash(); + } + } + + // + // do the damage + // + target->health -= damage; + if (target->health <= 0) + { // Death + target->special1 = damage; + if (target->type == MT_POD && source && source->type != MT_POD) + { // Make sure players get frags for chain-reaction kills + target->target = source; + } + if (player && inflictor && !player->chickenTics) + { // Check for flame death + if ((inflictor->flags2 & MF2_FIREDAMAGE) + || ((inflictor->type == MT_PHOENIXFX1) + && (target->health > -50) && (damage > 25))) + { + target->flags2 |= MF2_FIREDAMAGE; + } + } + 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 && source && !(source->flags2 & MF2_BOSS) + && !(target->type == MT_SORCERER2 && source->type == MT_WIZARD)) + { + // Target actor is not intent on another actor, + // so make him chase after source + target->target = source; + target->threshold = BASETHRESHOLD; + if (target->state == &states[target->info->spawnstate] + && target->info->seestate != S_NULL) + { + P_SetMobjState(target, target->info->seestate); + } + } +} diff --git a/src/heretic/p_lights.c b/src/heretic/p_lights.c new file mode 100644 index 00000000..5d97a55e --- /dev/null +++ b/src/heretic/p_lights.c @@ -0,0 +1,282 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +#include "doomdef.h" +#include "m_random.h" +#include "p_local.h" +#include "v_video.h" + +//================================================================== +//================================================================== +// +// BROKEN LIGHT FLASHING +// +//================================================================== +//================================================================== + +//================================================================== +// +// T_LightFlash +// +// After the map has been loaded, scan each sector for specials +// that spawn thinkers +// +//================================================================== +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; + + sector->special = 0; // nothing special about it during gameplay + + flash = Z_Malloc(sizeof(*flash), PU_LEVSPEC, 0); + P_AddThinker(&flash->thinker); + flash->thinker.function = 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 +// +// After the map has been loaded, scan each sector for specials that spawn thinkers +// +//================================================================== +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_SpawnLightFlash +// +// 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 = T_StrobeFlash; + flash->maxlight = sector->lightlevel; + flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + + if (flash->minlight == flash->maxlight) + flash->minlight = 0; + sector->special = 0; // nothing special about it during gameplay + + 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 = §ors[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 = T_Glow; + g->direction = -1; + + sector->special = 0; +} diff --git a/src/heretic/p_local.h b/src/heretic/p_local.h new file mode 100644 index 00000000..4fb58236 --- /dev/null +++ b/src/heretic/p_local.h @@ -0,0 +1,287 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_local.h + +#ifndef __P_LOCAL__ +#define __P_LOCAL__ + +#ifndef __R_LOCAL__ +#include "r_local.h" +#endif + +#define STARTREDPALS 1 +#define STARTBONUSPALS 9 +#define NUMREDPALS 8 +#define NUMBONUSPALS 4 + +#define FOOTCLIPSIZE 10*FRACUNIT + +#define TOCENTER -8 +#define FLOATSPEED (FRACUNIT*4) + +#define MAXHEALTH 100 +#define MAXCHICKENHEALTH 30 +#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 don't 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) + +typedef enum +{ + DI_EAST, + DI_NORTHEAST, + DI_NORTH, + DI_NORTHWEST, + DI_WEST, + DI_SOUTHWEST, + DI_SOUTH, + DI_SOUTHEAST, + DI_NODIR, + NUMDIRS +} dirtype_t; + +#define BASETHRESHOLD 100 // follow a player exlusively for 3 seconds + +// ***** P_TICK ***** + +extern thinker_t thinkercap; // both the head and tail of the thinker list +extern int TimerGame; // tic countdown for deathmatch + +void P_InitThinkers(void); +void P_AddThinker(thinker_t * thinker); +void P_RemoveThinker(thinker_t * thinker); + +// ***** P_PSPR ***** + +#define USE_GWND_AMMO_1 1 +#define USE_GWND_AMMO_2 1 +#define USE_CBOW_AMMO_1 1 +#define USE_CBOW_AMMO_2 1 +#define USE_BLSR_AMMO_1 1 +#define USE_BLSR_AMMO_2 5 +#define USE_SKRD_AMMO_1 1 +#define USE_SKRD_AMMO_2 5 +#define USE_PHRD_AMMO_1 1 +#define USE_PHRD_AMMO_2 1 +#define USE_MACE_AMMO_1 1 +#define USE_MACE_AMMO_2 5 + +void P_OpenWeapons(void); +void P_CloseWeapons(void); +void P_AddMaceSpot(mapthing_t * mthing); +void P_RepositionMace(mobj_t * mo); +void P_SetPsprite(player_t * player, int position, statenum_t stnum); +void P_SetupPsprites(player_t * curplayer); +void P_MovePsprites(player_t * curplayer); +void P_DropWeapon(player_t * player); +void P_ActivateBeak(player_t * player); +void P_PostChickenWeapon(player_t * player, weapontype_t weapon); +void P_UpdateBeak(player_t * player, pspdef_t * psp); + +// ***** P_USER ***** + +void P_PlayerThink(player_t * player); +void P_Thrust(player_t * player, angle_t angle, fixed_t move); +void P_PlayerRemoveArtifact(player_t * player, int slot); +void P_PlayerUseArtifact(player_t * player, artitype_t arti); +boolean P_UseArtifact(player_t * player, artitype_t arti); +int P_GetPlayerNum(player_t * player); + +// ***** P_MOBJ ***** + +#define FLOOR_SOLID 0 +#define FLOOR_WATER 1 +#define FLOOR_LAVA 2 +#define FLOOR_SLUDGE 3 + +#define ONFLOORZ INT_MIN +#define ONCEILINGZ INT_MAX +#define FLOATRANDZ (INT_MAX-1) + +extern mobjtype_t PuffType; +extern mobj_t *MissileMobj; + +mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); +void P_RemoveMobj(mobj_t * th); +boolean P_SetMobjState(mobj_t * mobj, statenum_t state); +boolean P_SetMobjStateNF(mobj_t * mobj, statenum_t state); +void P_ThrustMobj(mobj_t * mo, angle_t angle, fixed_t move); +int P_FaceMobj(mobj_t * source, mobj_t * target, angle_t * delta); +boolean P_SeekerMissile(mobj_t * actor, angle_t thresh, angle_t turnMax); +void P_MobjThinker(mobj_t * mobj); +void P_BlasterMobjThinker(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); +void P_BloodSplatter(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator); +void P_RipperBlood(mobj_t * mo); +int P_GetThingFloorType(mobj_t * thing); +int P_HitFloor(mobj_t * thing); +boolean P_CheckMissileSpawn(mobj_t * missile); +mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type); +mobj_t *P_SpawnMissileAngle(mobj_t * source, mobjtype_t type, + angle_t angle, fixed_t momz); +mobj_t *P_SpawnPlayerMissile(mobj_t * source, mobjtype_t type); +mobj_t *P_SPMAngle(mobj_t * source, mobjtype_t type, angle_t angle); + +// ***** P_ENEMY ***** + +void P_NoiseAlert(mobj_t * target, mobj_t * emmiter); +void P_InitMonsters(void); +void P_AddBossSpot(fixed_t x, fixed_t y, angle_t angle); +void P_Massacre(void); +void P_DSparilTeleport(mobj_t * actor); + +// ***** P_MAPUTL ***** + +typedef struct +{ + fixed_t x, y, dx, dy; +} divline_t; + +typedef struct +{ + fixed_t frac; // along trace line + boolean isaline; + union + { + mobj_t *thing; + line_t *line; + } d; +} intercept_t; + +#define MAXINTERCEPTS 128 +extern intercept_t intercepts[MAXINTERCEPTS], *intercept_p; +typedef boolean(*traverser_t) (intercept_t * in); + + +fixed_t P_AproxDistance(fixed_t dx, fixed_t dy); +int P_PointOnLineSide(fixed_t x, fixed_t y, line_t * line); +int P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t * line); +void P_MakeDivline(line_t * li, divline_t * dl); +fixed_t P_InterceptVector(divline_t * v2, divline_t * v1); +int P_BoxOnLineSide(fixed_t * tmbox, line_t * ld); + +extern fixed_t opentop, openbottom, openrange; +extern fixed_t lowfloor; +void P_LineOpening(line_t * linedef); + +boolean P_BlockLinesIterator(int x, int y, boolean(*func) (line_t *)); +boolean P_BlockThingsIterator(int x, int y, boolean(*func) (mobj_t *)); + +#define PT_ADDLINES 1 +#define PT_ADDTHINGS 2 +#define PT_EARLYOUT 4 + +extern divline_t trace; +boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, + int flags, boolean(*trav) (intercept_t *)); + +void P_UnsetThingPosition(mobj_t * thing); +void P_SetThingPosition(mobj_t * thing); + +// ***** P_MAP ***** + +extern boolean floatok; // if true, move would be ok if +extern fixed_t tmfloorz, tmceilingz; // within tmfloorz - tmceilingz + +extern line_t *ceilingline; +boolean P_TestMobjLocation(mobj_t * mobj); +boolean P_CheckPosition(mobj_t * thing, fixed_t x, fixed_t y); +mobj_t *P_CheckOnmobj(mobj_t * thing); +void P_FakeZMovement(mobj_t * mo); +boolean P_TryMove(mobj_t * thing, fixed_t x, fixed_t y); +boolean P_TeleportMove(mobj_t * thing, fixed_t x, fixed_t y); +void P_SlideMove(mobj_t * mo); +boolean P_CheckSight(mobj_t * t1, mobj_t * t2); +void P_UseLines(player_t * player); + +boolean P_ChangeSector(sector_t * sector, boolean crunch); + +extern mobj_t *linetarget; // who got hit (or NULL) +fixed_t P_AimLineAttack(mobj_t * t1, angle_t angle, fixed_t distance); + +void P_LineAttack(mobj_t * t1, angle_t angle, fixed_t distance, fixed_t slope, + int damage); + +void P_RadiusAttack(mobj_t * spot, mobj_t * source, int damage); + +// ***** P_SETUP ***** + +extern byte *rejectmatrix; // for fast sight rejection +extern short *blockmaplump; // offsets in blockmap are from here +extern short *blockmap; +extern int bmapwidth, bmapheight; // in mapblocks +extern fixed_t bmaporgx, bmaporgy; // origin of block map +extern mobj_t **blocklinks; // for thing chains + +// ***** P_INTER ***** + +extern int maxammo[NUMAMMO]; +extern int clipammo[NUMAMMO]; + +void P_SetMessage(player_t * player, char *message, boolean ultmsg); +void P_TouchSpecialThing(mobj_t * special, mobj_t * toucher); +void P_DamageMobj(mobj_t * target, mobj_t * inflictor, mobj_t * source, + int damage); +boolean P_GiveAmmo(player_t * player, ammotype_t ammo, int count); +boolean P_GiveArtifact(player_t * player, artitype_t arti, mobj_t * mo); +boolean P_GiveBody(player_t * player, int num); +boolean P_GivePower(player_t * player, powertype_t power); +boolean P_ChickenMorphPlayer(player_t * player); + +// ***** AM_MAP ***** + +boolean AM_Responder(event_t * ev); +void AM_Ticker(void); +void AM_Drawer(void); + +// ***** SB_BAR ***** + +extern int SB_state; +extern int ArtifactFlash; +void SB_PaletteFlash(void); + +#include "p_spec.h" + +#endif // __P_LOCAL__ diff --git a/src/heretic/p_map.c b/src/heretic/p_map.c new file mode 100644 index 00000000..0b1c6df1 --- /dev/null +++ b/src/heretic/p_map.c @@ -0,0 +1,1698 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// P_map.c + +#include <stdlib.h> + +#include "doomdef.h" +#include "i_system.h" +#include "m_bbox.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +/* +=============================================================================== + +NOTES: + + +=============================================================================== +*/ + +/* +=============================================================================== + +mobj_t NOTES + +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 it's 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 be 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 it's origin contained. + +A valid mobj_t is a mobj_t that has the proper subsector_t filled in for it's +xy coordinates and is linked into the subsector's sector 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. + + +=============================================================================== +*/ + +fixed_t tmbbox[4]; +mobj_t *tmthing; +int tmflags; +fixed_t tmx, tmy; + +boolean floatok; // if true, move would be ok if + // within tmfloorz - tmceilingz + +fixed_t tmfloorz, tmceilingz, tmdropoffz; + +// keep track of the line that lowers the ceiling, so missiles don't explode +// against sky hack walls +line_t *ceilingline; + +// keep track of special lines as they are hit, but don't process them +// until the move is proven valid +#define MAXSPECIALCROSS 8 +line_t *spechit[MAXSPECIALCROSS]; +int numspechit; + +mobj_t *onmobj; //generic global onmobj...used for landing on pods/players + +/* +=============================================================================== + + TELEPORT MOVE + +=============================================================================== +*/ + +/* +================== += += PIT_StompThing += +================== +*/ + +boolean PIT_StompThing(mobj_t * thing) +{ + fixed_t blockdist; + + if (!(thing->flags & MF_SHOOTABLE)) + return true; + + blockdist = thing->radius + tmthing->radius; + if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) + return true; // didn't hit it + + if (thing == tmthing) + return true; // don't clip against self + + if (!(tmthing->flags2 & MF2_TELESTOMP)) + { // Not allowed to stomp things + return (false); + } + + P_DamageMobj(thing, tmthing, tmthing, 10000); + + return true; +} + + +/* +=================== += += P_TeleportMove += +=================== +*/ + +boolean P_TeleportMove(mobj_t * thing, fixed_t x, fixed_t y) +{ + int xl, xh, yl, yh, bx, by; + subsector_t *newsubsec; + +// +// kill anything occupying the position +// + + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector(x, y); + ceilingline = NULL; + +// +// the base floor / ceiling is from the subsector that contains the +// point. Any contacted lines the step closer together will adjust them +// + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + +// +// stomp on any things contacted +// + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockThingsIterator(bx, by, PIT_StompThing)) + return false; + +// +// the move is ok, so link the thing into its new position +// + P_UnsetThingPosition(thing); + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition(thing); + + return true; +} + +/* +=============================================================================== + + MOVEMENT ITERATOR FUNCTIONS + +=============================================================================== +*/ + +/* +================== += += PIT_CheckLine += += Adjusts tmfloorz and tmceilingz as lines are contacted +================== +*/ + +boolean PIT_CheckLine(line_t * ld) +{ + if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] + || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] + || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] + || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP]) + { + return (true); + } + if (P_BoxOnLineSide(tmbbox, ld) != -1) + { + return (true); + } + +// a line has been hit +/* += += The moving thing's destination position will cross the given line. += If this should not be allowed, return false. += If the line is special, keep track of it to process later if the move += is proven ok. NOTE: specials are NOT sorted by order, so two special lines += that are only 8 pixels apart could be crossed in either order. +*/ + + if (!ld->backsector) + { // One sided line + if (tmthing->flags & MF_MISSILE) + { // Missiles can trigger impact specials + if (ld->special) + { + spechit[numspechit] = ld; + numspechit++; + } + } + return false; + } + if (!(tmthing->flags & MF_MISSILE)) + { + if (ld->flags & ML_BLOCKING) + { // Explicitly blocking everything + return (false); + } + if (!tmthing->player && ld->flags & ML_BLOCKMONSTERS + && tmthing->type != MT_POD) + { // Block monsters only + return (false); + } + } + P_LineOpening(ld); // set openrange, opentop, openbottom + // adjust floor / ceiling heights + if (opentop < tmceilingz) + { + tmceilingz = opentop; + ceilingline = ld; + } + if (openbottom > tmfloorz) + { + tmfloorz = openbottom; + } + if (lowfloor < tmdropoffz) + { + tmdropoffz = lowfloor; + } + if (ld->special) + { // Contacted a special line, add it to the list + spechit[numspechit] = ld; + numspechit++; + } + return (true); +} + +//--------------------------------------------------------------------------- +// +// FUNC PIT_CheckThing +// +//--------------------------------------------------------------------------- + +boolean PIT_CheckThing(mobj_t * thing) +{ + fixed_t blockdist; + boolean solid; + int damage; + + if (!(thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE))) + { // Can't hit thing + return (true); + } + blockdist = thing->radius + tmthing->radius; + if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) + { // Didn't hit thing + return (true); + } + if (thing == tmthing) + { // Don't clip against self + return (true); + } + if (tmthing->flags2 & MF2_PASSMOBJ) + { // check if a mobj passed over/under another object + if ((tmthing->type == MT_IMP || tmthing->type == MT_WIZARD) + && (thing->type == MT_IMP || thing->type == MT_WIZARD)) + { // don't let imps/wizards fly over other imps/wizards + return false; + } + if (tmthing->z > thing->z + thing->height + && !(thing->flags & MF_SPECIAL)) + { + return (true); + } + else if (tmthing->z + tmthing->height < thing->z + && !(thing->flags & MF_SPECIAL)) + { // under thing + return (true); + } + } + // Check for skulls slamming into things + if (tmthing->flags & MF_SKULLFLY) + { + damage = ((P_Random() % 8) + 1) * tmthing->damage; + P_DamageMobj(thing, tmthing, tmthing, damage); + tmthing->flags &= ~MF_SKULLFLY; + tmthing->momx = tmthing->momy = tmthing->momz = 0; + P_SetMobjState(tmthing, tmthing->info->seestate); + return (false); + } + // Check for missile + if (tmthing->flags & MF_MISSILE) + { + // Check for passing through a ghost + if ((thing->flags & MF_SHADOW) && (tmthing->flags2 & MF2_THRUGHOST)) + { + return (true); + } + // Check if it went over / under + if (tmthing->z > thing->z + thing->height) + { // Over thing + return (true); + } + if (tmthing->z + tmthing->height < thing->z) + { // Under thing + return (true); + } + if (tmthing->target && tmthing->target->type == thing->type) + { // Don't hit same species as originator + if (thing == tmthing->target) + { // Don't missile self + return (true); + } + if (thing->type != MT_PLAYER) + { // Hit same species as originator, explode, no damage + return (false); + } + } + if (!(thing->flags & MF_SHOOTABLE)) + { // Didn't do any damage + return !(thing->flags & MF_SOLID); + } + if (tmthing->flags2 & MF2_RIP) + { + if (!(thing->flags & MF_NOBLOOD)) + { // Ok to spawn some blood + P_RipperBlood(tmthing); + } + S_StartSound(tmthing, sfx_ripslop); + damage = ((P_Random() & 3) + 2) * tmthing->damage; + P_DamageMobj(thing, tmthing, tmthing->target, damage); + if (thing->flags2 & MF2_PUSHABLE + && !(tmthing->flags2 & MF2_CANNOTPUSH)) + { // Push thing + thing->momx += tmthing->momx >> 2; + thing->momy += tmthing->momy >> 2; + } + numspechit = 0; + return (true); + } + // Do damage + damage = ((P_Random() % 8) + 1) * tmthing->damage; + if (damage) + { + if (!(thing->flags & MF_NOBLOOD) && P_Random() < 192) + { + P_BloodSplatter(tmthing->x, tmthing->y, tmthing->z, thing); + } + P_DamageMobj(thing, tmthing, tmthing->target, damage); + } + return (false); + } + if (thing->flags2 & MF2_PUSHABLE && !(tmthing->flags2 & MF2_CANNOTPUSH)) + { // Push thing + thing->momx += tmthing->momx >> 2; + thing->momy += tmthing->momy >> 2; + } + // Check for special thing + if (thing->flags & MF_SPECIAL) + { + solid = thing->flags & MF_SOLID; + if (tmflags & MF_PICKUP) + { // Can be picked up by tmthing + P_TouchSpecialThing(thing, tmthing); // Can remove thing + } + return (!solid); + } + return (!(thing->flags & MF_SOLID)); +} + +//--------------------------------------------------------------------------- +// +// PIT_CheckOnmobjZ +// +//--------------------------------------------------------------------------- + +boolean PIT_CheckOnmobjZ(mobj_t * thing) +{ + fixed_t blockdist; + + if (!(thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE))) + { // Can't hit thing + return (true); + } + blockdist = thing->radius + tmthing->radius; + if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) + { // Didn't hit thing + return (true); + } + if (thing == tmthing) + { // Don't clip against self + return (true); + } + if (tmthing->z > thing->z + thing->height) + { + return (true); + } + else if (tmthing->z + tmthing->height < thing->z) + { // under thing + return (true); + } + if (thing->flags & MF_SOLID) + { + onmobj = thing; + } + return (!(thing->flags & MF_SOLID)); +} + +/* +=============================================================================== + + MOVEMENT CLIPPING + +=============================================================================== +*/ + +//---------------------------------------------------------------------------- +// +// FUNC P_TestMobjLocation +// +// Returns true if the mobj is not blocked by anything at its current +// location, otherwise returns false. +// +//---------------------------------------------------------------------------- + +boolean P_TestMobjLocation(mobj_t * mobj) +{ + int flags; + + flags = mobj->flags; + mobj->flags &= ~MF_PICKUP; + if (P_CheckPosition(mobj, mobj->x, mobj->y)) + { // XY is ok, now check Z + mobj->flags = flags; + if ((mobj->z < mobj->floorz) + || (mobj->z + mobj->height > mobj->ceilingz)) + { // Bad Z + return (false); + } + return (true); + } + mobj->flags = flags; + return (false); +} + +/* +================== += += P_CheckPosition += += This is purely informative, nothing is modified (except things picked up) + +in: +a mobj_t (can be valid or invalid) +a position to be checked (doesn't need to be related to the mobj_t->x,y) + +during: +special things are touched if MF_PICKUP +early out on solid lines? + +out: +newsubsec +floorz +ceilingz +tmdropoffz the lowest point contacted (monsters won't move to a dropoff) +speciallines[] +numspeciallines + +================== +*/ + +boolean P_CheckPosition(mobj_t * thing, fixed_t x, fixed_t y) +{ + int xl, xh, yl, yh, bx, by; + subsector_t *newsubsec; + + tmthing = thing; + tmflags = thing->flags; + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector(x, y); + ceilingline = NULL; + +// +// the base floor / ceiling is from the subsector that contains the +// point. Any contacted lines the step closer together will adjust them +// + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + if (tmflags & MF_NOCLIP) + return true; + +// +// check things first, possibly picking things up +// the bounding box is extended by MAXRADIUS because mobj_ts are grouped +// into mapblocks based on their origin point, and can overlap into adjacent +// blocks by up to MAXRADIUS units +// + xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockThingsIterator(bx, by, PIT_CheckThing)) + return false; +// +// check lines +// + xl = (tmbbox[BOXLEFT] - bmaporgx) >> MAPBLOCKSHIFT; + xh = (tmbbox[BOXRIGHT] - bmaporgx) >> MAPBLOCKSHIFT; + yl = (tmbbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT; + yh = (tmbbox[BOXTOP] - bmaporgy) >> MAPBLOCKSHIFT; + + for (bx = xl; bx <= xh; bx++) + for (by = yl; by <= yh; by++) + if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) + return false; + + return true; +} + +//============================================================================= +// +// P_CheckOnmobj(mobj_t *thing) +// +// Checks if the new Z position is legal +//============================================================================= + +mobj_t *P_CheckOnmobj(mobj_t * thing) +{ + int xl, xh, yl, yh, bx, by; + subsector_t *newsubsec; + fixed_t x; + fixed_t y; + mobj_t oldmo; + + x = thing->x; + y = thing->y; + tmthing = thing; + tmflags = thing->flags; + oldmo = *thing; // save the old mobj before the fake zmovement + P_FakeZMovement(tmthing); + + tmx = x; + tmy = y; + + tmbbox[BOXTOP] = y + tmthing->radius; + tmbbox[BOXBOTTOM] = y - tmthing->radius; + tmbbox[BOXRIGHT] = x + tmthing->radius; + tmbbox[BOXLEFT] = x - tmthing->radius; + + newsubsec = R_PointInSubsector(x, y); + ceilingline = NULL; + +// +// the base floor / ceiling is from the subsector that contains the +// point. Any contacted lines the step closer together will adjust them +// + tmfloorz = tmdropoffz = newsubsec->sector->floorheight; + tmceilingz = newsubsec->sector->ceilingheight; + + validcount++; + numspechit = 0; + + if (tmflags & MF_NOCLIP) + return NULL; + +// +// 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_CheckOnmobjZ)) + { + *tmthing = oldmo; + return onmobj; + } + *tmthing = oldmo; + return NULL; +} + +//============================================================================= +// +// P_FakeZMovement +// +// Fake the zmovement so that we can check if a move is legal +//============================================================================= + +void P_FakeZMovement(mobj_t * mo) +{ + int dist; + int delta; +// +// 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; + } + } + if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) + && leveltime & 2) + { + mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + } + +// +// clip movement +// + if (mo->z <= mo->floorz) + { // Hit the floor + mo->z = mo->floorz; + if (mo->momz < 0) + { + mo->momz = 0; + } + if (mo->flags & MF_SKULLFLY) + { // The skull slammed into something + mo->momz = -mo->momz; + } + if (mo->info->crashstate && (mo->flags & MF_CORPSE)) + { + return; + } + } + else if (mo->flags2 & MF2_LOGRAV) + { + if (mo->momz == 0) + mo->momz = -(GRAVITY >> 3) * 2; + else + mo->momz -= GRAVITY >> 3; + } + 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; + } + } +} + +//========================================================================== +// +// CheckMissileImpact +// +//========================================================================== + +void CheckMissileImpact(mobj_t * mobj) +{ + int i; + + if (!numspechit || !(mobj->flags & MF_MISSILE) || !mobj->target) + { + return; + } + if (!mobj->target->player) + { + return; + } + for (i = numspechit - 1; i >= 0; i--) + { + P_ShootSpecialLine(mobj->target, spechit[i]); + } +} + +/* +=================== += += P_TryMove += += Attempt to move to a new position, crossing special lines unless MF_TELEPORT += is set += +=================== +*/ + +boolean P_TryMove(mobj_t * thing, fixed_t x, fixed_t y) +{ + fixed_t oldx, oldy; + int side, oldside; + line_t *ld; + + floatok = false; + if (!P_CheckPosition(thing, x, y)) + { // Solid wall or thing + CheckMissileImpact(thing); + return false; + } + if (!(thing->flags & MF_NOCLIP)) + { + if (tmceilingz - tmfloorz < thing->height) + { // Doesn't fit + CheckMissileImpact(thing); + return false; + } + floatok = true; + if (!(thing->flags & MF_TELEPORT) + && tmceilingz - thing->z < thing->height + && !(thing->flags2 & MF2_FLY)) + { // mobj must lower itself to fit + CheckMissileImpact(thing); + return false; + } + if (thing->flags2 & MF2_FLY) + { + if (thing->z + thing->height > tmceilingz) + { + thing->momz = -8 * FRACUNIT; + return false; + } + else if (thing->z < tmfloorz + && tmfloorz - tmdropoffz > 24 * FRACUNIT) + { + thing->momz = 8 * FRACUNIT; + return false; + } + } + if (!(thing->flags & MF_TELEPORT) + // The Minotaur floor fire (MT_MNTRFX2) can step up any amount + && thing->type != MT_MNTRFX2 + && tmfloorz - thing->z > 24 * FRACUNIT) + { // Too big a step up + CheckMissileImpact(thing); + return false; + } + if ((thing->flags & MF_MISSILE) && tmfloorz > thing->z) + { + CheckMissileImpact(thing); + } + if (!(thing->flags & (MF_DROPOFF | MF_FLOAT)) + && tmfloorz - tmdropoffz > 24 * FRACUNIT) + { // Can't move over a dropoff + return false; + } + } + +// +// 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 (thing->flags2 & MF2_FOOTCLIP + && P_GetThingFloorType(thing) != FLOOR_SOLID) + { + thing->flags2 |= MF2_FEETARECLIPPED; + } + else if (thing->flags2 & MF2_FEETARECLIPPED) + { + thing->flags2 &= ~MF2_FEETARECLIPPED; + } + +// +// if any special lines were hit, do the effect +// + if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP))) + while (numspechit--) + { + // see if the line was crossed + ld = spechit[numspechit]; + side = P_PointOnLineSide(thing->x, thing->y, ld); + oldside = P_PointOnLineSide(oldx, oldy, ld); + if (side != oldside) + { + if (ld->special) + P_CrossSpecialLine(ld - lines, oldside, thing); + } + } + + return true; +} + +/* +================== += += P_ThingHeightClip += += Takes a valid thing and adjusts the thing->floorz, thing->ceilingz, += anf possibly thing->z += += This is called for all nearby monsters whenever a sector changes height += += If the thing doesn't fit, the z will be set to the lowest value and += false will be returned +================== +*/ + +boolean P_ThingHeightClip(mobj_t * thing) +{ + boolean onfloor; + + onfloor = (thing->z == thing->floorz); + + P_CheckPosition(thing, thing->x, thing->y); + // what about stranding a monster partially off an edge? + + thing->floorz = tmfloorz; + thing->ceilingz = tmceilingz; + + if (onfloor) + // walking monsters rise and fall with the floor + thing->z = thing->floorz; + else + { // don't adjust a floating monster unless forced to + if (thing->z + thing->height > thing->ceilingz) + thing->z = thing->ceilingz - thing->height; + } + + if (thing->ceilingz - thing->floorz < thing->height) + return false; + + return true; +} + + +/* +============================================================================== + + SLIDE MOVE + +Allows the player to slide along any angled walls + +============================================================================== +*/ + +fixed_t bestslidefrac, secondslidefrac; +line_t *bestslideline, *secondslideline; +mobj_t *slidemo; + +fixed_t tmxmove, 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, moveangle, deltaangle; + fixed_t movelen, newlen; + + + if (ld->slopetype == ST_HORIZONTAL) + { + tmymove = 0; + return; + } + if (ld->slopetype == ST_VERTICAL) + { + tmxmove = 0; + return; + } + + side = P_PointOnLineSide(slidemo->x, slidemo->y, ld); + + lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy); + if (side == 1) + lineangle += ANG180; + moveangle = R_PointToAngle2(0, 0, tmxmove, tmymove); + deltaangle = moveangle - lineangle; + if (deltaangle > ANG180) + deltaangle += ANG180; +// I_Error ("SlideLine: ang>ANG180"); + + lineangle >>= ANGLETOFINESHIFT; + deltaangle >>= ANGLETOFINESHIFT; + + movelen = P_AproxDistance(tmxmove, tmymove); + newlen = FixedMul(movelen, finecosine[deltaangle]); + tmxmove = FixedMul(newlen, finecosine[lineangle]); + tmymove = FixedMul(newlen, finesine[lineangle]); +} + +/* +============== += += PTR_SlideTraverse += +============== +*/ + +boolean PTR_SlideTraverse(intercept_t * in) +{ + line_t *li; + + if (!in->isaline) + I_Error("PTR_SlideTraverse: not a line?"); + + li = in->d.line; + if (!(li->flags & ML_TWOSIDED)) + { + if (P_PointOnLineSide(slidemo->x, slidemo->y, li)) + return true; // don't hit the back side + goto isblocking; + } + + P_LineOpening(li); // set openrange, opentop, openbottom + 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 + + return true; // this line doesn't block movement + +// 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, leady; + fixed_t trailx, traily; + fixed_t newx, 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; + } + + bestslidefrac -= 0x800; // fudge a bit to make sure it doesn't hit + 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 +// + bestslidefrac = FRACUNIT - (bestslidefrac + 0x800); // remainder + if (bestslidefrac > FRACUNIT) + bestslidefrac = FRACUNIT; + if (bestslidefrac <= 0) + return; + + tmxmove = FixedMul(mo->momx, bestslidefrac); + tmymove = FixedMul(mo->momy, bestslidefrac); + + P_HitSlideLine(bestslideline); // clip the moves + + mo->momx = tmxmove; + mo->momy = tmymove; + + if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove)) + { + goto retry; + } +} + + + +/* +============================================================================== + + P_LineAttack + +============================================================================== +*/ + + +mobj_t *linetarget; // who got hit (or NULL) +mobj_t *shootthing; +fixed_t shootz; // height if not aiming up or down + // ???: use slope for monsters? +int la_damage; +fixed_t attackrange; + +fixed_t aimslope; + +extern fixed_t topslope, bottomslope; // slopes to top and bottom of target + +/* +=============================================================================== += += PTR_AimTraverse += += Sets linetaget and aimslope when a target is aimed at +=============================================================================== +*/ + +boolean PTR_AimTraverse(intercept_t * in) +{ + line_t *li; + mobj_t *th; + fixed_t slope, thingtopslope, 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 + if (th->type == MT_POD) + { // Can't auto-aim at pods + return (true); + } + +// check angles to see if the thing can be aimed at + + dist = FixedMul(attackrange, in->frac); + thingtopslope = FixedDiv(th->z + th->height - shootz, dist); + if (thingtopslope < bottomslope) + return true; // shot over the thing + thingbottomslope = FixedDiv(th->z - shootz, dist); + if (thingbottomslope > topslope) + return true; // shot under the thing + +// +// this thing can be hit! +// + if (thingtopslope > topslope) + thingtopslope = topslope; + if (thingbottomslope < bottomslope) + thingbottomslope = bottomslope; + + aimslope = (thingtopslope + thingbottomslope) / 2; + linetarget = th; + + return false; // don't go any farther +} + + +/* +============================================================================== += += PTR_ShootTraverse += +============================================================================== +*/ + +boolean PTR_ShootTraverse(intercept_t * in) +{ + fixed_t x, y, z; + fixed_t frac; + line_t *li; + mobj_t *th; + fixed_t slope; + fixed_t dist; + fixed_t thingtopslope, thingbottomslope; + mobj_t *mo; + + 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; + } + + return true; // shot continues +// +// 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) + { + if (z > li->frontsector->ceilingheight) + return false; // don't shoot the sky! + if (li->backsector && li->backsector->ceilingpic == skyflatnum) + return false; // it's a sky hack wall + } + + P_SpawnPuff(x, y, z); + return false; // don't go any farther + } + +// +// 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 for physical attacks on a ghost +// + if (th->flags & MF_SHADOW && shootthing->player->readyweapon == wp_staff) + { + return (true); + } + +// 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)); + if (PuffType == MT_BLASTERPUFF1) + { // Make blaster big puff + mo = P_SpawnMobj(x, y, z, MT_BLASTERPUFF2); + S_StartSound(mo, sfx_blshit); + } + else + { + P_SpawnPuff(x, y, z); + } + if (la_damage) + { + if (!(in->d.thing->flags & MF_NOBLOOD) && P_Random() < 192) + { + P_BloodSplatter(x, y, z, in->d.thing); + } + P_DamageMobj(th, shootthing, shootthing, la_damage); + } + return (false); // don't go any farther +} + +/* +================= += += P_AimLineAttack += +================= +*/ + +fixed_t P_AimLineAttack(mobj_t * t1, angle_t angle, fixed_t distance) +{ + fixed_t x2, 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; + topslope = 100 * FRACUNIT / 160; // can't shoot outside view angles + bottomslope = -100 * FRACUNIT / 160; + attackrange = distance; + linetarget = NULL; + + P_PathTraverse(t1->x, t1->y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, + PTR_AimTraverse); + + if (linetarget) + return aimslope; + return 0; +} + + + +/* +================= += += P_LineAttack += += if damage == 0, it is just a test trace that will leave linetarget set += +================= +*/ + +void P_LineAttack(mobj_t * t1, angle_t angle, fixed_t distance, fixed_t slope, + int damage) +{ + fixed_t x2, 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; + if (t1->flags2 & MF2_FEETARECLIPPED) + { + shootz -= FOOTCLIPSIZE; + } + attackrange = distance; + aimslope = slope; + + P_PathTraverse(t1->x, t1->y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, + PTR_ShootTraverse); +} + + + +/* +============================================================================== + + USE LINES + +============================================================================== +*/ + +mobj_t *usething; + +boolean PTR_UseTraverse(intercept_t * in) +{ + if (!in->d.line->special) + { + P_LineOpening(in->d.line); + if (openrange <= 0) + { + //S_StartSound (usething, sfx_noway); + return false; // can't use through a wall + } + return true; // not a special line, but keep checking + } + + if (P_PointOnLineSide(usething->x, usething->y, in->d.line) == 1) + return false; // don't use back sides + + P_UseSpecialLine(usething, in->d.line); + + return false; // can't use for than one special line in a row +} + + +/* +================ += += P_UseLines += += Looks for special lines in front of the player to activate +================ +*/ + +void P_UseLines(player_t * player) +{ + int angle; + fixed_t x1, y1, x2, 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 += += Source is the creature that casued the explosion at spot +================= +*/ + +boolean PIT_RadiusAttack(mobj_t * thing) +{ + fixed_t dx, dy, dist; + + if (!(thing->flags & MF_SHOOTABLE)) + { + return true; + } + if (thing->type == MT_MINOTAUR || thing->type == MT_SORCERER1 + || thing->type == MT_SORCERER2) + { // Episode 2 and 3 bosses take no damage from PIT_RadiusAttack + return (true); + } + dx = abs(thing->x - bombspot->x); + dy = abs(thing->y - bombspot->y); + dist = dx > dy ? dx : dy; + dist = (dist - thing->radius) >> FRACBITS; + if (dist < 0) + { + dist = 0; + } + if (dist >= bombdamage) + { // Out of range + return true; + } + if (P_CheckSight(thing, bombspot)) + { // OK to damage, target is in direct path + P_DamageMobj(thing, bombspot, bombsource, bombdamage - dist); + } + return (true); +} + +/* +================= += += P_RadiusAttack += += Source is the creature that casued the explosion at spot +================= +*/ + +void P_RadiusAttack(mobj_t * spot, mobj_t * source, int damage) +{ + int x, y, xl, xh, yl, 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; + if (spot->type == MT_POD && spot->target) + { + bombsource = spot->target; + } + else + { + bombsource = source; + } + bombdamage = damage; + for (y = yl; y <= yh; y++) + for (x = xl; x <= xh; x++) + P_BlockThingsIterator(x, y, PIT_RadiusAttack); +} + + +/* +============================================================================== + + SECTOR HEIGHT CHANGING + += After modifying a sectors floor or ceiling height, call this += routine to adjust the positions of all things that touch the += sector. += += If anything doesn't fit anymore, true will be returned. += If crunch is true, they will take damage as they are being crushed += If Crunch is false, you should set the sector height back the way it += was and call P_ChangeSector again to undo the changes +============================================================================== +*/ + +boolean crushchange; +boolean nofit; + +/* +=============== += += PIT_ChangeSector += +=============== +*/ + +boolean PIT_ChangeSector(mobj_t * thing) +{ + mobj_t *mo; + + if (P_ThingHeightClip(thing)) + return true; // keep checking + + // crunch bodies to giblets + if (thing->health <= 0) + { + //P_SetMobjState (thing, S_GIBS); + thing->height = 0; + thing->radius = 0; + return true; // keep checking + } + + // crunch dropped items + if (thing->flags & MF_DROPPED) + { + P_RemoveMobj(thing); + return true; // keep checking + } + + if (!(thing->flags & MF_SHOOTABLE)) + return true; // assume it is bloody gibs or something + + nofit = true; + if (crushchange && !(leveltime & 3)) + { + P_DamageMobj(thing, NULL, NULL, 10); + // spray blood in a random direction + mo = P_SpawnMobj(thing->x, thing->y, thing->z + thing->height / 2, + MT_BLOOD); + mo->momx = (P_Random() - P_Random()) << 12; + mo->momy = (P_Random() - P_Random()) << 12; + } + + return true; // keep checking (crush other things) +} + +/* +=============== += += P_ChangeSector += +=============== +*/ + +boolean P_ChangeSector(sector_t * sector, boolean crunch) +{ + int x, y; + + nofit = false; + crushchange = crunch; + +// recheck 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; +} diff --git a/src/heretic/p_maputl.c b/src/heretic/p_maputl.c new file mode 100644 index 00000000..3d848af2 --- /dev/null +++ b/src/heretic/p_maputl.c @@ -0,0 +1,784 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_maputl.c + +#include <stdlib.h> + +#include "doomdef.h" +#include "m_bbox.h" +#include "p_local.h" + + +/* +=================== += += P_AproxDistance += += Gives an estimation of distance (not exact) += +=================== +*/ + +fixed_t P_AproxDistance(fixed_t dx, fixed_t dy) +{ + dx = abs(dx); + dy = 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, dy; + fixed_t left, 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 = 0, p2 = 0; + + 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, dy; + fixed_t left, 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) +{ +#if 1 + fixed_t frac, num, 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; +#else + float frac, num, den, v1x, v1y, v1dx, v1dy, v2x, v2y, v2dx, v2dy; + + v1x = (float) v1->x / FRACUNIT; + v1y = (float) v1->y / FRACUNIT; + v1dx = (float) v1->dx / FRACUNIT; + v1dy = (float) v1->dy / FRACUNIT; + v2x = (float) v2->x / FRACUNIT; + v2y = (float) v2->y / FRACUNIT; + v2dx = (float) v2->dx / FRACUNIT; + v2dy = (float) v2->dy / FRACUNIT; + + den = v1dy * v2dx - v1dx * v2dy; + if (den == 0) + return 0; // parallel + num = (v1x - v2x) * v1dy + (v2y - v1y) * v1dx; + frac = num / den; + + return frac * FRACUNIT; +#endif +} + +/* +================== += += P_LineOpening += += Sets opentop and openbottom to the window through a two sided line += OPTIMIZE: keep this precalculated +================== +*/ + +fixed_t opentop, openbottom, openrange; +fixed_t lowfloor; + +void P_LineOpening(line_t * linedef) +{ + sector_t *front, *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 += +=================== +*/ + +void P_UnsetThingPosition(mobj_t * thing) +{ + int blockx, 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, 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 = NULL; + 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 = NULL; + thing->bnext = *link; + if (*link) + (*link)->bprev = thing; + *link = thing; + } + else + { // thing is off the map + thing->bnext = thing->bprev = NULL; + } + } +} + + + +/* +=============================================================================== + + BLOCK MAP ITERATORS + +For each line/thing in the given mapblock, call the passed 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 +=================== +*/ + +boolean P_BlockLinesIterator(int x, int y, 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 += +================== +*/ + +boolean P_BlockThingsIterator(int x, int y, 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 + +=============================================================================== +*/ + +intercept_t intercepts[MAXINTERCEPTS], *intercept_p; + +divline_t trace; +boolean earlyout; +int ptflags; + +/* +================== += += 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 +================== +*/ + +boolean PIT_AddLineIntercepts(line_t * ld) +{ + int s1, 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 += +================== +*/ + +boolean PIT_AddThingIntercepts(mobj_t * thing) +{ + fixed_t x1, y1, x2, y2; + int s1, s2; + 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 +==================== +*/ + +boolean P_TraverseIntercepts(traverser_t func, fixed_t maxfrac) +{ + int count; + fixed_t dist; + intercept_t *scan, *in; + + count = intercept_p - intercepts; + in = 0; // shut up compiler warning + + while (count--) + { + dist = INT_MAX; + 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 + { // don't check these yet, ther 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 = INT_MAX; + } + + 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 +================== +*/ + +boolean P_PathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, + int flags, boolean(*trav) (intercept_t *)) +{ + fixed_t xt1, yt1, xt2, yt2; + fixed_t xstep, ystep; + fixed_t partial; + fixed_t xintercept, yintercept; + int mapx, mapy, mapxstep, 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, abs(x2 - x1)); + } + else if (xt2 < xt1) + { + mapxstep = -1; + partial = (x1 >> MAPBTOFRAC) & (FRACUNIT - 1); + ystep = FixedDiv(y2 - y1, 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, abs(y2 - y1)); + } + else if (yt2 < yt1) + { + mapystep = -1; + partial = (y1 >> MAPBTOFRAC) & (FRACUNIT - 1); + xstep = FixedDiv(x2 - x1, 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); +} diff --git a/src/heretic/p_mobj.c b/src/heretic/p_mobj.c new file mode 100644 index 00000000..e07ecf45 --- /dev/null +++ b/src/heretic/p_mobj.c @@ -0,0 +1,1633 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_mobj.c + +#include "doomdef.h" +#include "i_system.h" +#include "m_random.h" +#include "p_local.h" +#include "sounds.h" +#include "s_sound.h" + +void G_PlayerReborn(int player); +void P_SpawnMapThing(mapthing_t * mthing); + +mobjtype_t PuffType; +mobj_t *MissileMobj; + +static fixed_t FloatBobOffsets[64] = { + 0, 51389, 102283, 152192, + 200636, 247147, 291278, 332604, + 370727, 405280, 435929, 462380, + 484378, 501712, 514213, 521763, + 524287, 521763, 514213, 501712, + 484378, 462380, 435929, 405280, + 370727, 332604, 291278, 247147, + 200636, 152192, 102283, 51389, + -1, -51390, -102284, -152193, + -200637, -247148, -291279, -332605, + -370728, -405281, -435930, -462381, + -484380, -501713, -514215, -521764, + -524288, -521764, -514214, -501713, + -484379, -462381, -435930, -405280, + -370728, -332605, -291279, -247148, + -200637, -152193, -102284, -51389 +}; + +//---------------------------------------------------------------------------- +// +// FUNC P_SetMobjState +// +// Returns true if the mobj is still present. +// +//---------------------------------------------------------------------------- + +boolean P_SetMobjState(mobj_t * mobj, statenum_t state) +{ + state_t *st; + + if (state == S_NULL) + { // Remove mobj + 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; + if (st->action) + { // Call action function + st->action(mobj); + } + return (true); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_SetMobjStateNF +// +// Same as P_SetMobjState, but does not call the state function. +// +//---------------------------------------------------------------------------- + +boolean P_SetMobjStateNF(mobj_t * mobj, statenum_t state) +{ + state_t *st; + + if (state == S_NULL) + { // Remove mobj + 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; + return (true); +} + +//---------------------------------------------------------------------------- +// +// PROC P_ExplodeMissile +// +//---------------------------------------------------------------------------- + +void P_ExplodeMissile(mobj_t * mo) +{ + if (mo->type == MT_WHIRLWIND) + { + if (++mo->special2 < 60) + { + return; + } + } + mo->momx = mo->momy = mo->momz = 0; + P_SetMobjState(mo, mobjinfo[mo->type].deathstate); + //mo->tics -= P_Random()&3; + mo->flags &= ~MF_MISSILE; + if (mo->info->deathsound) + { + S_StartSound(mo, mo->info->deathsound); + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_FloorBounceMissile +// +//---------------------------------------------------------------------------- + +void P_FloorBounceMissile(mobj_t * mo) +{ + mo->momz = -mo->momz; + P_SetMobjState(mo, mobjinfo[mo->type].deathstate); +} + +//---------------------------------------------------------------------------- +// +// PROC P_ThrustMobj +// +//---------------------------------------------------------------------------- + +void P_ThrustMobj(mobj_t * mo, angle_t angle, fixed_t move) +{ + angle >>= ANGLETOFINESHIFT; + mo->momx += FixedMul(move, finecosine[angle]); + mo->momy += FixedMul(move, finesine[angle]); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_FaceMobj +// +// Returns 1 if 'source' needs to turn clockwise, or 0 if 'source' needs +// to turn counter clockwise. 'delta' is set to the amount 'source' +// needs to turn. +// +//---------------------------------------------------------------------------- + +int P_FaceMobj(mobj_t * source, mobj_t * target, angle_t * delta) +{ + angle_t diff; + angle_t angle1; + angle_t angle2; + + angle1 = source->angle; + angle2 = R_PointToAngle2(source->x, source->y, target->x, target->y); + if (angle2 > angle1) + { + diff = angle2 - angle1; + if (diff > ANG180) + { + *delta = ANG_MAX - diff; + return (0); + } + else + { + *delta = diff; + return (1); + } + } + else + { + diff = angle1 - angle2; + if (diff > ANG180) + { + *delta = ANG_MAX - diff; + return (1); + } + else + { + *delta = diff; + return (0); + } + } +} + +//---------------------------------------------------------------------------- +// +// FUNC P_SeekerMissile +// +// The missile special1 field must be mobj_t *target. Returns true if +// target was tracked, false if not. +// +//---------------------------------------------------------------------------- + +boolean P_SeekerMissile(mobj_t * actor, angle_t thresh, angle_t turnMax) +{ + int dir; + int dist; + angle_t delta; + angle_t angle; + mobj_t *target; + + target = (mobj_t *) actor->special1; + if (target == NULL) + { + return (false); + } + if (!(target->flags & MF_SHOOTABLE)) + { // Target died + actor->special1 = 0; + return (false); + } + dir = P_FaceMobj(actor, target, &delta); + if (delta > thresh) + { + delta >>= 1; + if (delta > turnMax) + { + delta = turnMax; + } + } + if (dir) + { // Turn clockwise + actor->angle += delta; + } + else + { // Turn counter clockwise + actor->angle -= delta; + } + angle = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul(actor->info->speed, finecosine[angle]); + actor->momy = FixedMul(actor->info->speed, finesine[angle]); + if (actor->z + actor->height < target->z || + target->z + target->height < actor->z) + { // Need to seek vertically + dist = P_AproxDistance(target->x - actor->x, target->y - actor->y); + dist = dist / actor->info->speed; + if (dist < 1) + { + dist = 1; + } + actor->momz = (target->z - actor->z) / dist; + } + return (true); +} + +//---------------------------------------------------------------------------- +// +// PROC P_XYMovement +// +//---------------------------------------------------------------------------- + +#define STOPSPEED 0x1000 +#define FRICTION_NORMAL 0xe800 +#define FRICTION_LOW 0xf900 +#define FRICTION_FLY 0xeb00 + +void P_XYMovement(mobj_t * mo) +{ + fixed_t ptryx, ptryy; + player_t *player; + fixed_t xmove, ymove; + int special; + static int windTab[3] = { 2048 * 5, 2048 * 10, 2048 * 25 }; + + if (!mo->momx && !mo->momy) + { + if (mo->flags & MF_SKULLFLY) + { // A flying mobj slammed into something + mo->flags &= ~MF_SKULLFLY; + mo->momx = mo->momy = mo->momz = 0; + P_SetMobjState(mo, mo->info->seestate); + } + return; + } + special = mo->subsector->sector->special; + if (mo->flags2 & MF2_WINDTHRUST) + { + switch (special) + { + case 40: + case 41: + case 42: // Wind_East + P_ThrustMobj(mo, 0, windTab[special - 40]); + break; + case 43: + case 44: + case 45: // Wind_North + P_ThrustMobj(mo, ANG90, windTab[special - 43]); + break; + case 46: + case 47: + case 48: // Wind_South + P_ThrustMobj(mo, ANG270, windTab[special - 46]); + break; + case 49: + case 50: + case 51: // Wind_West + P_ThrustMobj(mo, ANG180, windTab[special - 49]); + break; + } + } + 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->flags2 & MF2_SLIDE) + { // 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 + if (mo->type == MT_BLOODYSKULL) + { + mo->momx = mo->momy = 0; + mo->momz = -FRACUNIT; + } + else + { + P_RemoveMobj(mo); + } + return; + } + P_ExplodeMissile(mo); + } + //else if(mo->info->crashstate) + //{ + // mo->momx = mo->momy = 0; + // P_SetMobjState(mo, mo->info->crashstate); + // return; + //} + else + { + mo->momx = mo->momy = 0; + } + } + } + while (xmove || ymove); + + // Friction + + 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)) + { // No friction for missiles + return; + } + if (mo->z > mo->floorz && !(mo->flags2 & MF2_FLY) + && !(mo->flags2 & MF2_ONMOBJ)) + { // No friction when falling + return; + } + if (mo->flags & MF_CORPSE) + { // Don't 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) + { + if (player->chickenTics) + { + if ((unsigned) ((player->mo->state - states) + - S_CHICPLAY_RUN1) < 4) + { + P_SetMobjState(player->mo, S_CHICPLAY); + } + } + else + { + if ((unsigned) ((player->mo->state - states) + - S_PLAY_RUN1) < 4) + { + P_SetMobjState(player->mo, S_PLAY); + } + } + } + mo->momx = 0; + mo->momy = 0; + } + else + { + if (mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) + && !(mo->flags2 & MF2_ONMOBJ)) + { + mo->momx = FixedMul(mo->momx, FRICTION_FLY); + mo->momy = FixedMul(mo->momy, FRICTION_FLY); + } + else if (special == 15) // Friction_Low + { + mo->momx = FixedMul(mo->momx, FRICTION_LOW); + mo->momy = FixedMul(mo->momy, FRICTION_LOW); + } + else + { + mo->momx = FixedMul(mo->momx, FRICTION_NORMAL); + mo->momy = FixedMul(mo->momy, FRICTION_NORMAL); + } + } +} + + +/* +=============== += += P_ZMovement += +=============== +*/ + +void P_ZMovement(mobj_t * mo) +{ + int dist; + int 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; + } + } + if (mo->player && mo->flags2 & MF2_FLY && !(mo->z <= mo->floorz) + && leveltime & 2) + { + mo->z += finesine[(FINEANGLES / 20 * leveltime >> 2) & FINEMASK]; + } + +// +// clip movement +// + if (mo->z <= mo->floorz) + { // Hit the floor + if (mo->flags & MF_MISSILE) + { + mo->z = mo->floorz; + if (mo->flags2 & MF2_FLOORBOUNCE) + { + P_FloorBounceMissile(mo); + return; + } + else if (mo->type == MT_MNTRFX2) + { // Minotaur floor fire can go up steps + return; + } + else + { + P_ExplodeMissile(mo); + return; + } + } + if (mo->z - mo->momz > mo->floorz) + { // Spawn splashes, etc. + P_HitFloor(mo); + } + mo->z = mo->floorz; + if (mo->momz < 0) + { + if (mo->player && mo->momz < -GRAVITY * 8 && !(mo->flags2 & MF2_FLY)) // squat down + { + mo->player->deltaviewheight = mo->momz >> 3; + S_StartSound(mo, sfx_plroof); + // haleyjd: removed externdriver crap + mo->player->centering = true; + } + mo->momz = 0; + } + if (mo->flags & MF_SKULLFLY) + { // The skull slammed into something + mo->momz = -mo->momz; + } + if (mo->info->crashstate && (mo->flags & MF_CORPSE)) + { + P_SetMobjState(mo, mo->info->crashstate); + return; + } + } + else if (mo->flags2 & MF2_LOGRAV) + { + if (mo->momz == 0) + mo->momz = -(GRAVITY >> 3) * 2; + else + mo->momz -= GRAVITY >> 3; + } + 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) + { + if (mo->subsector->sector->ceilingpic == skyflatnum) + { + if (mo->type == MT_BLOODYSKULL) + { + mo->momx = mo->momy = 0; + mo->momz = -FRACUNIT; + } + else + { + P_RemoveMobj(mo); + } + return; + } + P_ExplodeMissile(mo); + return; + } + } +} + + +/* +================ += += P_NightmareRespawn += +================ +*/ + +void P_NightmareRespawn(mobj_t * mobj) +{ + fixed_t x, y, z; + subsector_t *ss; + mobj_t *mo; + mapthing_t *mthing; + + x = mobj->spawnpoint.x << FRACBITS; + y = mobj->spawnpoint.y << FRACBITS; + + if (!P_CheckPosition(mobj, x, y)) + return; // somthing is occupying it's position + + +// spawn a teleport fog at old spot + + mo = P_SpawnMobj(mobj->x, mobj->y, + mobj->subsector->sector->floorheight + TELEFOGHEIGHT, + MT_TFOG); + 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 + TELEFOGHEIGHT, 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; + 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); +} + +//---------------------------------------------------------------------------- +// +// PROC P_BlasterMobjThinker +// +// Thinker for the ultra-fast blaster PL2 ripper-spawning missile. +// +//---------------------------------------------------------------------------- + +void P_BlasterMobjThinker(mobj_t * mobj) +{ + int i; + fixed_t xfrac; + fixed_t yfrac; + fixed_t zfrac; + fixed_t z; + boolean changexy; + + // Handle movement + if (mobj->momx || mobj->momy || (mobj->z != mobj->floorz) || mobj->momz) + { + xfrac = mobj->momx >> 3; + yfrac = mobj->momy >> 3; + zfrac = mobj->momz >> 3; + changexy = xfrac || yfrac; + for (i = 0; i < 8; i++) + { + if (changexy) + { + if (!P_TryMove(mobj, mobj->x + xfrac, mobj->y + yfrac)) + { // Blocked move + P_ExplodeMissile(mobj); + return; + } + } + mobj->z += zfrac; + if (mobj->z <= mobj->floorz) + { // Hit the floor + mobj->z = mobj->floorz; + P_HitFloor(mobj); + P_ExplodeMissile(mobj); + return; + } + if (mobj->z + mobj->height > mobj->ceilingz) + { // Hit the ceiling + mobj->z = mobj->ceilingz - mobj->height; + P_ExplodeMissile(mobj); + return; + } + if (changexy && (P_Random() < 64)) + { + z = mobj->z - 8 * FRACUNIT; + if (z < mobj->floorz) + { + z = mobj->floorz; + } + P_SpawnMobj(mobj->x, mobj->y, z, MT_BLASTERSMOKE); + } + } + } + // Advance the state + if (mobj->tics != -1) + { + mobj->tics--; + while (!mobj->tics) + { + if (!P_SetMobjState(mobj, mobj->state->nextstate)) + { // mobj was removed + return; + } + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_MobjThinker +// +//---------------------------------------------------------------------------- + +void P_MobjThinker(mobj_t * mobj) +{ + mobj_t *onmo; + + // Handle X and Y momentums + if (mobj->momx || mobj->momy || (mobj->flags & MF_SKULLFLY)) + { + P_XYMovement(mobj); + if (mobj->thinker.function == (think_t) - 1) + { // mobj was removed + return; + } + } + if (mobj->flags2 & MF2_FLOATBOB) + { // Floating item bobbing motion + mobj->z = mobj->floorz + FloatBobOffsets[(mobj->health++) & 63]; + } + else if ((mobj->z != mobj->floorz) || mobj->momz) + { // Handle Z momentum and gravity + if (mobj->flags2 & MF2_PASSMOBJ) + { + if (!(onmo = P_CheckOnmobj(mobj))) + { + P_ZMovement(mobj); + } + else + { + if (mobj->player && mobj->momz < 0) + { + mobj->flags2 |= MF2_ONMOBJ; + mobj->momz = 0; + } + if (mobj->player && (onmo->player || onmo->type == MT_POD)) + { + mobj->momx = onmo->momx; + mobj->momy = onmo->momy; + if (onmo->z < onmo->floorz) + { + mobj->z += onmo->floorz - onmo->z; + if (onmo->player) + { + onmo->player->viewheight -= + onmo->floorz - onmo->z; + onmo->player->deltaviewheight = + (VIEWHEIGHT - onmo->player->viewheight) >> 3; + } + onmo->z = onmo->floorz; + } + } + } + } + else + { + P_ZMovement(mobj); + } + if (mobj->thinker.function == (think_t) - 1) + { // mobj was removed + return; + } + } + +// +// cycle through states, calling action functions at transitions +// + if (mobj->tics != -1) + { + mobj->tics--; + // you can cycle through multiple states in a tic + while (!mobj->tics) + { + if (!P_SetMobjState(mobj, mobj->state->nextstate)) + { // mobj was removed + return; + } + } + } + else + { // Check for monster 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; + fixed_t space; + + mobj = Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + memset(mobj, 0, sizeof(*mobj)); + info = &mobjinfo[type]; + mobj->type = type; + mobj->info = info; + mobj->x = x; + mobj->y = y; + mobj->radius = info->radius; + mobj->height = info->height; + mobj->flags = info->flags; + mobj->flags2 = info->flags2; + mobj->damage = info->damage; + mobj->health = info->spawnhealth; + if (gameskill != sk_nightmare) + { + mobj->reactiontime = info->reactiontime; + } + mobj->lastlook = P_Random() % MAXPLAYERS; + + // Set the state, but do not use P_SetMobjState, because action + // routines can't be called yet. If the spawnstate has an action + // routine, it will not be called. + 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 if (z == FLOATRANDZ) + { + space = ((mobj->ceilingz) - (mobj->info->height)) - mobj->floorz; + if (space > 48 * FRACUNIT) + { + space -= 40 * FRACUNIT; + mobj->z = + ((space * P_Random()) >> 8) + mobj->floorz + 40 * FRACUNIT; + } + else + { + mobj->z = mobj->floorz; + } + } + else + { + mobj->z = z; + } + if (mobj->flags2 & MF2_FOOTCLIP + && P_GetThingFloorType(mobj) != FLOOR_SOLID + && mobj->floorz == mobj->subsector->sector->floorheight) + { + mobj->flags2 |= MF2_FEETARECLIPPED; + } + else + { + mobj->flags2 &= ~MF2_FEETARECLIPPED; + } + + mobj->thinker.function = P_MobjThinker; + P_AddThinker(&mobj->thinker); + return (mobj); +} + +/* +=============== += += P_RemoveMobj += +=============== +*/ + +void P_RemoveMobj(mobj_t * mobj) +{ +// unlink from sector and block lists + P_UnsetThingPosition(mobj); +// stop any playing sound + S_StopSound(mobj); +// free block + P_RemoveThinker((thinker_t *) mobj); +} + +//============================================================================= + + +/* +============ += += 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, y, z; + mobj_t *mobj; + int i; + extern int playerkeys; + + if (!playeringame[mthing->type - 1]) + return; // not playing + + 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); + if (mthing->type > 1) // set color translations for player sprites + mobj->flags |= (mthing->type - 1) << MF_TRANSSHIFT; + + mobj->angle = ANG45 * (mthing->angle / 45); + mobj->player = p; + mobj->health = p->health; + p->mo = mobj; + p->playerstate = PST_LIVE; + p->refire = 0; + p->message = NULL; + p->damagecount = 0; + p->bonuscount = 0; + p->chickenTics = 0; + p->rain1 = NULL; + p->rain2 = NULL; + p->extralight = 0; + p->fixedcolormap = 0; + p->viewheight = VIEWHEIGHT; + P_SetupPsprites(p); // setup gun psprite + if (deathmatch) + { // Give all keys in death match mode + for (i = 0; i < NUMKEYS; i++) + { + p->keys[i] = true; + if (p == &players[consoleplayer]) + { + playerkeys = 7; + UpdateState |= I_STATBAR; + } + } + } + else if (p == &players[consoleplayer]) + { + playerkeys = 0; + UpdateState |= I_STATBAR; + } +} + +//---------------------------------------------------------------------------- +// +// PROC 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, y, z; + +// count deathmatch start positions + if (mthing->type == 11) + { + if (deathmatch_p < &deathmatchstarts[10]) + { + memcpy(deathmatch_p, mthing, sizeof(*mthing)); + deathmatch_p++; + } + return; + } + +// check for players specially + if (mthing->type <= 4) + { + // save spots for respawning in network games + playerstarts[mthing->type - 1] = *mthing; + if (!deathmatch) + { + P_SpawnPlayer(mthing); + } + return; + } + + // Ambient sound sequences + if (mthing->type >= 1200 && mthing->type < 1300) + { + P_AddAmbientSfx(mthing->type - 1200); + return; + } + + // Check for boss spots + if (mthing->type == 56) // Monster_BossSpot + { + P_AddBossSpot(mthing->x << FRACBITS, mthing->y << FRACBITS, + ANG45 * (mthing->angle / 45)); + return; + } + +// check for apropriate skill level + if (!netgame && (mthing->options & 16)) + return; + + if (gameskill == sk_baby) + bit = 1; + else if (gameskill == sk_nightmare) + bit = 4; + else + bit = 1 << (gameskill - 1); + if (!(mthing->options & bit)) + return; + +// find which type to spawn + for (i = 0; i < NUMMOBJTYPES; i++) + if (mthing->type == mobjinfo[i].doomednum) + break; + + if (i == NUMMOBJTYPES) + I_Error("P_SpawnMapThing: Unknown type %i at (%i, %i)", mthing->type, + mthing->x, mthing->y); + +// don't spawn keys and players in deathmatch + if (deathmatch && mobjinfo[i].flags & MF_NOTDMATCH) + return; + +// don't spawn any monsters if -nomonsters + if (nomonsters && (mobjinfo[i].flags & MF_COUNTKILL)) + return; + +// spawn it + switch (i) + { // Special stuff + case MT_WSKULLROD: + case MT_WPHOENIXROD: + case MT_AMSKRDWIMPY: + case MT_AMSKRDHEFTY: + case MT_AMPHRDWIMPY: + case MT_AMPHRDHEFTY: + case MT_AMMACEWIMPY: + case MT_AMMACEHEFTY: + case MT_ARTISUPERHEAL: + case MT_ARTITELEPORT: + case MT_ITEMSHIELD2: + if (gamemode == shareware) + { // Don't place on map in shareware version + return; + } + break; + case MT_WMACE: + if (gamemode != shareware) + { // Put in the mace spot list + P_AddMaceSpot(mthing); + return; + } + return; + default: + break; + } + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + if (mobjinfo[i].flags & MF_SPAWNCEILING) + { + z = ONCEILINGZ; + } + else if (mobjinfo[i].flags2 & MF2_SPAWNFLOAT) + { + z = FLOATRANDZ; + } + else + { + z = ONFLOORZ; + } + mobj = P_SpawnMobj(x, y, z, i); + if (mobj->flags2 & MF2_FLOATBOB) + { // Seed random starting index for bobbing motion + mobj->health = P_Random(); + } + if (mobj->tics > 0) + { + mobj->tics = 1 + (P_Random() % mobj->tics); + } + if (mobj->flags & MF_COUNTKILL) + { + totalkills++; + mobj->spawnpoint = *mthing; + } + if (mobj->flags & MF_COUNTITEM) + { + totalitems++; + } + mobj->angle = ANG45 * (mthing->angle / 45); + if (mthing->options & MTF_AMBUSH) + { + mobj->flags |= MF_AMBUSH; + } +} + +/* +=============================================================================== + + GAME SPAWN FUNCTIONS + +=============================================================================== +*/ + +//--------------------------------------------------------------------------- +// +// PROC P_SpawnPuff +// +//--------------------------------------------------------------------------- + +extern fixed_t attackrange; + +void P_SpawnPuff(fixed_t x, fixed_t y, fixed_t z) +{ + mobj_t *puff; + + z += ((P_Random() - P_Random()) << 10); + puff = P_SpawnMobj(x, y, z, PuffType); + if (puff->info->attacksound) + { + S_StartSound(puff, puff->info->attacksound); + } + switch (PuffType) + { + case MT_BEAKPUFF: + case MT_STAFFPUFF: + puff->momz = FRACUNIT; + break; + case MT_GAUNTLETPUFF1: + case MT_GAUNTLETPUFF2: + puff->momz = (fixed_t)(.8 * FRACUNIT); + default: + break; + } +} + +/* +================ += += 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 (damage <= 12 && damage >= 9) + P_SetMobjState (th,S_BLOOD2); + else if (damage < 9) + P_SetMobjState (th,S_BLOOD3); +} +*/ + +//--------------------------------------------------------------------------- +// +// PROC P_BloodSplatter +// +//--------------------------------------------------------------------------- + +void P_BloodSplatter(fixed_t x, fixed_t y, fixed_t z, mobj_t * originator) +{ + mobj_t *mo; + + mo = P_SpawnMobj(x, y, z, MT_BLOODSPLATTER); + mo->target = originator; + mo->momx = (P_Random() - P_Random()) << 9; + mo->momy = (P_Random() - P_Random()) << 9; + mo->momz = FRACUNIT * 2; +} + +//--------------------------------------------------------------------------- +// +// PROC P_RipperBlood +// +//--------------------------------------------------------------------------- + +void P_RipperBlood(mobj_t * mo) +{ + mobj_t *th; + fixed_t x, y, z; + + x = mo->x + ((P_Random() - P_Random()) << 12); + y = mo->y + ((P_Random() - P_Random()) << 12); + z = mo->z + ((P_Random() - P_Random()) << 12); + th = P_SpawnMobj(x, y, z, MT_BLOOD); + th->flags |= MF_NOGRAVITY; + th->momx = mo->momx >> 1; + th->momy = mo->momy >> 1; + th->tics += P_Random() & 3; +} + +//--------------------------------------------------------------------------- +// +// FUNC P_GetThingFloorType +// +//--------------------------------------------------------------------------- + +int P_GetThingFloorType(mobj_t * thing) +{ + return (TerrainTypes[thing->subsector->sector->floorpic]); +/* + if(thing->subsector->sector->floorpic + == W_GetNumForName("FLTWAWA1")-firstflat) + { + return(FLOOR_WATER); + } + else + { + return(FLOOR_SOLID); + } +*/ +} + +//--------------------------------------------------------------------------- +// +// FUNC P_HitFloor +// +//--------------------------------------------------------------------------- + +int P_HitFloor(mobj_t * thing) +{ + mobj_t *mo; + + if (thing->floorz != thing->subsector->sector->floorheight) + { // don't splash if landing on the edge above water/lava/etc.... + return (FLOOR_SOLID); + } + switch (P_GetThingFloorType(thing)) + { + case FLOOR_WATER: + P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASHBASE); + mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASH); + mo->target = thing; + mo->momx = (P_Random() - P_Random()) << 8; + mo->momy = (P_Random() - P_Random()) << 8; + mo->momz = 2 * FRACUNIT + (P_Random() << 8); + S_StartSound(mo, sfx_gloop); + return (FLOOR_WATER); + case FLOOR_LAVA: + P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_LAVASPLASH); + mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_LAVASMOKE); + mo->momz = FRACUNIT + (P_Random() << 7); + S_StartSound(mo, sfx_burn); + return (FLOOR_LAVA); + case FLOOR_SLUDGE: + P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGESPLASH); + mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGECHUNK); + mo->target = thing; + mo->momx = (P_Random() - P_Random()) << 8; + mo->momy = (P_Random() - P_Random()) << 8; + mo->momz = FRACUNIT + (P_Random() << 8); + return (FLOOR_SLUDGE); + } + return (FLOOR_SOLID); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_CheckMissileSpawn +// +// Returns true if the missile is at a valid spawn point, otherwise +// explodes it and returns false. +// +//--------------------------------------------------------------------------- + +boolean P_CheckMissileSpawn(mobj_t * missile) +{ + //missile->tics -= P_Random()&3; + + // move a little forward so an angle can be computed if it + // immediately explodes + missile->x += (missile->momx >> 1); + missile->y += (missile->momy >> 1); + missile->z += (missile->momz >> 1); + if (!P_TryMove(missile, missile->x, missile->y)) + { + P_ExplodeMissile(missile); + return (false); + } + return (true); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_SpawnMissile +// +// Returns NULL if the missile exploded immediately, otherwise returns +// a mobj_t pointer to the missile. +// +//--------------------------------------------------------------------------- + +mobj_t *P_SpawnMissile(mobj_t * source, mobj_t * dest, mobjtype_t type) +{ + fixed_t z; + mobj_t *th; + angle_t an; + int dist; + + switch (type) + { + case MT_MNTRFX1: // Minotaur swing attack missile + z = source->z + 40 * FRACUNIT; + break; + case MT_MNTRFX2: // Minotaur floor fire missile + z = ONFLOORZ; + break; + case MT_SRCRFX1: // Sorcerer Demon fireball + z = source->z + 48 * FRACUNIT; + break; + case MT_KNIGHTAXE: // Knight normal axe + case MT_REDAXE: // Knight red power axe + z = source->z + 36 * FRACUNIT; + break; + default: + z = source->z + 32 * FRACUNIT; + break; + } + if (source->flags2 & MF2_FEETARECLIPPED) + { + z -= FOOTCLIPSIZE; + } + th = P_SpawnMobj(source->x, source->y, z, type); + if (th->info->seesound) + { + S_StartSound(th, th->info->seesound); + } + th->target = source; // Originator + an = R_PointToAngle2(source->x, source->y, dest->x, dest->y); + if (dest->flags & MF_SHADOW) + { // Invisible target + an += (P_Random() - P_Random()) << 21; + } + 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; + return (P_CheckMissileSpawn(th) ? th : NULL); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_SpawnMissileAngle +// +// Returns NULL if the missile exploded immediately, otherwise returns +// a mobj_t pointer to the missile. +// +//--------------------------------------------------------------------------- + +mobj_t *P_SpawnMissileAngle(mobj_t * source, mobjtype_t type, + angle_t angle, fixed_t momz) +{ + fixed_t z; + mobj_t *mo; + + switch (type) + { + case MT_MNTRFX1: // Minotaur swing attack missile + z = source->z + 40 * FRACUNIT; + break; + case MT_MNTRFX2: // Minotaur floor fire missile + z = ONFLOORZ; + break; + case MT_SRCRFX1: // Sorcerer Demon fireball + z = source->z + 48 * FRACUNIT; + break; + default: + z = source->z + 32 * FRACUNIT; + break; + } + if (source->flags2 & MF2_FEETARECLIPPED) + { + z -= FOOTCLIPSIZE; + } + mo = P_SpawnMobj(source->x, source->y, z, type); + if (mo->info->seesound) + { + S_StartSound(mo, mo->info->seesound); + } + mo->target = source; // Originator + mo->angle = angle; + angle >>= ANGLETOFINESHIFT; + mo->momx = FixedMul(mo->info->speed, finecosine[angle]); + mo->momy = FixedMul(mo->info->speed, finesine[angle]); + mo->momz = momz; + return (P_CheckMissileSpawn(mo) ? mo : NULL); +} + +/* +================ += += P_SpawnPlayerMissile += += Tries to aim at a nearby monster +================ +*/ + +mobj_t *P_SpawnPlayerMissile(mobj_t * source, mobjtype_t type) +{ + angle_t an; + fixed_t x, y, z, slope; + + // Try to find a target + 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 = ((source->player->lookdir) << FRACBITS) / 173; + } + } + x = source->x; + y = source->y; + z = source->z + 4 * 8 * FRACUNIT + + ((source->player->lookdir) << FRACBITS) / 173; + if (source->flags2 & MF2_FEETARECLIPPED) + { + z -= FOOTCLIPSIZE; + } + MissileMobj = P_SpawnMobj(x, y, z, type); + if (MissileMobj->info->seesound) + { + S_StartSound(MissileMobj, MissileMobj->info->seesound); + } + MissileMobj->target = source; + MissileMobj->angle = an; + MissileMobj->momx = FixedMul(MissileMobj->info->speed, + finecosine[an >> ANGLETOFINESHIFT]); + MissileMobj->momy = FixedMul(MissileMobj->info->speed, + finesine[an >> ANGLETOFINESHIFT]); + MissileMobj->momz = FixedMul(MissileMobj->info->speed, slope); + if (MissileMobj->type == MT_BLASTERFX1) + { // Ultra-fast ripper spawning missile + MissileMobj->x += (MissileMobj->momx >> 3); + MissileMobj->y += (MissileMobj->momy >> 3); + MissileMobj->z += (MissileMobj->momz >> 3); + } + else + { // Normal missile + MissileMobj->x += (MissileMobj->momx >> 1); + MissileMobj->y += (MissileMobj->momy >> 1); + MissileMobj->z += (MissileMobj->momz >> 1); + } + if (!P_TryMove(MissileMobj, MissileMobj->x, MissileMobj->y)) + { // Exploded immediately + P_ExplodeMissile(MissileMobj); + return (NULL); + } + return (MissileMobj); +} + +//--------------------------------------------------------------------------- +// +// PROC P_SPMAngle +// +//--------------------------------------------------------------------------- + +mobj_t *P_SPMAngle(mobj_t * source, mobjtype_t type, angle_t angle) +{ + mobj_t *th; + angle_t an; + fixed_t x, y, z, slope; + +// +// see which target is to be aimed at +// + an = 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 = angle; + slope = ((source->player->lookdir) << FRACBITS) / 173; + } + } + x = source->x; + y = source->y; + z = source->z + 4 * 8 * FRACUNIT + + ((source->player->lookdir) << FRACBITS) / 173; + if (source->flags2 & MF2_FEETARECLIPPED) + { + z -= FOOTCLIPSIZE; + } + 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); + return (P_CheckMissileSpawn(th) ? th : NULL); +} + +//--------------------------------------------------------------------------- +// +// PROC A_ContMobjSound +// +//--------------------------------------------------------------------------- + +void A_ContMobjSound(mobj_t * actor) +{ + switch (actor->type) + { + case MT_KNIGHTAXE: + S_StartSound(actor, sfx_kgtatk); + break; + case MT_MUMMYFX1: + S_StartSound(actor, sfx_mumhed); + break; + default: + break; + } +} diff --git a/src/heretic/p_plats.c b/src/heretic/p_plats.c new file mode 100644 index 00000000..23aa99d9 --- /dev/null +++ b/src/heretic/p_plats.c @@ -0,0 +1,266 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_plats.c + +#include "doomdef.h" +#include "i_system.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +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 (!(leveltime & 31)) + { + S_StartSound(&plat->sector->soundorg, sfx_stnmov); + } + if (plat->type == raiseAndChange + || plat->type == raiseToNearestAndChange) + { + if (!(leveltime & 7)) + { + S_StartSound(&plat->sector->soundorg, + sfx_stnmov); + } + } + if (res == crushed && (!plat->crush)) + { + plat->count = plat->wait; + plat->status = down; + S_StartSound(&plat->sector->soundorg, sfx_pstart); + } + else if (res == pastdest) + { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound(&plat->sector->soundorg, sfx_pstop); + switch (plat->type) + { + case downWaitUpStay: + P_RemoveActivePlat(plat); + break; + case raiseAndChange: + 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(&plat->sector->soundorg, sfx_pstop); + } + else + { + if (!(leveltime & 31)) + { + S_StartSound(&plat->sector->soundorg, + sfx_stnmov); + } + } + break; + case waiting: + if (!--plat->count) + { + if (plat->sector->floorheight == plat->low) + plat->status = up; + else + plat->status = down; + S_StartSound(&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 = §ors[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 = 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; + sec->special = 0; // NO MORE DAMAGE, IF APPLICABLE + S_StartSound(&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(&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(&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(&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 = 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 = NULL; + } +} + +void P_AddActivePlat(plat_t * plat) +{ + int i; + for (i = 0; i < MAXPLATS; i++) + if (activeplats[i] == NULL) + { + activeplats[i] = plat; + return; + } + I_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 = NULL; + P_RemoveThinker(&(activeplats[i])->thinker); + activeplats[i] = NULL; + return; + } + I_Error("P_RemoveActivePlat: can't find plat!"); +} diff --git a/src/heretic/p_pspr.c b/src/heretic/p_pspr.c new file mode 100644 index 00000000..bd47c9a1 --- /dev/null +++ b/src/heretic/p_pspr.c @@ -0,0 +1,1908 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_pspr.c + +#include "doomdef.h" +#include "i_system.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +// Macros + +#define LOWERSPEED FRACUNIT*6 +#define RAISESPEED FRACUNIT*6 +#define WEAPONBOTTOM 128*FRACUNIT +#define WEAPONTOP 32*FRACUNIT +#define FLAME_THROWER_TICS 10*35 +#define MAGIC_JUNK 1234 +#define MAX_MACE_SPOTS 8 + +static int MaceSpotCount; +static struct +{ + fixed_t x; + fixed_t y; +} MaceSpots[MAX_MACE_SPOTS]; + +fixed_t bulletslope; + +static int WeaponAmmoUsePL1[NUMWEAPONS] = { + 0, // staff + USE_GWND_AMMO_1, // gold wand + USE_CBOW_AMMO_1, // crossbow + USE_BLSR_AMMO_1, // blaster + USE_SKRD_AMMO_1, // skull rod + USE_PHRD_AMMO_1, // phoenix rod + USE_MACE_AMMO_1, // mace + 0, // gauntlets + 0 // beak +}; + +static int WeaponAmmoUsePL2[NUMWEAPONS] = { + 0, // staff + USE_GWND_AMMO_2, // gold wand + USE_CBOW_AMMO_2, // crossbow + USE_BLSR_AMMO_2, // blaster + USE_SKRD_AMMO_2, // skull rod + USE_PHRD_AMMO_2, // phoenix rod + USE_MACE_AMMO_2, // mace + 0, // gauntlets + 0 // beak +}; + +weaponinfo_t wpnlev1info[NUMWEAPONS] = { + { // Staff + am_noammo, // ammo + S_STAFFUP, // upstate + S_STAFFDOWN, // downstate + S_STAFFREADY, // readystate + S_STAFFATK1_1, // atkstate + S_STAFFATK1_1, // holdatkstate + S_NULL // flashstate + }, + { // Gold wand + am_goldwand, // ammo + S_GOLDWANDUP, // upstate + S_GOLDWANDDOWN, // downstate + S_GOLDWANDREADY, // readystate + S_GOLDWANDATK1_1, // atkstate + S_GOLDWANDATK1_1, // holdatkstate + S_NULL // flashstate + }, + { // Crossbow + am_crossbow, // ammo + S_CRBOWUP, // upstate + S_CRBOWDOWN, // downstate + S_CRBOW1, // readystate + S_CRBOWATK1_1, // atkstate + S_CRBOWATK1_1, // holdatkstate + S_NULL // flashstate + }, + { // Blaster + am_blaster, // ammo + S_BLASTERUP, // upstate + S_BLASTERDOWN, // downstate + S_BLASTERREADY, // readystate + S_BLASTERATK1_1, // atkstate + S_BLASTERATK1_3, // holdatkstate + S_NULL // flashstate + }, + { // Skull rod + am_skullrod, // ammo + S_HORNRODUP, // upstate + S_HORNRODDOWN, // downstate + S_HORNRODREADY, // readystae + S_HORNRODATK1_1, // atkstate + S_HORNRODATK1_1, // holdatkstate + S_NULL // flashstate + }, + { // Phoenix rod + am_phoenixrod, // ammo + S_PHOENIXUP, // upstate + S_PHOENIXDOWN, // downstate + S_PHOENIXREADY, // readystate + S_PHOENIXATK1_1, // atkstate + S_PHOENIXATK1_1, // holdatkstate + S_NULL // flashstate + }, + { // Mace + am_mace, // ammo + S_MACEUP, // upstate + S_MACEDOWN, // downstate + S_MACEREADY, // readystate + S_MACEATK1_1, // atkstate + S_MACEATK1_2, // holdatkstate + S_NULL // flashstate + }, + { // Gauntlets + am_noammo, // ammo + S_GAUNTLETUP, // upstate + S_GAUNTLETDOWN, // downstate + S_GAUNTLETREADY, // readystate + S_GAUNTLETATK1_1, // atkstate + S_GAUNTLETATK1_3, // holdatkstate + S_NULL // flashstate + }, + { // Beak + am_noammo, // ammo + S_BEAKUP, // upstate + S_BEAKDOWN, // downstate + S_BEAKREADY, // readystate + S_BEAKATK1_1, // atkstate + S_BEAKATK1_1, // holdatkstate + S_NULL // flashstate + } +}; + +weaponinfo_t wpnlev2info[NUMWEAPONS] = { + { // Staff + am_noammo, // ammo + S_STAFFUP2, // upstate + S_STAFFDOWN2, // downstate + S_STAFFREADY2_1, // readystate + S_STAFFATK2_1, // atkstate + S_STAFFATK2_1, // holdatkstate + S_NULL // flashstate + }, + { // Gold wand + am_goldwand, // ammo + S_GOLDWANDUP, // upstate + S_GOLDWANDDOWN, // downstate + S_GOLDWANDREADY, // readystate + S_GOLDWANDATK2_1, // atkstate + S_GOLDWANDATK2_1, // holdatkstate + S_NULL // flashstate + }, + { // Crossbow + am_crossbow, // ammo + S_CRBOWUP, // upstate + S_CRBOWDOWN, // downstate + S_CRBOW1, // readystate + S_CRBOWATK2_1, // atkstate + S_CRBOWATK2_1, // holdatkstate + S_NULL // flashstate + }, + { // Blaster + am_blaster, // ammo + S_BLASTERUP, // upstate + S_BLASTERDOWN, // downstate + S_BLASTERREADY, // readystate + S_BLASTERATK2_1, // atkstate + S_BLASTERATK2_3, // holdatkstate + S_NULL // flashstate + }, + { // Skull rod + am_skullrod, // ammo + S_HORNRODUP, // upstate + S_HORNRODDOWN, // downstate + S_HORNRODREADY, // readystae + S_HORNRODATK2_1, // atkstate + S_HORNRODATK2_1, // holdatkstate + S_NULL // flashstate + }, + { // Phoenix rod + am_phoenixrod, // ammo + S_PHOENIXUP, // upstate + S_PHOENIXDOWN, // downstate + S_PHOENIXREADY, // readystate + S_PHOENIXATK2_1, // atkstate + S_PHOENIXATK2_2, // holdatkstate + S_NULL // flashstate + }, + { // Mace + am_mace, // ammo + S_MACEUP, // upstate + S_MACEDOWN, // downstate + S_MACEREADY, // readystate + S_MACEATK2_1, // atkstate + S_MACEATK2_1, // holdatkstate + S_NULL // flashstate + }, + { // Gauntlets + am_noammo, // ammo + S_GAUNTLETUP2, // upstate + S_GAUNTLETDOWN2, // downstate + S_GAUNTLETREADY2_1, // readystate + S_GAUNTLETATK2_1, // atkstate + S_GAUNTLETATK2_3, // holdatkstate + S_NULL // flashstate + }, + { // Beak + am_noammo, // ammo + S_BEAKUP, // upstate + S_BEAKDOWN, // downstate + S_BEAKREADY, // readystate + S_BEAKATK2_1, // atkstate + S_BEAKATK2_1, // holdatkstate + S_NULL // flashstate + } +}; + +//--------------------------------------------------------------------------- +// +// PROC P_OpenWeapons +// +// Called at level load before things are loaded. +// +//--------------------------------------------------------------------------- + +void P_OpenWeapons(void) +{ + MaceSpotCount = 0; +} + +//--------------------------------------------------------------------------- +// +// PROC P_AddMaceSpot +// +//--------------------------------------------------------------------------- + +void P_AddMaceSpot(mapthing_t * mthing) +{ + if (MaceSpotCount == MAX_MACE_SPOTS) + { + I_Error("Too many mace spots."); + } + MaceSpots[MaceSpotCount].x = mthing->x << FRACBITS; + MaceSpots[MaceSpotCount].y = mthing->y << FRACBITS; + MaceSpotCount++; +} + +//--------------------------------------------------------------------------- +// +// PROC P_RepositionMace +// +// Chooses the next spot to place the mace. +// +//--------------------------------------------------------------------------- + +void P_RepositionMace(mobj_t * mo) +{ + int spot; + subsector_t *ss; + + P_UnsetThingPosition(mo); + spot = P_Random() % MaceSpotCount; + mo->x = MaceSpots[spot].x; + mo->y = MaceSpots[spot].y; + ss = R_PointInSubsector(mo->x, mo->y); + mo->z = mo->floorz = ss->sector->floorheight; + mo->ceilingz = ss->sector->ceilingheight; + P_SetThingPosition(mo); +} + +//--------------------------------------------------------------------------- +// +// PROC P_CloseWeapons +// +// Called at level load after things are loaded. +// +//--------------------------------------------------------------------------- + +void P_CloseWeapons(void) +{ + int spot; + + if (!MaceSpotCount) + { // No maces placed + return; + } + if (!deathmatch && P_Random() < 64) + { // Sometimes doesn't show up if not in deathmatch + return; + } + spot = P_Random() % MaceSpotCount; + P_SpawnMobj(MaceSpots[spot].x, MaceSpots[spot].y, ONFLOORZ, MT_WMACE); +} + +//--------------------------------------------------------------------------- +// +// PROC 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 = NULL; + break; + } + state = &states[stnum]; + psp->state = state; + psp->tics = state->tics; // could be 0 + if (state->misc1) + { // Set coordinates. + psp->sx = state->misc1 << FRACBITS; + psp->sy = state->misc2 << FRACBITS; + } + if (state->action) + { // Call action routine. + state->action(player, psp); + if (!psp->state) + { + break; + } + } + stnum = psp->state->nextstate; + } + while (!psp->tics); // An initial state of 0 could cycle through. +} + +/* +================= += += P_CalcSwing += +================= +*/ + +/* +fixed_t swingx, swingy; +void P_CalcSwing (player_t *player) +{ + fixed_t swing; + int angle; + +// OPTIMIZE: tablify this + + swing = player->bob; + + angle = (FINEANGLES/70*leveltime)&FINEMASK; + swingx = FixedMul ( swing, finesine[angle]); + + angle = (FINEANGLES/70*leveltime+FINEANGLES/2)&FINEMASK; + swingy = -FixedMul ( swingx, finesine[angle]); +} +*/ + +//--------------------------------------------------------------------------- +// +// PROC P_ActivateBeak +// +//--------------------------------------------------------------------------- + +void P_ActivateBeak(player_t * player) +{ + player->pendingweapon = wp_nochange; + player->readyweapon = wp_beak; + player->psprites[ps_weapon].sy = WEAPONTOP; + P_SetPsprite(player, ps_weapon, S_BEAKREADY); +} + +//--------------------------------------------------------------------------- +// +// PROC P_PostChickenWeapon +// +//--------------------------------------------------------------------------- + +void P_PostChickenWeapon(player_t * player, weapontype_t weapon) +{ + if (weapon == wp_beak) + { // Should never happen + weapon = wp_staff; + } + player->pendingweapon = wp_nochange; + player->readyweapon = weapon; + player->psprites[ps_weapon].sy = WEAPONBOTTOM; + P_SetPsprite(player, ps_weapon, wpnlev1info[weapon].upstate); +} + +//--------------------------------------------------------------------------- +// +// PROC P_BringUpWeapon +// +// Starts bringing the pending weapon up from the bottom of the screen. +// +//--------------------------------------------------------------------------- + +void P_BringUpWeapon(player_t * player) +{ + statenum_t new; + + if (player->pendingweapon == wp_nochange) + { + player->pendingweapon = player->readyweapon; + } + if (player->pendingweapon == wp_gauntlets) + { + S_StartSound(player->mo, sfx_gntact); + } + if (player->powers[pw_weaponlevel2]) + { + new = wpnlev2info[player->pendingweapon].upstate; + } + else + { + new = wpnlev1info[player->pendingweapon].upstate; + } + player->pendingweapon = wp_nochange; + player->psprites[ps_weapon].sy = WEAPONBOTTOM; + P_SetPsprite(player, ps_weapon, new); +} + +//--------------------------------------------------------------------------- +// +// FUNC P_CheckAmmo +// +// Returns true if there is enough ammo to shoot. If not, selects the +// next weapon to use. +// +//--------------------------------------------------------------------------- + +boolean P_CheckAmmo(player_t * player) +{ + ammotype_t ammo; + int *ammoUse; + int count; + + ammo = wpnlev1info[player->readyweapon].ammo; + if (player->powers[pw_weaponlevel2] && !deathmatch) + { + ammoUse = WeaponAmmoUsePL2; + } + else + { + ammoUse = WeaponAmmoUsePL1; + } + count = ammoUse[player->readyweapon]; + if (ammo == am_noammo || player->ammo[ammo] >= count) + { + return (true); + } + // out of ammo, pick a weapon to change to + do + { + if (player->weaponowned[wp_skullrod] + && player->ammo[am_skullrod] > ammoUse[wp_skullrod]) + { + player->pendingweapon = wp_skullrod; + } + else if (player->weaponowned[wp_blaster] + && player->ammo[am_blaster] > ammoUse[wp_blaster]) + { + player->pendingweapon = wp_blaster; + } + else if (player->weaponowned[wp_crossbow] + && player->ammo[am_crossbow] > ammoUse[wp_crossbow]) + { + player->pendingweapon = wp_crossbow; + } + else if (player->weaponowned[wp_mace] + && player->ammo[am_mace] > ammoUse[wp_mace]) + { + player->pendingweapon = wp_mace; + } + else if (player->ammo[am_goldwand] > ammoUse[wp_goldwand]) + { + player->pendingweapon = wp_goldwand; + } + else if (player->weaponowned[wp_gauntlets]) + { + player->pendingweapon = wp_gauntlets; + } + else if (player->weaponowned[wp_phoenixrod] + && player->ammo[am_phoenixrod] > ammoUse[wp_phoenixrod]) + { + player->pendingweapon = wp_phoenixrod; + } + else + { + player->pendingweapon = wp_staff; + } + } + while (player->pendingweapon == wp_nochange); + if (player->powers[pw_weaponlevel2]) + { + P_SetPsprite(player, ps_weapon, + wpnlev2info[player->readyweapon].downstate); + } + else + { + P_SetPsprite(player, ps_weapon, + wpnlev1info[player->readyweapon].downstate); + } + return (false); +} + +//--------------------------------------------------------------------------- +// +// PROC P_FireWeapon +// +//--------------------------------------------------------------------------- + +void P_FireWeapon(player_t * player) +{ + weaponinfo_t *wpinfo; + statenum_t attackState; + + if (!P_CheckAmmo(player)) + { + return; + } + P_SetMobjState(player->mo, S_PLAY_ATK2); + wpinfo = player->powers[pw_weaponlevel2] ? &wpnlev2info[0] + : &wpnlev1info[0]; + attackState = player->refire ? wpinfo[player->readyweapon].holdatkstate + : wpinfo[player->readyweapon].atkstate; + P_SetPsprite(player, ps_weapon, attackState); + P_NoiseAlert(player->mo, player->mo); + if (player->readyweapon == wp_gauntlets && !player->refire) + { // Play the sound for the initial gauntlet attack + S_StartSound(player->mo, sfx_gntuse); + } +} + +//--------------------------------------------------------------------------- +// +// PROC P_DropWeapon +// +// The player died, so put the weapon away. +// +//--------------------------------------------------------------------------- + +void P_DropWeapon(player_t * player) +{ + if (player->powers[pw_weaponlevel2]) + { + P_SetPsprite(player, ps_weapon, + wpnlev2info[player->readyweapon].downstate); + } + else + { + P_SetPsprite(player, ps_weapon, + wpnlev1info[player->readyweapon].downstate); + } +} + +//--------------------------------------------------------------------------- +// +// PROC A_WeaponReady +// +// The player can fire the weapon or change to another weapon at this time. +// +//--------------------------------------------------------------------------- + +void A_WeaponReady(player_t * player, pspdef_t * psp) +{ + int angle; + + if (player->chickenTics) + { // Change to the chicken beak + P_ActivateBeak(player); + return; + } + // Change player from attack state + if (player->mo->state == &states[S_PLAY_ATK1] + || player->mo->state == &states[S_PLAY_ATK2]) + { + P_SetMobjState(player->mo, S_PLAY); + } + // Check for staff PL2 active sound + if ((player->readyweapon == wp_staff) + && (psp->state == &states[S_STAFFREADY2_1]) && P_Random() < 128) + { + S_StartSound(player->mo, sfx_stfcrk); + } + // Put the weapon away if the player has a pending weapon or has + // died. + if (player->pendingweapon != wp_nochange || !player->health) + { + if (player->powers[pw_weaponlevel2]) + { + P_SetPsprite(player, ps_weapon, + wpnlev2info[player->readyweapon].downstate); + } + else + { + P_SetPsprite(player, ps_weapon, + wpnlev1info[player->readyweapon].downstate); + } + return; + } + + // Check for fire. The phoenix rod does not auto fire. + if (player->cmd.buttons & BT_ATTACK) + { + if (!player->attackdown || (player->readyweapon != wp_phoenixrod)) + { + 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]); +} + +//--------------------------------------------------------------------------- +// +// PROC P_UpdateBeak +// +//--------------------------------------------------------------------------- + +void P_UpdateBeak(player_t * player, pspdef_t * psp) +{ + psp->sy = WEAPONTOP + (player->chickenPeck << (FRACBITS - 1)); +} + +//--------------------------------------------------------------------------- +// +// PROC A_BeakReady +// +//--------------------------------------------------------------------------- + +void A_BeakReady(player_t * player, pspdef_t * psp) +{ + if (player->cmd.buttons & BT_ATTACK) + { // Chicken beak attack + player->attackdown = true; + P_SetMobjState(player->mo, S_CHICPLAY_ATK1); + if (player->powers[pw_weaponlevel2]) + { + P_SetPsprite(player, ps_weapon, S_BEAKATK2_1); + } + else + { + P_SetPsprite(player, ps_weapon, S_BEAKATK1_1); + } + P_NoiseAlert(player->mo, player->mo); + } + else + { + if (player->mo->state == &states[S_CHICPLAY_ATK1]) + { // Take out of attack state + P_SetMobjState(player->mo, S_CHICPLAY); + } + player->attackdown = false; + } +} + +//--------------------------------------------------------------------------- +// +// PROC A_ReFire +// +// The player can re fire the weapon without lowering it entirely. +// +//--------------------------------------------------------------------------- + +void A_ReFire(player_t * player, pspdef_t * psp) +{ + if ((player->cmd.buttons & BT_ATTACK) + && player->pendingweapon == wp_nochange && player->health) + { + player->refire++; + P_FireWeapon(player); + } + else + { + player->refire = 0; + P_CheckAmmo(player); + } +} + +//--------------------------------------------------------------------------- +// +// PROC A_Lower +// +//--------------------------------------------------------------------------- + +void A_Lower(player_t * player, pspdef_t * psp) +{ + if (player->chickenTics) + { + psp->sy = WEAPONBOTTOM; + } + else + { + psp->sy += LOWERSPEED; + } + if (psp->sy < WEAPONBOTTOM) + { // Not lowered all the way yet + return; + } + if (player->playerstate == PST_DEAD) + { // Player is dead, so don't bring up a pending weapon + psp->sy = WEAPONBOTTOM; + return; + } + 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); +} + +//--------------------------------------------------------------------------- +// +// PROC A_BeakRaise +// +//--------------------------------------------------------------------------- + +void A_BeakRaise(player_t * player, pspdef_t * psp) +{ + psp->sy = WEAPONTOP; + P_SetPsprite(player, ps_weapon, + wpnlev1info[player->readyweapon].readystate); +} + +//--------------------------------------------------------------------------- +// +// PROC A_Raise +// +//--------------------------------------------------------------------------- + +void A_Raise(player_t * player, pspdef_t * psp) +{ + psp->sy -= RAISESPEED; + if (psp->sy > WEAPONTOP) + { // Not raised all the way yet + return; + } + psp->sy = WEAPONTOP; + if (player->powers[pw_weaponlevel2]) + { + P_SetPsprite(player, ps_weapon, + wpnlev2info[player->readyweapon].readystate); + } + else + { + P_SetPsprite(player, ps_weapon, + wpnlev1info[player->readyweapon].readystate); + } +} + +/* +=============== += += 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); + } + if (!linetarget) + { + an += 2 << 26; + bulletslope = (mo->player->lookdir << FRACBITS) / 173; + } + } +} + +//**************************************************************************** +// +// WEAPON ATTACKS +// +//**************************************************************************** + +//---------------------------------------------------------------------------- +// +// PROC A_BeakAttackPL1 +// +//---------------------------------------------------------------------------- + +void A_BeakAttackPL1(player_t * player, pspdef_t * psp) +{ + angle_t angle; + int damage; + int slope; + + damage = 1 + (P_Random() & 3); + angle = player->mo->angle; + slope = P_AimLineAttack(player->mo, angle, MELEERANGE); + PuffType = MT_BEAKPUFF; + P_LineAttack(player->mo, angle, MELEERANGE, slope, damage); + if (linetarget) + { + player->mo->angle = R_PointToAngle2(player->mo->x, + player->mo->y, linetarget->x, + linetarget->y); + } + S_StartSound(player->mo, sfx_chicpk1 + (P_Random() % 3)); + player->chickenPeck = 12; + psp->tics -= P_Random() & 7; +} + +//---------------------------------------------------------------------------- +// +// PROC A_BeakAttackPL2 +// +//---------------------------------------------------------------------------- + +void A_BeakAttackPL2(player_t * player, pspdef_t * psp) +{ + angle_t angle; + int damage; + int slope; + + damage = HITDICE(4); + angle = player->mo->angle; + slope = P_AimLineAttack(player->mo, angle, MELEERANGE); + PuffType = MT_BEAKPUFF; + P_LineAttack(player->mo, angle, MELEERANGE, slope, damage); + if (linetarget) + { + player->mo->angle = R_PointToAngle2(player->mo->x, + player->mo->y, linetarget->x, + linetarget->y); + } + S_StartSound(player->mo, sfx_chicpk1 + (P_Random() % 3)); + player->chickenPeck = 12; + psp->tics -= P_Random() & 3; +} + +//---------------------------------------------------------------------------- +// +// PROC A_StaffAttackPL1 +// +//---------------------------------------------------------------------------- + +void A_StaffAttackPL1(player_t * player, pspdef_t * psp) +{ + angle_t angle; + int damage; + int slope; + + damage = 5 + (P_Random() & 15); + angle = player->mo->angle; + angle += (P_Random() - P_Random()) << 18; + slope = P_AimLineAttack(player->mo, angle, MELEERANGE); + PuffType = MT_STAFFPUFF; + P_LineAttack(player->mo, angle, MELEERANGE, slope, damage); + if (linetarget) + { + //S_StartSound(player->mo, sfx_stfhit); + // turn to face target + player->mo->angle = R_PointToAngle2(player->mo->x, + player->mo->y, linetarget->x, + linetarget->y); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_StaffAttackPL2 +// +//---------------------------------------------------------------------------- + +void A_StaffAttackPL2(player_t * player, pspdef_t * psp) +{ + angle_t angle; + int damage; + int slope; + + // P_inter.c:P_DamageMobj() handles target momentums + damage = 18 + (P_Random() & 63); + angle = player->mo->angle; + angle += (P_Random() - P_Random()) << 18; + slope = P_AimLineAttack(player->mo, angle, MELEERANGE); + PuffType = MT_STAFFPUFF2; + P_LineAttack(player->mo, angle, MELEERANGE, slope, damage); + if (linetarget) + { + //S_StartSound(player->mo, sfx_stfpow); + // turn to face target + player->mo->angle = R_PointToAngle2(player->mo->x, + player->mo->y, linetarget->x, + linetarget->y); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireBlasterPL1 +// +//---------------------------------------------------------------------------- + +void A_FireBlasterPL1(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + angle_t angle; + int damage; + + mo = player->mo; + S_StartSound(mo, sfx_gldhit); + player->ammo[am_blaster] -= USE_BLSR_AMMO_1; + P_BulletSlope(mo); + damage = HITDICE(4); + angle = mo->angle; + if (player->refire) + { + angle += (P_Random() - P_Random()) << 18; + } + PuffType = MT_BLASTERPUFF1; + P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage); + S_StartSound(player->mo, sfx_blssht); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireBlasterPL2 +// +//---------------------------------------------------------------------------- + +void A_FireBlasterPL2(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + + player->ammo[am_blaster] -= + deathmatch ? USE_BLSR_AMMO_1 : USE_BLSR_AMMO_2; + mo = P_SpawnPlayerMissile(player->mo, MT_BLASTERFX1); + if (mo) + { + mo->thinker.function = P_BlasterMobjThinker; + } + S_StartSound(player->mo, sfx_blssht); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireGoldWandPL1 +// +//---------------------------------------------------------------------------- + +void A_FireGoldWandPL1(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + angle_t angle; + int damage; + + mo = player->mo; + player->ammo[am_goldwand] -= USE_GWND_AMMO_1; + P_BulletSlope(mo); + damage = 7 + (P_Random() & 7); + angle = mo->angle; + if (player->refire) + { + angle += (P_Random() - P_Random()) << 18; + } + PuffType = MT_GOLDWANDPUFF1; + P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage); + S_StartSound(player->mo, sfx_gldhit); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireGoldWandPL2 +// +//---------------------------------------------------------------------------- + +void A_FireGoldWandPL2(player_t * player, pspdef_t * psp) +{ + int i; + mobj_t *mo; + angle_t angle; + int damage; + fixed_t momz; + + mo = player->mo; + player->ammo[am_goldwand] -= + deathmatch ? USE_GWND_AMMO_1 : USE_GWND_AMMO_2; + PuffType = MT_GOLDWANDPUFF2; + P_BulletSlope(mo); + momz = FixedMul(mobjinfo[MT_GOLDWANDFX2].speed, bulletslope); + P_SpawnMissileAngle(mo, MT_GOLDWANDFX2, mo->angle - (ANG45 / 8), momz); + P_SpawnMissileAngle(mo, MT_GOLDWANDFX2, mo->angle + (ANG45 / 8), momz); + angle = mo->angle - (ANG45 / 8); + for (i = 0; i < 5; i++) + { + damage = 1 + (P_Random() & 7); + P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage); + angle += ((ANG45 / 8) * 2) / 4; + } + S_StartSound(player->mo, sfx_gldhit); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireMacePL1B +// +//---------------------------------------------------------------------------- + +void A_FireMacePL1B(player_t * player, pspdef_t * psp) +{ + mobj_t *pmo; + mobj_t *ball; + angle_t angle; + + if (player->ammo[am_mace] < USE_MACE_AMMO_1) + { + return; + } + player->ammo[am_mace] -= USE_MACE_AMMO_1; + pmo = player->mo; + ball = P_SpawnMobj(pmo->x, pmo->y, pmo->z + 28 * FRACUNIT + - FOOTCLIPSIZE * ((pmo->flags2 & MF2_FEETARECLIPPED) != + 0), MT_MACEFX2); + ball->momz = 2 * FRACUNIT + ((player->lookdir) << (FRACBITS - 5)); + angle = pmo->angle; + ball->target = pmo; + ball->angle = angle; + ball->z += (player->lookdir) << (FRACBITS - 4); + angle >>= ANGLETOFINESHIFT; + ball->momx = (pmo->momx >> 1) + + FixedMul(ball->info->speed, finecosine[angle]); + ball->momy = (pmo->momy >> 1) + + FixedMul(ball->info->speed, finesine[angle]); + S_StartSound(ball, sfx_lobsht); + P_CheckMissileSpawn(ball); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireMacePL1 +// +//---------------------------------------------------------------------------- + +void A_FireMacePL1(player_t * player, pspdef_t * psp) +{ + mobj_t *ball; + + if (P_Random() < 28) + { + A_FireMacePL1B(player, psp); + return; + } + if (player->ammo[am_mace] < USE_MACE_AMMO_1) + { + return; + } + player->ammo[am_mace] -= USE_MACE_AMMO_1; + psp->sx = ((P_Random() & 3) - 2) * FRACUNIT; + psp->sy = WEAPONTOP + (P_Random() & 3) * FRACUNIT; + ball = P_SPMAngle(player->mo, MT_MACEFX1, player->mo->angle + + (((P_Random() & 7) - 4) << 24)); + if (ball) + { + ball->special1 = 16; // tics till dropoff + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MacePL1Check +// +//---------------------------------------------------------------------------- + +void A_MacePL1Check(mobj_t * ball) +{ + angle_t angle; + + if (ball->special1 == 0) + { + return; + } + ball->special1 -= 4; + if (ball->special1 > 0) + { + return; + } + ball->special1 = 0; + ball->flags2 |= MF2_LOGRAV; + angle = ball->angle >> ANGLETOFINESHIFT; + ball->momx = FixedMul(7 * FRACUNIT, finecosine[angle]); + ball->momy = FixedMul(7 * FRACUNIT, finesine[angle]); + ball->momz -= ball->momz >> 1; +} + +//---------------------------------------------------------------------------- +// +// PROC A_MaceBallImpact +// +//---------------------------------------------------------------------------- + +void A_MaceBallImpact(mobj_t * ball) +{ + if ((ball->z <= ball->floorz) && (P_HitFloor(ball) != FLOOR_SOLID)) + { // Landed in some sort of liquid + P_RemoveMobj(ball); + return; + } + if ((ball->health != MAGIC_JUNK) && (ball->z <= ball->floorz) + && ball->momz) + { // Bounce + ball->health = MAGIC_JUNK; + ball->momz = (ball->momz * 192) >> 8; + ball->flags2 &= ~MF2_FLOORBOUNCE; + P_SetMobjState(ball, ball->info->spawnstate); + S_StartSound(ball, sfx_bounce); + } + else + { // Explode + ball->flags |= MF_NOGRAVITY; + ball->flags2 &= ~MF2_LOGRAV; + S_StartSound(ball, sfx_lobhit); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_MaceBallImpact2 +// +//---------------------------------------------------------------------------- + +void A_MaceBallImpact2(mobj_t * ball) +{ + mobj_t *tiny; + angle_t angle; + + if ((ball->z <= ball->floorz) && (P_HitFloor(ball) != FLOOR_SOLID)) + { // Landed in some sort of liquid + P_RemoveMobj(ball); + return; + } + if ((ball->z != ball->floorz) || (ball->momz < 2 * FRACUNIT)) + { // Explode + ball->momx = ball->momy = ball->momz = 0; + ball->flags |= MF_NOGRAVITY; + ball->flags2 &= ~(MF2_LOGRAV | MF2_FLOORBOUNCE); + } + else + { // Bounce + ball->momz = (ball->momz * 192) >> 8; + P_SetMobjState(ball, ball->info->spawnstate); + + tiny = P_SpawnMobj(ball->x, ball->y, ball->z, MT_MACEFX3); + angle = ball->angle + ANG90; + tiny->target = ball->target; + tiny->angle = angle; + angle >>= ANGLETOFINESHIFT; + tiny->momx = (ball->momx >> 1) + FixedMul(ball->momz - FRACUNIT, + finecosine[angle]); + tiny->momy = (ball->momy >> 1) + FixedMul(ball->momz - FRACUNIT, + finesine[angle]); + tiny->momz = ball->momz; + P_CheckMissileSpawn(tiny); + + tiny = P_SpawnMobj(ball->x, ball->y, ball->z, MT_MACEFX3); + angle = ball->angle - ANG90; + tiny->target = ball->target; + tiny->angle = angle; + angle >>= ANGLETOFINESHIFT; + tiny->momx = (ball->momx >> 1) + FixedMul(ball->momz - FRACUNIT, + finecosine[angle]); + tiny->momy = (ball->momy >> 1) + FixedMul(ball->momz - FRACUNIT, + finesine[angle]); + tiny->momz = ball->momz; + P_CheckMissileSpawn(tiny); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireMacePL2 +// +//---------------------------------------------------------------------------- + +void A_FireMacePL2(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + + player->ammo[am_mace] -= deathmatch ? USE_MACE_AMMO_1 : USE_MACE_AMMO_2; + mo = P_SpawnPlayerMissile(player->mo, MT_MACEFX4); + if (mo) + { + mo->momx += player->mo->momx; + mo->momy += player->mo->momy; + mo->momz = 2 * FRACUNIT + ((player->lookdir) << (FRACBITS - 5)); + if (linetarget) + { + mo->special1 = (int) linetarget; + } + } + S_StartSound(player->mo, sfx_lobsht); +} + +//---------------------------------------------------------------------------- +// +// PROC A_DeathBallImpact +// +//---------------------------------------------------------------------------- + +void A_DeathBallImpact(mobj_t * ball) +{ + int i; + mobj_t *target; + angle_t angle; + boolean newAngle; + + if ((ball->z <= ball->floorz) && (P_HitFloor(ball) != FLOOR_SOLID)) + { // Landed in some sort of liquid + P_RemoveMobj(ball); + return; + } + if ((ball->z <= ball->floorz) && ball->momz) + { // Bounce + newAngle = false; + target = (mobj_t *) ball->special1; + if (target) + { + if (!(target->flags & MF_SHOOTABLE)) + { // Target died + ball->special1 = 0; + } + else + { // Seek + angle = R_PointToAngle2(ball->x, ball->y, + target->x, target->y); + newAngle = true; + } + } + else + { // Find new target + angle = 0; + for (i = 0; i < 16; i++) + { + P_AimLineAttack(ball, angle, 10 * 64 * FRACUNIT); + if (linetarget && ball->target != linetarget) + { + ball->special1 = (int) linetarget; + angle = R_PointToAngle2(ball->x, ball->y, + linetarget->x, linetarget->y); + newAngle = true; + break; + } + angle += ANG45 / 2; + } + } + if (newAngle) + { + ball->angle = angle; + angle >>= ANGLETOFINESHIFT; + ball->momx = FixedMul(ball->info->speed, finecosine[angle]); + ball->momy = FixedMul(ball->info->speed, finesine[angle]); + } + P_SetMobjState(ball, ball->info->spawnstate); + S_StartSound(ball, sfx_pstop); + } + else + { // Explode + ball->flags |= MF_NOGRAVITY; + ball->flags2 &= ~MF2_LOGRAV; + S_StartSound(ball, sfx_phohit); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_SpawnRippers +// +//---------------------------------------------------------------------------- + +void A_SpawnRippers(mobj_t * actor) +{ + int i; + angle_t angle; + mobj_t *ripper; + + for (i = 0; i < 8; i++) + { + ripper = P_SpawnMobj(actor->x, actor->y, actor->z, MT_RIPPER); + angle = i * ANG45; + ripper->target = actor->target; + ripper->angle = angle; + angle >>= ANGLETOFINESHIFT; + ripper->momx = FixedMul(ripper->info->speed, finecosine[angle]); + ripper->momy = FixedMul(ripper->info->speed, finesine[angle]); + P_CheckMissileSpawn(ripper); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireCrossbowPL1 +// +//---------------------------------------------------------------------------- + +void A_FireCrossbowPL1(player_t * player, pspdef_t * psp) +{ + mobj_t *pmo; + + pmo = player->mo; + player->ammo[am_crossbow] -= USE_CBOW_AMMO_1; + P_SpawnPlayerMissile(pmo, MT_CRBOWFX1); + P_SPMAngle(pmo, MT_CRBOWFX3, pmo->angle - (ANG45 / 10)); + P_SPMAngle(pmo, MT_CRBOWFX3, pmo->angle + (ANG45 / 10)); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireCrossbowPL2 +// +//---------------------------------------------------------------------------- + +void A_FireCrossbowPL2(player_t * player, pspdef_t * psp) +{ + mobj_t *pmo; + + pmo = player->mo; + player->ammo[am_crossbow] -= + deathmatch ? USE_CBOW_AMMO_1 : USE_CBOW_AMMO_2; + P_SpawnPlayerMissile(pmo, MT_CRBOWFX2); + P_SPMAngle(pmo, MT_CRBOWFX2, pmo->angle - (ANG45 / 10)); + P_SPMAngle(pmo, MT_CRBOWFX2, pmo->angle + (ANG45 / 10)); + P_SPMAngle(pmo, MT_CRBOWFX3, pmo->angle - (ANG45 / 5)); + P_SPMAngle(pmo, MT_CRBOWFX3, pmo->angle + (ANG45 / 5)); +} + +//---------------------------------------------------------------------------- +// +// PROC A_BoltSpark +// +//---------------------------------------------------------------------------- + +void A_BoltSpark(mobj_t * bolt) +{ + mobj_t *spark; + + if (P_Random() > 50) + { + spark = P_SpawnMobj(bolt->x, bolt->y, bolt->z, MT_CRBOWFX4); + spark->x += (P_Random() - P_Random()) << 10; + spark->y += (P_Random() - P_Random()) << 10; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireSkullRodPL1 +// +//---------------------------------------------------------------------------- + +void A_FireSkullRodPL1(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + + if (player->ammo[am_skullrod] < USE_SKRD_AMMO_1) + { + return; + } + player->ammo[am_skullrod] -= USE_SKRD_AMMO_1; + mo = P_SpawnPlayerMissile(player->mo, MT_HORNRODFX1); + // Randomize the first frame + if (mo && P_Random() > 128) + { + P_SetMobjState(mo, S_HRODFX1_2); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_FireSkullRodPL2 +// +// The special2 field holds the player number that shot the rain missile. +// The special1 field is used for the seeking routines, then as a counter +// for the sound looping. +// +//---------------------------------------------------------------------------- + +void A_FireSkullRodPL2(player_t * player, pspdef_t * psp) +{ + player->ammo[am_skullrod] -= + deathmatch ? USE_SKRD_AMMO_1 : USE_SKRD_AMMO_2; + P_SpawnPlayerMissile(player->mo, MT_HORNRODFX2); + // Use MissileMobj instead of the return value from + // P_SpawnPlayerMissile because we need to give info to the mobj + // even if it exploded immediately. + if (netgame) + { // Multi-player game + MissileMobj->special2 = P_GetPlayerNum(player); + } + else + { // Always use red missiles in single player games + MissileMobj->special2 = 2; + } + if (linetarget) + { + MissileMobj->special1 = (int) linetarget; + } + S_StartSound(MissileMobj, sfx_hrnpow); +} + +//---------------------------------------------------------------------------- +// +// PROC A_SkullRodPL2Seek +// +//---------------------------------------------------------------------------- + +void A_SkullRodPL2Seek(mobj_t * actor) +{ + P_SeekerMissile(actor, ANG1_X * 10, ANG1_X * 30); +} + +//---------------------------------------------------------------------------- +// +// PROC A_AddPlayerRain +// +//---------------------------------------------------------------------------- + +void A_AddPlayerRain(mobj_t * actor) +{ + int playerNum; + player_t *player; + + playerNum = netgame ? actor->special2 : 0; + if (!playeringame[playerNum]) + { // Player left the game + return; + } + player = &players[playerNum]; + if (player->health <= 0) + { // Player is dead + return; + } + if (player->rain1 && player->rain2) + { // Terminate an active rain + if (player->rain1->health < player->rain2->health) + { + if (player->rain1->health > 16) + { + player->rain1->health = 16; + } + player->rain1 = NULL; + } + else + { + if (player->rain2->health > 16) + { + player->rain2->health = 16; + } + player->rain2 = NULL; + } + } + // Add rain mobj to list + if (player->rain1) + { + player->rain2 = actor; + } + else + { + player->rain1 = actor; + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_SkullRodStorm +// +//---------------------------------------------------------------------------- + +void A_SkullRodStorm(mobj_t * actor) +{ + fixed_t x; + fixed_t y; + mobj_t *mo; + int playerNum; + player_t *player; + + if (actor->health-- == 0) + { + P_SetMobjState(actor, S_NULL); + playerNum = netgame ? actor->special2 : 0; + if (!playeringame[playerNum]) + { // Player left the game + return; + } + player = &players[playerNum]; + if (player->health <= 0) + { // Player is dead + return; + } + if (player->rain1 == actor) + { + player->rain1 = NULL; + } + else if (player->rain2 == actor) + { + player->rain2 = NULL; + } + return; + } + if (P_Random() < 25) + { // Fudge rain frequency + return; + } + x = actor->x + ((P_Random() & 127) - 64) * FRACUNIT; + y = actor->y + ((P_Random() & 127) - 64) * FRACUNIT; + mo = P_SpawnMobj(x, y, ONCEILINGZ, MT_RAINPLR1 + actor->special2); + mo->target = actor->target; + mo->momx = 1; // Force collision detection + mo->momz = -mo->info->speed; + mo->special2 = actor->special2; // Transfer player number + P_CheckMissileSpawn(mo); + if (!(actor->special1 & 31)) + { + S_StartSound(actor, sfx_ramrain); + } + actor->special1++; +} + +//---------------------------------------------------------------------------- +// +// PROC A_RainImpact +// +//---------------------------------------------------------------------------- + +void A_RainImpact(mobj_t * actor) +{ + if (actor->z > actor->floorz) + { + P_SetMobjState(actor, S_RAINAIRXPLR1_1 + actor->special2); + } + else if (P_Random() < 40) + { + P_HitFloor(actor); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_HideInCeiling +// +//---------------------------------------------------------------------------- + +void A_HideInCeiling(mobj_t * actor) +{ + actor->z = actor->ceilingz + 4 * FRACUNIT; +} + +//---------------------------------------------------------------------------- +// +// PROC A_FirePhoenixPL1 +// +//---------------------------------------------------------------------------- + +void A_FirePhoenixPL1(player_t * player, pspdef_t * psp) +{ + angle_t angle; + + player->ammo[am_phoenixrod] -= USE_PHRD_AMMO_1; + P_SpawnPlayerMissile(player->mo, MT_PHOENIXFX1); + //P_SpawnPlayerMissile(player->mo, MT_MNTRFX2); + angle = player->mo->angle + ANG180; + angle >>= ANGLETOFINESHIFT; + player->mo->momx += FixedMul(4 * FRACUNIT, finecosine[angle]); + player->mo->momy += FixedMul(4 * FRACUNIT, finesine[angle]); +} + +//---------------------------------------------------------------------------- +// +// PROC A_PhoenixPuff +// +//---------------------------------------------------------------------------- + +void A_PhoenixPuff(mobj_t * actor) +{ + mobj_t *puff; + angle_t angle; + + P_SeekerMissile(actor, ANG1_X * 5, ANG1_X * 10); + puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF); + angle = actor->angle + ANG90; + angle >>= ANGLETOFINESHIFT; + puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine[angle]); + puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine[angle]); + puff->momz = 0; + puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF); + angle = actor->angle - ANG90; + angle >>= ANGLETOFINESHIFT; + puff->momx = FixedMul((fixed_t)(FRACUNIT * 1.3), finecosine[angle]); + puff->momy = FixedMul((fixed_t)(FRACUNIT * 1.3), finesine[angle]); + puff->momz = 0; +} + +// +// This function was present in the Heretic 1.0 executable for the +// removed "secondary phoenix flash" object (MT_PHOENIXFX_REMOVED). +// The purpose of this object is unknown, as is this function. +// + +void A_RemovedPhoenixFunc(mobj_t *actor) +{ + I_Error("Action function invoked for removed Phoenix action!"); +} + +//---------------------------------------------------------------------------- +// +// PROC A_InitPhoenixPL2 +// +//---------------------------------------------------------------------------- + +void A_InitPhoenixPL2(player_t * player, pspdef_t * psp) +{ + player->flamecount = FLAME_THROWER_TICS; +} + +//---------------------------------------------------------------------------- +// +// PROC A_FirePhoenixPL2 +// +// Flame thrower effect. +// +//---------------------------------------------------------------------------- + +void A_FirePhoenixPL2(player_t * player, pspdef_t * psp) +{ + mobj_t *mo; + mobj_t *pmo; + angle_t angle; + fixed_t x, y, z; + fixed_t slope; + + if (--player->flamecount == 0) + { // Out of flame + P_SetPsprite(player, ps_weapon, S_PHOENIXATK2_4); + player->refire = 0; + return; + } + pmo = player->mo; + angle = pmo->angle; + x = pmo->x + ((P_Random() - P_Random()) << 9); + y = pmo->y + ((P_Random() - P_Random()) << 9); + z = pmo->z + 26 * FRACUNIT + ((player->lookdir) << FRACBITS) / 173; + if (pmo->flags2 & MF2_FEETARECLIPPED) + { + z -= FOOTCLIPSIZE; + } + slope = ((player->lookdir) << FRACBITS) / 173 + (FRACUNIT / 10); + mo = P_SpawnMobj(x, y, z, MT_PHOENIXFX2); + mo->target = pmo; + mo->angle = angle; + mo->momx = pmo->momx + FixedMul(mo->info->speed, + finecosine[angle >> ANGLETOFINESHIFT]); + mo->momy = pmo->momy + FixedMul(mo->info->speed, + finesine[angle >> ANGLETOFINESHIFT]); + mo->momz = FixedMul(mo->info->speed, slope); + if (!player->refire || !(leveltime % 38)) + { + S_StartSound(player->mo, sfx_phopow); + } + P_CheckMissileSpawn(mo); +} + +//---------------------------------------------------------------------------- +// +// PROC A_ShutdownPhoenixPL2 +// +//---------------------------------------------------------------------------- + +void A_ShutdownPhoenixPL2(player_t * player, pspdef_t * psp) +{ + player->ammo[am_phoenixrod] -= USE_PHRD_AMMO_2; +} + +//---------------------------------------------------------------------------- +// +// PROC A_FlameEnd +// +//---------------------------------------------------------------------------- + +void A_FlameEnd(mobj_t * actor) +{ + actor->momz += (fixed_t)(1.5 * FRACUNIT); +} + +//---------------------------------------------------------------------------- +// +// PROC A_FloatPuff +// +//---------------------------------------------------------------------------- + +void A_FloatPuff(mobj_t * puff) +{ + puff->momz += (fixed_t)(1.8 * FRACUNIT); +} + +//--------------------------------------------------------------------------- +// +// PROC A_GauntletAttack +// +//--------------------------------------------------------------------------- + +void A_GauntletAttack(player_t * player, pspdef_t * psp) +{ + angle_t angle; + int damage; + int slope; + int randVal; + fixed_t dist; + + psp->sx = ((P_Random() & 3) - 2) * FRACUNIT; + psp->sy = WEAPONTOP + (P_Random() & 3) * FRACUNIT; + angle = player->mo->angle; + if (player->powers[pw_weaponlevel2]) + { + damage = HITDICE(2); + dist = 4 * MELEERANGE; + angle += (P_Random() - P_Random()) << 17; + PuffType = MT_GAUNTLETPUFF2; + } + else + { + damage = HITDICE(2); + dist = MELEERANGE + 1; + angle += (P_Random() - P_Random()) << 18; + PuffType = MT_GAUNTLETPUFF1; + } + slope = P_AimLineAttack(player->mo, angle, dist); + P_LineAttack(player->mo, angle, dist, slope, damage); + if (!linetarget) + { + if (P_Random() > 64) + { + player->extralight = !player->extralight; + } + S_StartSound(player->mo, sfx_gntful); + return; + } + randVal = P_Random(); + if (randVal < 64) + { + player->extralight = 0; + } + else if (randVal < 160) + { + player->extralight = 1; + } + else + { + player->extralight = 2; + } + if (player->powers[pw_weaponlevel2]) + { + P_GiveBody(player, damage >> 1); + S_StartSound(player->mo, sfx_gntpow); + } + else + { + S_StartSound(player->mo, sfx_gnthit); + } + // 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; +} + +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; +} + +//------------------------------------------------------------------------ +// +// PROC 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 = NULL; + } + // Spawn the ready weapon + player->pendingweapon = player->readyweapon; + P_BringUpWeapon(player); +} + +//------------------------------------------------------------------------ +// +// PROC 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++) + { + if ((state = psp->state) != 0) // a null state means not active + { + // drop tic count and possibly change state + if (psp->tics != -1) // a -1 tic count never changes + { + 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; +} diff --git a/src/heretic/p_setup.c b/src/heretic/p_setup.c new file mode 100644 index 00000000..a3bd2912 --- /dev/null +++ b/src/heretic/p_setup.c @@ -0,0 +1,660 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_main.c + +#include <math.h> +#include <stdlib.h> + +#include "doomdef.h" +#include "i_swap.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_bbox.h" +#include "p_local.h" +#include "s_sound.h" + +void P_SpawnMapThing(mapthing_t * mthing); + +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; + +short *blockmaplump; // offsets in blockmap are from here +short *blockmap; +int bmapwidth, bmapheight; // in mapblocks +fixed_t bmaporgx, bmaporgy; // origin of block map +mobj_t **blocklinks; // for thing chains + +byte *rejectmatrix; // for fast sight rejection + +mapthing_t deathmatchstarts[10], *deathmatch_p; +mapthing_t playerstarts[MAXPLAYERS]; + +/* +================= += += P_LoadVertexes += +================= +*/ + +void P_LoadVertexes(int lump) +{ + byte *data; + int i; + mapvertex_t *ml; + vertex_t *li; + + numvertexes = W_LumpLength(lump) / sizeof(mapvertex_t); + vertexes = Z_Malloc(numvertexes * sizeof(vertex_t), PU_LEVEL, 0); + data = W_CacheLumpNum(lump, PU_STATIC); + + ml = (mapvertex_t *) data; + li = vertexes; + for (i = 0; i < numvertexes; i++, li++, ml++) + { + li->x = SHORT(ml->x) << FRACBITS; + li->y = SHORT(ml->y) << FRACBITS; + } + + W_ReleaseLumpNum(lump); +} + + +/* +================= += += P_LoadSegs += +================= +*/ + +void P_LoadSegs(int lump) +{ + byte *data; + int i; + mapseg_t *ml; + seg_t *li; + line_t *ldef; + int linedef, side; + + numsegs = W_LumpLength(lump) / sizeof(mapseg_t); + segs = Z_Malloc(numsegs * sizeof(seg_t), PU_LEVEL, 0); + memset(segs, 0, numsegs * sizeof(seg_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + ml = (mapseg_t *) data; + li = segs; + for (i = 0; i < numsegs; i++, li++, ml++) + { + li->v1 = &vertexes[SHORT(ml->v1)]; + li->v2 = &vertexes[SHORT(ml->v2)]; + + li->angle = (SHORT(ml->angle)) << 16; + li->offset = (SHORT(ml->offset)) << 16; + linedef = SHORT(ml->linedef); + ldef = &lines[linedef]; + li->linedef = ldef; + side = SHORT(ml->side); + li->sidedef = &sides[ldef->sidenum[side]]; + li->frontsector = sides[ldef->sidenum[side]].sector; + if (ldef->flags & ML_TWOSIDED) + li->backsector = sides[ldef->sidenum[side ^ 1]].sector; + else + li->backsector = 0; + } + + W_ReleaseLumpNum(lump); +} + + +/* +================= += += P_LoadSubsectors += +================= +*/ + +void P_LoadSubsectors(int lump) +{ + byte *data; + int i; + mapsubsector_t *ms; + subsector_t *ss; + + numsubsectors = W_LumpLength(lump) / sizeof(mapsubsector_t); + subsectors = Z_Malloc(numsubsectors * sizeof(subsector_t), PU_LEVEL, 0); + data = W_CacheLumpNum(lump, PU_STATIC); + + ms = (mapsubsector_t *) data; + memset(subsectors, 0, numsubsectors * sizeof(subsector_t)); + ss = subsectors; + for (i = 0; i < numsubsectors; i++, ss++, ms++) + { + ss->numlines = SHORT(ms->numsegs); + ss->firstline = SHORT(ms->firstseg); + } + + W_ReleaseLumpNum(lump); +} + + +/* +================= += += P_LoadSectors += +================= +*/ + +void P_LoadSectors(int lump) +{ + byte *data; + int i; + mapsector_t *ms; + sector_t *ss; + + numsectors = W_LumpLength(lump) / sizeof(mapsector_t); + sectors = Z_Malloc(numsectors * sizeof(sector_t), PU_LEVEL, 0); + memset(sectors, 0, numsectors * sizeof(sector_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + ms = (mapsector_t *) data; + ss = sectors; + for (i = 0; i < numsectors; i++, ss++, ms++) + { + ss->floorheight = SHORT(ms->floorheight) << FRACBITS; + ss->ceilingheight = SHORT(ms->ceilingheight) << FRACBITS; + ss->floorpic = R_FlatNumForName(ms->floorpic); + ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + W_ReleaseLumpNum(lump); +} + + +/* +================= += += P_LoadNodes += +================= +*/ + +void P_LoadNodes(int lump) +{ + byte *data; + int i, j, k; + mapnode_t *mn; + node_t *no; + + numnodes = W_LumpLength(lump) / sizeof(mapnode_t); + nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0); + data = W_CacheLumpNum(lump, PU_STATIC); + + mn = (mapnode_t *) data; + no = nodes; + for (i = 0; i < numnodes; i++, no++, mn++) + { + no->x = SHORT(mn->x) << FRACBITS; + no->y = SHORT(mn->y) << FRACBITS; + no->dx = SHORT(mn->dx) << FRACBITS; + no->dy = SHORT(mn->dy) << FRACBITS; + for (j = 0; j < 2; j++) + { + no->children[j] = SHORT(mn->children[j]); + for (k = 0; k < 4; k++) + no->bbox[j][k] = SHORT(mn->bbox[j][k]) << FRACBITS; + } + } + + W_ReleaseLumpNum(lump); +} + + + +/* +================= += += P_LoadThings += +================= +*/ + +void P_LoadThings(int lump) +{ + byte *data; + int i; + mapthing_t spawnthing; + mapthing_t *mt; + int numthings; + + data = W_CacheLumpNum(lump, PU_STATIC); + numthings = W_LumpLength(lump) / sizeof(mapthing_t); + + mt = (mapthing_t *) data; + for (i = 0; i < numthings; i++, mt++) + { + spawnthing.x = SHORT(mt->x); + spawnthing.y = SHORT(mt->y); + spawnthing.angle = SHORT(mt->angle); + spawnthing.type = SHORT(mt->type); + spawnthing.options = SHORT(mt->options); + P_SpawnMapThing(&spawnthing); + } + + W_ReleaseLumpNum(lump); +} + + + +/* +================= += += P_LoadLineDefs += += Also counts secret lines for intermissions +================= +*/ + +void P_LoadLineDefs(int lump) +{ + byte *data; + int i; + maplinedef_t *mld; + line_t *ld; + vertex_t *v1, *v2; + + numlines = W_LumpLength(lump) / sizeof(maplinedef_t); + lines = Z_Malloc(numlines * sizeof(line_t), PU_LEVEL, 0); + memset(lines, 0, numlines * sizeof(line_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + mld = (maplinedef_t *) data; + ld = lines; + for (i = 0; i < numlines; i++, mld++, ld++) + { + ld->flags = SHORT(mld->flags); + ld->special = SHORT(mld->special); + ld->tag = SHORT(mld->tag); + v1 = ld->v1 = &vertexes[SHORT(mld->v1)]; + v2 = ld->v2 = &vertexes[SHORT(mld->v2)]; + ld->dx = v2->x - v1->x; + ld->dy = v2->y - v1->y; + if (!ld->dx) + ld->slopetype = ST_VERTICAL; + else if (!ld->dy) + ld->slopetype = ST_HORIZONTAL; + else + { + if (FixedDiv(ld->dy, ld->dx) > 0) + ld->slopetype = ST_POSITIVE; + else + ld->slopetype = ST_NEGATIVE; + } + + if (v1->x < v2->x) + { + ld->bbox[BOXLEFT] = v1->x; + ld->bbox[BOXRIGHT] = v2->x; + } + else + { + ld->bbox[BOXLEFT] = v2->x; + ld->bbox[BOXRIGHT] = v1->x; + } + if (v1->y < v2->y) + { + ld->bbox[BOXBOTTOM] = v1->y; + ld->bbox[BOXTOP] = v2->y; + } + else + { + ld->bbox[BOXBOTTOM] = v2->y; + ld->bbox[BOXTOP] = v1->y; + } + ld->sidenum[0] = SHORT(mld->sidenum[0]); + ld->sidenum[1] = SHORT(mld->sidenum[1]); + if (ld->sidenum[0] != -1) + ld->frontsector = sides[ld->sidenum[0]].sector; + else + ld->frontsector = 0; + if (ld->sidenum[1] != -1) + ld->backsector = sides[ld->sidenum[1]].sector; + else + ld->backsector = 0; + } + + W_ReleaseLumpNum(lump); +} + + +/* +================= += += P_LoadSideDefs += +================= +*/ + +void P_LoadSideDefs(int lump) +{ + byte *data; + int i; + mapsidedef_t *msd; + side_t *sd; + + numsides = W_LumpLength(lump) / sizeof(mapsidedef_t); + sides = Z_Malloc(numsides * sizeof(side_t), PU_LEVEL, 0); + memset(sides, 0, numsides * sizeof(side_t)); + data = W_CacheLumpNum(lump, PU_STATIC); + + msd = (mapsidedef_t *) data; + sd = sides; + for (i = 0; i < numsides; i++, msd++, sd++) + { + sd->textureoffset = SHORT(msd->textureoffset) << FRACBITS; + sd->rowoffset = SHORT(msd->rowoffset) << FRACBITS; + sd->toptexture = R_TextureNumForName(msd->toptexture); + sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); + sd->midtexture = R_TextureNumForName(msd->midtexture); + sd->sector = §ors[SHORT(msd->sector)]; + } + + W_ReleaseLumpNum(lump); +} + + + +/* +================= += += P_LoadBlockMap += +================= +*/ + +void P_LoadBlockMap(int lump) +{ + int i, count; + int lumplen; + + lumplen = W_LumpLength(lump); + + blockmaplump = Z_Malloc(lumplen, PU_LEVEL, NULL); + W_ReadLump(lump, blockmaplump); + blockmap = blockmaplump + 4; + + // Swap all short integers to native byte ordering: + + count = lumplen / 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); + 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, j, 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("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; + int parm; + char lumpname[9]; + int lumpnum; + mobj_t *mobj; + + totalkills = totalitems = totalsecret = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].killcount = players[i].secretcount + = players[i].itemcount = 0; + } + players[consoleplayer].viewz = 1; // will be set by player think + + S_Start(); // make sure all sounds are stopped before Z_FreeTags + + Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); + + P_InitThinkers(); + +// +// look for a regular (development) map first +// + lumpname[0] = 'E'; + lumpname[1] = '0' + episode; + lumpname[2] = 'M'; + lumpname[3] = '0' + map; + lumpname[4] = 0; + leveltime = 0; + + lumpnum = W_GetNumForName(lumpname); + +// 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_InitAmbientSound(); + P_InitMonsters(); + P_OpenWeapons(); + P_LoadThings(lumpnum + ML_THINGS); + P_CloseWeapons(); + +// +// if deathmatch, randomly spawn the active players +// + TimerGame = 0; + if (deathmatch) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { // must give a player spot before deathmatchspawn + mobj = P_SpawnMobj(playerstarts[i].x << 16, + playerstarts[i].y << 16, 0, MT_PLAYER); + players[i].mo = mobj; + G_DeathMatchSpawnPlayer(i); + P_RemoveMobj(mobj); + } + } + parm = M_CheckParm("-timer"); + if (parm && parm < myargc - 1) + { + TimerGame = atoi(myargv[parm + 1]) * 35 * 60; + } + } + +// set up world state + P_SpawnSpecials(); + +// build subsector connect matrix +// P_ConnectSubsectors (); + +// preload graphics + if (precache) + R_PrecacheLevel(); + +//printf ("free memory: 0x%x\n", Z_FreeMemory()); + +} + + +/* +================= += += P_Init += +================= +*/ + +void P_Init(void) +{ + P_InitSwitchList(); + P_InitPicAnims(); + P_InitTerrainTypes(); + P_InitLava(); + R_InitSprites(sprnames); +} diff --git a/src/heretic/p_sight.c b/src/heretic/p_sight.c new file mode 100644 index 00000000..002b32a9 --- /dev/null +++ b/src/heretic/p_sight.c @@ -0,0 +1,363 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// P_sight.c + +#include <stdlib.h> + +#include "doomdef.h" +#include "p_local.h" + +/* +============================================================================== + + P_CheckSight + +This uses specialized forms of the maputils routines for optimized performance + +============================================================================== +*/ + +fixed_t sightzstart; // eye z of looker +fixed_t topslope, bottomslope; // slopes to top and bottom of target + +int sightcounts[3]; + +/* +============== += += PTR_SightTraverse += +============== +*/ + +boolean PTR_SightTraverse(intercept_t * in) +{ + line_t *li; + fixed_t slope; + + li = in->d.line; + +// +// crosses a two sided line +// + P_LineOpening(li); + + if (openbottom >= opentop) // quick test for totally closed doors + return false; // stop + + if (li->frontsector->floorheight != li->backsector->floorheight) + { + slope = FixedDiv(openbottom - sightzstart, in->frac); + if (slope > bottomslope) + bottomslope = slope; + } + + if (li->frontsector->ceilingheight != li->backsector->ceilingheight) + { + slope = FixedDiv(opentop - sightzstart, in->frac); + if (slope < topslope) + topslope = slope; + } + + if (topslope <= bottomslope) + return false; // stop + + return true; // keep going +} + + + +/* +================== += += P_SightBlockLinesIterator += +=================== +*/ + +boolean P_SightBlockLinesIterator(int x, int y) +{ + int offset; + short *list; + line_t *ld; + int s1, s2; + divline_t dl; + + 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; + + s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace); + s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace); + if (s1 == s2) + continue; // line isn't crossed + P_MakeDivline(ld, &dl); + s1 = P_PointOnDivlineSide(trace.x, trace.y, &dl); + s2 = P_PointOnDivlineSide(trace.x + trace.dx, trace.y + trace.dy, + &dl); + if (s1 == s2) + continue; // line isn't crossed + + // try to early out the check + if (!ld->backsector) + return false; // stop checking + + // store the line for later intersection testing + intercept_p->d.line = ld; + intercept_p++; + + } + + return true; // everything was checked +} + +/* +==================== += += P_SightTraverseIntercepts += += Returns true if the traverser function returns true for all lines +==================== +*/ + +boolean P_SightTraverseIntercepts(void) +{ + int count; + fixed_t dist; + intercept_t *scan, *in; + divline_t dl; + + count = intercept_p - intercepts; +// +// calculate intercept distance +// + for (scan = intercepts; scan < intercept_p; scan++) + { + P_MakeDivline(scan->d.line, &dl); + scan->frac = P_InterceptVector(&trace, &dl); + } + +// +// go through in order +// + in = 0; // shut up compiler warning + + while (count--) + { + dist = INT_MAX; + for (scan = intercepts; scan < intercept_p; scan++) + if (scan->frac < dist) + { + dist = scan->frac; + in = scan; + } + + if (!PTR_SightTraverse(in)) + return false; // don't bother going farther + in->frac = INT_MAX; + } + + return true; // everything was traversed +} + + + +/* +================== += += P_SightPathTraverse += += 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 +================== +*/ + +boolean P_SightPathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) +{ + fixed_t xt1, yt1, xt2, yt2; + fixed_t xstep, ystep; + fixed_t partial; + fixed_t xintercept, yintercept; + int mapx, mapy, mapxstep, mapystep; + int count; + + 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; + +// points should never be out of bounds, but check once instead of +// each block + if (xt1 < 0 || yt1 < 0 || xt1 >= bmapwidth || yt1 >= bmapheight + || xt2 < 0 || yt2 < 0 || xt2 >= bmapwidth || yt2 >= bmapheight) + return false; + + if (xt2 > xt1) + { + mapxstep = 1; + partial = FRACUNIT - ((x1 >> MAPBTOFRAC) & (FRACUNIT - 1)); + ystep = FixedDiv(y2 - y1, abs(x2 - x1)); + } + else if (xt2 < xt1) + { + mapxstep = -1; + partial = (x1 >> MAPBTOFRAC) & (FRACUNIT - 1); + ystep = FixedDiv(y2 - y1, 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, abs(y2 - y1)); + } + else if (yt2 < yt1) + { + mapystep = -1; + partial = (y1 >> MAPBTOFRAC) & (FRACUNIT - 1); + xstep = FixedDiv(x2 - x1, 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 (!P_SightBlockLinesIterator(mapx, mapy)) + { + sightcounts[1]++; + 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; + } + + } + + +// +// couldn't early out, so go through the sorted list +// + sightcounts[2]++; + + return P_SightTraverseIntercepts(); +} + + + +/* +===================== += += P_CheckSight += += Returns true if a straight line between t1 and t2 is unobstructed += look from eyes of t1 to any part of t2 += +===================== +*/ + +boolean P_CheckSight(mobj_t * t1, mobj_t * t2) +{ + int s1, s2; + int pnum, bytenum, bitnum; + +// +// check for trivial rejection +// + s1 = (t1->subsector->sector - sectors); + s2 = (t2->subsector->sector - sectors); + pnum = s1 * numsectors + s2; + bytenum = pnum >> 3; + bitnum = 1 << (pnum & 7); + + if (rejectmatrix[bytenum] & bitnum) + { + sightcounts[0]++; + return false; // can't possibly be connected + } + +// +// check precisely +// + sightzstart = t1->z + t1->height - (t1->height >> 2); + topslope = (t2->z + t2->height) - sightzstart; + bottomslope = (t2->z) - sightzstart; + + return P_SightPathTraverse(t1->x, t1->y, t2->x, t2->y); +} diff --git a/src/heretic/p_spec.c b/src/heretic/p_spec.c new file mode 100644 index 00000000..49c067fa --- /dev/null +++ b/src/heretic/p_spec.c @@ -0,0 +1,1308 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_Spec.c + +#include "doomdef.h" +#include "deh_str.h" +#include "i_system.h" +#include "i_timer.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +// Macros + +#define MAX_AMBIENT_SFX 8 // Per level + +// Types + +typedef enum +{ + afxcmd_play, // (sound) + afxcmd_playabsvol, // (sound, volume) + afxcmd_playrelvol, // (sound, volume) + afxcmd_delay, // (ticks) + afxcmd_delayrand, // (andbits) + afxcmd_end // () +} afxcmd_t; + +// Data + +int *LevelAmbientSfx[MAX_AMBIENT_SFX]; +int *AmbSfxPtr; +int AmbSfxCount; +int AmbSfxTics; +int AmbSfxVolume; + +int AmbSndSeqInit[] = { // Startup + afxcmd_end +}; +int AmbSndSeq1[] = { // Scream + afxcmd_play, sfx_amb1, + afxcmd_end +}; +int AmbSndSeq2[] = { // Squish + afxcmd_play, sfx_amb2, + afxcmd_end +}; +int AmbSndSeq3[] = { // Drops + afxcmd_play, sfx_amb3, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_play, sfx_amb7, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_play, sfx_amb3, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_play, sfx_amb7, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_play, sfx_amb3, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_play, sfx_amb7, + afxcmd_delay, 16, + afxcmd_delayrand, 31, + afxcmd_end +}; +int AmbSndSeq4[] = { // SlowFootSteps + afxcmd_play, sfx_amb4, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 15, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_end +}; +int AmbSndSeq5[] = { // Heartbeat + afxcmd_play, sfx_amb5, + afxcmd_delay, 35, + afxcmd_play, sfx_amb5, + afxcmd_delay, 35, + afxcmd_play, sfx_amb5, + afxcmd_delay, 35, + afxcmd_play, sfx_amb5, + afxcmd_end +}; +int AmbSndSeq6[] = { // Bells + afxcmd_play, sfx_amb6, + afxcmd_delay, 17, + afxcmd_playrelvol, sfx_amb6, -8, + afxcmd_delay, 17, + afxcmd_playrelvol, sfx_amb6, -8, + afxcmd_delay, 17, + afxcmd_playrelvol, sfx_amb6, -8, + afxcmd_end +}; +int AmbSndSeq7[] = { // Growl + afxcmd_play, sfx_bstsit, + afxcmd_end +}; +int AmbSndSeq8[] = { // Magic + afxcmd_play, sfx_amb8, + afxcmd_end +}; +int AmbSndSeq9[] = { // Laughter + afxcmd_play, sfx_amb9, + afxcmd_delay, 16, + afxcmd_playrelvol, sfx_amb9, -4, + afxcmd_delay, 16, + afxcmd_playrelvol, sfx_amb9, -4, + afxcmd_delay, 16, + afxcmd_playrelvol, sfx_amb10, -4, + afxcmd_delay, 16, + afxcmd_playrelvol, sfx_amb10, -4, + afxcmd_delay, 16, + afxcmd_playrelvol, sfx_amb10, -4, + afxcmd_end +}; +int AmbSndSeq10[] = { // FastFootsteps + afxcmd_play, sfx_amb4, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb4, -3, + afxcmd_delay, 8, + afxcmd_playrelvol, sfx_amb11, -3, + afxcmd_end +}; + +int *AmbientSfx[] = { + AmbSndSeq1, // Scream + AmbSndSeq2, // Squish + AmbSndSeq3, // Drops + AmbSndSeq4, // SlowFootsteps + AmbSndSeq5, // Heartbeat + AmbSndSeq6, // Bells + AmbSndSeq7, // Growl + AmbSndSeq8, // Magic + AmbSndSeq9, // Laughter + AmbSndSeq10 // FastFootsteps +}; + +animdef_t animdefs[] = { + // false = flat + // true = texture + {false, "FLTWAWA3", "FLTWAWA1", 8}, // Water + {false, "FLTSLUD3", "FLTSLUD1", 8}, // Sludge + {false, "FLTTELE4", "FLTTELE1", 6}, // Teleport + {false, "FLTFLWW3", "FLTFLWW1", 9}, // River - West + {false, "FLTLAVA4", "FLTLAVA1", 8}, // Lava + {false, "FLATHUH4", "FLATHUH1", 8}, // Super Lava + {true, "LAVAFL3", "LAVAFL1", 6}, // Texture: Lavaflow + {true, "WATRWAL3", "WATRWAL1", 4}, // Texture: Waterfall + {-1} +}; + +anim_t anims[MAXANIMS]; +anim_t *lastanim; + +int *TerrainTypes; +struct +{ + char *name; + int type; +} TerrainTypeDefs[] = +{ + { "FLTWAWA1", FLOOR_WATER }, + { "FLTFLWW1", FLOOR_WATER }, + { "FLTLAVA1", FLOOR_LAVA }, + { "FLATHUH1", FLOOR_LAVA }, + { "FLTSLUD1", FLOOR_SLUDGE }, + { "END", -1 } +}; + +mobj_t LavaInflictor; + +//---------------------------------------------------------------------------- +// +// PROC P_InitLava +// +//---------------------------------------------------------------------------- + +void P_InitLava(void) +{ + memset(&LavaInflictor, 0, sizeof(mobj_t)); + LavaInflictor.type = MT_PHOENIXFX2; + LavaInflictor.flags2 = MF2_FIREDAMAGE | MF2_NODMGTHRUST; +} + +//---------------------------------------------------------------------------- +// +// PROC P_InitTerrainTypes +// +//---------------------------------------------------------------------------- + +void P_InitTerrainTypes(void) +{ + int i; + int lump; + int size; + + size = (numflats + 1) * sizeof(int); + TerrainTypes = Z_Malloc(size, PU_STATIC, 0); + memset(TerrainTypes, 0, size); + for (i = 0; TerrainTypeDefs[i].type != -1; i++) + { + lump = W_CheckNumForName(TerrainTypeDefs[i].name); + if (lump != -1) + { + TerrainTypes[lump - firstflat] = TerrainTypeDefs[i].type; + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_InitPicAnims +// +//---------------------------------------------------------------------------- + +void P_InitPicAnims(void) +{ + char *startname; + char *endname; + int i; + + lastanim = anims; + for (i = 0; animdefs[i].istexture != -1; i++) + { + startname = DEH_String(animdefs[i].startname); + endname = DEH_String(animdefs[i].endname); + + if (animdefs[i].istexture) + { // Texture animation + if (R_CheckTextureNumForName(startname) == -1) + { // Texture doesn't exist + continue; + } + lastanim->picnum = R_TextureNumForName(endname); + lastanim->basepic = R_TextureNumForName(startname); + } + else + { // Flat animation + if (W_CheckNumForName(startname) == -1) + { // Flat doesn't exist + continue; + } + lastanim->picnum = R_FlatNumForName(endname); + lastanim->basepic = R_FlatNumForName(startname); + } + lastanim->istexture = animdefs[i].istexture; + lastanim->numpics = lastanim->picnum - lastanim->basepic + 1; + if (lastanim->numpics < 2) + { + I_Error("P_InitPicAnims: bad cycle from %s to %s", + startname, endname); + } + lastanim->speed = animdefs[i].speed; + lastanim++; + } +} + +/* +============================================================================== + + UTILITIES + +============================================================================== +*/ + +// +// 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]]; +} + +// +// 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; +} + +// +// Given the sector number and the line number, 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; +} + +//================================================================== +// +// Return sector_t * of sector next to current. NULL if not two-sided line +// +//================================================================== +sector_t *getNextSector(line_t * line, sector_t * sec) +{ + if (!(line->flags & ML_TWOSIDED)) + return NULL; + + if (line->frontsector == sec) + return line->backsector; + + return line->frontsector; +} + +//================================================================== +// +// 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; +} + +//================================================================== +// +// 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; +} + +//================================================================== +// +// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS +// +//================================================================== +fixed_t P_FindNextHighestFloor(sector_t * sec, int currentheight) +{ + int i; + int h; + fixed_t min; + line_t *check; + sector_t *other; + fixed_t height = currentheight; + + min = INT_MAX; + + for (i = 0, h = 0; i < sec->linecount; i++) + { + check = sec->lines[i]; + other = getNextSector(check, sec); + + if (other != NULL && other->floorheight > height) + { + if (other->floorheight < min) + { + min = other->floorheight; + } + + ++h; + } + } + + // Compatibility note, in case of demo desyncs. + + if (h > 20) + { + fprintf(stderr, "P_FindNextHighestFloor: exceeded Vanilla limit\n"); + } + + 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 = INT_MAX; + + 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; + + line = &lines[linenum]; + if (!thing->player) + { // Check if trigger allowed by non-player mobj + switch (line->special) + { + case 39: // Trigger_TELEPORT + case 97: // Retrigger_TELEPORT + case 4: // Trigger_Raise_Door + //case 10: // PLAT DOWN-WAIT-UP-STAY TRIGGER + //case 88: // PLAT DOWN-WAIT-UP-STAY RETRIGGER + break; + default: + return; + break; + } + } + switch (line->special) + { + //==================================================== + // TRIGGERS + //==================================================== + case 2: // Open Door + EV_DoDoor(line, open, VDOORSPEED); + line->special = 0; + break; + case 3: // Close Door + EV_DoDoor(line, close, VDOORSPEED); + line->special = 0; + break; + case 4: // Raise Door + EV_DoDoor(line, normal, VDOORSPEED); + 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: // Trigger_Build_Stairs (8 pixel steps) + EV_BuildStairs(line, 8 * FRACUNIT); + line->special = 0; + break; + case 106: // Trigger_Build_Stairs_16 (16 pixel steps) + EV_BuildStairs(line, 16 * FRACUNIT); + 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, VDOORSPEED); + 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(); + line->special = 0; + 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 105: // Trigger_SecretExit + G_SecretExitLevel(); + line->special = 0; + break; + + //==================================================== + // RE-DOABLE TRIGGERS + //==================================================== + + 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, close, VDOORSPEED); + break; + case 76: // Close Door 30 + EV_DoDoor(line, close30ThenOpen, VDOORSPEED); + 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, open, VDOORSPEED); + 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, normal, VDOORSPEED); + break; + case 100: // Retrigger_Raise_Door_Turbo + EV_DoDoor(line, normal, VDOORSPEED * 3); + 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; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_ShootSpecialLine +// +// Called when a thing shoots a special line. +// +//---------------------------------------------------------------------------- + +void P_ShootSpecialLine(mobj_t * thing, line_t * line) +{ + if (!thing->player) + { // Check if trigger allowed by non-player mobj + switch (line->special) + { + case 46: // Impact_OpenDoor + break; + default: + return; + break; + } + } + switch (line->special) + { + case 24: // Impact_RaiseFloor + EV_DoFloor(line, raiseFloor); + P_ChangeSwitchTexture(line, 0); + break; + case 46: // Impact_OpenDoor + EV_DoDoor(line, open, VDOORSPEED); + P_ChangeSwitchTexture(line, 1); + break; + case 47: // Impact_RaiseFloorNear&Change + EV_DoPlat(line, raiseToNearestAndChange, 0); + P_ChangeSwitchTexture(line, 0); + break; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_PlayerInSpecialSector +// +// Called every tic frame that the player origin is in a special sector. +// +//---------------------------------------------------------------------------- + +void P_PlayerInSpecialSector(player_t * player) +{ + sector_t *sector; + static int pushTab[5] = { + 2048 * 5, + 2048 * 10, + 2048 * 25, + 2048 * 30, + 2048 * 35 + }; + + sector = player->mo->subsector->sector; + if (player->mo->z != sector->floorheight) + { // Player is not touching the floor + return; + } + switch (sector->special) + { + case 7: // Damage_Sludge + if (!(leveltime & 31)) + { + P_DamageMobj(player->mo, NULL, NULL, 4); + } + break; + case 5: // Damage_LavaWimpy + if (!(leveltime & 15)) + { + P_DamageMobj(player->mo, &LavaInflictor, NULL, 5); + P_HitFloor(player->mo); + } + break; + case 16: // Damage_LavaHefty + if (!(leveltime & 15)) + { + P_DamageMobj(player->mo, &LavaInflictor, NULL, 8); + P_HitFloor(player->mo); + } + break; + case 4: // Scroll_EastLavaDamage + P_Thrust(player, 0, 2048 * 28); + if (!(leveltime & 15)) + { + P_DamageMobj(player->mo, &LavaInflictor, NULL, 5); + P_HitFloor(player->mo); + } + break; + case 9: // SecretArea + player->secretcount++; + sector->special = 0; + break; + case 11: // Exit_SuperDamage (DOOM E1M8 finale) + /* + player->cheats &= ~CF_GODMODE; + if(!(leveltime&0x1f)) + { + P_DamageMobj(player->mo, NULL, NULL, 20); + } + if(player->health <= 10) + { + G_ExitLevel(); + } + */ + break; + + case 25: + case 26: + case 27: + case 28: + case 29: // Scroll_North + P_Thrust(player, ANG90, pushTab[sector->special - 25]); + break; + case 20: + case 21: + case 22: + case 23: + case 24: // Scroll_East + P_Thrust(player, 0, pushTab[sector->special - 20]); + break; + case 30: + case 31: + case 32: + case 33: + case 34: // Scroll_South + P_Thrust(player, ANG270, pushTab[sector->special - 30]); + break; + case 35: + case 36: + case 37: + case 38: + case 39: // Scroll_West + P_Thrust(player, ANG180, pushTab[sector->special - 35]); + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + // Wind specials are handled in (P_mobj):P_XYMovement + break; + + case 15: // Friction_Low + // Only used in (P_mobj):P_XYMovement and (P_user):P_Thrust + break; + + default: + I_Error("P_PlayerInSpecialSector: " + "unknown special %i", sector->special); + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_UpdateSpecials +// +// Animate planes, scroll walls, etc. +// +//---------------------------------------------------------------------------- + +void P_UpdateSpecials(void) +{ + int i; + int pic; + anim_t *anim; + line_t *line; + + // Animate flats and textures + 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; + } + } + } + // Update scrolling texture offsets + for (i = 0; i < numlinespecials; i++) + { + line = linespeciallist[i]; + switch (line->special) + { + case 48: // Effect_Scroll_Left + sides[line->sidenum[0]].textureoffset += FRACUNIT; + break; + case 99: // Effect_Scroll_Right + sides[line->sidenum[0]].textureoffset -= FRACUNIT; + break; + } + } + // Handle 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(buttonlist[i].soundorg, sfx_switch); + memset(&buttonlist[i], 0, sizeof(button_t)); + } + } + } +} + +//============================================================ +// +// Special Stuff that can't 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 = §ors[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 = 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 = 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 += +=============================================================================== +*/ + +short numlinespecials; +line_t *linespeciallist[MAXLINEANIMS]; + +void P_SpawnSpecials(void) +{ + sector_t *sector; + int i; + int episode; + + episode = 1; + if (W_CheckNumForName(DEH_String("texture2")) >= 0) + episode = 2; + + // + // 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; + } + } + + + // + // Init line EFFECTs + // + numlinespecials = 0; + for (i = 0; i < numlines; i++) + switch (lines[i].special) + { + case 48: // Effect_Scroll_Left + case 99: // Effect_Scroll_Right + linespeciallist[numlinespecials] = &lines[i]; + numlinespecials++; + break; + } + + // + // Init other misc stuff + // + for (i = 0; i < MAXCEILINGS; i++) + activeceilings[i] = NULL; + for (i = 0; i < MAXPLATS; i++) + activeplats[i] = NULL; + for (i = 0; i < MAXBUTTONS; i++) + memset(&buttonlist[i], 0, sizeof(button_t)); +} + +//---------------------------------------------------------------------------- +// +// PROC P_InitAmbientSound +// +//---------------------------------------------------------------------------- + +void P_InitAmbientSound(void) +{ + AmbSfxCount = 0; + AmbSfxVolume = 0; + AmbSfxTics = 10 * TICRATE; + AmbSfxPtr = AmbSndSeqInit; +} + +//---------------------------------------------------------------------------- +// +// PROC P_AddAmbientSfx +// +// Called by (P_mobj):P_SpawnMapThing during (P_setup):P_SetupLevel. +// +//---------------------------------------------------------------------------- + +void P_AddAmbientSfx(int sequence) +{ + if (AmbSfxCount == MAX_AMBIENT_SFX) + { + I_Error("Too many ambient sound sequences"); + } + LevelAmbientSfx[AmbSfxCount++] = AmbientSfx[sequence]; +} + +//---------------------------------------------------------------------------- +// +// PROC P_AmbientSound +// +// Called every tic by (P_tick):P_Ticker. +// +//---------------------------------------------------------------------------- + +void P_AmbientSound(void) +{ + afxcmd_t cmd; + int sound; + boolean done; + + if (!AmbSfxCount) + { // No ambient sound sequences on current level + return; + } + if (--AmbSfxTics) + { + return; + } + done = false; + do + { + cmd = *AmbSfxPtr++; + switch (cmd) + { + case afxcmd_play: + AmbSfxVolume = P_Random() >> 2; + S_StartSoundAtVolume(NULL, *AmbSfxPtr++, AmbSfxVolume); + break; + case afxcmd_playabsvol: + sound = *AmbSfxPtr++; + AmbSfxVolume = *AmbSfxPtr++; + S_StartSoundAtVolume(NULL, sound, AmbSfxVolume); + break; + case afxcmd_playrelvol: + sound = *AmbSfxPtr++; + AmbSfxVolume += *AmbSfxPtr++; + if (AmbSfxVolume < 0) + { + AmbSfxVolume = 0; + } + else if (AmbSfxVolume > 127) + { + AmbSfxVolume = 127; + } + S_StartSoundAtVolume(NULL, sound, AmbSfxVolume); + break; + case afxcmd_delay: + AmbSfxTics = *AmbSfxPtr++; + done = true; + break; + case afxcmd_delayrand: + AmbSfxTics = P_Random() & (*AmbSfxPtr++); + done = true; + break; + case afxcmd_end: + AmbSfxTics = 6 * TICRATE + P_Random(); + AmbSfxPtr = LevelAmbientSfx[P_Random() % AmbSfxCount]; + done = true; + break; + default: + I_Error("P_AmbientSound: Unknown afxcmd %d", cmd); + break; + } + } + while (done == false); +} diff --git a/src/heretic/p_spec.h b/src/heretic/p_spec.h new file mode 100644 index 00000000..7f5e2c43 --- /dev/null +++ b/src/heretic/p_spec.h @@ -0,0 +1,398 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_spec.h + +/* +=============================================================================== + + P_SPEC + +=============================================================================== +*/ + +// +// Animating textures and planes +// +typedef struct +{ + boolean istexture; + int picnum; + int basepic; + int numpics; + int speed; +} anim_t; + +// +// source animation definition +// +typedef struct +{ + boolean istexture; // if false, it's a flat + char endname[9]; + char startname[9]; + int speed; +} animdef_t; + +#define MAXANIMS 32 + +extern anim_t anims[MAXANIMS], *lastanim; +extern int *TerrainTypes; + +// +// Animating line specials +// +#define MAXLINEANIMS 64 +extern short numlinespecials; +extern line_t *linespeciallist[MAXLINEANIMS]; + +// Define values for map objects +#define MO_TELEPORTMAN 14 + +// at game start +void P_InitPicAnims(void); +void P_InitTerrainTypes(void); +void P_InitLava(void); + +// at map load +void P_SpawnSpecials(void); +void P_InitAmbientSound(void); +void P_AddAmbientSfx(int sequence); + +// every tic +void P_UpdateSpecials(void); +void P_AmbientSound(void); + +// when needed +boolean P_UseSpecialLine(mobj_t * thing, line_t * line); +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; + 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 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; + void *soundorg; +} button_t; + +#define MAXSWITCHES 50 // max # of wall switches in a level +#define MAXBUTTONS 16 // 4 players, 4 buttons each at once, max. +#define BUTTONTIME 35 // 1 second + +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 +} 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; + 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 +{ + normal, + close30ThenOpen, + close, + open, + raiseIn5Mins +} vldoor_e; + +typedef struct +{ + thinker_t thinker; + vldoor_e type; + sector_t *sector; + fixed_t topheight; + fixed_t speed; + int direction; // 1 = up, 0 = waiting at top, -1 = down + int topwait; // tics to wait at the top + // (keep in case a door going down is reset) + int topcountdown; // when it reaches 0, start going down +} 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, fixed_t speed); +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 +} ceiling_e; + +typedef struct +{ + thinker_t thinker; + ceiling_e type; + sector_t *sector; + fixed_t bottomheight, topheight; + fixed_t speed; + boolean crush; + int direction; // 1 = up, 0 = waiting, -1 = down + int tag; // ID + 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 +{ + lowerFloor, // lower floor to highest surrounding floor + lowerFloorToLowest, // lower floor to lowest surrounding floor + turboLower, // lower floor to highest surrounding floor VERY FAST + raiseFloor, // raise floor to lowest surrounding CEILING + raiseFloorToNearest, // raise floor to next highest surrounding floor + raiseToTexture, // raise floor to shortest height texture around it + lowerAndChange, // lower floor to lowest surrounding floor and change + // floorpic + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + donutRaise, + raiseBuildStep // One step of a staircase +} floor_e; + +typedef struct +{ + thinker_t thinker; + floor_e type; + 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, boolean crush, int floorOrCeiling, + int direction); + +int EV_BuildStairs(line_t * line, fixed_t stepDelta); +int EV_DoFloor(line_t * line, floor_e floortype); +void T_MoveFloor(floormove_t * floor); + +/* +=============================================================================== + + P_TELEPT + +=============================================================================== +*/ + +boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle); +boolean EV_Teleport(line_t * line, int side, mobj_t * thing); diff --git a/src/heretic/p_switch.c b/src/heretic/p_switch.c new file mode 100644 index 00000000..2ec758b6 --- /dev/null +++ b/src/heretic/p_switch.c @@ -0,0 +1,414 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#include "doomdef.h" +#include "deh_str.h" +#include "i_system.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +//================================================================== +// +// CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE +// +//================================================================== +switchlist_t alphSwitchList[] = { + {"SW1OFF", "SW1ON", 1}, + {"SW2OFF", "SW2ON", 1}, + +/* + {"SW1CTY", "SW2CTY", 1}, + {"SW1ORGRY", "SW2ORGRY", 1}, + {"SW1GRSTN", "SW2GRSTN", 1}, + {"SW1SNDP", "SW2SNDP", 1}, + {"SW1SPINE", "SW2SPINE", 1}, + {"SW1SQPEB", "SW2SQPEB", 1}, + {"SW1TRST1", "SW2TRST1", 1}, + {"SW1CSTL", "SW2CSTL", 1}, + {"SW1MOSS", "SW2MOSS", 1}, + {"SW1SNDSQ", "SW2SNDSQ", 1}, + {"SW1RED", "SW2RED", 1}, + {"SW1WOOD", "SW2WOOD", 1}, + {"SW1BROWN", "SW2BROWN", 1}, + + {"SW1TRST2", "SW2TRST2", 2}, + {"SW1MSC", "SW2MSC", 2}, + {"SW1MSC2", "SW2MSC2", 2}, + {"SW1GRDMD", "SW2GRDMD", 2}, +*/ + +#if 0 + {"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}, + + {"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}, +#endif + {"\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 != shareware) + episode = 2; + + 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(DEH_String(alphSwitchList[i].name1)); + switchlist[index++] = + R_TextureNumForName(DEH_String(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; + + 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 = &line->frontsector->soundorg; + return; + } + + I_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_switch; + //if (line->special == 11) // EXIT SWITCH? + // 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 +=============================================================================== +*/ + +boolean P_UseSpecialLine(mobj_t * thing, line_t * line) +{ + // + // Switches that other things can activate + // + if (!thing->player) + { + if (line->flags & ML_SECRET) + return false; // never open secret doors + switch (line->special) + { + case 1: // MANUAL DOOR RAISE + case 32: // MANUAL BLUE + case 33: // MANUAL RED + case 34: // MANUAL YELLOW + break; + default: + return false; + } + } + + // + // 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 + EV_VerticalDoor(line, thing); + break; + //=============================================== + // SWITCHES + //=============================================== + case 7: // Switch_Build_Stairs (8 pixel steps) + if (EV_BuildStairs(line, 8 * FRACUNIT)) + { + P_ChangeSwitchTexture(line, 0); + } + break; + case 107: // Switch_Build_Stairs_16 (16 pixel steps) + if (EV_BuildStairs(line, 16 * FRACUNIT)) + { + P_ChangeSwitchTexture(line, 0); + } + break; + case 9: // Change Donut + if (EV_DoDonut(line)) + P_ChangeSwitchTexture(line, 0); + break; + case 11: // Exit level + G_ExitLevel(); + P_ChangeSwitchTexture(line, 0); + 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, normal, VDOORSPEED)) + 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: // Lower Ceiling And Crush + if (EV_DoCeiling(line, lowerAndCrush)) + P_ChangeSwitchTexture(line, 0); + break; + case 50: // Close Door + if (EV_DoDoor(line, close, VDOORSPEED)) + P_ChangeSwitchTexture(line, 0); + break; + case 51: // Secret EXIT + G_SecretExitLevel(); + P_ChangeSwitchTexture(line, 0); + 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, open, VDOORSPEED)) + P_ChangeSwitchTexture(line, 0); + break; + //=============================================== + // BUTTONS + //=============================================== + case 42: // Close Door + if (EV_DoDoor(line, close, VDOORSPEED)) + 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, open, VDOORSPEED)) + 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, normal, VDOORSPEED)) + 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; + } + + return true; +} diff --git a/src/heretic/p_telept.c b/src/heretic/p_telept.c new file mode 100644 index 00000000..91d10f9b --- /dev/null +++ b/src/heretic/p_telept.c @@ -0,0 +1,173 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_telept.c + +#include "doomdef.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +//---------------------------------------------------------------------------- +// +// FUNC P_Teleport +// +//---------------------------------------------------------------------------- + +boolean P_Teleport(mobj_t * thing, fixed_t x, fixed_t y, angle_t angle) +{ + fixed_t oldx; + fixed_t oldy; + fixed_t oldz; + fixed_t aboveFloor; + fixed_t fogDelta; + player_t *player; + unsigned an; + mobj_t *fog; + + oldx = thing->x; + oldy = thing->y; + oldz = thing->z; + aboveFloor = thing->z - thing->floorz; + if (!P_TeleportMove(thing, x, y)) + { + return (false); + } + if (thing->player) + { + player = thing->player; + if (player->powers[pw_flight] && aboveFloor) + { + thing->z = thing->floorz + aboveFloor; + if (thing->z + thing->height > thing->ceilingz) + { + thing->z = thing->ceilingz - thing->height; + } + player->viewz = thing->z + player->viewheight; + } + else + { + thing->z = thing->floorz; + player->viewz = thing->z + player->viewheight; + player->lookdir = 0; + } + } + else if (thing->flags & MF_MISSILE) + { + thing->z = thing->floorz + aboveFloor; + if (thing->z + thing->height > thing->ceilingz) + { + thing->z = thing->ceilingz - thing->height; + } + } + else + { + thing->z = thing->floorz; + } + // Spawn teleport fog at source and destination + fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; + fog = P_SpawnMobj(oldx, oldy, oldz + fogDelta, MT_TFOG); + S_StartSound(fog, sfx_telept); + an = angle >> ANGLETOFINESHIFT; + fog = P_SpawnMobj(x + 20 * finecosine[an], + y + 20 * finesine[an], thing->z + fogDelta, MT_TFOG); + S_StartSound(fog, sfx_telept); + if (thing->player && !thing->player->powers[pw_weaponlevel2]) + { // Freeze player for about .5 sec + thing->reactiontime = 18; + } + thing->angle = angle; + if (thing->flags2 & MF2_FOOTCLIP + && P_GetThingFloorType(thing) != FLOOR_SOLID) + { + thing->flags2 |= MF2_FEETARECLIPPED; + } + else if (thing->flags2 & MF2_FEETARECLIPPED) + { + thing->flags2 &= ~MF2_FEETARECLIPPED; + } + if (thing->flags & MF_MISSILE) + { + angle >>= ANGLETOFINESHIFT; + thing->momx = FixedMul(thing->info->speed, finecosine[angle]); + thing->momy = FixedMul(thing->info->speed, finesine[angle]); + } + else + { + thing->momx = thing->momy = thing->momz = 0; + } + return (true); +} + +//---------------------------------------------------------------------------- +// +// FUNC EV_Teleport +// +//---------------------------------------------------------------------------- + +boolean EV_Teleport(line_t * line, int side, mobj_t * thing) +{ + int i; + int tag; + mobj_t *m; + thinker_t *thinker; + sector_t *sector; + + if (thing->flags2 & MF2_NOTELEPORT) + { + return (false); + } + if (side == 1) + { // Don't teleport when crossing back side + return (false); + } + 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) + { + if (thinker->function != P_MobjThinker) + { // Not a mobj + continue; + } + m = (mobj_t *) thinker; + if (m->type != MT_TELEPORTMAN) + { // Not a teleportman + continue; + } + sector = m->subsector->sector; + if (sector - sectors != i) + { // Wrong sector + continue; + } + return (P_Teleport(thing, m->x, m->y, m->angle)); + } + } + } + return (false); +} diff --git a/src/heretic/p_tick.c b/src/heretic/p_tick.c new file mode 100644 index 00000000..9758be3a --- /dev/null +++ b/src/heretic/p_tick.c @@ -0,0 +1,668 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_tick.c + +#include "doomdef.h" +#include "i_system.h" +#include "p_local.h" +#include "v_video.h" + +int leveltime; +int TimerGame; + +/* +==================== += += P_ArchivePlayers += +==================== +*/ + +void P_ArchivePlayers(void) +{ + int i; + int j; + player_t dest; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + { + continue; + } + memcpy(&dest, &players[i], 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); + } + } + SV_Write(&dest, sizeof(player_t)); + } +} + +/* +==================== += += P_UnArchivePlayers += +==================== +*/ + +void P_UnArchivePlayers(void) +{ + int i, j; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + memcpy(&players[i], save_p, sizeof(player_t)); + save_p += sizeof(player_t); + players[i].mo = NULL; // will be set when unarc thinker + players[i].message = NULL; + players[i].attacker = NULL; + for (j = 0; j < NUMPSPRITES; j++) + if (players[i].psprites[j].state) + players[i].psprites[j].state + = &states[(int) players[i].psprites[j].state]; + } +} + +//============================================================================= + + +/* +==================== += += P_ArchiveWorld += +==================== +*/ + +void P_ArchiveWorld(void) +{ + int i, j; + sector_t *sec; + line_t *li; + side_t *si; + + // Sectors + for (i = 0, sec = sectors; i < numsectors; i++, sec++) + { + SV_WriteWord(sec->floorheight >> FRACBITS); + SV_WriteWord(sec->ceilingheight >> FRACBITS); + SV_WriteWord(sec->floorpic); + SV_WriteWord(sec->ceilingpic); + SV_WriteWord(sec->lightlevel); + SV_WriteWord(sec->special); // needed? + SV_WriteWord(sec->tag); // needed? + } + + // Lines + for (i = 0, li = lines; i < numlines; i++, li++) + { + SV_WriteWord(li->flags); + SV_WriteWord(li->special); + SV_WriteWord(li->tag); + for (j = 0; j < 2; j++) + { + if (li->sidenum[j] == -1) + { + continue; + } + si = &sides[li->sidenum[j]]; + SV_WriteWord(si->textureoffset >> FRACBITS); + SV_WriteWord(si->rowoffset >> FRACBITS); + SV_WriteWord(si->toptexture); + SV_WriteWord(si->bottomtexture); + SV_WriteWord(si->midtexture); + } + } +} + +/* +==================== += += P_UnArchiveWorld += +==================== +*/ + +void P_UnArchiveWorld(void) +{ + int i, 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; +} + +//============================================================================= + +typedef enum +{ + tc_end, + tc_mobj +} thinkerclass_t; + +/* +==================== += += P_ArchiveThinkers += +==================== +*/ + +void P_ArchiveThinkers(void) +{ + thinker_t *th; + mobj_t mobj; + + for (th = thinkercap.next; th != &thinkercap; th = th->next) + { + if (th->function == P_MobjThinker) + { + SV_WriteByte(tc_mobj); + memcpy(&mobj, th, sizeof(mobj_t)); + mobj.state = (state_t *) (mobj.state - states); + if (mobj.player) + { + mobj.player = (player_t *) ((mobj.player - players) + 1); + } + SV_Write(&mobj, sizeof(mobj_t)); + continue; + } + //I_Error("P_ArchiveThinkers: Unknown thinker function"); + } + + // Add a terminating marker + SV_WriteByte(tc_end); +} + +/* +==================== += += P_UnArchiveThinkers += +==================== +*/ + +void P_UnArchiveThinkers(void) +{ + byte tclass; + thinker_t *currentthinker, *next; + mobj_t *mobj; + +// +// remove all the current thinkers +// + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) + { + next = currentthinker->next; + if (currentthinker->function == 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: + mobj = Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + memcpy(mobj, save_p, sizeof(*mobj)); + save_p += sizeof(*mobj); + mobj->state = &states[(int) mobj->state]; + mobj->target = NULL; + if (mobj->player) + { + mobj->player = &players[(int) 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 = P_MobjThinker; + P_AddThinker(&mobj->thinker); + break; + + default: + I_Error("Unknown tclass %i in savegame", tclass); + } + + } + +} + +//============================================================================= + + +/* +==================== += += P_ArchiveSpecials += +==================== +*/ +enum +{ + tc_ceiling, + tc_door, + tc_floor, + tc_plat, + tc_flash, + tc_strobe, + tc_glow, + tc_endspecials +} specials_e; + +void P_ArchiveSpecials(void) +{ +/* +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 +*/ + + thinker_t *th; + ceiling_t ceiling; + vldoor_t door; + floormove_t floor; + plat_t plat; + lightflash_t flash; + strobe_t strobe; + glow_t glow; + + for (th = thinkercap.next; th != &thinkercap; th = th->next) + { + if (th->function == T_MoveCeiling) + { + SV_WriteByte(tc_ceiling); + memcpy(&ceiling, th, sizeof(ceiling_t)); + ceiling.sector = (sector_t *) (ceiling.sector - sectors); + SV_Write(&ceiling, sizeof(ceiling_t)); + continue; + } + if (th->function == T_VerticalDoor) + { + SV_WriteByte(tc_door); + memcpy(&door, th, sizeof(vldoor_t)); + door.sector = (sector_t *) (door.sector - sectors); + SV_Write(&door, sizeof(vldoor_t)); + continue; + } + if (th->function == T_MoveFloor) + { + SV_WriteByte(tc_floor); + memcpy(&floor, th, sizeof(floormove_t)); + floor.sector = (sector_t *) (floor.sector - sectors); + SV_Write(&floor, sizeof(floormove_t)); + continue; + } + if (th->function == T_PlatRaise) + { + SV_WriteByte(tc_plat); + memcpy(&plat, th, sizeof(plat_t)); + plat.sector = (sector_t *) (plat.sector - sectors); + SV_Write(&plat, sizeof(plat_t)); + continue; + } + if (th->function == T_LightFlash) + { + SV_WriteByte(tc_flash); + memcpy(&flash, th, sizeof(lightflash_t)); + flash.sector = (sector_t *) (flash.sector - sectors); + SV_Write(&flash, sizeof(lightflash_t)); + continue; + } + if (th->function == T_StrobeFlash) + { + SV_WriteByte(tc_strobe); + memcpy(&strobe, th, sizeof(strobe_t)); + strobe.sector = (sector_t *) (strobe.sector - sectors); + SV_Write(&strobe, sizeof(strobe_t)); + continue; + } + if (th->function == T_Glow) + { + SV_WriteByte(tc_glow); + memcpy(&glow, th, sizeof(glow_t)); + glow.sector = (sector_t *) (glow.sector - sectors); + SV_Write(&glow, sizeof(glow_t)); + continue; + } + } + // Add a terminating marker + SV_WriteByte(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: + ceiling = Z_Malloc(sizeof(*ceiling), PU_LEVEL, NULL); + memcpy(ceiling, save_p, sizeof(*ceiling)); + save_p += sizeof(*ceiling); + ceiling->sector = §ors[(int) ceiling->sector]; + ceiling->sector->specialdata = T_MoveCeiling; + if (ceiling->thinker.function) + ceiling->thinker.function = T_MoveCeiling; + P_AddThinker(&ceiling->thinker); + P_AddActiveCeiling(ceiling); + break; + + case tc_door: + door = Z_Malloc(sizeof(*door), PU_LEVEL, NULL); + memcpy(door, save_p, sizeof(*door)); + save_p += sizeof(*door); + door->sector = §ors[(int) door->sector]; + door->sector->specialdata = door; + door->thinker.function = T_VerticalDoor; + P_AddThinker(&door->thinker); + break; + + case tc_floor: + floor = Z_Malloc(sizeof(*floor), PU_LEVEL, NULL); + memcpy(floor, save_p, sizeof(*floor)); + save_p += sizeof(*floor); + floor->sector = §ors[(int) floor->sector]; + floor->sector->specialdata = T_MoveFloor; + floor->thinker.function = T_MoveFloor; + P_AddThinker(&floor->thinker); + break; + + case tc_plat: + plat = Z_Malloc(sizeof(*plat), PU_LEVEL, NULL); + memcpy(plat, save_p, sizeof(*plat)); + save_p += sizeof(*plat); + plat->sector = §ors[(int) plat->sector]; + plat->sector->specialdata = T_PlatRaise; + if (plat->thinker.function) + plat->thinker.function = T_PlatRaise; + P_AddThinker(&plat->thinker); + P_AddActivePlat(plat); + break; + + case tc_flash: + flash = Z_Malloc(sizeof(*flash), PU_LEVEL, NULL); + memcpy(flash, save_p, sizeof(*flash)); + save_p += sizeof(*flash); + flash->sector = §ors[(int) flash->sector]; + flash->thinker.function = T_LightFlash; + P_AddThinker(&flash->thinker); + break; + + case tc_strobe: + strobe = Z_Malloc(sizeof(*strobe), PU_LEVEL, NULL); + memcpy(strobe, save_p, sizeof(*strobe)); + save_p += sizeof(*strobe); + strobe->sector = §ors[(int) strobe->sector]; + strobe->thinker.function = T_StrobeFlash; + P_AddThinker(&strobe->thinker); + break; + + case tc_glow: + glow = Z_Malloc(sizeof(*glow), PU_LEVEL, NULL); + memcpy(glow, save_p, sizeof(*glow)); + save_p += sizeof(*glow); + glow->sector = §ors[(int) glow->sector]; + glow->thinker.function = T_Glow; + P_AddThinker(&glow->thinker); + break; + + default: + I_Error("P_UnarchiveSpecials:Unknown tclass %i " + "in savegame", tclass); + } + + } + +} + + + +/* +=============================================================================== + + 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. + +=============================================================================== +*/ + +thinker_t thinkercap; // both the head and tail of the thinker list + +/* +=============== += += 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) +{ + thinker->function = (think_t) - 1; +} + +/* +=============== += += P_AllocateThinker += += Allocates memory and adds a new thinker at the end of the list += +=============== +*/ + +void P_AllocateThinker(thinker_t * thinker) +{ +} + + +/* +=============== += += P_RunThinkers += +=============== +*/ + +void P_RunThinkers(void) +{ + thinker_t *currentthinker; + + currentthinker = thinkercap.next; + while (currentthinker != &thinkercap) + { + if (currentthinker->function == (think_t) - 1) + { // time to remove it + currentthinker->next->prev = currentthinker->prev; + currentthinker->prev->next = currentthinker->next; + Z_Free(currentthinker); + } + else + { + if (currentthinker->function) + currentthinker->function(currentthinker); + } + currentthinker = currentthinker->next; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_Ticker +// +//---------------------------------------------------------------------------- + +void P_Ticker(void) +{ + int i; + + if (paused) + { + return; + } + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + P_PlayerThink(&players[i]); + } + } + if (TimerGame) + { + if (!--TimerGame) + { + G_ExitLevel(); + } + } + P_RunThinkers(); + P_UpdateSpecials(); + P_AmbientSound(); + leveltime++; +} diff --git a/src/heretic/p_user.c b/src/heretic/p_user.c new file mode 100644 index 00000000..16dbed49 --- /dev/null +++ b/src/heretic/p_user.c @@ -0,0 +1,1028 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// P_user.c + +#include <stdlib.h> + +#include "doomdef.h" +#include "deh_str.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +void P_PlayerNextArtifact(player_t * player); + +// Macros + +#define MAXBOB 0x100000 // 16 pixels of bob + +// Data + +boolean onground; +int newtorch; // used in the torch flicker effect. +int newtorchdelta; + +boolean WeaponInShareware[] = { + true, // Staff + true, // Gold wand + true, // Crossbow + true, // Blaster + false, // Skull rod + false, // Phoenix rod + false, // Mace + true, // Gauntlets + true // Beak +}; + +/* +================== += += P_Thrust += += moves the given origin along a given angle += +================== +*/ + +void P_Thrust(player_t * player, angle_t angle, fixed_t move) +{ + angle >>= ANGLETOFINESHIFT; + if (player->powers[pw_flight] && !(player->mo->z <= player->mo->floorz)) + { + player->mo->momx += FixedMul(move, finecosine[angle]); + player->mo->momy += FixedMul(move, finesine[angle]); + } + else if (player->mo->subsector->sector->special == 15) // Friction_Low + { + player->mo->momx += FixedMul(move >> 2, finecosine[angle]); + player->mo->momy += FixedMul(move >> 2, finesine[angle]); + } + else + { + 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 + + 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->mo->flags2 & MF2_FLY && !onground) + { + player->bob = FRACUNIT / 2; + } + + if ((player->cheats & CF_NOMOMENTUM)) + { + 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; + } + } + + if (player->chickenTics) + { + player->viewz = player->mo->z + player->viewheight - (20 * FRACUNIT); + } + else + { + player->viewz = player->mo->z + player->viewheight + bob; + } + if (player->mo->flags2 & MF2_FEETARECLIPPED + && player->playerstate != PST_DEAD + && player->mo->z <= player->mo->floorz) + { + player->viewz -= FOOTCLIPSIZE; + } + if (player->viewz > player->mo->ceilingz - 4 * FRACUNIT) + { + player->viewz = player->mo->ceilingz - 4 * FRACUNIT; + } + if (player->viewz < player->mo->floorz + 4 * FRACUNIT) + { + player->viewz = player->mo->floorz + 4 * FRACUNIT; + } +} + +/* +================= += += P_MovePlayer += +================= +*/ + +void P_MovePlayer(player_t * player) +{ + int look; + int fly; + ticcmd_t *cmd; + + cmd = &player->cmd; + player->mo->angle += (cmd->angleturn << 16); + + onground = (player->mo->z <= player->mo->floorz + || (player->mo->flags2 & MF2_ONMOBJ)); + + if (player->chickenTics) + { // Chicken speed + if (cmd->forwardmove && (onground || player->mo->flags2 & MF2_FLY)) + P_Thrust(player, player->mo->angle, cmd->forwardmove * 2500); + if (cmd->sidemove && (onground || player->mo->flags2 & MF2_FLY)) + P_Thrust(player, player->mo->angle - ANG90, cmd->sidemove * 2500); + } + else + { // Normal speed + if (cmd->forwardmove && (onground || player->mo->flags2 & MF2_FLY)) + P_Thrust(player, player->mo->angle, cmd->forwardmove * 2048); + if (cmd->sidemove && (onground || player->mo->flags2 & MF2_FLY)) + P_Thrust(player, player->mo->angle - ANG90, cmd->sidemove * 2048); + } + + if (cmd->forwardmove || cmd->sidemove) + { + if (player->chickenTics) + { + if (player->mo->state == &states[S_CHICPLAY]) + { + P_SetMobjState(player->mo, S_CHICPLAY_RUN1); + } + } + else + { + if (player->mo->state == &states[S_PLAY]) + { + P_SetMobjState(player->mo, S_PLAY_RUN1); + } + } + } + + look = cmd->lookfly & 15; + if (look > 7) + { + look -= 16; + } + if (look) + { + if (look == TOCENTER) + { + player->centering = true; + } + else + { + player->lookdir += 5 * look; + if (player->lookdir > 90 || player->lookdir < -110) + { + player->lookdir -= 5 * look; + } + } + } + if (player->centering) + { + if (player->lookdir > 0) + { + player->lookdir -= 8; + } + else if (player->lookdir < 0) + { + player->lookdir += 8; + } + if (abs(player->lookdir) < 8) + { + player->lookdir = 0; + player->centering = false; + } + } + fly = cmd->lookfly >> 4; + if (fly > 7) + { + fly -= 16; + } + if (fly && player->powers[pw_flight]) + { + if (fly != TOCENTER) + { + player->flyheight = fly * 2; + if (!(player->mo->flags2 & MF2_FLY)) + { + player->mo->flags2 |= MF2_FLY; + player->mo->flags |= MF_NOGRAVITY; + } + } + else + { + player->mo->flags2 &= ~MF2_FLY; + player->mo->flags &= ~MF_NOGRAVITY; + } + } + else if (fly > 0) + { + P_PlayerUseArtifact(player, arti_fly); + } + if (player->mo->flags2 & MF2_FLY) + { + player->mo->momz = player->flyheight * FRACUNIT; + if (player->flyheight) + { + player->flyheight /= 2; + } + } +} + +/* +================= += += P_DeathThink += +================= +*/ + +#define ANG5 (ANG90/18) + +void P_DeathThink(player_t * player) +{ + angle_t angle, delta; + extern int inv_ptr; + extern int curpos; + int lookDelta; + + P_MovePsprites(player); + + onground = (player->mo->z <= player->mo->floorz); + if (player->mo->type == MT_BLOODYSKULL) + { // Flying bloody skull + player->viewheight = 6 * FRACUNIT; + player->deltaviewheight = 0; + //player->damagecount = 20; + if (onground) + { + if (player->lookdir < 60) + { + lookDelta = (60 - player->lookdir) / 8; + if (lookDelta < 1 && (leveltime & 1)) + { + lookDelta = 1; + } + else if (lookDelta > 6) + { + lookDelta = 6; + } + player->lookdir += lookDelta; + } + } + } + else + { // Fall to ground + player->deltaviewheight = 0; + if (player->viewheight > 6 * FRACUNIT) + player->viewheight -= FRACUNIT; + if (player->viewheight < 6 * FRACUNIT) + player->viewheight = 6 * FRACUNIT; + if (player->lookdir > 0) + { + player->lookdir -= 6; + } + else if (player->lookdir < 0) + { + player->lookdir += 6; + } + if (abs(player->lookdir) < 6) + { + player->lookdir = 0; + } + } + 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) + { + if (player == &players[consoleplayer]) + { + I_SetPalette(W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE)); + inv_ptr = 0; + curpos = 0; + newtorch = 0; + newtorchdelta = 0; + } + player->playerstate = PST_REBORN; + // Let the mobj know the player has entered the reborn state. Some + // mobjs need to know when it's ok to remove themselves. + player->mo->special2 = 666; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_ChickenPlayerThink +// +//---------------------------------------------------------------------------- + +void P_ChickenPlayerThink(player_t * player) +{ + mobj_t *pmo; + + if (player->health > 0) + { // Handle beak movement + P_UpdateBeak(player, &player->psprites[ps_weapon]); + } + if (player->chickenTics & 15) + { + return; + } + pmo = player->mo; + if (!(pmo->momx + pmo->momy) && P_Random() < 160) + { // Twitch view angle + pmo->angle += (P_Random() - P_Random()) << 19; + } + if ((pmo->z <= pmo->floorz) && (P_Random() < 32)) + { // Jump and noise + pmo->momz += FRACUNIT; + P_SetMobjState(pmo, S_CHICPLAY_PAIN); + return; + } + if (P_Random() < 48) + { // Just noise + S_StartSound(pmo, sfx_chicact); + } +} + +//---------------------------------------------------------------------------- +// +// FUNC P_GetPlayerNum +// +//---------------------------------------------------------------------------- + +int P_GetPlayerNum(player_t * player) +{ + int i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (player == &players[i]) + { + return (i); + } + } + return (0); +} + +//---------------------------------------------------------------------------- +// +// FUNC P_UndoPlayerChicken +// +//---------------------------------------------------------------------------- + +boolean P_UndoPlayerChicken(player_t * player) +{ + mobj_t *fog; + mobj_t *mo; + mobj_t *pmo; + fixed_t x; + fixed_t y; + fixed_t z; + angle_t angle; + int playerNum; + weapontype_t weapon; + int oldFlags; + int oldFlags2; + + pmo = player->mo; + x = pmo->x; + y = pmo->y; + z = pmo->z; + angle = pmo->angle; + weapon = pmo->special1; + oldFlags = pmo->flags; + oldFlags2 = pmo->flags2; + P_SetMobjState(pmo, S_FREETARGMOBJ); + mo = P_SpawnMobj(x, y, z, MT_PLAYER); + if (P_TestMobjLocation(mo) == false) + { // Didn't fit + P_RemoveMobj(mo); + mo = P_SpawnMobj(x, y, z, MT_CHICPLAYER); + mo->angle = angle; + mo->health = player->health; + mo->special1 = weapon; + mo->player = player; + mo->flags = oldFlags; + mo->flags2 = oldFlags2; + player->mo = mo; + player->chickenTics = 2 * 35; + return (false); + } + playerNum = P_GetPlayerNum(player); + if (playerNum != 0) + { // Set color translation + mo->flags |= playerNum << MF_TRANSSHIFT; + } + mo->angle = angle; + mo->player = player; + mo->reactiontime = 18; + if (oldFlags2 & MF2_FLY) + { + mo->flags2 |= MF2_FLY; + mo->flags |= MF_NOGRAVITY; + } + player->chickenTics = 0; + player->powers[pw_weaponlevel2] = 0; + player->health = mo->health = MAXHEALTH; + player->mo = mo; + angle >>= ANGLETOFINESHIFT; + fog = P_SpawnMobj(x + 20 * finecosine[angle], + y + 20 * finesine[angle], z + TELEFOGHEIGHT, MT_TFOG); + S_StartSound(fog, sfx_telept); + P_PostChickenWeapon(player, weapon); + return (true); +} + +//---------------------------------------------------------------------------- +// +// PROC P_PlayerThink +// +//---------------------------------------------------------------------------- + +void P_PlayerThink(player_t * player) +{ + ticcmd_t *cmd; + weapontype_t newweapon; + + extern boolean ultimatemsg; + + // No-clip cheat + if (player->cheats & CF_NOCLIP) + { + player->mo->flags |= MF_NOCLIP; + } + else + { + player->mo->flags &= ~MF_NOCLIP; + } + cmd = &player->cmd; + if (player->mo->flags & MF_JUSTATTACKED) + { // Gauntlets attack auto forward motion + cmd->angleturn = 0; + cmd->forwardmove = 0xc800 / 512; + cmd->sidemove = 0; + player->mo->flags &= ~MF_JUSTATTACKED; + } +// messageTics is above the rest of the counters so that messages will +// go away, even in death. + player->messageTics--; // Can go negative + if (!player->messageTics) + { // Refresh the screen when a message goes away + ultimatemsg = false; // clear out any chat messages. + BorderTopRefresh = true; + } + if (player->playerstate == PST_DEAD) + { + P_DeathThink(player); + return; + } + if (player->chickenTics) + { + P_ChickenPlayerThink(player); + } + // Handle movement + if (player->mo->reactiontime) + { // Player is frozen + player->mo->reactiontime--; + } + else + { + P_MovePlayer(player); + } + P_CalcHeight(player); + if (player->mo->subsector->sector->special) + { + P_PlayerInSpecialSector(player); + } + if (cmd->arti) + { // Use an artifact + if (cmd->arti == 0xff) + { + P_PlayerNextArtifact(player); + } + else + { + P_PlayerUseArtifact(player, cmd->arti); + } + } + // Check for weapon change + if (cmd->buttons & BT_SPECIAL) + { // A special event has no other buttons + cmd->buttons = 0; + } + if (cmd->buttons & BT_CHANGE) + { + // The actual changing of the weapon is done when the weapon + // psprite can do it (A_WeaponReady), so it doesn't happen in + // the middle of an attack. + newweapon = (cmd->buttons & BT_WEAPONMASK) >> BT_WEAPONSHIFT; + if (newweapon == wp_staff && player->weaponowned[wp_gauntlets] + && !(player->readyweapon == wp_gauntlets)) + { + newweapon = wp_gauntlets; + } + if (player->weaponowned[newweapon] + && newweapon != player->readyweapon) + { + if (WeaponInShareware[newweapon] || 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; + } + // Chicken counter + if (player->chickenTics) + { + if (player->chickenPeck) + { // Chicken attack counter + player->chickenPeck -= 3; + } + if (!--player->chickenTics) + { // Attempt to undo the chicken + P_UndoPlayerChicken(player); + } + } + // Cycle psprites + P_MovePsprites(player); + // Other Counters + 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_flight]) + { + if (!--player->powers[pw_flight]) + { + // haleyjd: removed externdriver crap + if (player->mo->z != player->mo->floorz) + { + player->centering = true; + } + + player->mo->flags2 &= ~MF2_FLY; + player->mo->flags &= ~MF_NOGRAVITY; + BorderTopRefresh = true; //make sure the sprite's cleared out + } + } + if (player->powers[pw_weaponlevel2]) + { + if (!--player->powers[pw_weaponlevel2]) + { + if ((player->readyweapon == wp_phoenixrod) + && (player->psprites[ps_weapon].state + != &states[S_PHOENIXREADY]) + && (player->psprites[ps_weapon].state + != &states[S_PHOENIXUP])) + { + P_SetPsprite(player, ps_weapon, S_PHOENIXREADY); + player->ammo[am_phoenixrod] -= USE_PHRD_AMMO_2; + player->refire = 0; + } + else if ((player->readyweapon == wp_gauntlets) + || (player->readyweapon == wp_staff)) + { + player->pendingweapon = player->readyweapon; + } + BorderTopRefresh = true; + } + } + if (player->damagecount) + { + player->damagecount--; + } + if (player->bonuscount) + { + player->bonuscount--; + } + // Colormaps + if (player->powers[pw_invulnerability]) + { + if (player->powers[pw_invulnerability] > BLINKTHRESHOLD + || (player->powers[pw_invulnerability] & 8)) + { + player->fixedcolormap = INVERSECOLORMAP; + } + else + { + player->fixedcolormap = 0; + } + } + else if (player->powers[pw_infrared]) + { + if (player->powers[pw_infrared] <= BLINKTHRESHOLD) + { + if (player->powers[pw_infrared] & 8) + { + player->fixedcolormap = 0; + } + else + { + player->fixedcolormap = 1; + } + } + else if (!(leveltime & 16) && player == &players[consoleplayer]) + { + if (newtorch) + { + if (player->fixedcolormap + newtorchdelta > 7 + || player->fixedcolormap + newtorchdelta < 1 + || newtorch == player->fixedcolormap) + { + newtorch = 0; + } + else + { + player->fixedcolormap += newtorchdelta; + } + } + else + { + newtorch = (M_Random() & 7) + 1; + newtorchdelta = (newtorch == player->fixedcolormap) ? + 0 : ((newtorch > player->fixedcolormap) ? 1 : -1); + } + } + } + else + { + player->fixedcolormap = 0; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_ArtiTele +// +//---------------------------------------------------------------------------- + +void P_ArtiTele(player_t * player) +{ + int i; + int selections; + fixed_t destX; + fixed_t destY; + angle_t destAngle; + + if (deathmatch) + { + selections = deathmatch_p - deathmatchstarts; + i = P_Random() % selections; + destX = deathmatchstarts[i].x << FRACBITS; + destY = deathmatchstarts[i].y << FRACBITS; + destAngle = ANG45 * (deathmatchstarts[i].angle / 45); + } + else + { + destX = playerstarts[0].x << FRACBITS; + destY = playerstarts[0].y << FRACBITS; + destAngle = ANG45 * (playerstarts[0].angle / 45); + } + P_Teleport(player->mo, destX, destY, destAngle); + S_StartSound(NULL, sfx_wpnup); // Full volume laugh +} + +//---------------------------------------------------------------------------- +// +// PROC P_PlayerNextArtifact +// +//---------------------------------------------------------------------------- + +void P_PlayerNextArtifact(player_t * player) +{ + extern int inv_ptr; + extern int curpos; + + if (player == &players[consoleplayer]) + { + inv_ptr--; + if (inv_ptr < 6) + { + curpos--; + if (curpos < 0) + { + curpos = 0; + } + } + if (inv_ptr < 0) + { + inv_ptr = player->inventorySlotNum - 1; + if (inv_ptr < 6) + { + curpos = inv_ptr; + } + else + { + curpos = 6; + } + } + player->readyArtifact = player->inventory[inv_ptr].type; + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_PlayerRemoveArtifact +// +//---------------------------------------------------------------------------- + +void P_PlayerRemoveArtifact(player_t * player, int slot) +{ + int i; + extern int inv_ptr; + extern int curpos; + + player->artifactCount--; + if (!(--player->inventory[slot].count)) + { // Used last of a type - compact the artifact list + player->readyArtifact = arti_none; + player->inventory[slot].type = arti_none; + for (i = slot + 1; i < player->inventorySlotNum; i++) + { + player->inventory[i - 1] = player->inventory[i]; + } + player->inventorySlotNum--; + if (player == &players[consoleplayer]) + { // Set position markers and get next readyArtifact + inv_ptr--; + if (inv_ptr < 6) + { + curpos--; + if (curpos < 0) + { + curpos = 0; + } + } + if (inv_ptr >= player->inventorySlotNum) + { + inv_ptr = player->inventorySlotNum - 1; + } + if (inv_ptr < 0) + { + inv_ptr = 0; + } + player->readyArtifact = player->inventory[inv_ptr].type; + } + } +} + +//---------------------------------------------------------------------------- +// +// PROC P_PlayerUseArtifact +// +//---------------------------------------------------------------------------- + +void P_PlayerUseArtifact(player_t * player, artitype_t arti) +{ + int i; + + for (i = 0; i < player->inventorySlotNum; i++) + { + if (player->inventory[i].type == arti) + { // Found match - try to use + if (P_UseArtifact(player, arti)) + { // Artifact was used - remove it from inventory + P_PlayerRemoveArtifact(player, i); + if (player == &players[consoleplayer]) + { + S_StartSound(NULL, sfx_artiuse); + ArtifactFlash = 4; + } + } + else + { // Unable to use artifact, advance pointer + P_PlayerNextArtifact(player); + } + break; + } + } +} + +//---------------------------------------------------------------------------- +// +// FUNC P_UseArtifact +// +// Returns true if artifact was used. +// +//---------------------------------------------------------------------------- + +boolean P_UseArtifact(player_t * player, artitype_t arti) +{ + mobj_t *mo; + angle_t angle; + + switch (arti) + { + case arti_invulnerability: + if (!P_GivePower(player, pw_invulnerability)) + { + return (false); + } + break; + case arti_invisibility: + if (!P_GivePower(player, pw_invisibility)) + { + return (false); + } + break; + case arti_health: + if (!P_GiveBody(player, 25)) + { + return (false); + } + break; + case arti_superhealth: + if (!P_GiveBody(player, 100)) + { + return (false); + } + break; + case arti_tomeofpower: + if (player->chickenTics) + { // Attempt to undo chicken + if (P_UndoPlayerChicken(player) == false) + { // Failed + P_DamageMobj(player->mo, NULL, NULL, 10000); + } + else + { // Succeeded + player->chickenTics = 0; + S_StartSound(player->mo, sfx_wpnup); + } + } + else + { + if (!P_GivePower(player, pw_weaponlevel2)) + { + return (false); + } + if (player->readyweapon == wp_staff) + { + P_SetPsprite(player, ps_weapon, S_STAFFREADY2_1); + } + else if (player->readyweapon == wp_gauntlets) + { + P_SetPsprite(player, ps_weapon, S_GAUNTLETREADY2_1); + } + } + break; + case arti_torch: + if (!P_GivePower(player, pw_infrared)) + { + return (false); + } + break; + case arti_firebomb: + angle = player->mo->angle >> ANGLETOFINESHIFT; + mo = P_SpawnMobj(player->mo->x + 24 * finecosine[angle], + player->mo->y + 24 * finesine[angle], + player->mo->z - + 15 * FRACUNIT * + (player->mo->flags2 & MF2_FEETARECLIPPED) != 0, + MT_FIREBOMB); + mo->target = player->mo; + break; + case arti_egg: + mo = player->mo; + P_SpawnPlayerMissile(mo, MT_EGGFX); + P_SPMAngle(mo, MT_EGGFX, mo->angle - (ANG45 / 6)); + P_SPMAngle(mo, MT_EGGFX, mo->angle + (ANG45 / 6)); + P_SPMAngle(mo, MT_EGGFX, mo->angle - (ANG45 / 3)); + P_SPMAngle(mo, MT_EGGFX, mo->angle + (ANG45 / 3)); + break; + case arti_fly: + if (!P_GivePower(player, pw_flight)) + { + return (false); + } + break; + case arti_teleport: + P_ArtiTele(player); + break; + default: + return (false); + } + return (true); +} diff --git a/src/heretic/r_bsp.c b/src/heretic/r_bsp.c new file mode 100644 index 00000000..16ef8ae1 --- /dev/null +++ b/src/heretic/r_bsp.c @@ -0,0 +1,484 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_bsp.c + +#include "doomdef.h" +#include "m_bbox.h" +#include "r_local.h" + +seg_t *curline; +side_t *sidedef; +line_t *linedef; +sector_t *frontsector, *backsector; + +drawseg_t drawsegs[MAXDRAWSEGS], *ds_p; + +void R_StoreWallRange(int start, int stop); + +/* +==================== += += R_ClearDrawSegs += +==================== +*/ + +void R_ClearDrawSegs(void) +{ + ds_p = drawsegs; +} + +//============================================================================= + + +/* +=============================================================================== += += ClipWallSegment += += Clips the given range of columns and includes it in the new clip list +=============================================================================== +*/ + +typedef struct +{ + int first, last; +} cliprange_t; + +#define MAXSEGS 32 + +cliprange_t solidsegs[MAXSEGS], *newend; // newend is one past the last valid seg + + +void R_ClipSolidWallSegment(int first, int last) +{ + cliprange_t *next, *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); + start->first = first; // adjust the clip size + } + + if (last <= start->last) + return; // bottom contained in start + + 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 + start->last = next->last; // adjust the clip size + goto crunch; + } + } + + // there is a fragment after *next + R_StoreWallRange(next->last + 1, last); + start->last = last; // adjust the clip size + + +// remove start+1 to next from the clip list, +// because start now covers their area + crunch: + if (next == start) + return; // post just extended past the bottom of one post + + 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 +=============================================================================== +*/ + +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); + } + + if (last <= start->last) + return; // bottom contained in start + + 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, x2; + angle_t angle1, angle2, span, 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; + if (span >= ANG180) + return; // back side + + rw_angle1 = angle1; // global angle needed by segcalc + angle1 -= viewangle; + angle2 -= viewangle; + + tspan = angle1 + clipangle; + if (tspan > 2 * clipangle) + { + tspan -= 2 * clipangle; + if (tspan >= span) + return; // totally off the left edge + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2 * clipangle) + { + tspan -= 2 * clipangle; + if (tspan >= span) + return; // totally off the left edge + angle2 = -clipangle; + } + +// +// 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]; + if (x1 == x2) + return; // does not cross a pixel + + backsector = line->backsector; + + if (!backsector) + goto clipsolid; // single sided line + + if (backsector->ceilingheight <= frontsector->floorheight + || backsector->floorheight >= frontsector->ceilingheight) + goto clipsolid; // closed door + + if (backsector->ceilingheight != frontsector->ceilingheight + || backsector->floorheight != frontsector->floorheight) + goto clippass; // window + +// reject empty lines used for triggers and special events + 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 += += Returns true if some part of the bbox might be visible += +=============================================================================== +*/ + +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} +}; + + +boolean R_CheckBBox(fixed_t * bspcoord) +{ + int boxx, boxy, boxpos; + fixed_t x1, y1, x2, y2; + angle_t angle1, angle2, span, tspan; + cliprange_t *start; + int sx1, 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; + if (span >= ANG180) + return true; // sitting on a line + tspan = angle1 + clipangle; + if (tspan > 2 * clipangle) + { + tspan -= 2 * clipangle; + if (tspan >= span) + return false; // totally off the left edge + angle1 = clipangle; + } + tspan = clipangle - angle2; + if (tspan > 2 * clipangle) + { + tspan -= 2 * clipangle; + if (tspan >= span) + return false; // totally off the left edge + angle2 = -clipangle; + } + + +// 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]; + if (sx1 == sx2) + return false; // does not cross a pixel + sx2--; + + start = solidsegs; + while (start->last < sx2) + start++; + if (sx1 >= start->first && sx2 <= start->last) + return false; // the clippost contains the new span + + return true; +} + + +/* +================ += += R_Subsector += += Draw one or more segments +================ +*/ + +void R_Subsector(int num) +{ + int count; + seg_t *line; + subsector_t *sub; + +#ifdef RANGECHECK + if (num >= numsubsectors) + I_Error("R_Subsector: ss %i with numss = %i", num, numsubsectors); +#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, + frontsector->special); + else + floorplane = NULL; + if (frontsector->ceilingheight > viewz + || frontsector->ceilingpic == skyflatnum) + ceilingplane = R_FindPlane(frontsector->ceilingheight, + frontsector->ceilingpic, + frontsector->lightlevel, 0); + else + ceilingplane = NULL; + + R_AddSprites(frontsector); + + while (count--) + { + R_AddLine(line); + line++; + } +} + + +/* +=============================================================================== += += RenderBSPNode += +=============================================================================== +*/ + +void R_RenderBSPNode(int bspnum) +{ + node_t *bsp; + int side; + + 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); + + R_RenderBSPNode(bsp->children[side]); // recursively divide front space + + if (R_CheckBBox(bsp->bbox[side ^ 1])) // possibly divide back space + R_RenderBSPNode(bsp->children[side ^ 1]); +} diff --git a/src/heretic/r_data.c b/src/heretic/r_data.c new file mode 100644 index 00000000..5bb79aa4 --- /dev/null +++ b/src/heretic/r_data.c @@ -0,0 +1,751 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// R_data.c + +#include "doomdef.h" +#include "deh_str.h" + +#include "i_swap.h" +#include "i_system.h" +#include "r_local.h" +#include "p_local.h" + +extern void CheckAbortStartup(void); + +typedef struct +{ + int originx; // block origin (allways UL), which has allready + int originy; // accounted for the patch's internal origin + 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 +{ + char name[8]; // for switch changing, etc + short width; + short height; + short patchcount; + texpatch_t patches[1]; // [patchcount] drawn back to front + // into the cached texture +} texture_t; + + + +int firstflat, lastflat, numflats; +int firstpatch, lastpatch, numpatches; +int firstspritelump, lastspritelump, numspritelumps; + +int numtextures; +texture_t **textures; +int *texturewidthmask; +fixed_t *textureheight; // needed for texture pegging +int *texturecompositesize; +short **texturecolumnlump; +unsigned short **texturecolumnofs; +byte **texturecomposite; + +int *flattranslation; // for global animation +int *texturetranslation; // for global animation + +fixed_t *spritewidth; // needed for pre rendering +fixed_t *spriteoffset; +fixed_t *spritetopoffset; + +lighttable_t *colormaps; + + +/* +============================================================================== + + 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, position; + byte *source, *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) + memcpy(cache + position, source, count); + + patch = (column_t *) ((byte *) patch + patch->length + 4); + } +} + + +/* +=================== += += R_GenerateComposite += +=================== +*/ + +void R_GenerateComposite(int texnum) +{ + byte *block; + texture_t *texture; + texpatch_t *patch; + patch_t *realpatch; + int x, x1, 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++) + { + if (collump[x] >= 0) + continue; // column does not have multiple patches + 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, it is purgable + Z_ChangeTag(block, PU_CACHE); +} + + +/* +=================== += += R_GenerateLookup += +=================== +*/ + +void R_GenerateLookup(int texnum) +{ + texture_t *texture; + byte *patchcount; // [texture->width] + texpatch_t *patch; + patch_t *realpatch; + int x, x1, x2; + int i; + short *collump; + unsigned short *colofs; + + texture = textures[texnum]; + + texturecomposite[texnum] = 0; // composited not created yet + texturecompositesize[texnum] = 0; + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + +// +// 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 *) Z_Malloc(texture->width, PU_STATIC, &patchcount); + 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]) + { + printf("R_GenerateLookup: column without a patch (%s)\n", + texture->name); + return; + } +// I_Error ("R_GenerateLookup: column without a patch"); + if (patchcount[x] > 1) + { + collump[x] = -1; // use the cached block + colofs[x] = texturecompositesize[texnum]; + if (texturecompositesize[texnum] > 0x10000 - texture->height) + I_Error("R_GenerateLookup: texture %i is >64k", texnum); + texturecompositesize[texnum] += texture->height; + } + } + + Z_Free(patchcount); +} + + +/* +================ += += R_GetColumn += +================ +*/ + +byte *R_GetColumn(int tex, int col) +{ + int lump, 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, j; + int *maptex, *maptex2, *maptex1; + char name[9], *names, *name_p; + int *patchlookup; + int totalwidth; + int nummappatches; + int offset, maxoff, maxoff2; + int numtextures1, numtextures2; + int *directory; + char *texture1, *texture2, *pnames; + + texture1 = DEH_String("TEXTURE1"); + texture2 = DEH_String("TEXTURE2"); + pnames = DEH_String("PNAMES"); + +// +// 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 = Z_Malloc(nummappatches * sizeof(*patchlookup), PU_STATIC, NULL); + for (i = 0; i < nummappatches; i++) + { + strncpy(name, name_p + i * 8, 8); + patchlookup[i] = W_CheckNumForName(name); + } + W_ReleaseLumpName(pnames); + +// +// load the map texture definitions from textures.lmp +// + 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 = NULL; + numtextures2 = 0; + maxoff2 = 0; + } + numtextures = numtextures1 + numtextures2; + + // + // Init the startup thermometer at this point... + // + { + int start, end; + int spramount; + start = W_GetNumForName(DEH_String("S_START")); + end = W_GetNumForName(DEH_String("S_END")); + spramount = end - start + 1; + InitThermo(spramount + numtextures + 6); + } + + 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; + + for (i = 0; i < numtextures; i++, directory++) + { +#ifdef __NEXT__ + if (!(i & 63)) + printf("."); +#else + IncThermo(); +#endif + if (i == numtextures1) + { // start looking in second texture file + maptex = maptex2; + maxoff = maxoff2; + directory = maptex + 1; + } + + offset = LONG(*directory); + if (offset > maxoff) + I_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); + 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("R_InitTextures: Missing patch in texture %s", + texture->name); + } + texturecolumnlump[i] = Z_Malloc(texture->width * sizeof(short), + PU_STATIC, 0); + texturecolumnofs[i] = Z_Malloc(texture->width * sizeof(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(patchlookup); + + W_ReleaseLumpName(texture1); + if (maptex2) + { + W_ReleaseLumpName(texture2); + } + +// +// precalculate whatever possible +// + for (i = 0; i < numtextures; i++) + { + R_GenerateLookup(i); + CheckAbortStartup(); + } + +// +// translation table for global animation +// + texturetranslation = Z_Malloc((numtextures + 1) * sizeof(int), PU_STATIC, 0); + for (i = 0; i < numtextures; i++) + texturetranslation[i] = i; +} + + +/* +================ += += R_InitFlats += +================= +*/ + +void R_InitFlats(void) +{ + int i; + + firstflat = W_GetNumForName(DEH_String("F_START")) + 1; + lastflat = W_GetNumForName(DEH_String("F_END")) - 1; + numflats = lastflat - firstflat + 1; + +// 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 doesn't += need to be cached just for the header during rendering +================= +*/ + +void R_InitSpriteLumps(void) +{ + int i; + patch_t *patch; + + firstspritelump = W_GetNumForName(DEH_String("S_START")) + 1; + lastspritelump = W_GetNumForName(DEH_String("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++) + { +#ifdef __NEXT__ + if (!(i & 63)) + printf("."); +#else + IncThermo(); +#endif + 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(DEH_String("COLORMAP")); + length = W_LumpLength(lump); + colormaps = Z_Malloc(length, PU_STATIC, 0); + 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) +{ + //tprintf("\nR_InitTextures ", 0); + R_InitTextures(); + printf ("."); + //tprintf("R_InitFlats\n", 0); + R_InitFlats(); + IncThermo(); + printf ("."); + //tprintf("R_InitSpriteLumps ", 0); + R_InitSpriteLumps(); + IncThermo(); + printf ("."); + R_InitColormaps(); +} + + +//============================================================================= + +/* +================ += += R_FlatNumForName += +================ +*/ + +int R_FlatNumForName(char *name) +{ + int i; + char namet[9]; + + i = W_CheckNumForName(name); + if (i == -1) + { + namet[8] = 0; + memcpy(namet, name, 8); + I_Error("R_FlatNumForName: %s not found", namet); + } + return i - firstflat; +} + + +/* +================ += += R_CheckTextureNumForName += +================ +*/ + +int R_CheckTextureNumForName(char *name) +{ + int i; + + if (name[0] == '-') // no texture marker + return 0; + + for (i = 0; i < numtextures; i++) + if (!strncasecmp(textures[i]->name, name, 8)) + return i; + + return -1; +} + + +/* +================ += += R_TextureNumForName += +================ +*/ + +int R_TextureNumForName(char *name) +{ + int i; + //char namet[9]; + + i = R_CheckTextureNumForName(name); + if (i == -1) + I_Error("R_TextureNumForName: %s not found", name); + + return i; +} + + +/* +================= += += R_PrecacheLevel += += Preloads all relevent graphics for the level +================= +*/ + +int flatmemory, texturememory, spritememory; + +void R_PrecacheLevel(void) +{ + char *flatpresent; + char *texturepresent; + char *spritepresent; + int i, j, k, lump; + texture_t *texture; + thinker_t *th; + spriteframe_t *sf; + + if (demoplayback) + return; + +// +// precache flats +// + flatpresent = Z_Malloc(numflats, PU_STATIC, NULL); + 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); + } + + Z_Free(flatpresent); + +// +// precache textures +// + texturepresent = Z_Malloc(numtextures, PU_STATIC, NULL); + 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; + } + + 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); + } + } + + Z_Free(texturepresent); + +// +// precache sprites +// + spritepresent = Z_Malloc(numsprites, PU_STATIC, NULL); + memset(spritepresent, 0, numsprites); + + for (th = thinkercap.next; th != &thinkercap; th = th->next) + { + if (th->function == 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); + } + } + } + + Z_Free(spritepresent); +} diff --git a/src/heretic/r_draw.c b/src/heretic/r_draw.c new file mode 100644 index 00000000..88653df1 --- /dev/null +++ b/src/heretic/r_draw.c @@ -0,0 +1,498 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_draw.c + +#include "doomdef.h" +#include "deh_str.h" +#include "r_local.h" +#include "i_video.h" +#include "v_video.h" + +/* + +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. + +*/ + +byte *viewimage; +int viewwidth, scaledviewwidth, viewheight, viewwindowx, viewwindowy; +byte *ylookup[MAXHEIGHT]; +int columnofs[MAXWIDTH]; +byte translations[3][256]; // color tables for different players + +/* +================== += += 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; +byte *dc_source; // first pixel in a column (possibly virtual) + +int dccount; // just for profiling + +void R_DrawColumn(void) +{ + int count; + byte *dest; + fixed_t frac, 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("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do + { + *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); +} + +void R_DrawColumnLow(void) +{ + int count; + byte *dest; + fixed_t frac, 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("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +// dccount++; +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do + { + *dest = dc_colormap[dc_source[(frac >> FRACBITS) & 127]]; + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); +} + +// Translucent column draw - blended with background using tinttable. + +void R_DrawTLColumn(void) +{ + int count; + byte *dest; + fixed_t frac, fracstep; + + if (!dc_yl) + dc_yl = 1; + if (dc_yh == viewheight - 1) + dc_yh = viewheight - 2; + + count = dc_yh - dc_yl; + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH || dc_yl < 0 || dc_yh >= SCREENHEIGHT) + I_Error("R_DrawTLColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do + { + *dest = + tinttable[((*dest) << 8) + + dc_colormap[dc_source[(frac >> FRACBITS) & 127]]]; + + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); +} + +/* +======================== += += R_DrawTranslatedColumn += +======================== +*/ + +byte *dc_translation; +byte *translationtables; + +void R_DrawTranslatedColumn(void) +{ + int count; + byte *dest; + fixed_t frac, 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("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do + { + *dest = dc_colormap[dc_translation[dc_source[frac >> FRACBITS]]]; + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); +} + +void R_DrawTranslatedTLColumn(void) +{ + int count; + byte *dest; + fixed_t frac, 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("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, dc_x); +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = dc_iscale; + frac = dc_texturemid + (dc_yl - centery) * fracstep; + + do + { + *dest = tinttable[((*dest) << 8) + + + dc_colormap[dc_translation + [dc_source[frac >> FRACBITS]]]]; + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); +} + +//-------------------------------------------------------------------------- +// +// PROC R_InitTranslationTables +// +//-------------------------------------------------------------------------- + +void R_InitTranslationTables(void) +{ + int i; + + V_LoadTintTable(); + + // Allocate translation tables + translationtables = Z_Malloc(256 * 3, PU_STATIC, 0); + + // Fill out the translation tables + for (i = 0; i < 256; i++) + { + if (i >= 225 && i <= 240) + { + translationtables[i] = 114 + (i - 225); // yellow + translationtables[i + 256] = 145 + (i - 225); // red + translationtables[i + 512] = 190 + (i - 225); // blue + } + else + { + translationtables[i] = translationtables[i + 256] + = translationtables[i + 512] = i; + } + } +} + +/* +================ += += R_DrawSpan += +================ +*/ + +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; +byte *ds_source; // start of a 64*64 tile image + +int dscount; // just for profiling + +void R_DrawSpan(void) +{ + fixed_t xfrac, yfrac; + byte *dest; + int count, spot; + +#ifdef RANGECHECK + if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH + || (unsigned) ds_y > SCREENHEIGHT) + I_Error("R_DrawSpan: %i to %i at %i", ds_x1, ds_x2, ds_y); +// dscount++; +#endif + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + columnofs[ds_x1]; + count = ds_x2 - ds_x1; + do + { + spot = ((yfrac >> (16 - 6)) & (63 * 64)) + ((xfrac >> 16) & 63); + *dest++ = ds_colormap[ds_source[spot]]; + xfrac += ds_xstep; + yfrac += ds_ystep; + } + while (count--); +} + +void R_DrawSpanLow(void) +{ + fixed_t xfrac, yfrac; + byte *dest; + int count, spot; + +#ifdef RANGECHECK + if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH + || (unsigned) ds_y > SCREENHEIGHT) + I_Error("R_DrawSpan: %i to %i at %i", ds_x1, ds_x2, ds_y); +// dscount++; +#endif + + xfrac = ds_xfrac; + yfrac = ds_yfrac; + + dest = ylookup[ds_y] + columnofs[ds_x1]; + count = ds_x2 - ds_x1; + do + { + spot = ((yfrac >> (16 - 6)) & (63 * 64)) + ((xfrac >> 16) & 63); + *dest++ = ds_colormap[ds_source[spot]]; + xfrac += ds_xstep; + yfrac += ds_ystep; + } + while (count--); +} + + + +/* +================ += += R_InitBuffer += +================= +*/ + +void R_InitBuffer(int width, int height) +{ + int i; + + viewwindowx = (SCREENWIDTH - width) >> 1; + for (i = 0; i < width; i++) + columnofs[i] = viewwindowx + i; + if (width == SCREENWIDTH) + viewwindowy = 0; + else + viewwindowy = (SCREENHEIGHT - SBARHEIGHT - height) >> 1; + for (i = 0; i < height; i++) + ylookup[i] = I_VideoBuffer + (i + viewwindowy) * SCREENWIDTH; +} + + +/* +================== += += R_DrawViewBorder += += Draws the border around the view for different size windows +================== +*/ + +boolean BorderNeedRefresh; + +void R_DrawViewBorder(void) +{ + byte *src, *dest; + int x, y; + + if (scaledviewwidth == SCREENWIDTH) + return; + + if (gamemode == shareware) + { + src = W_CacheLumpName(DEH_String("FLOOR04"), PU_CACHE); + } + else + { + src = W_CacheLumpName(DEH_String("FLAT513"), PU_CACHE); + } + dest = I_VideoBuffer; + + for (y = 0; y < SCREENHEIGHT - SBARHEIGHT; y++) + { + for (x = 0; x < SCREENWIDTH / 64; x++) + { + memcpy(dest, src + ((y & 63) << 6), 64); + dest += 64; + } + if (SCREENWIDTH & 63) + { + memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63); + dest += (SCREENWIDTH & 63); + } + } + for (x = viewwindowx; x < viewwindowx + viewwidth; x += 16) + { + V_DrawPatch(x, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordt"), PU_CACHE)); + V_DrawPatch(x, viewwindowy + viewheight, + W_CacheLumpName(DEH_String("bordb"), PU_CACHE)); + } + for (y = viewwindowy; y < viewwindowy + viewheight; y += 16) + { + V_DrawPatch(viewwindowx - 4, y, + W_CacheLumpName(DEH_String("bordl"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, y, + W_CacheLumpName(DEH_String("bordr"), PU_CACHE)); + } + V_DrawPatch(viewwindowx - 4, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordtl"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordtr"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, viewwindowy + viewheight, + W_CacheLumpName(DEH_String("bordbr"), PU_CACHE)); + V_DrawPatch(viewwindowx - 4, viewwindowy + viewheight, + W_CacheLumpName(DEH_String("bordbl"), PU_CACHE)); +} + +/* +================== += += R_DrawTopBorder += += Draws the top border around the view for different size windows +================== +*/ + +boolean BorderTopRefresh; + +void R_DrawTopBorder(void) +{ + byte *src, *dest; + int x, y; + + if (scaledviewwidth == SCREENWIDTH) + return; + + if (gamemode == shareware) + { + src = W_CacheLumpName(DEH_String("FLOOR04"), PU_CACHE); + } + else + { + src = W_CacheLumpName(DEH_String("FLAT513"), PU_CACHE); + } + dest = I_VideoBuffer; + + for (y = 0; y < 30; y++) + { + for (x = 0; x < SCREENWIDTH / 64; x++) + { + memcpy(dest, src + ((y & 63) << 6), 64); + dest += 64; + } + if (SCREENWIDTH & 63) + { + memcpy(dest, src + ((y & 63) << 6), SCREENWIDTH & 63); + dest += (SCREENWIDTH & 63); + } + } + if (viewwindowy < 25) + { + for (x = viewwindowx; x < viewwindowx + viewwidth; x += 16) + { + V_DrawPatch(x, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordt"), PU_CACHE)); + } + V_DrawPatch(viewwindowx - 4, viewwindowy, + W_CacheLumpName(DEH_String("bordl"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, viewwindowy, + W_CacheLumpName(DEH_String("bordr"), PU_CACHE)); + V_DrawPatch(viewwindowx - 4, viewwindowy + 16, + W_CacheLumpName(DEH_String("bordl"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, viewwindowy + 16, + W_CacheLumpName(DEH_String("bordr"), PU_CACHE)); + + V_DrawPatch(viewwindowx - 4, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordtl"), PU_CACHE)); + V_DrawPatch(viewwindowx + viewwidth, viewwindowy - 4, + W_CacheLumpName(DEH_String("bordtr"), PU_CACHE)); + } +} diff --git a/src/heretic/r_local.h b/src/heretic/r_local.h new file mode 100644 index 00000000..184b0288 --- /dev/null +++ b/src/heretic/r_local.h @@ -0,0 +1,484 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_local.h + +#ifndef __R_LOCAL__ +#define __R_LOCAL__ + +#include "i_video.h" +#include "v_patch.h" + +#define ANGLETOSKYSHIFT 22 // sky map is 256*128*4 maps + +#define BASEYCENTER 100 + +#define MAXWIDTH 1120 +#define MAXHEIGHT 832 + +#define PI 3.141592657 + +#define CENTERY (SCREENHEIGHT/2) + +#define MINZ (FRACUNIT*4) + +#define FIELDOFVIEW 2048 // fineangles in the SCREENWIDTH wide window + +// +// lighting constants +// +#define LIGHTLEVELS 16 +#define LIGHTSEGSHIFT 4 +#define MAXLIGHTSCALE 48 +#define LIGHTSCALESHIFT 12 +#define MAXLIGHTZ 128 +#define LIGHTZSHIFT 20 +#define NUMCOLORMAPS 32 // number of diminishing +#define INVERSECOLORMAP 32 + +/* +============================================================================== + + INTERNAL MAP TYPES + +============================================================================== +*/ + +//================ used by play and refresh + +typedef struct +{ + fixed_t x, y; +} vertex_t; + +struct line_s; + +typedef struct +{ + fixed_t floorheight, ceilingheight; + short floorpic, ceilingpic; + short lightlevel; + short special, tag; + + int soundtraversed; // 0 = untraversed, 1,2 = sndlines -1 + mobj_t *soundtarget; // thing that made a sound (or null) + + int blockbox[4]; // mapblock bounding box for height changes + degenmobj_t soundorg; // for any sounds played by the sector + + int validcount; // if == validcount, already checked + mobj_t *thinglist; // list of mobjs in sector + void *specialdata; // thinker_t for reversable actions + int linecount; + struct line_s **lines; // [linecount] size +} sector_t; + +typedef struct +{ + fixed_t textureoffset; // add this to the calculated texture col + fixed_t rowoffset; // add this to the calculated texture top + short toptexture, bottomtexture, midtexture; + sector_t *sector; +} side_t; + +typedef enum +{ ST_HORIZONTAL, ST_VERTICAL, ST_POSITIVE, ST_NEGATIVE } slopetype_t; + +typedef struct line_s +{ + vertex_t *v1, *v2; + fixed_t dx, dy; // v2 - v1 for side checking + short flags; + short special, tag; + short sidenum[2]; // sidenum[1] will be -1 if one sided + fixed_t bbox[4]; + slopetype_t slopetype; // to aid move clipping + sector_t *frontsector, *backsector; + int validcount; // if == validcount, already checked + void *specialdata; // thinker_t for reversable actions +} line_t; + + +typedef struct subsector_s +{ + sector_t *sector; + short numlines; + short firstline; +} subsector_t; + +typedef struct +{ + vertex_t *v1, *v2; + fixed_t offset; + angle_t angle; + side_t *sidedef; + line_t *linedef; + sector_t *frontsector; + sector_t *backsector; // NULL for one sided lines +} seg_t; + +typedef struct +{ + fixed_t x, y, dx, dy; // partition line + fixed_t bbox[2][4]; // bounding box for each child + unsigned short children[2]; // if NF_SUBSECTOR its a subsector +} node_t; + + +/* +============================================================================== + + OTHER TYPES + +============================================================================== +*/ + +typedef byte lighttable_t; // this could be wider for >8 bit display + +#define MAXVISPLANES 128 +#define MAXOPENINGS SCREENWIDTH*64 + +typedef struct +{ + fixed_t height; + int picnum; + int lightlevel; + int special; + int minx, maxx; + byte pad1; // leave pads for [minx-1]/[maxx+1] + byte top[SCREENWIDTH]; + byte pad2; + byte pad3; + byte bottom[SCREENWIDTH]; + byte pad4; +} visplane_t; + +typedef struct drawseg_s +{ + seg_t *curline; + int x1, x2; + fixed_t scale1, scale2, scalestep; + int silhouette; // 0=none, 1=bottom, 2=top, 3=both + fixed_t bsilheight; // don't clip sprites above this + fixed_t tsilheight; // don't clip sprites below this +// pointers to lists for sprite clipping + short *sprtopclip; // adjusted so [x1] is first value + short *sprbottomclip; // adjusted so [x1] is first value + short *maskedtexturecol; // adjusted so [x1] is first value +} drawseg_t; + +#define SIL_NONE 0 +#define SIL_BOTTOM 1 +#define SIL_TOP 2 +#define SIL_BOTH 3 + +#define MAXDRAWSEGS 256 + +// A vissprite_t is a thing that will be drawn during a refresh +typedef struct vissprite_s +{ + struct vissprite_s *prev, *next; + int x1, x2; + fixed_t gx, gy; // for line side calculation + fixed_t gz, gzt; // global bottom / top for silhouette clipping + fixed_t startfrac; // horizontal position of x1 + fixed_t scale; + fixed_t xiscale; // negative if flipped + fixed_t texturemid; + int patch; + lighttable_t *colormap; + int mobjflags; // for color translation and shadow draw + boolean psprite; // true if psprite + fixed_t footclip; // foot clipping +} vissprite_t; + + +extern visplane_t *floorplane, *ceilingplane; + +// Sprites are patches with a special naming convention so they can be +// recognized by R_InitSprites. 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. Some sprites will only have one picture used +// for all views. + +typedef struct +{ + boolean rotate; // if false use 0 for any position + short lump[8]; // lump to use for view angles 0-7 + byte flip[8]; // flip (1 = flip) to use for view angles 0-7 +} spriteframe_t; + +typedef struct +{ + int numframes; + spriteframe_t *spriteframes; +} spritedef_t; + +extern spritedef_t *sprites; +extern int numsprites; + +//============================================================================= + +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; + + + +extern fixed_t viewx, viewy, 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; + +// +// R_main.c +// +extern int viewwidth, viewheight, viewwindowx, viewwindowy; +extern int centerx, centery; +extern int flyheight; +extern fixed_t centerxfrac; +extern fixed_t centeryfrac; +extern fixed_t projection; + +extern int validcount; + +extern int sscount, linecount, loopcount; +extern lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +extern lighttable_t *scalelightfixed[MAXLIGHTSCALE]; +extern lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; + +extern int extralight; +extern lighttable_t *fixedcolormap; + +extern fixed_t viewcos, viewsin; + +extern int detailshift; // 0 = high, 1 = low + +extern void (*colfunc) (void); +extern void (*basecolfunc) (void); +extern void (*tlcolfunc) (void); +extern void (*spanfunc) (void); + +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); + + +// +// R_bsp.c +// +extern seg_t *curline; +extern side_t *sidedef; +extern line_t *linedef; +extern sector_t *frontsector, *backsector; + +extern int rw_x; +extern int rw_stopx; + +extern boolean segtextured; +extern boolean markfloor; // false if the back side is the same plane +extern boolean markceiling; +extern boolean skymap; + +extern drawseg_t drawsegs[MAXDRAWSEGS], *ds_p; + +extern lighttable_t **hscalelight, **vscalelight, **dscalelight; + +typedef void (*drawfunc_t) (int start, int stop); +void R_ClearClipSegs(void); + +void R_ClearDrawSegs(void); +void R_InitSkyMap(void); +void R_RenderBSPNode(int bspnum); + +// +// R_segs.c +// +extern int rw_angle1; // angle to line origin + +void R_RenderMaskedSegRange(drawseg_t * ds, int x1, int x2); + + +// +// R_plane.c +// +typedef void (*planefunction_t) (int top, int bottom); +extern planefunction_t floorfunc, ceilingfunc; + +extern int skyflatnum; + +extern short openings[MAXOPENINGS], *lastopening; + +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, + int special); +visplane_t *R_CheckPlane(visplane_t * pl, int start, int stop); + + +// +// R_debug.m +// +extern int drawbsp; + +// +// R_data.c +// +extern fixed_t *textureheight; // needed for texture pegging +extern fixed_t *spritewidth; // needed for pre rendering (fracs) +extern fixed_t *spriteoffset; +extern fixed_t *spritetopoffset; +extern lighttable_t *colormaps; +extern int viewwidth, scaledviewwidth, viewheight; +extern int firstflat; +extern int numflats; + +extern int *flattranslation; // for global animation +extern int *texturetranslation; // for global animation + +extern int firstspritelump, lastspritelump, numspritelumps; + +byte *R_GetColumn(int tex, int col); +void R_InitData(void); +void R_PrecacheLevel(void); + + +// +// R_things.c +// +#define MAXVISSPRITES 128 + +extern vissprite_t vissprites[MAXVISSPRITES], *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 sprbotscreen; + +extern fixed_t pspritescale, pspriteiscale; + + +void R_DrawMaskedColumn(column_t * column, signed int baseclip); + + +void R_SortVisSprites(void); + +void R_AddSprites(sector_t * sec); +void R_AddPSprites(void); +void R_DrawSprites(void); +void R_InitSprites(char **namelist); +void R_ClearSprites(void); +void R_DrawMasked(void); +void R_ClipVisSprite(vissprite_t * vis, int xl, int xh); + +//============================================================================= +// +// R_draw.c +// +//============================================================================= + +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; +extern byte *dc_source; // first pixel in a column + +void R_DrawColumn(void); +void R_DrawColumnLow(void); +void R_DrawTLColumn(void); +void R_DrawTLColumnLow(void); +void R_DrawTranslatedColumn(void); +void R_DrawTranslatedTLColumn(void); +void R_DrawTranslatedColumnLow(void); + +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; +extern byte *ds_source; // start of a 64*64 tile image + +extern byte *translationtables; +extern byte *dc_translation; + +void R_DrawSpan(void); +void R_DrawSpanLow(void); + +void R_InitBuffer(int width, int height); +void R_InitTranslationTables(void); + +#endif // __R_LOCAL__ diff --git a/src/heretic/r_main.c b/src/heretic/r_main.c new file mode 100644 index 00000000..1101c234 --- /dev/null +++ b/src/heretic/r_main.c @@ -0,0 +1,827 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_main.c + +#include <stdlib.h> +#include <math.h> +#include "doomdef.h" +#include "m_bbox.h" +#include "r_local.h" +#include "tables.h" + +int viewangleoffset; + +// haleyjd: removed WATCOMC + +int validcount = 1; // increment every time a check is made + +lighttable_t *fixedcolormap; +extern lighttable_t **walllights; + +int centerx, centery; +fixed_t centerxfrac, centeryfrac; +fixed_t projection; + +int framecount; // just for profiling purposes + +int sscount, linecount, loopcount; + +fixed_t viewx, viewy, viewz; +angle_t viewangle; +fixed_t viewcos, viewsin; +player_t *viewplayer; + +int detailshift; // 0 = high, 1 = low + +// +// 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]; + +lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +lighttable_t *scalelightfixed[MAXLIGHTSCALE]; +lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; + +int extralight; // bumped light from gun blasts + +void (*colfunc) (void); +void (*basecolfunc) (void); +void (*tlcolfunc) (void); +void (*transcolfunc) (void); +void (*spanfunc) (void); + +/* +=================== += += R_AddPointToBox += +=================== +*/ + +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 += += Returns side 0 (front) or 1 (back) +=============================================================================== +*/ + +int R_PointOnSide(fixed_t x, fixed_t y, node_t * node) +{ + fixed_t dx, dy; + fixed_t left, 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) + return 1; // (left is negative) + return 0; + } + + left = FixedMul(node->dy >> FRACBITS, dx); + right = FixedMul(dy, node->dx >> FRACBITS); + + if (right < left) + return 0; // front side + return 1; // back side +} + + +int R_PointOnSegSide(fixed_t x, fixed_t y, seg_t * line) +{ + fixed_t lx, ly; + fixed_t ldx, ldy; + fixed_t dx, dy; + fixed_t left, 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) + return 1; // (left is negative) + return 0; + } + + left = FixedMul(ldy >> FRACBITS, dx); + right = FixedMul(dy, ldx >> FRACBITS); + + if (right < left) + return 0; // front side + return 1; // back side +} + + +/* +=============================================================================== += += R_PointToAngle += +=============================================================================== +*/ + +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) + return tantoangle[SlopeDiv(y, x)]; // octant 0 + else + return ANG90 - 1 - tantoangle[SlopeDiv(x, y)]; // octant 1 + } + else + { // y<0 + y = -y; + if (x > y) + return -tantoangle[SlopeDiv(y, x)]; // octant 8 + else + return ANG270 + tantoangle[SlopeDiv(x, y)]; // octant 7 + } + } + else + { // x<0 + x = -x; + if (y >= 0) + { // y>= 0 + if (x > y) + return ANG180 - 1 - tantoangle[SlopeDiv(y, x)]; // octant 3 + else + return ANG90 + tantoangle[SlopeDiv(x, y)]; // octant 2 + } + else + { // y<0 + y = -y; + if (x > y) + return ANG180 + tantoangle[SlopeDiv(y, x)]; // octant 4 + else + return ANG270 - 1 - tantoangle[SlopeDiv(x, y)]; // octant 5 + } + } + + 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, dy, temp; + fixed_t dist; + + dx = abs(x - viewx); + dy = abs(y - viewy); + + if (dy > dx) + { + temp = dx; + dx = dy; + dy = temp; + } + + angle = + (tantoangle[FixedDiv(dy, dx) >> DBITS] + ANG90) >> ANGLETOFINESHIFT; + + dist = FixedDiv(dx, finesine[angle]); // use as cosine + + return dist; +} + + + +/* +================= += += R_InitPointToAngle += +================= +*/ + +void R_InitPointToAngle(void) +{ +// now getting from tables.c +#if 0 + int i; + long t; + float f; +// +// slope (tangent) to angle lookup +// + for (i = 0; i <= SLOPERANGE; i++) + { + f = atan((float) i / SLOPERANGE) / (3.141592657 * 2); + t = 0xffffffff * f; + tantoangle[i] = t; + } +#endif +} + +//============================================================================= + +/* +================ += += R_ScaleFromGlobalAngle += += Returns the texture mapping scale for the current line at the given angle += rw_distance must be calculated first +================ +*/ + +fixed_t R_ScaleFromGlobalAngle(angle_t visangle) +{ + fixed_t scale; + int anglea, angleb; + int sinea, sineb; + fixed_t num, den; + +#if 0 + { + fixed_t dist, z; + fixed_t sinv, cosv; + + sinv = finesine[(visangle - rw_normalangle) >> ANGLETOFINESHIFT]; + dist = FixedDiv(rw_distance, sinv); + cosv = finecosine[(viewangle - visangle) >> ANGLETOFINESHIFT]; + z = abs(FixedMul(dist, cosv)); + scale = FixedDiv(projection, z); + return scale; + } +#endif + + anglea = ANG90 + (visangle - viewangle); + angleb = ANG90 + (visangle - rw_normalangle); +// bothe 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) +{ +// now getting from tables.c +#if 0 + int i; + float a, fv; + int t; + +// +// viewangle tangent table +// + for (i = 0; i < FINEANGLES / 2; i++) + { + a = (i - FINEANGLES / 4 + 0.5) * PI * 2 / FINEANGLES; + fv = FRACUNIT * tan(a); + t = fv; + finetangent[i] = t; + } + +// +// finesine table +// + for (i = 0; i < 5 * FINEANGLES / 4; i++) + { +// OPTIMIZE: mirror... + a = (i + 0.5) * PI * 2 / FINEANGLES; + t = FRACUNIT * sin(a); + finesine[i] = t; + } +#endif + +} + + +/* +================= += += 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 xtoviewangleangle[] +// +// 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 += +==================== +*/ + +#define DISTMAP 2 + +void R_InitLightTables(void) +{ + int i, j, level, 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 += += Don't really change anything here, because i might be in the middle of += a refresh. The change will take effect next refresh. += +============== +*/ + +boolean setsizeneeded; +int setblocks, setdetail; + +void R_SetViewSize(int blocks, int detail) +{ + setsizeneeded = true; + setblocks = blocks; + setdetail = detail; +} + +/* +============== += += R_ExecuteSetViewSize += +============== +*/ + +void R_ExecuteSetViewSize(void) +{ + fixed_t cosadj, dy; + int i, j, level, startmap; + + setsizeneeded = false; + + if (setblocks == 11) + { + scaledviewwidth = SCREENWIDTH; + viewheight = SCREENHEIGHT; + } + else + { + scaledviewwidth = setblocks * 32; + viewheight = (setblocks * 158 / 10); + } + + 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; + tlcolfunc = R_DrawTLColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpan; + } + else + { + colfunc = basecolfunc = R_DrawColumnLow; + tlcolfunc = R_DrawTLColumn; + 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 = abs(dy); + yslope[i] = FixedDiv((viewwidth << detailshift) / 2 * FRACUNIT, dy); + } + + for (i = 0; i < viewwidth; i++) + { + cosadj = 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; + } + } + +// +// draw the border +// + R_DrawViewBorder(); // erase old menu stuff +} + + +/* +============== += += R_Init += +============== +*/ + +int detailLevel; +int screenblocks = 10; + +void R_Init(void) +{ + //tprintf("R_InitData ", 1); + R_InitData(); + printf ("."); + //tprintf("R_InitPointToAngle\n", 0); + R_InitPointToAngle(); + printf ("."); + //tprintf("R_InitTables ", 0); + R_InitTables(); + // viewwidth / viewheight / detailLevel are set by the defaults + printf ("."); + R_SetViewSize(screenblocks, detailLevel); + //tprintf("R_InitPlanes\n", 0); + R_InitPlanes(); + printf ("."); + //tprintf("R_InitLightTables ", 0); + R_InitLightTables(); + printf ("."); + //tprintf("R_InitSkyMap\n", 0); + R_InitSkyMap(); + printf ("."); + R_InitTranslationTables(); + framecount = 0; +} + + +/* +============== += += R_PointInSubsector += +============== +*/ + +subsector_t *R_PointInSubsector(fixed_t x, fixed_t y) +{ + node_t *node; + int side, nodenum; + + if (!numnodes) // single subsector is a special case + 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]; + +} + +//---------------------------------------------------------------------------- +// +// PROC R_SetupFrame +// +//---------------------------------------------------------------------------- + +void R_SetupFrame(player_t * player) +{ + int i; + int tableAngle; + int tempCentery; + + //drawbsp = 1; + viewplayer = player; + // haleyjd: removed WATCOMC + // haleyjd FIXME: viewangleoffset handling? + viewangle = player->mo->angle + viewangleoffset; + tableAngle = viewangle >> ANGLETOFINESHIFT; + if (player->chickenTics && player->chickenPeck) + { // Set chicken attack view position + viewx = player->mo->x + player->chickenPeck * finecosine[tableAngle]; + viewy = player->mo->y + player->chickenPeck * finesine[tableAngle]; + } + else + { // Normal view position + viewx = player->mo->x; + viewy = player->mo->y; + } + extralight = player->extralight; + viewz = player->viewz; + + tempCentery = viewheight / 2 + (player->lookdir) * screenblocks / 10; + if (centery != tempCentery) + { + centery = tempCentery; + centeryfrac = centery << FRACBITS; + for (i = 0; i < viewheight; i++) + { + yslope[i] = FixedDiv((viewwidth << detailshift) / 2 * FRACUNIT, + abs(((i - centery) << FRACBITS) + + FRACUNIT / 2)); + } + } + viewsin = finesine[tableAngle]; + viewcos = finecosine[tableAngle]; + 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++; + if (BorderNeedRefresh) + { + if (setblocks < 10) + { + R_DrawViewBorder(); + } + BorderNeedRefresh = false; + BorderTopRefresh = false; + UpdateState |= I_FULLSCRN; + } + if (BorderTopRefresh) + { + if (setblocks < 10) + { + R_DrawTopBorder(); + } + BorderTopRefresh = false; + UpdateState |= I_MESSAGES; + } +} + +/* +============== += += R_RenderView += +============== +*/ + +void R_RenderPlayerView(player_t * player) +{ + R_SetupFrame(player); + R_ClearClipSegs(); + R_ClearDrawSegs(); + R_ClearPlanes(); + R_ClearSprites(); + NetUpdate(); // check for new console commands + R_RenderBSPNode(numnodes - 1); // the head node is the last node output + NetUpdate(); // check for new console commands + R_DrawPlanes(); + NetUpdate(); // check for new console commands + R_DrawMasked(); + NetUpdate(); // check for new console commands +} diff --git a/src/heretic/r_plane.c b/src/heretic/r_plane.c new file mode 100644 index 00000000..cc86b118 --- /dev/null +++ b/src/heretic/r_plane.c @@ -0,0 +1,521 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_planes.c + +#include <stdlib.h> +#include "doomdef.h" +#include "deh_str.h" +#include "i_system.h" +#include "r_local.h" + +planefunction_t floorfunc, ceilingfunc; + +// +// sky mapping +// +int skyflatnum; +int skytexture; +int skytexturemid; +fixed_t skyiscale; + +// +// opening +// + +visplane_t visplanes[MAXVISPLANES], *lastvisplane; +visplane_t *floorplane, *ceilingplane; + +short openings[MAXOPENINGS], *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, baseyscale; + +fixed_t cachedheight[SCREENHEIGHT]; +fixed_t cacheddistance[SCREENHEIGHT]; +fixed_t cachedxstep[SCREENHEIGHT]; +fixed_t cachedystep[SCREENHEIGHT]; + + +/* +================ += += R_InitSkyMap += += Called whenever the view size changes += +================ +*/ + +void R_InitSkyMap(void) +{ + skyflatnum = R_FlatNumForName(DEH_String("F_SKY1")); + skytexturemid = 200 * FRACUNIT; + skyiscale = FRACUNIT; +} + + +/* +==================== += += R_InitPlanes += += Only at game startup +==================== +*/ + +void R_InitPlanes(void) +{ +} + + +/* +================ += += R_MapPlane += +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, length; + unsigned index; + +#ifdef RANGECHECK + if (x2 < x1 || x1 < 0 || x2 >= viewwidth || (unsigned) y > viewheight) + I_Error("R_MapPlane: %i, %i at %i", x1, x2, y); +#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; + + spanfunc(); // high or low detail +} + +//============================================================================= + +/* +==================== += += 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 +// + memset(cachedheight, 0, sizeof(cachedheight)); + angle = (viewangle - ANG90) >> ANGLETOFINESHIFT; // left to right mapping + + // 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, int special) +{ + visplane_t *check; + + if (picnum == skyflatnum) + { + // all skies map together + height = 0; + lightlevel = 0; + } + + for (check = visplanes; check < lastvisplane; check++) + { + if (height == check->height + && picnum == check->picnum + && lightlevel == check->lightlevel && special == check->special) + break; + } + + if (check < lastvisplane) + { + return (check); + } + + if (lastvisplane - visplanes == MAXVISPLANES) + { + I_Error("R_FindPlane: no more visplanes"); + } + + lastvisplane++; + check->height = height; + check->picnum = picnum; + check->lightlevel = lightlevel; + check->special = special; + check->minx = SCREENWIDTH; + check->maxx = -1; + memset(check->top, 0xff, sizeof(check->top)); + return (check); +} + +/* +=============== += += R_CheckPlane += +=============== +*/ + +visplane_t *R_CheckPlane(visplane_t * pl, int start, int stop) +{ + int intrl, intrh; + int unionl, 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; + return pl; // use the same one + } + +// make a new visplane + + lastvisplane->height = pl->height; + lastvisplane->picnum = pl->picnum; + lastvisplane->lightlevel = pl->lightlevel; + lastvisplane->special = pl->special; + pl = lastvisplane++; + pl->minx = start; + pl->maxx = stop; + 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, stop; + int lumpnum; + int angle; + byte *tempSource; + + byte *dest; + int count; + fixed_t frac, fracstep; + + extern byte *ylookup[MAXHEIGHT]; + extern int columnofs[MAXWIDTH]; + +#ifdef RANGECHECK + if (ds_p - drawsegs > MAXDRAWSEGS) + I_Error("R_DrawPlanes: drawsegs overflow (%i)", ds_p - drawsegs); + if (lastvisplane - visplanes > MAXVISPLANES) + I_Error("R_DrawPlanes: visplane overflow (%i)", + lastvisplane - visplanes); + if (lastopening - openings > MAXOPENINGS) + I_Error("R_DrawPlanes: opening overflow (%i)", + lastopening - openings); +#endif + + for (pl = visplanes; pl < lastvisplane; pl++) + { + if (pl->minx > pl->maxx) + continue; + // + // sky flat + // + if (pl->picnum == skyflatnum) + { + dc_iscale = skyiscale; + dc_colormap = colormaps; // sky is allways drawn full bright + 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); + + count = dc_yh - dc_yl; + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned) dc_x >= SCREENWIDTH || dc_yl < 0 + || dc_yh >= SCREENHEIGHT) + I_Error("R_DrawColumn: %i to %i at %i", dc_yl, dc_yh, + dc_x); +#endif + + dest = ylookup[dc_yl] + columnofs[dc_x]; + + fracstep = 1; + frac = (dc_texturemid >> FRACBITS) + (dc_yl - centery); + do + { + *dest = dc_source[frac]; + dest += SCREENWIDTH; + frac += fracstep; + } + while (count--); + +// colfunc (); + } + } + continue; + } + + // + // regular flat + // + lumpnum = firstflat + flattranslation[pl->picnum]; + + tempSource = W_CacheLumpNum(lumpnum, PU_STATIC); + + switch (pl->special) + { + case 25: + case 26: + case 27: + case 28: + case 29: // Scroll_North + ds_source = tempSource; + break; + case 20: + case 21: + case 22: + case 23: + case 24: // Scroll_East + ds_source = tempSource + ((63 - ((leveltime >> 1) & 63)) << + (pl->special - 20) & 63); + //ds_source = tempSource+((leveltime>>1)&63); + break; + case 30: + case 31: + case 32: + case 33: + case 34: // Scroll_South + ds_source = tempSource; + break; + case 35: + case 36: + case 37: + case 38: + case 39: // Scroll_West + ds_source = tempSource; + break; + case 4: // Scroll_EastLavaDamage + ds_source = + tempSource + (((63 - ((leveltime >> 1) & 63)) << 3) & 63); + break; + default: + ds_source = tempSource; + } + planeheight = 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]); + + W_ReleaseLumpNum(lumpnum); + } +} diff --git a/src/heretic/r_segs.c b/src/heretic/r_segs.c new file mode 100644 index 00000000..4e95a2ae --- /dev/null +++ b/src/heretic/r_segs.c @@ -0,0 +1,669 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +//************************************************************************** +//** +//** R_SEGS.C +//** +//** This version has the tall-sector-crossing-precision-bug fixed. +//** +//************************************************************************** + +#include <stdlib.h> + +#include "doomdef.h" +#include "r_local.h" + +// OPTIMIZE: closed two sided lines as single sided + +boolean segtextured; // true if any of the segs textures might be vis +boolean markfloor; // false if the back side is the same plane +boolean markceiling; +boolean maskedtexture; +int toptexture, bottomtexture, midtexture; + + +angle_t rw_normalangle; +int rw_angle1; // angle to line origin + +// +// 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, worldbottom, worldhigh, worldlow; + +fixed_t pixhigh, pixlow; +fixed_t pixhighstep, pixlowstep; +fixed_t topfrac, topstep; +fixed_t bottomfrac, 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 +// 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] != SHRT_MAX) + { + 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, -1); + maskedtexturecol[dc_x] = SHRT_MAX; + } + 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 +================ +*/ + +#define HEIGHTBITS 12 +#define HEIGHTUNIT (1<<HEIGHTBITS) + +void R_RenderSegLoop(void) +{ + angle_t angle; + unsigned index; + int yl, yh, mid; + fixed_t texturecolumn; + int top, bottom; + + texturecolumn = 0; // shut up compiler warning + + for (; rw_x < rw_stopx; rw_x++) + { +// +// mark floor / ceiling areas +// + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; + if (yl < ceilingclip[rw_x] + 1) + yl = ceilingclip[rw_x] + 1; // no space above wall + 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; + if (mid <= ceilingclip[rw_x]) + mid = ceilingclip[rw_x] + 1; // no space above wall + 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; + + if (ds_p == &drawsegs[MAXDRAWSEGS]) + return; // don't overflow and crash + +#ifdef RANGECHECK + if (start >= viewwidth || start > stop) + I_Error("Bad R_RenderWallRange: %i to %i", start, stop); +#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 = abs(rw_normalangle - 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 + { + // + // 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); + } +#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 = NULL; + + 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]; + rw_midtexturemid = vtop - viewz; // bottom of texture at bottom + } + else + rw_midtexturemid = worldtop; // top of texture at top + rw_midtexturemid += sidedef->rowoffset; + ds_p->silhouette = SIL_BOTH; + ds_p->sprtopclip = screenheightarray; + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->tsilheight = INT_MIN; + } + else + { +// +// two sided line +// + ds_p->sprtopclip = ds_p->sprbottomclip = NULL; + 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 = INT_MAX; +// 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 = INT_MIN; +// ds_p->sprtopclip = screenheightarray; + } + + if (backsector->ceilingheight <= frontsector->floorheight) + { + ds_p->sprbottomclip = negonearray; + ds_p->bsilheight = INT_MAX; + ds_p->silhouette |= SIL_BOTTOM; + } + if (backsector->floorheight >= frontsector->ceilingheight) + { + ds_p->sprtopclip = screenheightarray; + ds_p->tsilheight = INT_MIN; + 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 + markfloor = false; // same plane on both sides + + if (worldhigh != worldtop + || backsector->ceilingpic != frontsector->ceilingpic + || backsector->lightlevel != frontsector->lightlevel) + markceiling = true; + else + markceiling = false; // same plane on both sides + + if (backsector->ceilingheight <= frontsector->floorheight + || backsector->floorheight >= frontsector->ceilingheight) + markceiling = markfloor = true; // closed door + + if (worldhigh < worldtop) + { // top texture + toptexture = texturetranslation[sidedef->toptexture]; + if (linedef->flags & ML_DONTPEGTOP) + rw_toptexturemid = worldtop; // top of texture at top + else + { + vtop = backsector->ceilingheight + + textureheight[sidedef->toptexture]; + rw_toptexturemid = vtop - viewz; // bottom of texture + } + } + if (worldlow > worldbottom) + { // bottom texture + bottomtexture = texturetranslation[sidedef->bottomtexture]; + if (linedef->flags & ML_DONTPEGBOTTOM) + { // bottom of texture at bottom + rw_bottomtexturemid = worldtop; // top of texture at top + } + 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) + offsetangle = -offsetangle; + 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 definately invisible and doesn't need to be marked +// + if (frontsector->floorheight >= viewz) + markfloor = false; // above view plane + if (frontsector->ceilingheight <= viewz + && frontsector->ceilingpic != skyflatnum) + markceiling = false; // below view plane + +// +// 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) + { + 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) + { + 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 = INT_MIN; + } + if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM)) + { + ds_p->silhouette |= SIL_BOTTOM; + ds_p->bsilheight = INT_MAX; + } + ds_p++; +} diff --git a/src/heretic/r_things.c b/src/heretic/r_things.c new file mode 100644 index 00000000..e5ff4b4b --- /dev/null +++ b/src/heretic/r_things.c @@ -0,0 +1,1027 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// R_things.c +#include <stdio.h> +#include <stdlib.h> +#include "doomdef.h" +#include "deh_str.h" +#include "i_swap.h" +#include "i_system.h" +#include "r_local.h" + +void R_DrawColumn(void); +void R_DrawTLColumn(void); + +typedef struct +{ + int x1, 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, pspriteiscale; + +lighttable_t **spritelights; + +// constant arrays used for psprite clipping and initializing clipping +short negonearray[SCREENWIDTH]; +short screenheightarray[SCREENWIDTH]; + +/* +=============================================================================== + + INITIALIZATION FUNCTIONS + +=============================================================================== +*/ + +// variables used to look up and range check thing_t sprites patches +spritedef_t *sprites; +int numsprites; + +spriteframe_t sprtemp[26]; +int maxframe; +char *spritename; + + + +/* +================= += += R_InstallSpriteLump += += Local function for R_InitSprites +================= +*/ + +void R_InstallSpriteLump(int lump, unsigned frame, unsigned rotation, + boolean flipped) +{ + int r; + + if (frame >= 26 || rotation > 8) + I_Error("R_InstallSpriteLump: Bad frame characters in lump %i", lump); + + if ((int) frame > maxframe) + maxframe = frame; + + if (rotation == 0) + { +// the lump should be used for all rotations + if (sprtemp[frame].rotate == false) + I_Error("R_InitSprites: Sprite %s frame %c has multip rot=0 lump", + spritename, 'A' + frame); + if (sprtemp[frame].rotate == true) + I_Error + ("R_InitSprites: Sprite %s frame %c has rotations and a rot=0 lump", + spritename, 'A' + frame); + + 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 + ("R_InitSprites: Sprite %s frame %c has rotations and a rot=0 lump", + spritename, 'A' + frame); + + sprtemp[frame].rotate = true; + + rotation--; // make 0 based + if (sprtemp[frame].lump[rotation] != -1) + I_Error + ("R_InitSprites: Sprite %s : %c : %c has two lumps mapped to it", + spritename, 'A' + frame, '1' + rotation); + + 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, l, frame, rotation; + int start, end; + +// count the number of sprite names + check = namelist; + while (*check != NULL) + check++; + numsprites = check - namelist; + + if (!numsprites) + return; + + sprites = Z_Malloc(numsprites * sizeof(*sprites), PU_STATIC, NULL); + + 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 = DEH_String(namelist[i]); + memset(sprtemp, -1, sizeof(sprtemp)); + + maxframe = -1; + + // + // scan the lumps, filling in the frames for whatever is found + // + for (l = start + 1; l < end; l++) + if (!strncasecmp(lumpinfo[l].name, spritename, 4)) + { + frame = lumpinfo[l].name[4] - 'A'; + rotation = lumpinfo[l].name[5] - '0'; + R_InstallSpriteLump(l, 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) + { + //continue; + sprites[i].numframes = 0; + if (gamemode == shareware) + continue; + I_Error("R_InitSprites: No lumps found for sprite %s", + spritename); + } + + 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("R_InitSprites: No patches found for %s frame %c", + spritename, frame + 'A'); + 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 + ("R_InitSprites: Sprite %s frame %c is missing rotations", + spritename, frame + 'A'); + } + } + + // + // 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, NULL); + memcpy(sprites[i].spriteframes, sprtemp, + maxframe * sizeof(spriteframe_t)); + } + +} + + +/* +=============================================================================== + + GAME FUNCTIONS + +=============================================================================== +*/ + +vissprite_t vissprites[MAXVISSPRITES], *vissprite_p; +int newvissprite; + + +/* +=================== += += 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 +================ +*/ + +short *mfloorclip; +short *mceilingclip; +fixed_t spryscale; +fixed_t sprtopscreen; +fixed_t sprbotscreen; + +void R_DrawMaskedColumn(column_t * column, signed int baseclip) +{ + int topscreen, 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_yh >= baseclip && baseclip != -1) + dc_yh = baseclip; + + if (dc_yl <= dc_yh) + { + dc_source = (byte *) column + 3; + dc_texturemid = basetexturemid - (column->topdelta << FRACBITS); +// dc_source = (byte *)column + 3 - column->topdelta; + colfunc(); // either R_DrawColumn or R_DrawTLColumn + } + 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; + fixed_t baseclip; + + + patch = W_CacheLumpNum(vis->patch + firstspritelump, PU_CACHE); + + dc_colormap = vis->colormap; + +// if(!dc_colormap) +// colfunc = tlcolfunc; // NULL colormap = shadow draw + + if (vis->mobjflags & MF_SHADOW) + { + if (vis->mobjflags & MF_TRANSLATION) + { + colfunc = R_DrawTranslatedTLColumn; + dc_translation = translationtables - 256 + + ((vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT - 8)); + } + else + { // Draw using shadow column function + colfunc = tlcolfunc; + } + } + else if (vis->mobjflags & MF_TRANSLATION) + { + // Draw using translated column function + colfunc = R_DrawTranslatedColumn; + dc_translation = translationtables - 256 + + ((vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT - 8)); + } + + dc_iscale = abs(vis->xiscale) >> detailshift; + dc_texturemid = vis->texturemid; + frac = vis->startfrac; + spryscale = vis->scale; + + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + +// check to see if weapon is a vissprite + if (vis->psprite) + { + dc_texturemid += FixedMul(((centery - viewheight / 2) << FRACBITS), + vis->xiscale); + sprtopscreen += (viewheight / 2 - centery) << FRACBITS; + } + + if (vis->footclip && !vis->psprite) + { + sprbotscreen = sprtopscreen + FixedMul(patch->height << FRACBITS, + spryscale); + baseclip = (sprbotscreen - FixedMul(vis->footclip << FRACBITS, + spryscale)) >> FRACBITS; + } + else + { + baseclip = -1; + } + + 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("R_DrawSpriteRange: bad texturecolumn"); +#endif + column = (column_t *) ((byte *) patch + + LONG(patch->columnofs[texturecolumn])); + R_DrawMaskedColumn(column, baseclip); + } + + colfunc = basecolfunc; +} + + + +/* +=================== += += R_ProjectSprite += += Generates a vissprite for a thing if it might be visible += +=================== +*/ + +void R_ProjectSprite(mobj_t * thing) +{ + fixed_t trx, try; + fixed_t gxt, gyt; + fixed_t tx, tz; + fixed_t xscale; + int x1, x2; + spritedef_t *sprdef; + spriteframe_t *sprframe; + int lump; + unsigned rot; + boolean flip; + int index; + vissprite_t *vis; + angle_t ang; + fixed_t iscale; + + if (thing->flags2 & MF2_DONTDRAW) + { // Never make a vissprite when MF2_DONTDRAW is flagged. + return; + } + +// +// transform the origin point +// + trx = thing->x - viewx; + try = thing->y - viewy; + + gxt = FixedMul(trx, viewcos); + gyt = -FixedMul(try, viewsin); + tz = gxt - gyt; + + if (tz < MINZ) + return; // thing is behind view plane + xscale = FixedDiv(projection, tz); + + gxt = -FixedMul(trx, viewsin); + gyt = FixedMul(try, viewcos); + tx = -(gyt + gxt); + + if (abs(tx) > (tz << 2)) + return; // too far off the side + +// +// decide which patch to use for sprite reletive to player +// +#ifdef RANGECHECK + if ((unsigned) thing->sprite >= numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", thing->sprite); +#endif + sprdef = &sprites[thing->sprite]; +#ifdef RANGECHECK + if ((thing->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", + thing->sprite, thing->frame); +#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 = (boolean) sprframe->flip[rot]; + } + else + { // use single rotation for all views + lump = sprframe->lump[0]; + flip = (boolean) sprframe->flip[0]; + } + +// +// calculate edges of the shape +// + tx -= spriteoffset[lump]; + x1 = (centerxfrac + FixedMul(tx, xscale)) >> FRACBITS; + if (x1 > viewwidth) + return; // off the right side + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul(tx, xscale)) >> FRACBITS) - 1; + if (x2 < 0) + return; // off the left side + + +// +// store information in a vissprite +// + vis = R_NewVisSprite(); + vis->mobjflags = thing->flags; + vis->psprite = false; + vis->scale = xscale << detailshift; + vis->gx = thing->x; + vis->gy = thing->y; + vis->gz = thing->z; + vis->gzt = thing->z + spritetopoffset[lump]; + + // foot clipping + if (thing->flags2 & MF2_FEETARECLIPPED + && thing->z <= thing->subsector->sector->floorheight) + { + vis->footclip = 10; + } + else + vis->footclip = 0; + vis->texturemid = vis->gzt - viewz - (vis->footclip << FRACBITS); + + 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) +// vis->colormap = NULL; // shadow draw +// else ... + + if (fixedcolormap) + vis->colormap = fixedcolormap; // fixed map + else if (thing->frame & FF_FULLBRIGHT) + vis->colormap = colormaps; // full bright + else + { // diminished light + index = xscale >> (LIGHTSCALESHIFT - detailshift); + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE - 1; + vis->colormap = spritelights[index]; + } +} + + + + +/* +======================== += += R_AddSprites += +======================== +*/ + +void R_AddSprites(sector_t * sec) +{ + mobj_t *thing; + int lightnum; + + if (sec->validcount == validcount) + return; // already added + + 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]; + + + for (thing = sec->thinglist; thing; thing = thing->snext) + R_ProjectSprite(thing); +} + + +/* +======================== += += R_DrawPSprite += +======================== +*/ + +int PSpriteSY[NUMWEAPONS] = { + 0, // staff + 5 * FRACUNIT, // goldwand + 15 * FRACUNIT, // crossbow + 15 * FRACUNIT, // blaster + 15 * FRACUNIT, // skullrod + 15 * FRACUNIT, // phoenix rod + 15 * FRACUNIT, // mace + 15 * FRACUNIT, // gauntlets + 15 * FRACUNIT // beak +}; + +void R_DrawPSprite(pspdef_t * psp) +{ + fixed_t tx; + int x1, x2; + spritedef_t *sprdef; + spriteframe_t *sprframe; + int lump; + boolean flip; + vissprite_t *vis, avis; + + int tempangle; + +// +// decide which patch to use +// +#ifdef RANGECHECK + if ((unsigned) psp->state->sprite >= numsprites) + I_Error("R_ProjectSprite: invalid sprite number %i ", + psp->state->sprite); +#endif + sprdef = &sprites[psp->state->sprite]; +#ifdef RANGECHECK + if ((psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error("R_ProjectSprite: invalid sprite frame %i : %i ", + psp->state->sprite, psp->state->frame); +#endif + sprframe = &sprdef->spriteframes[psp->state->frame & FF_FRAMEMASK]; + + lump = sprframe->lump[0]; + flip = (boolean) sprframe->flip[0]; + +// +// calculate edges of the shape +// + tx = psp->sx - 160 * FRACUNIT; + + tx -= spriteoffset[lump]; + if (viewangleoffset) + { + tempangle = + ((centerxfrac / 1024) * (viewangleoffset >> ANGLETOFINESHIFT)); + } + else + { + tempangle = 0; + } + x1 = (centerxfrac + FixedMul(tx, pspritescale) + tempangle) >> FRACBITS; + if (x1 > viewwidth) + return; // off the right side + tx += spritewidth[lump]; + x2 = ((centerxfrac + FixedMul(tx, pspritescale) + + tempangle) >> FRACBITS) - 1; + if (x2 < 0) + return; // off the left side + +// +// store information in a vissprite +// + vis = &avis; + vis->mobjflags = 0; + vis->psprite = true; + vis->texturemid = + (BASEYCENTER << FRACBITS) + FRACUNIT / 2 - (psp->sy - + spritetopoffset[lump]); + if (viewheight == SCREENHEIGHT) + { + vis->texturemid -= PSpriteSY[players[consoleplayer].readyweapon]; + } + 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) + { + // Invisibility + vis->colormap = spritelights[MAXLIGHTSCALE - 1]; + vis->mobjflags |= MF_SHADOW; + } + 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, 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 += +======================== +*/ + +vissprite_t vsprsortedhead; + +void R_SortVisSprites(void) +{ + int i, count; + vissprite_t *ds, *best; + vissprite_t unsorted; + fixed_t bestscale; + + count = 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 = INT_MAX; + 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], cliptop[SCREENWIDTH]; + int x, r1, r2; + fixed_t scale, 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)) + continue; // doesn't cover sprite + + 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))) + { + if (ds->maskedtexturecol) // masked mid texture + R_RenderMaskedSegRange(ds, r1, r2); + continue; // seg is behind sprite + } + +// +// 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 +// +// Added for the sideviewing with an external device + if (viewangleoffset <= 1024 << ANGLETOFINESHIFT || viewangleoffset >= + -1024 << ANGLETOFINESHIFT) + { // don't draw on side views + R_DrawPlayerSprites(); + } + +// if (!viewangleoffset) // don't draw on side views +// R_DrawPlayerSprites (); +} diff --git a/src/heretic/s_sound.c b/src/heretic/s_sound.c new file mode 100644 index 00000000..73942201 --- /dev/null +++ b/src/heretic/s_sound.c @@ -0,0 +1,608 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +#include <stdlib.h> + +#include "doomdef.h" +#include "i_system.h" +#include "m_random.h" +#include "sounds.h" +#include "s_sound.h" +#include "i_sound.h" +#include "r_local.h" +#include "p_local.h" + +#include "sounds.h" + +#include "w_wad.h" +#include "z_zone.h" + +/* +=============================================================================== + + MUSIC & SFX API + +=============================================================================== +*/ + +void S_ShutDown(void); +boolean S_StopSoundID(int sound_id, int priority); + +static channel_t channel[MAX_CHANNELS]; + +static void *rs; // Handle for the registered song +int mus_song = -1; +int mus_lumpnum; +void *mus_sndptr; +byte *soundCurve; + +int snd_MaxVolume = 10; +int snd_MusicVolume = 10; +int snd_Channels = 16; + +int AmbChan; + +void S_Start(void) +{ + int i; + + S_StartSong((gameepisode - 1) * 9 + gamemap - 1, true); + + //stop all sounds + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].handle) + { + S_StopSound(channel[i].mo); + } + } + memset(channel, 0, 8 * sizeof(channel_t)); +} + +void S_StartSong(int song, boolean loop) +{ + int mus_len; + + if (song == mus_song) + { // don't replay an old song + return; + } + + if (rs != NULL) + { + I_StopSong(); + I_UnRegisterSong(rs); + } + + if (song < mus_e1m1 || song > NUMMUSIC) + { + return; + } + mus_lumpnum = W_GetNumForName(S_music[song].name); + mus_sndptr = W_CacheLumpNum(mus_lumpnum, PU_MUSIC); + mus_len = W_LumpLength(mus_lumpnum); + rs = I_RegisterSong(mus_sndptr, mus_len); + I_PlaySong(rs, loop); //'true' denotes endless looping. + mus_song = song; +} + +static mobj_t *GetSoundListener(void) +{ + static degenmobj_t dummy_listener; + + // If we are at the title screen, the console player doesn't have an + // object yet, so return a pointer to a static dummy listener instead. + + if (players[consoleplayer].mo != NULL) + { + return players[consoleplayer].mo; + } + else + { + dummy_listener.x = 0; + dummy_listener.y = 0; + dummy_listener.z = 0; + + return (mobj_t *) &dummy_listener; + } +} + +void S_StartSound(void *_origin, int sound_id) +{ + mobj_t *origin = _origin; + mobj_t *listener; + int dist, vol; + int i; + int priority; + int sep; + int angle; + int absx; + int absy; + + static int sndcount = 0; + int chan; + + listener = GetSoundListener(); + + if (sound_id == 0 || snd_MaxVolume == 0) + return; + if (origin == NULL) + { + origin = listener; + } + +// calculate the distance before other stuff so that we can throw out +// sounds that are beyond the hearing range. + absx = abs(origin->x - listener->x); + absy = abs(origin->y - listener->y); + dist = absx + absy - (absx > absy ? absy >> 1 : absx >> 1); + dist >>= FRACBITS; +// dist = P_AproxDistance(origin->x-viewx, origin->y-viewy)>>FRACBITS; + + if (dist >= MAX_SND_DIST) + { +// dist = MAX_SND_DIST - 1; + return; //sound is beyond the hearing range... + } + if (dist < 0) + { + dist = 0; + } + priority = S_sfx[sound_id].priority; + priority *= (10 - (dist / 160)); + if (!S_StopSoundID(sound_id, priority)) + { + return; // other sounds have greater priority + } + for (i = 0; i < snd_Channels; i++) + { + if (origin->player) + { + i = snd_Channels; + break; // let the player have more than one sound. + } + if (origin == channel[i].mo) + { // only allow other mobjs one sound + S_StopSound(channel[i].mo); + break; + } + } + if (i >= snd_Channels) + { + if (sound_id >= sfx_wind) + { + if (AmbChan != -1 && S_sfx[sound_id].priority <= + S_sfx[channel[AmbChan].sound_id].priority) + { + return; //ambient channel already in use + } + else + { + AmbChan = -1; + } + } + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].mo == NULL) + { + break; + } + } + if (i >= snd_Channels) + { + //look for a lower priority sound to replace. + sndcount++; + if (sndcount >= snd_Channels) + { + sndcount = 0; + } + for (chan = 0; chan < snd_Channels; chan++) + { + i = (sndcount + chan) % snd_Channels; + if (priority >= channel[i].priority) + { + chan = -1; //denote that sound should be replaced. + break; + } + } + if (chan != -1) + { + return; //no free channels. + } + else //replace the lower priority sound. + { + if (channel[i].handle) + { + if (I_SoundIsPlaying(channel[i].handle)) + { + I_StopSound(channel[i].handle); + } + if (S_sfx[channel[i].sound_id].usefulness > 0) + { + S_sfx[channel[i].sound_id].usefulness--; + } + + if (AmbChan == i) + { + AmbChan = -1; + } + } + } + } + } + if (S_sfx[sound_id].lumpnum == 0) + { + S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]); + } + + // calculate the volume based upon the distance from the sound origin. +// vol = (snd_MaxVolume*16 + dist*(-snd_MaxVolume*16)/MAX_SND_DIST)>>9; + vol = soundCurve[dist]; + + if (origin == listener) + { + sep = 128; + } + else + { + angle = R_PointToAngle2(listener->x, listener->y, + origin->x, origin->y); + angle = (angle - viewangle) >> 24; + sep = angle * 2 - 128; + if (sep < 64) + sep = -sep; + if (sep > 192) + sep = 512 - sep; + } + + // TODO: Play pitch-shifted sounds as in Vanilla Heretic + + channel[i].pitch = (byte) (127 + (M_Random() & 7) - (M_Random() & 7)); + channel[i].handle = I_StartSound(&S_sfx[sound_id], i, vol, sep); + channel[i].mo = origin; + channel[i].sound_id = sound_id; + channel[i].priority = priority; + if (sound_id >= sfx_wind) + { + AmbChan = i; + } + if (S_sfx[sound_id].usefulness == -1) + { + S_sfx[sound_id].usefulness = 1; + } + else + { + S_sfx[sound_id].usefulness++; + } +} + +void S_StartSoundAtVolume(void *_origin, int sound_id, int volume) +{ + mobj_t *origin = _origin; + mobj_t *listener; + int i; + + listener = GetSoundListener(); + + if (sound_id == 0 || snd_MaxVolume == 0) + return; + if (origin == NULL) + { + origin = listener; + } + + if (volume == 0) + { + return; + } + volume = (volume * (snd_MaxVolume + 1) * 8) >> 7; + +// no priority checking, as ambient sounds would be the LOWEST. + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].mo == NULL) + { + break; + } + } + if (i >= snd_Channels) + { + return; + } + if (S_sfx[sound_id].lumpnum == 0) + { + S_sfx[sound_id].lumpnum = I_GetSfxLumpNum(&S_sfx[sound_id]); + } + + // TODO: Pitch shifting. + channel[i].pitch = (byte) (127 - (M_Random() & 3) + (M_Random() & 3)); + channel[i].handle = I_StartSound(&S_sfx[sound_id], i, volume, 128); + channel[i].mo = origin; + channel[i].sound_id = sound_id; + channel[i].priority = 1; //super low priority. + if (S_sfx[sound_id].usefulness == -1) + { + S_sfx[sound_id].usefulness = 1; + } + else + { + S_sfx[sound_id].usefulness++; + } +} + +boolean S_StopSoundID(int sound_id, int priority) +{ + int i; + int lp; //least priority + int found; + + if (S_sfx[sound_id].numchannels == -1) + { + return (true); + } + lp = -1; //denote the argument sound_id + found = 0; + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].sound_id == sound_id && channel[i].mo) + { + found++; //found one. Now, should we replace it?? + if (priority >= channel[i].priority) + { // if we're gonna kill one, then this'll be it + lp = i; + priority = channel[i].priority; + } + } + } + if (found < S_sfx[sound_id].numchannels) + { + return (true); + } + else if (lp == -1) + { + return (false); // don't replace any sounds + } + if (channel[lp].handle) + { + if (I_SoundIsPlaying(channel[lp].handle)) + { + I_StopSound(channel[lp].handle); + } + if (S_sfx[channel[i].sound_id].usefulness > 0) + { + S_sfx[channel[i].sound_id].usefulness--; + } + channel[lp].mo = NULL; + } + return (true); +} + +void S_StopSound(void *_origin) +{ + mobj_t *origin = _origin; + int i; + + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].mo == origin) + { + I_StopSound(channel[i].handle); + if (S_sfx[channel[i].sound_id].usefulness > 0) + { + S_sfx[channel[i].sound_id].usefulness--; + } + channel[i].handle = 0; + channel[i].mo = NULL; + if (AmbChan == i) + { + AmbChan = -1; + } + } + } +} + +void S_SoundLink(mobj_t * oldactor, mobj_t * newactor) +{ + int i; + + for (i = 0; i < snd_Channels; i++) + { + if (channel[i].mo == oldactor) + channel[i].mo = newactor; + } +} + +void S_PauseSound(void) +{ + I_PauseSong(); +} + +void S_ResumeSound(void) +{ + I_ResumeSong(); +} + +void S_UpdateSounds(mobj_t * listener) +{ + int i, dist, vol; + int angle; + int sep; + int priority; + int absx; + int absy; + + listener = GetSoundListener(); + if (snd_MaxVolume == 0) + { + return; + } + + for (i = 0; i < snd_Channels; i++) + { + if (!channel[i].handle || S_sfx[channel[i].sound_id].usefulness == -1) + { + continue; + } + if (!I_SoundIsPlaying(channel[i].handle)) + { + if (S_sfx[channel[i].sound_id].usefulness > 0) + { + S_sfx[channel[i].sound_id].usefulness--; + } + channel[i].handle = 0; + channel[i].mo = NULL; + channel[i].sound_id = 0; + if (AmbChan == i) + { + AmbChan = -1; + } + } + if (channel[i].mo == NULL || channel[i].sound_id == 0 + || channel[i].mo == listener) + { + continue; + } + else + { + absx = abs(channel[i].mo->x - listener->x); + absy = abs(channel[i].mo->y - listener->y); + dist = absx + absy - (absx > absy ? absy >> 1 : absx >> 1); + dist >>= FRACBITS; +// dist = P_AproxDistance(channel[i].mo->x-listener->x, channel[i].mo->y-listener->y)>>FRACBITS; + + if (dist >= MAX_SND_DIST) + { + S_StopSound(channel[i].mo); + continue; + } + if (dist < 0) + dist = 0; + +// calculate the volume based upon the distance from the sound origin. +// vol = (*((byte *)W_CacheLumpName("SNDCURVE", PU_CACHE)+dist)*(snd_MaxVolume*8))>>7; + vol = soundCurve[dist]; + + angle = R_PointToAngle2(listener->x, listener->y, + channel[i].mo->x, channel[i].mo->y); + angle = (angle - viewangle) >> 24; + sep = angle * 2 - 128; + if (sep < 64) + sep = -sep; + if (sep > 192) + sep = 512 - sep; + // TODO: Pitch shifting. + I_UpdateSoundParams(channel[i].handle, vol, sep); + priority = S_sfx[channel[i].sound_id].priority; + priority *= (10 - (dist >> 8)); + channel[i].priority = priority; + } + } +} + +void S_Init(void) +{ + soundCurve = Z_Malloc(MAX_SND_DIST, PU_STATIC, NULL); + I_InitSound(false); + if (snd_Channels > 8) + { + snd_Channels = 8; + } + I_SetMusicVolume(snd_MusicVolume * 8); + S_SetMaxVolume(true); + + I_AtExit(S_ShutDown, true); +} + +void S_GetChannelInfo(SoundInfo_t * s) +{ + int i; + ChanInfo_t *c; + + s->channelCount = snd_Channels; + s->musicVolume = snd_MusicVolume; + s->soundVolume = snd_MaxVolume; + for (i = 0; i < snd_Channels; i++) + { + c = &s->chan[i]; + c->id = channel[i].sound_id; + c->priority = channel[i].priority; + c->name = S_sfx[c->id].name; + c->mo = channel[i].mo; + + if (c->mo != NULL) + { + c->distance = P_AproxDistance(c->mo->x - viewx, c->mo->y - viewy) + >> FRACBITS; + } + else + { + c->distance = 0; + } + } +} + +void S_SetMaxVolume(boolean fullprocess) +{ + int i; + + if (!fullprocess) + { + soundCurve[0] = + (*((byte *) W_CacheLumpName("SNDCURVE", PU_CACHE)) * + (snd_MaxVolume * 8)) >> 7; + } + else + { + for (i = 0; i < MAX_SND_DIST; i++) + { + soundCurve[i] = + (*((byte *) W_CacheLumpName("SNDCURVE", PU_CACHE) + i) * + (snd_MaxVolume * 8)) >> 7; + } + } +} + +static boolean musicPaused; +void S_SetMusicVolume(void) +{ + I_SetMusicVolume(snd_MusicVolume * 8); + if (snd_MusicVolume == 0) + { + I_PauseSong(); + musicPaused = true; + } + else if (musicPaused) + { + musicPaused = false; + I_ResumeSong(); + } +} + +void S_ShutDown(void) +{ + I_StopSong(); + I_UnRegisterSong(rs); + I_ShutdownSound(); +} + diff --git a/src/heretic/s_sound.h b/src/heretic/s_sound.h new file mode 100644 index 00000000..e678fc99 --- /dev/null +++ b/src/heretic/s_sound.h @@ -0,0 +1,46 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// soundst.h + +#ifndef __SOUNDSTH__ +#define __SOUNDSTH__ + +extern int snd_MaxVolume; +extern int snd_MusicVolume; + +void S_Start(void); +void S_StartSound(void *origin, int sound_id); +void S_StartSoundAtVolume(void *origin, int sound_id, int volume); +void S_StopSound(void *origin); +void S_PauseSound(void); +void S_ResumeSound(void); +void S_UpdateSounds(mobj_t * listener); +void S_StartSong(int song, boolean loop); +void S_Init(void); +void S_GetChannelInfo(SoundInfo_t * s); +void S_SetMaxVolume(boolean fullprocess); +void S_SetMusicVolume(void); + +#endif diff --git a/src/heretic/sb_bar.c b/src/heretic/sb_bar.c new file mode 100644 index 00000000..51bd3b9c --- /dev/null +++ b/src/heretic/sb_bar.c @@ -0,0 +1,1295 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// SB_bar.c + +#include "doomdef.h" +#include "deh_str.h" +#include "i_video.h" +#include "m_cheat.h" +#include "m_misc.h" +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" +#include "v_video.h" + +// Types + +typedef struct Cheat_s +{ + void (*func) (player_t * player, struct Cheat_s * cheat); + cheatseq_t *seq; +} Cheat_t; + +// Private Functions + +static void DrawSoundInfo(void); +static void ShadeLine(int x, int y, int height, int shade); +static void ShadeChain(void); +static void DrINumber(signed int val, int x, int y); +static void DrBNumber(signed int val, int x, int y); +static void DrawCommonBar(void); +static void DrawMainBar(void); +static void DrawInventoryBar(void); +static void DrawFullScreenStuff(void); +static boolean HandleCheats(byte key); +static void CheatGodFunc(player_t * player, Cheat_t * cheat); +static void CheatNoClipFunc(player_t * player, Cheat_t * cheat); +static void CheatWeaponsFunc(player_t * player, Cheat_t * cheat); +static void CheatPowerFunc(player_t * player, Cheat_t * cheat); +static void CheatHealthFunc(player_t * player, Cheat_t * cheat); +static void CheatKeysFunc(player_t * player, Cheat_t * cheat); +static void CheatSoundFunc(player_t * player, Cheat_t * cheat); +static void CheatTickerFunc(player_t * player, Cheat_t * cheat); +static void CheatArtifact1Func(player_t * player, Cheat_t * cheat); +static void CheatArtifact2Func(player_t * player, Cheat_t * cheat); +static void CheatArtifact3Func(player_t * player, Cheat_t * cheat); +static void CheatWarpFunc(player_t * player, Cheat_t * cheat); +static void CheatChickenFunc(player_t * player, Cheat_t * cheat); +static void CheatMassacreFunc(player_t * player, Cheat_t * cheat); +static void CheatIDKFAFunc(player_t * player, Cheat_t * cheat); +static void CheatIDDQDFunc(player_t * player, Cheat_t * cheat); + +// Public Data + +boolean DebugSound; // debug flag for displaying sound info + +boolean inventory; +int curpos; +int inv_ptr; +int ArtifactFlash; + +static int DisplayTicker = 0; + +// Private Data + +static int HealthMarker; +static int ChainWiggle; +static player_t *CPlayer; +int playpalette; + +patch_t *PatchLTFACE; +patch_t *PatchRTFACE; +patch_t *PatchBARBACK; +patch_t *PatchCHAIN; +patch_t *PatchSTATBAR; +patch_t *PatchLIFEGEM; +//patch_t *PatchEMPWEAP; +//patch_t *PatchLIL4BOX; +patch_t *PatchLTFCTOP; +patch_t *PatchRTFCTOP; +//patch_t *PatchARMORBOX; +//patch_t *PatchARTIBOX; +patch_t *PatchSELECTBOX; +//patch_t *PatchKILLSPIC; +//patch_t *PatchMANAPIC; +//patch_t *PatchPOWERICN; +patch_t *PatchINVLFGEM1; +patch_t *PatchINVLFGEM2; +patch_t *PatchINVRTGEM1; +patch_t *PatchINVRTGEM2; +patch_t *PatchINumbers[10]; +patch_t *PatchNEGATIVE; +patch_t *PatchSmNumbers[10]; +patch_t *PatchBLACKSQ; +patch_t *PatchINVBAR; +patch_t *PatchARMCLEAR; +patch_t *PatchCHAINBACK; +//byte *ShadeTables; +int FontBNumBase; +int spinbooklump; +int spinflylump; + +// Toggle god mode +cheatseq_t CheatGodSeq = CHEAT("quicken", 0); + +// Toggle no clipping mode +cheatseq_t CheatNoClipSeq = CHEAT("kitty", 0); + +// Get all weapons and ammo +cheatseq_t CheatWeaponsSeq = CHEAT("rambo", 0); + +// Toggle tome of power +cheatseq_t CheatPowerSeq = CHEAT("shazam", 0); + +// Get full health +cheatseq_t CheatHealthSeq = CHEAT("ponce", 0); + +// Get all keys +cheatseq_t CheatKeysSeq = CHEAT("skel", 0); + +// Toggle sound debug info +cheatseq_t CheatSoundSeq = CHEAT("noise", 0); + +// Toggle ticker +cheatseq_t CheatTickerSeq = CHEAT("ticker", 0); + +// Get an artifact 1st stage (ask for type) +cheatseq_t CheatArtifact1Seq = CHEAT("gimme", 0); + +// Get an artifact 2nd stage (ask for count) +cheatseq_t CheatArtifact2Seq = CHEAT("gimme", 1); + +// Get an artifact final stage +cheatseq_t CheatArtifact3Seq = CHEAT("gimme", 2); + +// Warp to new level +cheatseq_t CheatWarpSeq = CHEAT("engage", 2); + +// Save a screenshot +cheatseq_t CheatChickenSeq = CHEAT("cockadoodledoo", 0); + +// Kill all monsters +cheatseq_t CheatMassacreSeq = CHEAT("massacre", 0); + +cheatseq_t CheatIDKFASeq = CHEAT("idkfa", 0); +cheatseq_t CheatIDDQDSeq = CHEAT("iddqd", 0); + +static Cheat_t Cheats[] = { + {CheatGodFunc, &CheatGodSeq}, + {CheatNoClipFunc, &CheatNoClipSeq}, + {CheatWeaponsFunc, &CheatWeaponsSeq}, + {CheatPowerFunc, &CheatPowerSeq}, + {CheatHealthFunc, &CheatHealthSeq}, + {CheatKeysFunc, &CheatKeysSeq}, + {CheatSoundFunc, &CheatSoundSeq}, + {CheatTickerFunc, &CheatTickerSeq}, + {CheatArtifact1Func, &CheatArtifact1Seq}, + {CheatArtifact2Func, &CheatArtifact2Seq}, + {CheatArtifact3Func, &CheatArtifact3Seq}, + {CheatWarpFunc, &CheatWarpSeq}, + {CheatChickenFunc, &CheatChickenSeq}, + {CheatMassacreFunc, &CheatMassacreSeq}, + {CheatIDKFAFunc, &CheatIDKFASeq}, + {CheatIDDQDFunc, &CheatIDDQDSeq}, + {NULL, NULL} +}; + +//--------------------------------------------------------------------------- +// +// PROC SB_Init +// +//--------------------------------------------------------------------------- + +void SB_Init(void) +{ + int i; + int startLump; + + PatchLTFACE = W_CacheLumpName(DEH_String("LTFACE"), PU_STATIC); + PatchRTFACE = W_CacheLumpName(DEH_String("RTFACE"), PU_STATIC); + PatchBARBACK = W_CacheLumpName(DEH_String("BARBACK"), PU_STATIC); + PatchINVBAR = W_CacheLumpName(DEH_String("INVBAR"), PU_STATIC); + PatchCHAIN = W_CacheLumpName(DEH_String("CHAIN"), PU_STATIC); + if (deathmatch) + { + PatchSTATBAR = W_CacheLumpName(DEH_String("STATBAR"), PU_STATIC); + } + else + { + PatchSTATBAR = W_CacheLumpName(DEH_String("LIFEBAR"), PU_STATIC); + } + if (!netgame) + { // single player game uses red life gem + PatchLIFEGEM = W_CacheLumpName(DEH_String("LIFEGEM2"), PU_STATIC); + } + else + { + PatchLIFEGEM = W_CacheLumpNum(W_GetNumForName(DEH_String("LIFEGEM0")) + + consoleplayer, PU_STATIC); + } + PatchLTFCTOP = W_CacheLumpName(DEH_String("LTFCTOP"), PU_STATIC); + PatchRTFCTOP = W_CacheLumpName(DEH_String("RTFCTOP"), PU_STATIC); + PatchSELECTBOX = W_CacheLumpName(DEH_String("SELECTBOX"), PU_STATIC); + PatchINVLFGEM1 = W_CacheLumpName(DEH_String("INVGEML1"), PU_STATIC); + PatchINVLFGEM2 = W_CacheLumpName(DEH_String("INVGEML2"), PU_STATIC); + PatchINVRTGEM1 = W_CacheLumpName(DEH_String("INVGEMR1"), PU_STATIC); + PatchINVRTGEM2 = W_CacheLumpName(DEH_String("INVGEMR2"), PU_STATIC); + PatchBLACKSQ = W_CacheLumpName(DEH_String("BLACKSQ"), PU_STATIC); + PatchARMCLEAR = W_CacheLumpName(DEH_String("ARMCLEAR"), PU_STATIC); + PatchCHAINBACK = W_CacheLumpName(DEH_String("CHAINBACK"), PU_STATIC); + startLump = W_GetNumForName(DEH_String("IN0")); + for (i = 0; i < 10; i++) + { + PatchINumbers[i] = W_CacheLumpNum(startLump + i, PU_STATIC); + } + PatchNEGATIVE = W_CacheLumpName(DEH_String("NEGNUM"), PU_STATIC); + FontBNumBase = W_GetNumForName(DEH_String("FONTB16")); + startLump = W_GetNumForName(DEH_String("SMALLIN0")); + for (i = 0; i < 10; i++) + { + PatchSmNumbers[i] = W_CacheLumpNum(startLump + i, PU_STATIC); + } + playpalette = W_GetNumForName(DEH_String("PLAYPAL")); + spinbooklump = W_GetNumForName(DEH_String("SPINBK0")); + spinflylump = W_GetNumForName(DEH_String("SPFLY0")); +} + +//--------------------------------------------------------------------------- +// +// PROC SB_Ticker +// +//--------------------------------------------------------------------------- + +void SB_Ticker(void) +{ + int delta; + int curHealth; + + if (leveltime & 1) + { + ChainWiggle = P_Random() & 1; + } + curHealth = players[consoleplayer].mo->health; + if (curHealth < 0) + { + curHealth = 0; + } + if (curHealth < HealthMarker) + { + delta = (HealthMarker - curHealth) >> 2; + if (delta < 1) + { + delta = 1; + } + else if (delta > 8) + { + delta = 8; + } + HealthMarker -= delta; + } + else if (curHealth > HealthMarker) + { + delta = (curHealth - HealthMarker) >> 2; + if (delta < 1) + { + delta = 1; + } + else if (delta > 8) + { + delta = 8; + } + HealthMarker += delta; + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrINumber +// +// Draws a three digit number. +// +//--------------------------------------------------------------------------- + +static void DrINumber(signed int val, int x, int y) +{ + patch_t *patch; + int oldval; + + oldval = val; + if (val < 0) + { + if (val < -9) + { + V_DrawPatch(x + 1, y + 1, W_CacheLumpName(DEH_String("LAME"), PU_CACHE)); + } + else + { + val = -val; + V_DrawPatch(x + 18, y, PatchINumbers[val]); + V_DrawPatch(x + 9, y, PatchNEGATIVE); + } + return; + } + if (val > 99) + { + patch = PatchINumbers[val / 100]; + V_DrawPatch(x, y, patch); + } + val = val % 100; + if (val > 9 || oldval > 99) + { + patch = PatchINumbers[val / 10]; + V_DrawPatch(x + 9, y, patch); + } + val = val % 10; + patch = PatchINumbers[val]; + V_DrawPatch(x + 18, y, patch); +} + +//--------------------------------------------------------------------------- +// +// PROC DrBNumber +// +// Draws a three digit number using FontB +// +//--------------------------------------------------------------------------- + +static void DrBNumber(signed int val, int x, int y) +{ + patch_t *patch; + int xpos; + int oldval; + + oldval = val; + xpos = x; + if (val < 0) + { + val = 0; + } + if (val > 99) + { + patch = W_CacheLumpNum(FontBNumBase + val / 100, PU_CACHE); + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); + } + val = val % 100; + xpos += 12; + if (val > 9 || oldval > 99) + { + patch = W_CacheLumpNum(FontBNumBase + val / 10, PU_CACHE); + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); + } + val = val % 10; + xpos += 12; + patch = W_CacheLumpNum(FontBNumBase + val, PU_CACHE); + V_DrawShadowedPatch(xpos + 6 - patch->width / 2, y, patch); +} + +//--------------------------------------------------------------------------- +// +// PROC DrSmallNumber +// +// Draws a small two digit number. +// +//--------------------------------------------------------------------------- + +static void DrSmallNumber(int val, int x, int y) +{ + patch_t *patch; + + if (val == 1) + { + return; + } + if (val > 9) + { + patch = PatchSmNumbers[val / 10]; + V_DrawPatch(x, y, patch); + } + val = val % 10; + patch = PatchSmNumbers[val]; + V_DrawPatch(x + 4, y, patch); +} + +//--------------------------------------------------------------------------- +// +// PROC ShadeLine +// +//--------------------------------------------------------------------------- + +static void ShadeLine(int x, int y, int height, int shade) +{ + byte *dest; + byte *shades; + + shades = colormaps + 9 * 256 + shade * 2 * 256; + dest = I_VideoBuffer + y * SCREENWIDTH + x; + while (height--) + { + *(dest) = *(shades + *dest); + dest += SCREENWIDTH; + } +} + +//--------------------------------------------------------------------------- +// +// PROC ShadeChain +// +//--------------------------------------------------------------------------- + +static void ShadeChain(void) +{ + int i; + + for (i = 0; i < 16; i++) + { + ShadeLine(277 + i, 190, 10, i / 2); + ShadeLine(19 + i, 190, 10, 7 - (i / 2)); + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawSoundInfo +// +// Displays sound debugging information. +// +//--------------------------------------------------------------------------- + +static void DrawSoundInfo(void) +{ + int i; + SoundInfo_t s; + ChanInfo_t *c; + char text[32]; + int x; + int y; + int xPos[7] = { 1, 75, 112, 156, 200, 230, 260 }; + + if (leveltime & 16) + { + MN_DrTextA(DEH_String("*** SOUND DEBUG INFO ***"), xPos[0], 20); + } + S_GetChannelInfo(&s); + if (s.channelCount == 0) + { + return; + } + x = 0; + MN_DrTextA(DEH_String("NAME"), xPos[x++], 30); + MN_DrTextA(DEH_String("MO.T"), xPos[x++], 30); + MN_DrTextA(DEH_String("MO.X"), xPos[x++], 30); + MN_DrTextA(DEH_String("MO.Y"), xPos[x++], 30); + MN_DrTextA(DEH_String("ID"), xPos[x++], 30); + MN_DrTextA(DEH_String("PRI"), xPos[x++], 30); + MN_DrTextA(DEH_String("DIST"), xPos[x++], 30); + for (i = 0; i < s.channelCount; i++) + { + c = &s.chan[i]; + x = 0; + y = 40 + i * 10; + if (c->mo == NULL) + { // Channel is unused + MN_DrTextA(DEH_String("------"), xPos[0], y); + continue; + } + sprintf(text, "%s", c->name); + M_ForceUppercase(text); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->mo->type); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->mo->x >> FRACBITS); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->mo->y >> FRACBITS); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->id); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->priority); + MN_DrTextA(text, xPos[x++], y); + sprintf(text, "%d", c->distance); + MN_DrTextA(text, xPos[x++], y); + } + UpdateState |= I_FULLSCRN; + BorderNeedRefresh = true; +} + +//--------------------------------------------------------------------------- +// +// PROC SB_Drawer +// +//--------------------------------------------------------------------------- + +char patcharti[][10] = { + {"ARTIBOX"}, // none + {"ARTIINVU"}, // invulnerability + {"ARTIINVS"}, // invisibility + {"ARTIPTN2"}, // health + {"ARTISPHL"}, // superhealth + {"ARTIPWBK"}, // tomeofpower + {"ARTITRCH"}, // torch + {"ARTIFBMB"}, // firebomb + {"ARTIEGGC"}, // egg + {"ARTISOAR"}, // fly + {"ARTIATLP"} // teleport +}; + +char ammopic[][10] = { + {"INAMGLD"}, + {"INAMBOW"}, + {"INAMBST"}, + {"INAMRAM"}, + {"INAMPNX"}, + {"INAMLOB"} +}; + +int SB_state = -1; +static int oldarti = 0; +static int oldartiCount = 0; +static int oldfrags = -9999; +static int oldammo = -1; +static int oldarmor = -1; +static int oldweapon = -1; +static int oldhealth = -1; +static int oldlife = -1; +static int oldkeys = -1; + +int playerkeys = 0; + +extern boolean automapactive; + +void SB_Drawer(void) +{ + int frame; + static boolean hitCenterFrame; + + // Sound info debug stuff + if (DebugSound == true) + { + DrawSoundInfo(); + } + CPlayer = &players[consoleplayer]; + if (viewheight == SCREENHEIGHT && !automapactive) + { + DrawFullScreenStuff(); + SB_state = -1; + } + else + { + if (SB_state == -1) + { + V_DrawPatch(0, 158, PatchBARBACK); + if (players[consoleplayer].cheats & CF_GODMODE) + { + V_DrawPatch(16, 167, + W_CacheLumpName(DEH_String("GOD1"), PU_CACHE)); + V_DrawPatch(287, 167, + W_CacheLumpName(DEH_String("GOD2"), PU_CACHE)); + } + oldhealth = -1; + } + DrawCommonBar(); + if (!inventory) + { + if (SB_state != 0) + { + // Main interface + V_DrawPatch(34, 160, PatchSTATBAR); + oldarti = 0; + oldammo = -1; + oldarmor = -1; + oldweapon = -1; + oldfrags = -9999; //can't use -1, 'cuz of negative frags + oldlife = -1; + oldkeys = -1; + } + DrawMainBar(); + SB_state = 0; + } + else + { + if (SB_state != 1) + { + V_DrawPatch(34, 160, PatchINVBAR); + } + DrawInventoryBar(); + SB_state = 1; + } + } + SB_PaletteFlash(); + + // Flight icons + if (CPlayer->powers[pw_flight]) + { + if (CPlayer->powers[pw_flight] > BLINKTHRESHOLD + || !(CPlayer->powers[pw_flight] & 16)) + { + frame = (leveltime / 3) & 15; + if (CPlayer->mo->flags2 & MF2_FLY) + { + if (hitCenterFrame && (frame != 15 && frame != 0)) + { + V_DrawPatch(20, 17, W_CacheLumpNum(spinflylump + 15, + PU_CACHE)); + } + else + { + V_DrawPatch(20, 17, W_CacheLumpNum(spinflylump + frame, + PU_CACHE)); + hitCenterFrame = false; + } + } + else + { + if (!hitCenterFrame && (frame != 15 && frame != 0)) + { + V_DrawPatch(20, 17, W_CacheLumpNum(spinflylump + frame, + PU_CACHE)); + hitCenterFrame = false; + } + else + { + V_DrawPatch(20, 17, W_CacheLumpNum(spinflylump + 15, + PU_CACHE)); + hitCenterFrame = true; + } + } + BorderTopRefresh = true; + UpdateState |= I_MESSAGES; + } + else + { + BorderTopRefresh = true; + UpdateState |= I_MESSAGES; + } + } + + if (CPlayer->powers[pw_weaponlevel2] && !CPlayer->chickenTics) + { + if (CPlayer->powers[pw_weaponlevel2] > BLINKTHRESHOLD + || !(CPlayer->powers[pw_weaponlevel2] & 16)) + { + frame = (leveltime / 3) & 15; + V_DrawPatch(300, 17, + W_CacheLumpNum(spinbooklump + frame, PU_CACHE)); + BorderTopRefresh = true; + UpdateState |= I_MESSAGES; + } + else + { + BorderTopRefresh = true; + UpdateState |= I_MESSAGES; + } + } +/* + if(CPlayer->powers[pw_weaponlevel2] > BLINKTHRESHOLD + || (CPlayer->powers[pw_weaponlevel2]&8)) + { + V_DrawPatch(291, 0, W_CacheLumpName("ARTIPWBK", PU_CACHE)); + } + else + { + BorderTopRefresh = true; + } + } +*/ +} + +// sets the new palette based upon current values of player->damagecount +// and player->bonuscount +void SB_PaletteFlash(void) +{ + static int sb_palette = 0; + int palette; + byte *pal; + + CPlayer = &players[consoleplayer]; + + if (CPlayer->damagecount) + { + palette = (CPlayer->damagecount + 7) >> 3; + if (palette >= NUMREDPALS) + { + palette = NUMREDPALS - 1; + } + palette += STARTREDPALS; + } + else if (CPlayer->bonuscount) + { + palette = (CPlayer->bonuscount + 7) >> 3; + if (palette >= NUMBONUSPALS) + { + palette = NUMBONUSPALS - 1; + } + palette += STARTBONUSPALS; + } + else + { + palette = 0; + } + if (palette != sb_palette) + { + sb_palette = palette; + pal = (byte *) W_CacheLumpNum(playpalette, PU_CACHE) + palette * 768; + I_SetPalette(pal); + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawCommonBar +// +//--------------------------------------------------------------------------- + +void DrawCommonBar(void) +{ + int chainY; + int healthPos; + + V_DrawPatch(0, 148, PatchLTFCTOP); + V_DrawPatch(290, 148, PatchRTFCTOP); + + if (oldhealth != HealthMarker) + { + oldhealth = HealthMarker; + healthPos = HealthMarker; + if (healthPos < 0) + { + healthPos = 0; + } + if (healthPos > 100) + { + healthPos = 100; + } + healthPos = (healthPos * 256) / 100; + chainY = + (HealthMarker == CPlayer->mo->health) ? 191 : 191 + ChainWiggle; + V_DrawPatch(0, 190, PatchCHAINBACK); + V_DrawPatch(2 + (healthPos % 17), chainY, PatchCHAIN); + V_DrawPatch(17 + healthPos, chainY, PatchLIFEGEM); + V_DrawPatch(0, 190, PatchLTFACE); + V_DrawPatch(276, 190, PatchRTFACE); + ShadeChain(); + UpdateState |= I_STATBAR; + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawMainBar +// +//--------------------------------------------------------------------------- + +void DrawMainBar(void) +{ + int i; + int temp; + + // Ready artifact + if (ArtifactFlash) + { + V_DrawPatch(180, 161, PatchBLACKSQ); + + temp = W_GetNumForName(DEH_String("useartia")) + ArtifactFlash - 1; + + V_DrawPatch(182, 161, W_CacheLumpNum(temp, PU_CACHE)); + ArtifactFlash--; + oldarti = -1; // so that the correct artifact fills in after the flash + UpdateState |= I_STATBAR; + } + else if (oldarti != CPlayer->readyArtifact + || oldartiCount != CPlayer->inventory[inv_ptr].count) + { + V_DrawPatch(180, 161, PatchBLACKSQ); + if (CPlayer->readyArtifact > 0) + { + V_DrawPatch(179, 160, + W_CacheLumpName(DEH_String(patcharti[CPlayer->readyArtifact]), + PU_CACHE)); + DrSmallNumber(CPlayer->inventory[inv_ptr].count, 201, 182); + } + oldarti = CPlayer->readyArtifact; + oldartiCount = CPlayer->inventory[inv_ptr].count; + UpdateState |= I_STATBAR; + } + + // Frags + if (deathmatch) + { + temp = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + temp += CPlayer->frags[i]; + } + if (temp != oldfrags) + { + V_DrawPatch(57, 171, PatchARMCLEAR); + DrINumber(temp, 61, 170); + oldfrags = temp; + UpdateState |= I_STATBAR; + } + } + else + { + temp = HealthMarker; + if (temp < 0) + { + temp = 0; + } + else if (temp > 100) + { + temp = 100; + } + if (oldlife != temp) + { + oldlife = temp; + V_DrawPatch(57, 171, PatchARMCLEAR); + DrINumber(temp, 61, 170); + UpdateState |= I_STATBAR; + } + } + + // Keys + if (oldkeys != playerkeys) + { + if (CPlayer->keys[key_yellow]) + { + V_DrawPatch(153, 164, W_CacheLumpName(DEH_String("ykeyicon"), PU_CACHE)); + } + if (CPlayer->keys[key_green]) + { + V_DrawPatch(153, 172, W_CacheLumpName(DEH_String("gkeyicon"), PU_CACHE)); + } + if (CPlayer->keys[key_blue]) + { + V_DrawPatch(153, 180, W_CacheLumpName(DEH_String("bkeyicon"), PU_CACHE)); + } + oldkeys = playerkeys; + UpdateState |= I_STATBAR; + } + // Ammo + temp = CPlayer->ammo[wpnlev1info[CPlayer->readyweapon].ammo]; + if (oldammo != temp || oldweapon != CPlayer->readyweapon) + { + V_DrawPatch(108, 161, PatchBLACKSQ); + if (temp && CPlayer->readyweapon > 0 && CPlayer->readyweapon < 7) + { + DrINumber(temp, 109, 162); + V_DrawPatch(111, 172, + W_CacheLumpName(DEH_String(ammopic[CPlayer->readyweapon - 1]), + PU_CACHE)); + } + oldammo = temp; + oldweapon = CPlayer->readyweapon; + UpdateState |= I_STATBAR; + } + + // Armor + if (oldarmor != CPlayer->armorpoints) + { + V_DrawPatch(224, 171, PatchARMCLEAR); + DrINumber(CPlayer->armorpoints, 228, 170); + oldarmor = CPlayer->armorpoints; + UpdateState |= I_STATBAR; + } +} + +//--------------------------------------------------------------------------- +// +// PROC DrawInventoryBar +// +//--------------------------------------------------------------------------- + +void DrawInventoryBar(void) +{ + char *patch; + int i; + int x; + + x = inv_ptr - curpos; + UpdateState |= I_STATBAR; + V_DrawPatch(34, 160, PatchINVBAR); + for (i = 0; i < 7; i++) + { + //V_DrawPatch(50+i*31, 160, W_CacheLumpName("ARTIBOX", PU_CACHE)); + if (CPlayer->inventorySlotNum > x + i + && CPlayer->inventory[x + i].type != arti_none) + { + patch = DEH_String(patcharti[CPlayer->inventory[x + i].type]); + + V_DrawPatch(50 + i * 31, 160, W_CacheLumpName(patch, PU_CACHE)); + DrSmallNumber(CPlayer->inventory[x + i].count, 69 + i * 31, 182); + } + } + V_DrawPatch(50 + curpos * 31, 189, PatchSELECTBOX); + if (x != 0) + { + V_DrawPatch(38, 159, !(leveltime & 4) ? PatchINVLFGEM1 : + PatchINVLFGEM2); + } + if (CPlayer->inventorySlotNum - x > 7) + { + V_DrawPatch(269, 159, !(leveltime & 4) ? + PatchINVRTGEM1 : PatchINVRTGEM2); + } +} + +void DrawFullScreenStuff(void) +{ + char *patch; + int i; + int x; + int temp; + + UpdateState |= I_FULLSCRN; + if (CPlayer->mo->health > 0) + { + DrBNumber(CPlayer->mo->health, 5, 180); + } + else + { + DrBNumber(0, 5, 180); + } + if (deathmatch) + { + temp = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + temp += CPlayer->frags[i]; + } + } + DrINumber(temp, 45, 185); + } + if (!inventory) + { + if (CPlayer->readyArtifact > 0) + { + patch = DEH_String(patcharti[CPlayer->readyArtifact]); + V_DrawTLPatch(286, 170, W_CacheLumpName(DEH_String("ARTIBOX"), PU_CACHE)); + V_DrawPatch(286, 170, W_CacheLumpName(patch, PU_CACHE)); + DrSmallNumber(CPlayer->inventory[inv_ptr].count, 307, 192); + } + } + else + { + x = inv_ptr - curpos; + for (i = 0; i < 7; i++) + { + V_DrawTLPatch(50 + i * 31, 168, + W_CacheLumpName(DEH_String("ARTIBOX"), PU_CACHE)); + if (CPlayer->inventorySlotNum > x + i + && CPlayer->inventory[x + i].type != arti_none) + { + patch = DEH_String(patcharti[CPlayer->inventory[x + i].type]); + V_DrawPatch(50 + i * 31, 168, + W_CacheLumpName(patch, PU_CACHE)); + DrSmallNumber(CPlayer->inventory[x + i].count, 69 + i * 31, + 190); + } + } + V_DrawPatch(50 + curpos * 31, 197, PatchSELECTBOX); + if (x != 0) + { + V_DrawPatch(38, 167, !(leveltime & 4) ? PatchINVLFGEM1 : + PatchINVLFGEM2); + } + if (CPlayer->inventorySlotNum - x > 7) + { + V_DrawPatch(269, 167, !(leveltime & 4) ? + PatchINVRTGEM1 : PatchINVRTGEM2); + } + } +} + +//-------------------------------------------------------------------------- +// +// FUNC SB_Responder +// +//-------------------------------------------------------------------------- + +boolean SB_Responder(event_t * event) +{ + if (event->type == ev_keydown) + { + if (HandleCheats(event->data1)) + { // Need to eat the key + return (true); + } + } + return (false); +} + +//-------------------------------------------------------------------------- +// +// FUNC HandleCheats +// +// Returns true if the caller should eat the key. +// +//-------------------------------------------------------------------------- + +static boolean HandleCheats(byte key) +{ + int i; + boolean eat; + + if (netgame || gameskill == sk_nightmare) + { // Can't cheat in a net-game, or in nightmare mode + return (false); + } + if (players[consoleplayer].health <= 0) + { // Dead players can't cheat + return (false); + } + eat = false; + for (i = 0; Cheats[i].func != NULL; i++) + { + if (cht_CheckCheat(Cheats[i].seq, key)) + { + Cheats[i].func(&players[consoleplayer], &Cheats[i]); + S_StartSound(NULL, sfx_dorcls); + } + } + return (eat); +} + +//-------------------------------------------------------------------------- +// +// CHEAT FUNCTIONS +// +//-------------------------------------------------------------------------- + +static void CheatGodFunc(player_t * player, Cheat_t * cheat) +{ + player->cheats ^= CF_GODMODE; + if (player->cheats & CF_GODMODE) + { + P_SetMessage(player, DEH_String(TXT_CHEATGODON), false); + } + else + { + P_SetMessage(player, DEH_String(TXT_CHEATGODOFF), false); + } + SB_state = -1; +} + +static void CheatNoClipFunc(player_t * player, Cheat_t * cheat) +{ + player->cheats ^= CF_NOCLIP; + if (player->cheats & CF_NOCLIP) + { + P_SetMessage(player, DEH_String(TXT_CHEATNOCLIPON), false); + } + else + { + P_SetMessage(player, DEH_String(TXT_CHEATNOCLIPOFF), false); + } +} + +static void CheatWeaponsFunc(player_t * player, Cheat_t * cheat) +{ + int i; + //extern boolean *WeaponInShareware; + + player->armorpoints = 200; + player->armortype = 2; + if (!player->backpack) + { + for (i = 0; i < NUMAMMO; i++) + { + player->maxammo[i] *= 2; + } + player->backpack = true; + } + for (i = 0; i < NUMWEAPONS - 1; i++) + { + player->weaponowned[i] = true; + } + if (gamemode == shareware) + { + player->weaponowned[wp_skullrod] = false; + player->weaponowned[wp_phoenixrod] = false; + player->weaponowned[wp_mace] = false; + } + for (i = 0; i < NUMAMMO; i++) + { + player->ammo[i] = player->maxammo[i]; + } + P_SetMessage(player, DEH_String(TXT_CHEATWEAPONS), false); +} + +static void CheatPowerFunc(player_t * player, Cheat_t * cheat) +{ + if (player->powers[pw_weaponlevel2]) + { + player->powers[pw_weaponlevel2] = 0; + P_SetMessage(player, DEH_String(TXT_CHEATPOWEROFF), false); + } + else + { + P_UseArtifact(player, arti_tomeofpower); + P_SetMessage(player, DEH_String(TXT_CHEATPOWERON), false); + } +} + +static void CheatHealthFunc(player_t * player, Cheat_t * cheat) +{ + if (player->chickenTics) + { + player->health = player->mo->health = MAXCHICKENHEALTH; + } + else + { + player->health = player->mo->health = MAXHEALTH; + } + P_SetMessage(player, DEH_String(TXT_CHEATHEALTH), false); +} + +static void CheatKeysFunc(player_t * player, Cheat_t * cheat) +{ + extern int playerkeys; + + player->keys[key_yellow] = true; + player->keys[key_green] = true; + player->keys[key_blue] = true; + playerkeys = 7; // Key refresh flags + P_SetMessage(player, DEH_String(TXT_CHEATKEYS), false); +} + +static void CheatSoundFunc(player_t * player, Cheat_t * cheat) +{ + DebugSound = !DebugSound; + if (DebugSound) + { + P_SetMessage(player, DEH_String(TXT_CHEATSOUNDON), false); + } + else + { + P_SetMessage(player, DEH_String(TXT_CHEATSOUNDOFF), false); + } +} + +static void CheatTickerFunc(player_t * player, Cheat_t * cheat) +{ + DisplayTicker = !DisplayTicker; + if (DisplayTicker) + { + P_SetMessage(player, DEH_String(TXT_CHEATTICKERON), false); + } + else + { + P_SetMessage(player, DEH_String(TXT_CHEATTICKEROFF), false); + } + + I_DisplayFPSDots(DisplayTicker); +} + +static void CheatArtifact1Func(player_t * player, Cheat_t * cheat) +{ + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS1), false); +} + +static void CheatArtifact2Func(player_t * player, Cheat_t * cheat) +{ + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS2), false); +} + +static void CheatArtifact3Func(player_t * player, Cheat_t * cheat) +{ + char args[2]; + int i; + int j; + artitype_t type; + int count; + + cht_GetParam(cheat->seq, args); + type = args[0] - 'a' + 1; + count = args[1] - '0'; + if (type == 26 && count == 0) + { // All artifacts + for (i = arti_none + 1; i < NUMARTIFACTS; i++) + { + if (gamemode == shareware + && (i == arti_superhealth || i == arti_teleport)) + { + continue; + } + for (j = 0; j < 16; j++) + { + P_GiveArtifact(player, i, NULL); + } + } + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS3), false); + } + else if (type > arti_none && type < NUMARTIFACTS + && count > 0 && count < 10) + { + if (gamemode == shareware + && (type == arti_superhealth || type == arti_teleport)) + { + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTSFAIL), false); + return; + } + for (i = 0; i < count; i++) + { + P_GiveArtifact(player, type, NULL); + } + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTS3), false); + } + else + { // Bad input + P_SetMessage(player, DEH_String(TXT_CHEATARTIFACTSFAIL), false); + } +} + +static void CheatWarpFunc(player_t * player, Cheat_t * cheat) +{ + char args[2]; + int episode; + int map; + + cht_GetParam(cheat->seq, args); + + episode = args[0] - '0'; + map = args[1] - '0'; + if (D_ValidEpisodeMap(gamemission, gamemode, episode, map)) + { + G_DeferedInitNew(gameskill, episode, map); + P_SetMessage(player, DEH_String(TXT_CHEATWARP), false); + } +} + +static void CheatChickenFunc(player_t * player, Cheat_t * cheat) +{ + extern boolean P_UndoPlayerChicken(player_t * player); + + if (player->chickenTics) + { + if (P_UndoPlayerChicken(player)) + { + P_SetMessage(player, DEH_String(TXT_CHEATCHICKENOFF), false); + } + } + else if (P_ChickenMorphPlayer(player)) + { + P_SetMessage(player, DEH_String(TXT_CHEATCHICKENON), false); + } +} + +static void CheatMassacreFunc(player_t * player, Cheat_t * cheat) +{ + P_Massacre(); + P_SetMessage(player, DEH_String(TXT_CHEATMASSACRE), false); +} + +static void CheatIDKFAFunc(player_t * player, Cheat_t * cheat) +{ + int i; + if (player->chickenTics) + { + return; + } + for (i = 1; i < 8; i++) + { + player->weaponowned[i] = false; + } + player->pendingweapon = wp_staff; + P_SetMessage(player, DEH_String(TXT_CHEATIDKFA), true); +} + +static void CheatIDDQDFunc(player_t * player, Cheat_t * cheat) +{ + P_DamageMobj(player->mo, NULL, player->mo, 10000); + P_SetMessage(player, DEH_String(TXT_CHEATIDDQD), true); +} diff --git a/src/heretic/sounds.c b/src/heretic/sounds.c new file mode 100644 index 00000000..f62110c6 --- /dev/null +++ b/src/heretic/sounds.c @@ -0,0 +1,257 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + + +// sounds.c + +#include "doomdef.h" +#include "i_sound.h" +#include "sounds.h" + +// Music info + +#define MUSIC(name) \ + { name, 0, NULL, NULL } + +musicinfo_t S_music[] = { + MUSIC("MUS_E1M1"), // 1-1 + MUSIC("MUS_E1M2"), + MUSIC("MUS_E1M3"), + MUSIC("MUS_E1M4"), + MUSIC("MUS_E1M5"), + MUSIC("MUS_E1M6"), + MUSIC("MUS_E1M7"), + MUSIC("MUS_E1M8"), + MUSIC("MUS_E1M9"), + + MUSIC("MUS_E2M1"), // 2-1 + MUSIC("MUS_E2M2"), + MUSIC("MUS_E2M3"), + MUSIC("MUS_E2M4"), + MUSIC("MUS_E1M4"), + MUSIC("MUS_E2M6"), + MUSIC("MUS_E2M7"), + MUSIC("MUS_E2M8"), + MUSIC("MUS_E2M9"), + + MUSIC("MUS_E1M1"), // 3-1 + MUSIC("MUS_E3M2"), + MUSIC("MUS_E3M3"), + MUSIC("MUS_E1M6"), + MUSIC("MUS_E1M3"), + MUSIC("MUS_E1M2"), + MUSIC("MUS_E1M5"), + MUSIC("MUS_E1M9"), + MUSIC("MUS_E2M6"), + + MUSIC("MUS_E1M6"), // 4-1 + MUSIC("MUS_E1M2"), + MUSIC("MUS_E1M3"), + MUSIC("MUS_E1M4"), + MUSIC("MUS_E1M5"), + MUSIC("MUS_E1M1"), + MUSIC("MUS_E1M7"), + MUSIC("MUS_E1M8"), + MUSIC("MUS_E1M9"), + + MUSIC("MUS_E2M1"), // 5-1 + MUSIC("MUS_E2M2"), + MUSIC("MUS_E2M3"), + MUSIC("MUS_E2M4"), + MUSIC("MUS_E1M4"), + MUSIC("MUS_E2M6"), + MUSIC("MUS_E2M7"), + MUSIC("MUS_E2M8"), + MUSIC("MUS_E2M9"), + + MUSIC("MUS_E3M2"), // 6-1 + MUSIC("MUS_E3M3"), // 6-2 + MUSIC("MUS_E1M6"), // 6-3 + + MUSIC("MUS_TITL"), + MUSIC("MUS_INTR"), + MUSIC("MUS_CPTD") +}; + +// Sound info + + /* Macro for original heretic sfxinfo_t +#define SOUND(name, priority, numchannels) \ + { name, NULL, priority, -1, NULL, 0, numchannels } +#define SOUND_LINK(name, link_id, priority, numchannels) \ + { name, &S_sfx[link_id], priority, -1, NULL, 0, numchannels } + */ + +#define SOUND(name, priority, numchannels) \ + { NULL, name, priority, NULL, -1, -1, -1, 0, numchannels, NULL } +#define SOUND_LINK(name, link_id, priority, numchannels) \ + { NULL, name, priority, &S_sfx[link_id], 0, 0, -1, 0, numchannels, NULL } + +sfxinfo_t S_sfx[] = { + SOUND("", 0, 0), + SOUND("gldhit", 32, 2), + SOUND("gntful", 32, -1), + SOUND("gnthit", 32, -1), + SOUND("gntpow", 32, -1), + SOUND("gntact", 32, -1), + SOUND("gntuse", 32, -1), + SOUND("phosht", 32, 2), + SOUND("phohit", 32, -1), + SOUND_LINK("-phopow", sfx_hedat1, 32, 1), + SOUND("lobsht", 20, 2), + SOUND("lobhit", 20, 2), + SOUND("lobpow", 20, 2), + SOUND("hrnsht", 32, 2), + SOUND("hrnhit", 32, 2), + SOUND("hrnpow", 32, 2), + SOUND("ramphit", 32, 2), + SOUND("ramrain", 10, 2), + SOUND("bowsht", 32, 2), + SOUND("stfhit", 32, 2), + SOUND("stfpow", 32, 2), + SOUND("stfcrk", 32, 2), + SOUND("impsit", 32, 2), + SOUND("impat1", 32, 2), + SOUND("impat2", 32, 2), + SOUND("impdth", 80, 2), + SOUND_LINK("-impact", sfx_impsit, 20, 2), + SOUND("imppai", 32, 2), + SOUND("mumsit", 32, 2), + SOUND("mumat1", 32, 2), + SOUND("mumat2", 32, 2), + SOUND("mumdth", 80, 2), + SOUND_LINK("-mumact", sfx_mumsit, 20, 2), + SOUND("mumpai", 32, 2), + SOUND("mumhed", 32, 2), + SOUND("bstsit", 32, 2), + SOUND("bstatk", 32, 2), + SOUND("bstdth", 80, 2), + SOUND("bstact", 20, 2), + SOUND("bstpai", 32, 2), + SOUND("clksit", 32, 2), + SOUND("clkatk", 32, 2), + SOUND("clkdth", 80, 2), + SOUND("clkact", 20, 2), + SOUND("clkpai", 32, 2), + SOUND("snksit", 32, 2), + SOUND("snkatk", 32, 2), + SOUND("snkdth", 80, 2), + SOUND("snkact", 20, 2), + SOUND("snkpai", 32, 2), + SOUND("kgtsit", 32, 2), + SOUND("kgtatk", 32, 2), + SOUND("kgtat2", 32, 2), + SOUND("kgtdth", 80, 2), + SOUND_LINK("-kgtact", sfx_kgtsit, 20, 2), + SOUND("kgtpai", 32, 2), + SOUND("wizsit", 32, 2), + SOUND("wizatk", 32, 2), + SOUND("wizdth", 80, 2), + SOUND("wizact", 20, 2), + SOUND("wizpai", 32, 2), + SOUND("minsit", 32, 2), + SOUND("minat1", 32, 2), + SOUND("minat2", 32, 2), + SOUND("minat3", 32, 2), + SOUND("mindth", 80, 2), + SOUND("minact", 20, 2), + SOUND("minpai", 32, 2), + SOUND("hedsit", 32, 2), + SOUND("hedat1", 32, 2), + SOUND("hedat2", 32, 2), + SOUND("hedat3", 32, 2), + SOUND("heddth", 80, 2), + SOUND("hedact", 20, 2), + SOUND("hedpai", 32, 2), + SOUND("sorzap", 32, 2), + SOUND("sorrise", 32, 2), + SOUND("sorsit", 200, 2), + SOUND("soratk", 32, 2), + SOUND("soract", 200, 2), + SOUND("sorpai", 200, 2), + SOUND("sordsph", 200, 2), + SOUND("sordexp", 200, 2), + SOUND("sordbon", 200, 2), + SOUND_LINK("-sbtsit", sfx_bstsit, 32, 2), + SOUND_LINK("-sbtatk", sfx_bstatk, 32, 2), + SOUND("sbtdth", 80, 2), + SOUND("sbtact", 20, 2), + SOUND("sbtpai", 32, 2), + SOUND("plroof", 32, 2), + SOUND("plrpai", 32, 2), + SOUND("plrdth", 80, 2), + SOUND("gibdth", 100, 2), + SOUND("plrwdth", 80, 2), + SOUND("plrcdth", 100, 2), + SOUND("itemup", 32, 2), + SOUND("wpnup", 32, 2), + SOUND("telept", 50, 2), + SOUND("doropn", 40, 2), + SOUND("dorcls", 40, 2), + SOUND("dormov", 40, 2), + SOUND("artiup", 32, 2), + SOUND("switch", 40, 2), + SOUND("pstart", 40, 2), + SOUND("pstop", 40, 2), + SOUND("stnmov", 40, 2), + SOUND("chicpai", 32, 2), + SOUND("chicatk", 32, 2), + SOUND("chicdth", 40, 2), + SOUND("chicact", 32, 2), + SOUND("chicpk1", 32, 2), + SOUND("chicpk2", 32, 2), + SOUND("chicpk3", 32, 2), + SOUND("keyup", 50, 2), + SOUND("ripslop", 16, 2), + SOUND("newpod", 16, -1), + SOUND("podexp", 40, -1), + SOUND("bounce", 16, 2), + SOUND_LINK("-volsht", sfx_bstatk, 16, 2), + SOUND_LINK("-volhit", sfx_lobhit, 16, 2), + SOUND("burn", 10, 2), + SOUND("splash", 10, 1), + SOUND("gloop", 10, 2), + SOUND("respawn", 10, 1), + SOUND("blssht", 32, 2), + SOUND("blshit", 32, 2), + SOUND("chat", 100, 1), + SOUND("artiuse", 32, 1), + SOUND("gfrag", 100, 1), + SOUND("waterfl", 16, 2), + + // Monophonic sounds + + SOUND("wind", 16, 1), + SOUND("amb1", 1, 1), + SOUND("amb2", 1, 1), + SOUND("amb3", 1, 1), + SOUND("amb4", 1, 1), + SOUND("amb5", 1, 1), + SOUND("amb6", 1, 1), + SOUND("amb7", 1, 1), + SOUND("amb8", 1, 1), + SOUND("amb9", 1, 1), + SOUND("amb10", 1, 1), + SOUND("amb11", 1, 0) +}; diff --git a/src/heretic/sounds.h b/src/heretic/sounds.h new file mode 100644 index 00000000..e50a6ad6 --- /dev/null +++ b/src/heretic/sounds.h @@ -0,0 +1,299 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1993-2008 Raven Software +// Copyright(C) 2008 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +//----------------------------------------------------------------------------- + +// sounds.h + +#ifndef __SOUNDSH__ +#define __SOUNDSH__ + +#include "i_sound.h" + +#define MAX_SND_DIST 1600 +#define MAX_CHANNELS 16 + +// Music identifiers + +typedef enum +{ + 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_e4m1, + mus_e4m2, + mus_e4m3, + mus_e4m4, + mus_e4m5, + mus_e4m6, + mus_e4m7, + mus_e4m8, + mus_e4m9, + + mus_e5m1, + mus_e5m2, + mus_e5m3, + mus_e5m4, + mus_e5m5, + mus_e5m6, + mus_e5m7, + mus_e5m8, + mus_e5m9, + + mus_e6m1, + mus_e6m2, + mus_e6m3, + + mus_titl, + mus_intr, + mus_cptd, + NUMMUSIC +} musicenum_t; + +#if 0 +typedef struct +{ + char name[8]; +} musicinfo_t; + +typedef struct sfxinfo_s +{ + char name[8]; + struct sfxinfo_s *link; // Make alias for another sound + unsigned short priority; // Higher priority takes precendence + int usefulness; // Determines when a sound should be cached out + void *snd_ptr; + int lumpnum; + int numchannels; // total number of channels a sound type may occupy +} sfxinfo_t; + +#endif + +typedef struct +{ + mobj_t *mo; + int sound_id; + int handle; + int pitch; + int priority; +} channel_t; + +typedef struct +{ + int id; + unsigned short priority; + char *name; + mobj_t *mo; + int distance; +} ChanInfo_t; + +typedef struct +{ + int channelCount; + int musicVolume; + int soundVolume; + ChanInfo_t chan[8]; +} SoundInfo_t; + +// Sound identifiers + +typedef enum +{ + sfx_None, + sfx_gldhit, + sfx_gntful, + sfx_gnthit, + sfx_gntpow, + sfx_gntact, + sfx_gntuse, + sfx_phosht, + sfx_phohit, + sfx_phopow, + sfx_lobsht, + sfx_lobhit, + sfx_lobpow, + sfx_hrnsht, + sfx_hrnhit, + sfx_hrnpow, + sfx_ramphit, + sfx_ramrain, + sfx_bowsht, + sfx_stfhit, + sfx_stfpow, + sfx_stfcrk, + sfx_impsit, + sfx_impat1, + sfx_impat2, + sfx_impdth, + sfx_impact, + sfx_imppai, + sfx_mumsit, + sfx_mumat1, + sfx_mumat2, + sfx_mumdth, + sfx_mumact, + sfx_mumpai, + sfx_mumhed, + sfx_bstsit, + sfx_bstatk, + sfx_bstdth, + sfx_bstact, + sfx_bstpai, + sfx_clksit, + sfx_clkatk, + sfx_clkdth, + sfx_clkact, + sfx_clkpai, + sfx_snksit, + sfx_snkatk, + sfx_snkdth, + sfx_snkact, + sfx_snkpai, + sfx_kgtsit, + sfx_kgtatk, + sfx_kgtat2, + sfx_kgtdth, + sfx_kgtact, + sfx_kgtpai, + sfx_wizsit, + sfx_wizatk, + sfx_wizdth, + sfx_wizact, + sfx_wizpai, + sfx_minsit, + sfx_minat1, + sfx_minat2, + sfx_minat3, + sfx_mindth, + sfx_minact, + sfx_minpai, + sfx_hedsit, + sfx_hedat1, + sfx_hedat2, + sfx_hedat3, + sfx_heddth, + sfx_hedact, + sfx_hedpai, + sfx_sorzap, + sfx_sorrise, + sfx_sorsit, + sfx_soratk, + sfx_soract, + sfx_sorpai, + sfx_sordsph, + sfx_sordexp, + sfx_sordbon, + sfx_sbtsit, + sfx_sbtatk, + sfx_sbtdth, + sfx_sbtact, + sfx_sbtpai, + sfx_plroof, + sfx_plrpai, + sfx_plrdth, // Normal + sfx_gibdth, // Extreme + sfx_plrwdth, // Wimpy + sfx_plrcdth, // Crazy + sfx_itemup, + sfx_wpnup, + sfx_telept, + sfx_doropn, + sfx_dorcls, + sfx_dormov, + sfx_artiup, + sfx_switch, + sfx_pstart, + sfx_pstop, + sfx_stnmov, + sfx_chicpai, + sfx_chicatk, + sfx_chicdth, + sfx_chicact, + sfx_chicpk1, + sfx_chicpk2, + sfx_chicpk3, + sfx_keyup, + sfx_ripslop, + sfx_newpod, + sfx_podexp, + sfx_bounce, + sfx_volsht, + sfx_volhit, + sfx_burn, + sfx_splash, + sfx_gloop, + sfx_respawn, + sfx_blssht, + sfx_blshit, + sfx_chat, + sfx_artiuse, + sfx_gfrag, + sfx_waterfl, + + // Monophonic sounds + + sfx_wind, + sfx_amb1, + sfx_amb2, + sfx_amb3, + sfx_amb4, + sfx_amb5, + sfx_amb6, + sfx_amb7, + sfx_amb8, + sfx_amb9, + sfx_amb10, + sfx_amb11, + NUMSFX +} sfxenum_t; + +extern sfxinfo_t S_sfx[]; +extern musicinfo_t S_music[]; + +#endif |