raze/source/sw/src/game.cpp
Christoph Oelckers cc5e6d19c3 - fixed sound updating by moving the engine's UpdateSound call into the main loop.
This needs to be called unconditionally for every frame being rendered, not all of the game modules did that.
Placing this call here ensures that it is independent of anything the games do.
2020-08-26 04:10:16 +02:00

978 lines
23 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment
This file is part of Shadow Warrior version 1.2
Shadow Warrior 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "ns.h"
#define MAIN
#define QUIET
#include "build.h"
#include "baselayer.h"
#include "baselayer.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "interp.h"
#include "interpso.h"
#include "tags.h"
#include "sector.h"
#include "sprite.h"
#include "weapon.h"
#include "player.h"
#include "lists.h"
#include "network.h"
#include "pal.h"
#include "mytypes.h"
#include "menus.h"
#include "gamecontrol.h"
#include "misc.h"
#include "misc.h"
#include "break.h"
#include "light.h"
#include "misc.h"
#include "jsector.h"
#include "common.h"
#include "gameconfigfile.h"
#include "printf.h"
#include "m_argv.h"
#include "debugbreak.h"
#include "menu.h"
#include "raze_music.h"
#include "statistics.h"
#include "gstrings.h"
#include "mapinfo.h"
#include "v_video.h"
#include "raze_sound.h"
#include "secrets.h"
#include "screenjob.h"
#include "inputstate.h"
#include "gamestate.h"
//#include "crc32.h"
CVAR(Bool, sw_ninjahack, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
CVAR(Bool, sw_darts, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
BEGIN_SW_NS
void Logo(const CompletionFunc& completion);
void StatScreen(int FinishAnim, CompletionFunc completion);
void getinput(SW_PACKET*, SWBOOL);
void pClearSpriteList(PLAYERp pp);
extern int sw_snd_scratch;
int GameVersion = 20;
int Follow_posx=0,Follow_posy=0;
SWBOOL NoMeters = false;
SWBOOL FinishAnim = 0;
SWBOOL ReloadPrompt = false;
SWBOOL NewGame = false;
SWBOOL SavegameLoaded = false;
//Miscellaneous variables
SWBOOL FinishedLevel = false;
short screenpeek = 0;
SWBOOL PedanticMode;
SWBOOL LocationInfo = 0;
void drawoverheadmap(int cposx, int cposy, int czoom, short cang);
SWBOOL PreCaching = TRUE;
int GodMode = false;
short Skill = 2;
short TotalKillable;
const GAME_SET gs_defaults =
{
// Network game settings
0, // GameType
0, // Monsters
false, // HurtTeammate
TRUE, // SpawnMarkers Markers
false, // TeamPlay
0, // Kill Limit
0, // Time Limit
0, // Color
TRUE, // nuke
};
GAME_SET gs;
SWBOOL PlayerTrackingMode = false;
SWBOOL SlowMode = false;
SWBOOL FrameAdvanceTics = 3;
SWBOOL DebugOperate = false;
void LoadingLevelScreen(void);
uint8_t FakeMultiNumPlayers;
int totalsynctics;
MapRecord* NextLevel = nullptr;
SWBOOL ExitLevel = false;
int OrigCommPlayers=0;
extern uint8_t CommPlayers;
extern SWBOOL CommEnabled;
extern int bufferjitter;
SWBOOL CameraTestMode = false;
char ds[645]; // debug string
extern short NormalVisibility;
SWBOOL CommandSetup = false;
char buffer[80], ch;
uint8_t DebugPrintColor = 255;
FString ThemeSongs[6];
int ThemeTrack[6];
/// L O C A L P R O T O T Y P E S /////////////////////////////////////////////////////////
void SybexScreen(void);
/////////////////////////////////////////////////////////////////////////////////////////////
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static const char* actions[] = {
"Move_Forward",
"Move_Backward",
"Turn_Left",
"Turn_Right",
"Strafe",
"Fire",
"Open",
"Run",
"Alt_Fire", // Duke3D", Blood
"Jump",
"Crouch",
"Look_Up",
"Look_Down",
"Look_Left",
"Look_Right",
"Strafe_Left",
"Strafe_Right",
"Aim_Up",
"Aim_Down",
"SendMessage",
"Shrink_Screen",
"Enlarge_Screen",
"Show_Opponents_Weapon",
"See_Coop_View",
"Mouse_Aiming",
"Dpad_Select",
"Dpad_Aiming",
"Last_Weapon",
"Alt_Weapon",
"Third_Person_View",
"Toggle_Crouch", // This is the last one used by EDuke32"",
};
void GameInterface::app_init()
{
GameTicRate = 40;
InitCheats();
buttonMap.SetButtons(actions, NUM_ACTIONS);
automapping = 1;
gs = gs_defaults;
for (int i = 0; i < MAX_SW_PLAYERS; i++)
INITLIST(&Player[i].PanelSpriteList);
DebugOperate = TRUE;
enginecompatibility_mode = ENGINECOMPATIBILITY_19961112;
if (SW_SHAREWARE)
Printf("SHADOW WARRIOR(tm) Version 1.2 (Shareware Version)\n");
else
Printf("SHADOW WARRIOR(tm) Version 1.2\n");
if (sw_snd_scratch == 0) // This is always 0 at this point - this check is only here to prevent whole program optimization from eliminating the variable.
Printf("Copyright (c) 1997 3D Realms Entertainment\n");
registerosdcommands();
registerinputcommands();
engineInit();
auto pal = fileSystem.LoadFile("3drealms.pal", 0);
if (pal.Size() >= 768)
{
for (auto& c : pal)
c <<= 2;
paletteSetColorTable(DREALMSPAL, pal.Data(), true, true);
}
InitPalette();
// sets numplayers, connecthead, connectpoint2, myconnectindex
numplayers = 1; myconnectindex = 0;
connecthead = 0; connectpoint2[0] = -1;
if (SW_SHAREWARE && numplayers > 4)
{
I_FatalError("To play a Network game with more than 4 players you must purchase "
"the full version. Read the Ordering Info screens for details.");
}
TileFiles.LoadArtSet("tiles%03d.art");
InitFonts();
//Connect();
SortBreakInfo();
parallaxtype = 1;
SW_InitMultiPsky();
memset(Track, 0, sizeof(Track));
memset(Player, 0, sizeof(Player));
for (int i = 0; i < MAX_SW_PLAYERS; i++)
INITLIST(&Player[i].PanelSpriteList);
LoadKVXFromScript("swvoxfil.txt"); // Load voxels from script file
LoadPLockFromScript("swplock.txt"); // Get Parental Lock setup info
LoadCustomInfoFromScript("engine/swcustom.txt"); // load the internal definitions. These also apply to the shareware version.
if (!SW_SHAREWARE)
LoadCustomInfoFromScript("swcustom.txt"); // Load user customisation information
if (!loaddefinitionsfile(G_DefFile())) Printf("Definitions file loaded.\n");
userConfig.AddDefs.reset();
enginePostInit();
videoInit();
InitFX();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void StartMenu()
{
M_StartControlPanel(false);
if (SW_SHAREWARE && FinishAnim)
{
// go to ordering menu only if shareware
M_SetMenu(NAME_CreditsMenu);
}
else
{
M_SetMenu(NAME_Mainmenu);
}
FinishAnim = 0;
gamestate = GS_MENUSCREEN;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void DrawMenuLevelScreen(void)
{
const int TITLE_PIC = 2324;
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(TITLE_PIC), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal,
DTA_Color, shadeToLight(20), TAG_DONE);
if (CommEnabled)
{
MNU_DrawString(160, 170, "Lo Wang is waiting for other players...", 1, 16, 0);
MNU_DrawString(160, 180, "They are afraid!", 1, 16, 0);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitLevelGlobals(void)
{
ChopTics = 0;
automapMode = am_off;
zoom = 768;
PlayerGravity = 24;
wait_active_check_offset = 0;
PlaxCeilGlobZadjust = PlaxFloorGlobZadjust = Z(500);
FinishedLevel = false;
AnimCnt = 0;
left_foot = false;
screenpeek = myconnectindex;
numinterpolations = short_numinterpolations = 0;
gNet.TimeLimitClock = gNet.TimeLimit;
serpwasseen = false;
sumowasseen = false;
zillawasseen = false;
memset(BossSpriteNum,-1,sizeof(BossSpriteNum));
PedanticMode = false;
}
//---------------------------------------------------------------------------
//
// GLOBAL RESETS NOT DONE for LOAD GAME
//
//---------------------------------------------------------------------------
void InitLevelGlobals2(void)
{
InitTimingVars();
TotalKillable = 0;
Bunny_Count = 0;
FinishAnim = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitLevel(void)
{
Terminate3DSounds();
// A few IMPORTANT GLOBAL RESETS
InitLevelGlobals();
Mus_Stop();
auto maprec = NextLevel;
NextLevel = nullptr;
if (!maprec) maprec = currentLevel;
if (!maprec)
{
I_Error("Attempt to start game without level");
return;
}
InitLevelGlobals2();
if (NewGame)
{
for (int i = 0; i < MAX_SW_PLAYERS; i++)
{
// don't jack with the playerreadyflag
int ready_bak = Player[i].playerreadyflag;
int ver_bak = Player[i].PlayerVersion;
memset(&Player[i], 0, sizeof(Player[i]));
Player[i].playerreadyflag = ready_bak;
Player[i].PlayerVersion = ver_bak;
INITLIST(&Player[i].PanelSpriteList);
}
memset(puser, 0, sizeof(puser));
}
int16_t ang;
if (engineLoadBoard(maprec->fileName, SW_SHAREWARE ? 1 : 0, (vec3_t*)&Player[0], &ang, &Player[0].cursectnum) == -1)
{
I_Error("Map not found: %s", maprec->fileName.GetChars());
}
currentLevel = maprec;
SECRET_SetMapName(currentLevel->DisplayName(), currentLevel->name);
STAT_NewLevel(currentLevel->fileName);
Player[0].q16ang = fix16_from_int(ang);
SetupPreCache();
if (sector[0].extra != -1)
{
NormalVisibility = g_visibility = sector[0].extra;
sector[0].extra = 0;
}
else
NormalVisibility = g_visibility;
//
// Do Player stuff first
//
InitAllPlayers();
QueueReset();
PreMapCombineFloors();
InitMultiPlayerInfo();
InitAllPlayerSprites();
//
// Do setup for sprite, track, panel, sector, etc
//
// Set levels up
InitTimingVars();
SpriteSetup();
SpriteSetupPost(); // post processing - already gone once through the loop
InitLighting();
TrackSetup();
PlayerPanelSetup();
SectorSetup();
JS_InitMirrors();
JS_InitLockouts(); // Setup the lockout linked lists
JS_ToggleLockouts(); // Init lockouts on/off
PlaceSectorObjectsOnTracks();
PlaceActorsOnTracks();
PostSetupSectorObject();
SetupMirrorTiles();
initlava();
// reset NewGame
NewGame = false;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitPlayerGameSettings(void)
{
int pnum;
if (CommEnabled)
{
// everyone gets the same Auto Aim
TRAVERSE_CONNECT(pnum)
{
if (gNet.AutoAim)
SET(Player[pnum].Flags, PF_AUTO_AIM);
else
RESET(Player[pnum].Flags, PF_AUTO_AIM);
}
}
else
{
if (cl_autoaim)
SET(Player[myconnectindex].Flags, PF_AUTO_AIM);
else
RESET(Player[myconnectindex].Flags, PF_AUTO_AIM);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitRunLevel(void)
{
//SendVersion(GameVersion);
//waitforeverybody();
Mus_Stop();
DoTheCache();
// auto aim / auto run / etc
InitPlayerGameSettings();
// send packets with player info
InitNetPlayerOptions();
// Initialize Game part of network code (When ready2send != 0)
InitNetVars();
if (currentLevel)
{
PlaySong(currentLevel->labelName, currentLevel->music, currentLevel->cdSongId);
}
InitPrediction(&Player[myconnectindex]);
waitforeverybody();
//CheckVersion(GameVersion);
// IMPORTANT - MUST be right before game loop AFTER waitforeverybody
InitTimingVars();
if (snd_ambience)
StartAmbientSound();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void TerminateLevel(void)
{
videoFadePalette(0, 0, 0, 0);
if (!currentLevel) return;
int i, nexti, stat, pnum, ndx;
SECT_USERp* sectu;
// Free any track points
for (ndx = 0; ndx < MAX_TRACKS; ndx++)
{
if (Track[ndx].TrackPoint)
{
FreeMem(Track[ndx].TrackPoint);
// !JIM! I added null assigner
Track[ndx].TrackPoint = NULL;
}
}
// Clear the tracks
memset(Track, 0, sizeof(Track));
StopFX();
// Clear all anims and any memory associated with them
// Clear before killing sprites - save a little time
//AnimClear();
for (stat = STAT_PLAYER0; stat < STAT_PLAYER0 + numplayers; stat++)
{
pnum = stat - STAT_PLAYER0;
TRAVERSE_SPRITE_STAT(headspritestat[stat], i, nexti)
{
if (User[i])
memcpy(&puser[pnum], User[i], sizeof(USER));
}
}
// Kill User memory and delete sprites
// for (stat = 0; stat < STAT_ALL; stat++)
for (stat = 0; stat < MAXSTATUS; stat++)
{
TRAVERSE_SPRITE_STAT(headspritestat[stat], i, nexti)
{
KillSprite(i);
}
}
// Free SectUser memory
for (sectu = &SectUser[0];
sectu < &SectUser[MAXSECTORS];
sectu++)
{
if (*sectu)
{
FreeMem(*sectu);
*sectu = NULL;
}
}
//memset(&User[0], 0, sizeof(User));
memset(&SectUser[0], 0, sizeof(SectUser));
TRAVERSE_CONNECT(pnum)
{
PLAYERp pp = Player + pnum;
// Free panel sprites for players
pClearSpriteList(pp);
pp->cookieTime = 0;
memset(pp->cookieQuote, 0, sizeof(pp->cookieQuote));
pp->DoPlayerAction = NULL;
pp->SpriteP = NULL;
pp->PlayerSprite = -1;
pp->UnderSpriteP = NULL;
pp->PlayerUnderSprite = -1;
memset(pp->HasKey, 0, sizeof(pp->HasKey));
//pp->WpnFlags = 0;
pp->CurWpn = NULL;
memset(pp->Wpn, 0, sizeof(pp->Wpn));
memset(pp->InventoryTics, 0, sizeof(pp->InventoryTics));
pp->Killer = -1;
INITLIST(&pp->PanelSpriteList);
}
JS_UnInitLockouts();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void MoveTicker(void)
{
int pnum;
//getpackets();
if (PredictionOn && CommEnabled)
{
while (predictmovefifoplc < Player[myconnectindex].movefifoend)
{
DoPrediction(ppp);
}
}
//While you have new input packets to process...
if (!CommEnabled)
bufferjitter = 0;
while (Player[myconnectindex].movefifoend - movefifoplc > bufferjitter)
{
//Make sure you have at least 1 packet from everyone else
for (pnum = connecthead; pnum >= 0; pnum = connectpoint2[pnum])
{
if (movefifoplc == Player[pnum].movefifoend)
{
break;
}
}
//Pnum is >= 0 only if last loop was broken, meaning a player wasn't caught up
if (pnum >= 0)
break;
domovethings();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void EndOfLevel()
{
STAT_Update(false);
// for good measure do this
ready2send = 0;
waitforeverybody();
if (FinishedLevel)
{
//ResetPalette(mpp);
FinishedLevel = false;
COVER_SetReverb(0); // Reset reverb
Player[myconnectindex].Reverb = 0;
StopSound();
// NextLevel must be null while the intermission is running, but we still need the value for later
auto localNextLevel = NextLevel;
NextLevel = nullptr;
if (FinishAnim == ANIM_SUMO && localNextLevel == nullptr) // next level hasn't been set for this.
localNextLevel = FindMapByLevelNum(currentLevel->levelNumber + 1);
StatScreen(FinishAnim, [=](bool)
{
NextLevel = localNextLevel;
TerminateLevel();
if (NextLevel == nullptr)
{
STAT_Update(true);
PlaySong(nullptr, ThemeSongs[0], ThemeTrack[0]);
StartMenu();
}
else gamestate = GS_LEVEL;
});
}
else
{
TerminateLevel();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameTicker(void)
{
if (!ExitLevel)
{
if (SavegameLoaded)
{
InitLevelGlobals();
SavegameLoaded = false;
// contains what is needed from calls below
if (snd_ambience)
StartAmbientSound();
// crappy little hack to prevent play clock from being overwritten
// for load games
int SavePlayClock = PlayClock;
InitTimingVars();
PlayClock = SavePlayClock;
ExitLevel = false;
}
else if (NextLevel)
{
InitLevel();
InitRunLevel();
ExitLevel = false;
}
ready2send = 1;
int const currentTic = I_GetTime();
gameclock = I_GetBuildTime();
if (paused)
{
buttonMap.ResetButtonStates();
smoothratio = MaxSmoothRatio;
}
else
{
while (ready2send && currentTic - lastTic >= 1)
{
lastTic = currentTic;
ogameclock = gameclock;
UpdateInputs();
MoveTicker();
}
smoothratio = I_GetTimeFrac() * MaxSmoothRatio;
// Get input again to update q16ang/q16horiz.
if (!PedanticMode)
getinput(&loc, TRUE);
}
drawscreen(Player + screenpeek, smoothratio);
ready2send = 0;
}
if (ExitLevel)
{
ExitLevel = false;
EndOfLevel();
}
}
void GameInterface::RunGameFrame()
{
try
{
// if the menu initiazed a new game or loaded a savegame, switch to play mode.
if (SavegameLoaded || NextLevel) gamestate = GS_LEVEL;
handleevents();
updatePauseStatus();
D_ProcessEvents();
DoUpdateSounds();
switch (gamestate)
{
default:
case GS_STARTUP:
I_ResetTime();
lastTic = -1;
ogameclock = gameclock = 0;
if (userConfig.CommandMap.IsNotEmpty())
{
}
else
{
if (!userConfig.nologo) Logo([](bool) { StartMenu(); });
else StartMenu();
}
break;
case GS_MENUSCREEN:
case GS_FULLCONSOLE:
DrawMenuLevelScreen();
break;
case GS_LEVEL:
GameTicker();
break;
case GS_INTERMISSION:
case GS_INTRO:
RunScreenJobFrame(); // This handles continuation through its completion callback.
break;
}
}
catch (CRecoverableError&)
{
// Make sure we do not leave the game in an unstable state
TerminateLevel();
NextLevel = nullptr;
SavegameLoaded = false;
ExitLevel = false;
FinishAnim = 0;
FinishedLevel = false;
throw;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int RandomRange(int range)
{
uint32_t rand_num;
uint32_t value;
if (range <= 0)
return 0;
rand_num = RANDOM();
if (rand_num == 65535U)
rand_num--;
// shift values to give more precision
value = (rand_num << 14) / ((65535UL << 14) / range);
if (value >= (uint32_t)range)
value = range - 1;
return value;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int StdRandomRange(int range)
{
uint32_t rand_num;
uint32_t value;
if (range <= 0)
return 0;
rand_num = STD_RANDOM();
if (rand_num == RAND_MAX)
rand_num--;
// shift values to give more precision
#if (RAND_MAX > 0x7fff)
value = rand_num / (((int)RAND_MAX) / range);
#else
value = (rand_num << 14) / ((((int)RAND_MAX) << 14) / range);
#endif
if (value >= (uint32_t)range)
value = range - 1;
return value;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
#include "saveable.h"
saveable_module saveable_build{};
void Saveable_Init_Dynamic()
{
static saveable_data saveable_build_data[] =
{
{sector, MAXSECTORS*sizeof(sectortype)},
{sprite, MAXSPRITES*sizeof(spritetype)},
{wall, MAXWALLS*sizeof(walltype)},
};
saveable_build.data = saveable_build_data;
saveable_build.numdata = NUM_SAVEABLE_ITEMS(saveable_build_data);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
ReservedSpace GameInterface::GetReservedScreenSpace(int viewsize)
{
return { 0, 48 };
}
::GameInterface* CreateInterface()
{
return new GameInterface;
}
GameStats GameInterface::getStats()
{
PLAYERp pp = Player + myconnectindex;
return { pp->Kills, TotalKillable, pp->SecretsFound, LevelSecrets, PlayClock / 120, 0 };
}
void GameInterface::FreeGameData()
{
TerminateLevel();
}
END_SW_NS