//------------------------------------------------------------------------- /* 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 SpawnActors(BloodSpawnSpriteDef& sprites) { TArray 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