raze/source/games/blood/src/blood.cpp
2022-10-22 12:17:04 +02:00

793 lines
18 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
This file is part of NBlood.
NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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.
*/
//-------------------------------------------------------------------------
#include "ns.h" // Must come before everything else!
#include "build.h"
#include "g_input.h"
#include "automap.h"
#include "blood.h"
#include "choke.h"
#include "view.h"
#include "misc.h"
#include "gameconfigfile.h"
#include "gamecontrol.h"
#include "m_argv.h"
#include "statistics.h"
#include "razemenu.h"
#include "raze_sound.h"
#include "secrets.h"
#include "gamestate.h"
#include "screenjob_.h"
#include "mapinfo.h"
#include "d_net.h"
#include "v_video.h"
#include "v_draw.h"
#include "texturemanager.h"
#include "statusbar.h"
#include "vm.h"
BEGIN_BLD_NS
IMPLEMENT_CLASS(DBloodActor, false, true)
IMPLEMENT_POINTERS_START(DBloodActor)
IMPLEMENT_POINTER(prevmarker)
IMPLEMENT_POINTER(ownerActor)
IMPLEMENT_POINTER(genDudeExtra.pLifeLeech)
IMPLEMENT_POINTER(genDudeExtra.slave[0])
IMPLEMENT_POINTER(genDudeExtra.slave[1])
IMPLEMENT_POINTER(genDudeExtra.slave[2])
IMPLEMENT_POINTER(genDudeExtra.slave[3])
IMPLEMENT_POINTER(genDudeExtra.slave[4])
IMPLEMENT_POINTER(genDudeExtra.slave[5])
IMPLEMENT_POINTER(genDudeExtra.slave[6])
IMPLEMENT_POINTER(xspr.burnSource)
IMPLEMENT_POINTER(xspr.target)
IMPLEMENT_POINTERS_END
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void MarkSprInSect();
size_t DBloodActor::PropagateMark()
{
if (hit.hit.type == kHitSprite) GC::Mark(hit.hit.hitActor);
if (hit.ceilhit.type == kHitSprite) GC::Mark(hit.ceilhit.hitActor);
if (hit.florhit.type == kHitSprite) GC::Mark(hit.florhit.hitActor);
condition[0].Mark();
condition[1].Mark();
return Super::PropagateMark();
}
static void markgcroots()
{
GC::MarkArray(gProxySpritesList, gProxySpritesCount);
GC::MarkArray(gSightSpritesList, gSightSpritesCount);
GC::MarkArray(gPhysSpritesList, gPhysSpritesCount);
GC::MarkArray(gImpactSpritesList, gImpactSpritesCount);
MarkSprInSect();
for (auto& pl : gPlayer)
{
GC::Mark(pl.actor);
GC::MarkArray(pl.ctfFlagState, 2);
GC::Mark(pl.aimTarget);
GC::MarkArray(pl.aimTargets, 16);
GC::Mark(pl.fragger);
GC::Mark(pl.voodooTarget);
}
}
void InitCheats();
bool bNoDemo = false;
int gNetPlayers;
int gChokeCounter = 0;
int blood_globalflags;
PLAYER gPlayerTemp[kMaxPlayers];
int gHealthTemp[kMaxPlayers];
int16_t startang;
sectortype* startsector;
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void QuitGame(void)
{
throw CExitEvent(0);
}
void EndLevel(void)
{
gViewPos = VIEWPOS_0;
sndKillAllSounds();
sfxKillAllSounds();
ambKillAll();
seqKillAll();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
TArray<DBloodActor*> SpawnActors(BloodSpawnSpriteDef& sprites)
{
TArray<DBloodActor*> spawns(sprites.sprites.Size(), true);
InitSpriteLists();
int j = 0;
for (unsigned i = 0; i < sprites.sprites.Size(); i++)
{
if (sprites.sprites[i].statnum == MAXSTATUS)
{
spawns.Pop();
continue;
}
auto sprt = &sprites.sprites[i];
auto actor = InsertSprite(sprt->sectp, sprt->statnum);
spawns[j++] = actor;
actor->time = i;
actor->spr = sprites.sprites[i];
if (sprites.sprext.Size()) actor->sprext = sprites.sprext[i];
else actor->sprext = {};
actor->spsmooth = {};
if (sprites.sprites[i].extra > 0)
{
actor->addX();
actor->xspr = sprites.xspr[i];
}
}
leveltimer = sprites.sprites.Size();
return spawns;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PropagateMarkerReferences(void)
{
BloodStatIterator it(kStatMarker);
while (auto actor = it.Next())
{
switch (actor->spr.type)
{
case kMarkerOff:
case kMarkerAxis:
case kMarkerWarpDest:
{
int nOwner = actor->spr.intowner;
if (validSectorIndex(nOwner))
{
if (sector[nOwner].hasX())
{
sector[nOwner].xs().marker0 = actor;
continue;
}
}
}
break;
case kMarkerOn:
{
int nOwner = actor->spr.intowner;
if (validSectorIndex(nOwner))
{
if (sector[nOwner].hasX())
{
sector[nOwner].xs().marker1 = actor;
continue;
}
}
}
break;
}
DeleteSprite(actor);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void StartLevel(MapRecord* level, bool newgame)
{
if (!level) return;
gFrameCount = 0;
PlayClock = 0;
inputState.ClearAllInput();
currentLevel = level;
if (gGameOptions.nGameType == 0)
{
///////
gGameOptions.weaponsV10x = cl_bloodoldweapbalance;
///////
}
#if 0
else if (gGameOptions.nGameType > 0 && newgame)
{
// todo
gBlueFlagDropped = false;
gRedFlagDropped = false;
}
#endif
//drawLoadingScreen();
BloodSpawnSpriteDef sprites;
DVector3 startpos;
dbLoadMap(currentLevel->fileName, startpos, &startang, &startsector, nullptr, sprites);
SECRET_SetMapName(currentLevel->DisplayName(), currentLevel->name);
STAT_NewLevel(currentLevel->fileName);
TITLE_InformName(currentLevel->name);
wsrand(dbReadMapCRC(currentLevel->LabelName()));
gKillMgr.Clear();
gSecretMgr.Clear();
automapping = 1;
// Here is where later the actors must be spawned.
auto actorlist = SpawnActors(sprites);
PropagateMarkerReferences();
int modernTypesErased = 0;
for (auto actor : actorlist)
{
if (actor->exists() && actor->hasX())
{
if ((actor->xspr.lSkill & (1 << gGameOptions.nDifficulty)) || (actor->xspr.lS && gGameOptions.nGameType == 0)
|| (actor->xspr.lB && gGameOptions.nGameType == 2) || (actor->xspr.lT && gGameOptions.nGameType == 3)
|| (actor->xspr.lC && gGameOptions.nGameType == 1)) {
DeleteSprite(actor);
continue;
}
#ifdef NOONE_EXTENSIONS
if (!gModernMap && nnExtEraseModernStuff(actor))
modernTypesErased++;
#endif
}
}
#ifdef NOONE_EXTENSIONS
if (!gModernMap && modernTypesErased > 0)
Printf(PRINT_NONOTIFY, "> Modern types erased: %d.\n", modernTypesErased);
#endif
startpos.Z = getflorzofslopeptr(startsector, startpos.X, startpos.Y);
for (int i = 0; i < kMaxPlayers; i++) {
gStartZone[i].pos = startpos;
gStartZone[i].sector = startsector;
gStartZone[i].angle = DAngle::fromBuild(startang);
#ifdef NOONE_EXTENSIONS
// Create spawn zones for players in teams mode.
if (gModernMap && i <= kMaxPlayers / 2) {
gStartZoneTeam1[i].pos = startpos;
gStartZoneTeam1[i].sector = startsector;
gStartZoneTeam1[i].angle = DAngle::fromBuild(startang);
gStartZoneTeam2[i].pos = startpos;
gStartZoneTeam2[i].sector = startsector;
gStartZoneTeam2[i].angle = DAngle::fromBuild(startang);
}
#endif
}
InitSectorFX();
warpInit(actorlist);
actInit(actorlist);
evInit(actorlist);
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
if (newgame)
{
playerInit(i, 0);
}
playerStart(i, 1);
}
if (!newgame)
{
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
PLAYER* pPlayer = &gPlayer[i];
pPlayer->actor->xspr.health &= 0xf000;
pPlayer->actor->xspr.health |= gHealthTemp[i];
pPlayer->weaponQav = gPlayerTemp[i].weaponQav;
pPlayer->curWeapon = gPlayerTemp[i].curWeapon;
pPlayer->weaponState = gPlayerTemp[i].weaponState;
pPlayer->weaponAmmo = gPlayerTemp[i].weaponAmmo;
pPlayer->qavCallback = gPlayerTemp[i].qavCallback;
pPlayer->qavLoop = gPlayerTemp[i].qavLoop;
pPlayer->weaponTimer = gPlayerTemp[i].weaponTimer;
pPlayer->nextWeapon = gPlayerTemp[i].nextWeapon;
pPlayer->qavLastTick = gPlayerTemp[i].qavLastTick;
pPlayer->qavTimer = gPlayerTemp[i].qavTimer;
}
}
PreloadCache();
InitMirrors();
trInit(actorlist);
if (!gPlayer[myconnectindex].packSlots[1].isActive) // if diving suit is not active, turn off reverb sound effect
sfxSetReverb(0);
ambInit();
Net_ClearFifo();
gChokeCounter = 0;
M_ClearMenus();
// viewSetMessage("");
viewSetErrorMessage("");
paused = 0;
levelTryPlayMusic();
gChoke.reset();
setLevelStarted(level);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void NewLevel(MapRecord *sng, int skill, bool newgame)
{
if (skill != -1) gGameOptions.nDifficulty = skill;
gSkill = gGameOptions.nDifficulty;
StartLevel(sng, newgame);
gameaction = ga_level;
}
void GameInterface::NewGame(MapRecord *sng, int skill, bool)
{
gGameOptions.uGameFlags = 0;
cheatReset();
NewLevel(sng, skill, true);
}
void GameInterface::NextLevel(MapRecord *map, int skill)
{
NewLevel(map, skill, false);
}
int GameInterface::GetCurrentSkill()
{
return gGameOptions.nDifficulty;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::Ticker()
{
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
auto& inp = gPlayer[i].input;
auto oldactions = inp.actions;
inp = playercmds[i].ucmd;
inp.actions |= oldactions & ~(SB_BUTTON_MASK | SB_RUN | SB_WEAPONMASK_BITS); // should be everything non-button and non-weapon
int newweap = inp.getNewWeapon();
if (newweap > 0 && newweap < WeaponSel_MaxBlood) gPlayer[i].newWeapon = newweap;
}
BloodSpriteIterator it;
while (DBloodActor* act = it.Next()) act->interpolated = false;
ClearMovementInterpolations();
UpdateInterpolations();
if (!(paused || (gGameOptions.nGameType == 0 && M_Active())))
{
thinktime.Reset();
thinktime.Clock();
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
viewBackupView(i);
playerProcess(&gPlayer[i]);
}
PLAYER* pPlayer = &gPlayer[myconnectindex];
trProcessBusy();
evProcess(PlayClock);
seqProcess(4);
DoSectorPanning();
actortime.Reset();
actortime.Clock();
actProcessSprites();
actPostProcess();
actortime.Unclock();
viewCorrectPrediction();
ambProcess(pPlayer);
viewUpdateDelirium(pPlayer);
gi->UpdateSounds();
if (pPlayer->hand == 1)
{
const int CHOKERATE = 8;
const int COUNTRATE = 30;
gChokeCounter += CHOKERATE;
while (gChokeCounter >= COUNTRATE)
{
gChoke.callback(pPlayer);
gChokeCounter -= COUNTRATE;
}
}
thinktime.Unclock();
gFrameCount++;
PlayClock += kTicsPerFrame;
if (PlayClock == 8) gameaction = ga_autosave; // let the game run for 1 frame before saving.
for (int i = 0; i < 8; i++)
{
team_ticker[i] -= 4;
if (team_ticker[i] < 0)
team_ticker[i] = 0;
}
if (gGameOptions.uGameFlags & GF_AdvanceLevel)
{
gGameOptions.uGameFlags &= ~GF_AdvanceLevel;
seqKillAll();
STAT_Update(gNextLevel == nullptr);
CompleteLevel(gNextLevel);
}
r_NoInterpolate = false;
}
else r_NoInterpolate = true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::DrawBackground()
{
twod->ClearScreen();
auto tex = TexMan.CheckForTexture("titlescreen", ETextureType::Any);
DrawTexture(twod, TexMan.GetGameTexture(tex), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
}
#define x(a, b) registerName(#a, b);
static void SetTileNames()
{
auto registerName = [](const char* name, int index)
{
TexMan.AddAlias(name, tileGetTexture(index));
TileFiles.addName(name, index);
};
#include "namelist.h"
// Oh Joy! Plasma Pak changes the tile number of the title screen, but we preferably want mods that use the original one to display it.
// So let's make this remapping depend on the CRC.
if (tileGetCRC32(2518) == 1170870757 && (tileGetCRC32(2046) != 290208654 || tileWidth(2518) == 0)) registerName("titlescreen", 2046);
else registerName("titlescreen", 2518);
}
#undef x
void ReadAllRFS();
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::loadPalette(void)
{
// in nearly typical Blood fashion it had to use an inverse of the original translucency settings...
static glblend_t const bloodglblend =
{
{
{ 1.f / 3.f, STYLEALPHA_Src, STYLEALPHA_InvSrc, 0 },
{ 2.f / 3.f, STYLEALPHA_Src, STYLEALPHA_InvSrc, 0 },
},
};
static const char* PLU[15] = {
"NORMAL.PLU",
"SATURATE.PLU",
"BEAST.PLU",
"TOMMY.PLU",
"SPIDER3.PLU",
"GRAY.PLU",
"GRAYISH.PLU",
"SPIDER1.PLU",
"SPIDER2.PLU",
"FLAME.PLU",
"COLD.PLU",
"P1.PLU",
"P2.PLU",
"P3.PLU",
"P4.PLU"
};
static const char* PAL[5] = {
"BLOOD.PAL",
"WATER.PAL",
"BEAST.PAL",
"SEWER.PAL",
"INVULN1.PAL"
};
for (auto& x : glblend) x = bloodglblend;
for (int i = 0; i < 5; i++)
{
auto pal = fileSystem.LoadFile(PAL[i]);
if (pal.Size() < 768) I_FatalError("%s: file too small", PAL[i]);
paletteSetColorTable(i, pal.Data(), false, false);
}
numshades = 64;
for (int i = 0; i < MAXPALOOKUPS; i++)
{
int lump = i < 15 ? fileSystem.FindFile(PLU[i]) : fileSystem.FindResource(i, "PLU");
if (lump < 0)
{
if (i < 15) I_FatalError("%s: file not found", PLU[i]);
else continue;
}
auto data = fileSystem.GetFileData(lump);
if (data.Size() != 64 * 256)
{
if (i < 15) I_FatalError("%s: Incorrect PLU size", PLU[i]);
else continue;
}
lookups.setTable(i, data.Data());
}
lookups.setFadeColor(1, 255, 255, 255);
paletteloaded = PALETTE_SHADE | PALETTE_TRANSLUC | PALETTE_MAIN;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::app_init()
{
GC::AddMarkerFunc(markgcroots);
InitCheats();
memcpy(&gGameOptions, &gSingleGameOptions, sizeof(GAMEOPTIONS));
gGameOptions.nMonsterSettings = !userConfig.nomonsters;
ReadAllRFS();
levelLoadDefaults();
//---------
SetTileNames();
C_InitConback(TexMan.CheckForTexture("BACKTILE", ETextureType::Any), true, 0.25);
Printf(PRINT_NONOTIFY, "Initializing view subsystem\n");
viewInit();
Printf(PRINT_NONOTIFY, "Initializing dynamic fire\n");
FireInit();
Printf(PRINT_NONOTIFY, "Initializing weapon animations\n");
WeaponInit();
Printf(PRINT_NONOTIFY, "Initializing sound system\n");
sndInit();
myconnectindex = connecthead = 0;
gNetPlayers = numplayers = 1;
connectpoint2[0] = -1;
gGameOptions.nGameType = 0;
UpdateNetworkMenus();
gChoke.init(518, chokeCallback);
UpdateDacs(0, true);
enginecompatibility_mode = ENGINECOMPATIBILITY_19960925;
gViewIndex = myconnectindex;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void gameInit()
{
gViewIndex = myconnectindex;
UpdateNetworkMenus();
if (gGameOptions.nGameType > 0)
{
inputState.ClearAllInput();
}
}
void GameInterface::Startup()
{
gameInit();
PlayLogos(ga_mainmenu, ga_mainmenu, true);
}
void GameInterface::Render()
{
drawtime.Reset();
drawtime.Clock();
viewDrawScreen();
drawtime.Unclock();
}
void sndPlaySpecialMusicOrNothing(int nMusic)
{
if (!Mus_Play(quoteMgr.GetQuote(nMusic), true))
{
Mus_Stop();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
extern IniFile* BloodINI;
void GameInterface::FreeGameData()
{
if (BloodINI) delete BloodINI;
}
void GameInterface::FreeLevelData()
{
EndLevel();
::GameInterface::FreeLevelData();
}
ReservedSpace GameInterface::GetReservedScreenSpace(int viewsize)
{
int top = 0;
if (gGameOptions.nGameType > 0 && gGameOptions.nGameType <= 3)
{
top = (tileHeight(2229) * ((gNetPlayers + 3) / 4));
}
return { top, 25 };
}
::GameInterface* CreateInterface()
{
return new GameInterface;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
enum
{
kLoadScreenCRC = -2051908571,
kLoadScreenWideBackWidth = 256,
kLoadScreenWideSideWidth = 128,
};
DEFINE_ACTION_FUNCTION(_Blood, OriginalLoadScreen)
{
static int bLoadScreenCrcMatch = -1;
if (bLoadScreenCrcMatch == -1) bLoadScreenCrcMatch = tileGetCRC32(kLoadScreen) == kLoadScreenCRC;
ACTION_RETURN_INT(bLoadScreenCrcMatch);
}
DEFINE_ACTION_FUNCTION(_Blood, PlayIntroMusic)
{
Mus_Play("PESTIS.MID", false);
return 0;
}
DEFINE_ACTION_FUNCTION(_Blood, sndStartSample)
{
PARAM_PROLOGUE;
PARAM_INT(id);
PARAM_INT(vol);
PARAM_INT(chan);
PARAM_BOOL(looped);
PARAM_INT(chanflags);
sndStartSample(id, vol, chan, looped, EChanFlags::FromInt(chanflags));
return 0;
}
DEFINE_ACTION_FUNCTION(_Blood, sndStartSampleNamed)
{
PARAM_PROLOGUE;
PARAM_STRING(id);
PARAM_INT(vol);
PARAM_INT(chan);
sndStartSample(id, vol, chan);
return 0;
}
DEFINE_ACTION_FUNCTION(_Blood, PowerupIcon)
{
PARAM_PROLOGUE;
PARAM_INT(pwup);
int tile = -1;
if (pwup >= 0 && pwup < (int)countof(gPowerUpInfo))
{
tile = gPowerUpInfo[pwup].picnum;
}
FGameTexture* tex = tileGetTexture(tile);
ACTION_RETURN_INT(tex ? tex->GetID().GetIndex() : -1);
}
DEFINE_ACTION_FUNCTION(_Blood, GetViewPlayer)
{
PARAM_PROLOGUE;
ACTION_RETURN_POINTER(&gPlayer[gViewIndex]);
}
DEFINE_ACTION_FUNCTION(_BloodPlayer, GetHealth)
{
PARAM_SELF_STRUCT_PROLOGUE(PLAYER);
ACTION_RETURN_INT(self->actor->xspr.health);
}
DEFINE_ACTION_FUNCTION_NATIVE(_BloodPlayer, powerupCheck, powerupCheck)
{
PARAM_SELF_STRUCT_PROLOGUE(PLAYER);
PARAM_INT(pwup);
ACTION_RETURN_INT(powerupCheck(self, pwup));
}
END_BLD_NS