diff --git a/source/core/g_mapinfo.cpp b/source/core/g_mapinfo.cpp index 95b6729e9..4759a6844 100644 --- a/source/core/g_mapinfo.cpp +++ b/source/core/g_mapinfo.cpp @@ -47,6 +47,7 @@ #include "gamecontrol.h" #include "coreactor.h" #include "texinfo.h" +#include "serializer_raze.h" #include "buildtiles.h" @@ -1815,3 +1816,4 @@ void G_ParseMapInfo () I_FatalError ("No volumes defined."); } } + diff --git a/source/core/mapinfo.cpp b/source/core/mapinfo.cpp index 193188f30..d35a47037 100644 --- a/source/core/mapinfo.cpp +++ b/source/core/mapinfo.cpp @@ -50,8 +50,8 @@ TArray clusters; TArray volumes; TArray> mapList; // must be allocated as pointers because it can whack the currentlLevel pointer if this was a flat array. static TMap musicReplacements; -MapRecord *currentLevel; // level that is currently played. -MapRecord* lastLevel; // Same here, for the last level. +MapRecord* currentLevel; // level that is currently played. +MapLocals Level; CCMD(listmaps) diff --git a/source/core/mapinfo.h b/source/core/mapinfo.h index 5b8f8d89d..addf8660a 100644 --- a/source/core/mapinfo.h +++ b/source/core/mapinfo.h @@ -7,6 +7,7 @@ #include "vectors.h" #include "screenjob.h" #include "maptypes.h" +#include "d_net.h" #ifdef GetMessage #undef GetMessage // Windows strikes... @@ -189,6 +190,35 @@ struct MapRecord } }; +struct StatRecord +{ + int max; + int got; + int player[MAXPLAYERS]; + + void addTotal(int amount = 1) + { + max += amount; + if (amount < 0 && max < 0) max = 0; + } + + void add(int playerno, int amount = 1) + { + got += amount; + if (amount < 0 && got < 0) got = 0; + if (playerno >= 0 && playerno < MAXPLAYERS) + { + player[playerno] += amount; + if (amount < 0 && player[playerno] < 0) player[playerno] = 0; + } + } + + void clear() + { + memset(this, 0, sizeof(*this)); + } +}; + struct SummaryInfo { int kills; @@ -203,8 +233,69 @@ struct SummaryInfo bool endofgame; }; +struct MapLocals +{ + StatRecord kills, secrets, superSecrets; + + void fillSummary(SummaryInfo& sum) + { + sum.kills = kills.got; + sum.maxkills = kills.max; + sum.secrets = secrets.got; + sum.maxsecrets = std::max(secrets.got, secrets.max); // If we found more than there are, increase the total. Blood's secret maintenance is too broken to get right. + sum.supersecrets = superSecrets.got; + + + // todo: centralize the remaining info as well. + } + + void clearStats() + { + kills.clear(); + secrets.clear(); + superSecrets.clear(); + } + + void setKills(int num) + { + kills.clear(); + kills.max = num; + } + + void setSecrets(int num, int supernum = 0) + { + secrets.clear(); + secrets.max = num; + superSecrets.clear(); + superSecrets.max = supernum; + } + + void addKillCount(int amount = 1) + { + kills.addTotal(amount); + } + + void addKill(int playerno, int amount = 1) + { + kills.add(playerno, amount); + } + + void addSecret(int playerno, int amount = 1) + { + secrets.add(playerno, amount); + } + + void addSuperSecret(int playerno, int amount = 1) + { + superSecrets.add(playerno, amount); + } + +}; + + extern GlobalCutscenes globalCutscenes; -extern MapRecord *currentLevel; +extern MapRecord* currentLevel; // level that is currently played. +extern MapLocals Level; void SetMusicReplacement(const char *mapname, const char *music); void ReplaceMusics(bool namehack = false); diff --git a/source/core/savegamehelp.cpp b/source/core/savegamehelp.cpp index 0a4c0b100..f1849d658 100644 --- a/source/core/savegamehelp.cpp +++ b/source/core/savegamehelp.cpp @@ -211,7 +211,7 @@ bool WriteSavegame(const char* filename, const char *name) .AddString("Map Label", lev->labelName) .AddString("Map Time", timeStr); - const char *fn = currentLevel->fileName; + const char *fn = lev->fileName.GetChars(); if (*fn == '/') fn++; if (strncmp(fn, "file://", 7) != 0) // this only has meaning for non-usermaps { @@ -649,12 +649,39 @@ void DCoreActor::Serialize(FSerializer& arc) } } +FSerializer& Serialize(FSerializer& arc, const char* key, StatRecord& c, StatRecord* def) +{ + if (arc.BeginObject(key)) + { + arc("max", c.max) + ("got", c.got) + .Array("player", c.player, MAXPLAYERS) + .EndObject(); + } + return arc; +} + +FSerializer& Serialize(FSerializer& arc, const char* key, MapLocals& c, MapLocals* def) +{ + if (arc.BeginObject(key)) + { + arc("kills", c.kills) + ("secrets", c.secrets) + ("superSecrets", c.superSecrets) + .EndObject(); + } + return arc; +} + + void SerializeMap(FSerializer& arc) { if (arc.BeginObject("engine")) { - arc.Array("statlist", statList, MAXSTATUS) + arc("maplocals", Level) + // everything here should move into MapLocals as well later. + .Array("statlist", statList, MAXSTATUS) ("sectors", sector, sectorbackup) ("walls", wall, wallbackup) diff --git a/source/games/blood/src/actor.cpp b/source/games/blood/src/actor.cpp index f70aa412d..f2cb3cc0e 100644 --- a/source/games/blood/src/actor.cpp +++ b/source/games/blood/src/actor.cpp @@ -2427,7 +2427,7 @@ static void actInitDudes() { if (gGameOptions.nMonsterSettings == 0) { - gKillMgr.SetCount(0); + Level.setKills(0); BloodStatIterator it(kStatDude); while (auto act = it.Next()) { @@ -2438,16 +2438,17 @@ static void actInitDudes() } else { - // by NoOne: WTF is this? - /////////////// + int TotalKills = 0; + BloodStatIterator it(kStatDude); while (auto act = it.Next()) { - if (act->spr.type < kDudeBase || act->spr.type >= kDudeMax) + if (!act->IsDudeActor()) I_Error("Non-enemy sprite (%d) in the enemy sprite list.\n", act->GetIndex()); + if (AllowedKillType(act)) TotalKills++; } + Level.setKills(TotalKills); - gKillMgr.CountTotalKills(); /////////////// for (int i = 0; i < kDudeMax - kDudeBase; i++) @@ -3395,7 +3396,7 @@ void actKillDude(DBloodActor* killerActor, DBloodActor* actor, DAMAGE_TYPE damag if (!getSequence(getDudeInfo(nType + kDudeBase)->seqStartID + nSeq)) { seqKill(actor); - gKillMgr.AddKill(actor); + AddKill(killerActor, actor); actPostSprite(actor, kStatFree); return; } @@ -3575,7 +3576,7 @@ void actKillDude(DBloodActor* killerActor, DBloodActor* actor, DAMAGE_TYPE damag for (int i = 0; i < 4; i++) fxSpawnBlood(actor, damage); } - gKillMgr.AddKill(actor); + AddKill(killerActor, actor); actCheckRespawn(actor); actor->spr.type = kThingBloodChunks; actPostSprite(actor, kStatThing); diff --git a/source/games/blood/src/aispid.cpp b/source/games/blood/src/aispid.cpp index 586e7a8ba..5f4f24ad0 100644 --- a/source/games/blood/src/aispid.cpp +++ b/source/games/blood/src/aispid.cpp @@ -155,7 +155,7 @@ void SpidBirthSeqCallback(int, DBloodActor* actor) { pDudeExtraE->birthCounter++; spawned->SetOwner(spawned); - gKillMgr.AddKillCount(spawned); + if (AllowedKillType(spawned)) Level.addKillCount(); } } diff --git a/source/games/blood/src/aiunicult.cpp b/source/games/blood/src/aiunicult.cpp index ccd774b28..ce6202529 100644 --- a/source/games/blood/src/aiunicult.cpp +++ b/source/games/blood/src/aiunicult.cpp @@ -250,7 +250,7 @@ void genDudeAttack1(int, DBloodActor* actor) aiActivateDude(spawned); } - gKillMgr.AddKillCount(spawned); + if (AllowedKillType(spawned)) Level.addKillCount(); pExtra->slave[pExtra->slaveCount++] = spawned; if (!playGenDudeSound(actor, kGenDudeSndAttackNormal)) sfxPlay3DSoundVolume(actor, 379, 1, 0, 0x10000 - Random3(0x3000)); @@ -1919,7 +1919,7 @@ DBloodActor* genDudeSpawn(DBloodActor* source, DBloodActor* actor, double nDist) spawned->spr.scale = source->spr.scale; } - gKillMgr.AddKillCount(spawned); + if (AllowedKillType(spawned)) Level.addKillCount(); aiInitSprite(spawned); return spawned; } @@ -2483,7 +2483,7 @@ void genDudePostDeath(DBloodActor* actor, DAMAGE_TYPE damageType, int damage) fxSpawnBlood(actor, damage); } - gKillMgr.AddKill(actor); + AddKill(actor, actor); actor->spr.type = kThingBloodChunks; actPostSprite(actor, kStatThing); diff --git a/source/games/blood/src/blood.cpp b/source/games/blood/src/blood.cpp index 73a7f574f..ccf358a17 100644 --- a/source/games/blood/src/blood.cpp +++ b/source/games/blood/src/blood.cpp @@ -272,8 +272,7 @@ void StartLevel(MapRecord* level, bool newgame) wsrand(dbReadMapCRC(currentLevel->LabelName())); gHitInfo.hitSector = nullptr; gHitInfo.hitWall = nullptr; - gKillMgr.Clear(); - gSecretMgr.Clear(); + Level.clearStats(); automapping = 1; // Here is where later the actors must be spawned. diff --git a/source/games/blood/src/bloodactor.h b/source/games/blood/src/bloodactor.h index 01bfb2ee9..a260dde36 100644 --- a/source/games/blood/src/bloodactor.h +++ b/source/games/blood/src/bloodactor.h @@ -140,6 +140,11 @@ public: return true; } } + int GetType() const + { + return spr.type; + } + }; // subclassed to add a game specific actor() method diff --git a/source/games/blood/src/endgame.cpp b/source/games/blood/src/endgame.cpp index 45d1db488..3207fa0b4 100644 --- a/source/games/blood/src/endgame.cpp +++ b/source/games/blood/src/endgame.cpp @@ -1,25 +1,19 @@ -//------------------------------------------------------------------------- /* -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. -*/ -//------------------------------------------------------------------------- + * Copyright (C) 2020-2023 Christoph Oelckers + * + * This file is part of Raze + * + * 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. + * + */ #include "ns.h" // Must come before everything else! @@ -52,12 +46,8 @@ void GameInterface::LevelCompleted(MapRecord* map, int skill) Mus_Stop(); SummaryInfo info{}; + Level.fillSummary(info); - info.kills = gKillMgr.Kills; - info.maxkills = gKillMgr.TotalKills; - info.secrets = gSecretMgr.Founds; - info.maxsecrets = gSecretMgr.Total; - info.supersecrets = gSecretMgr.Super; info.endofgame = map == nullptr; ShowIntermission(currentLevel, map, &info, [=](bool) @@ -67,77 +57,21 @@ void GameInterface::LevelCompleted(MapRecord* map, int skill) }); } - -void CKillMgr::SetCount(int nCount) +bool AllowedKillType(DBloodActor* actor) { - TotalKills = nCount; + if (!actor || actor->spr.statnum != kStatDude) + return false; + auto type = actor->GetType(); + return type != kDudeBat && type != kDudeRat && type != kDudeInnocent && type != kDudeBurningInnocent; } -void CKillMgr::AddKill(DBloodActor* actor) +void AddKill(DBloodActor* killer, DBloodActor* killed) { - if (actor->spr.statnum == kStatDude && actor->spr.type != kDudeBat && actor->spr.type != kDudeRat && actor->spr.type != kDudeInnocent && actor->spr.type != kDudeBurningInnocent) - Kills++; -} - -void CKillMgr::AddKillCount(DBloodActor* actor) -{ - if (actor->spr.statnum == kStatDude && actor->spr.type != kDudeBat && actor->spr.type != kDudeRat && actor->spr.type != kDudeInnocent && actor->spr.type != kDudeBurningInnocent) - TotalKills++; -} - -void CKillMgr::CountTotalKills(void) -{ - TotalKills = 0; - BloodStatIterator it(kStatDude); - while (auto actor = it.Next()) + if (AllowedKillType(killed)) { - if (actor->spr.type < kDudeBase || actor->spr.type >= kDudeMax) - I_Error("Non-enemy sprite (%d) in the enemy sprite list.", actor->GetIndex()); - if (actor->spr.statnum == kStatDude && actor->spr.type != kDudeBat && actor->spr.type != kDudeRat && actor->spr.type != kDudeInnocent && actor->spr.type != kDudeBurningInnocent) - TotalKills++; + int playernum = killer->IsPlayerActor() ? killer->GetType() - kDudePlayer1 : -1; + Level.addKill(playernum, 1); } } -void CKillMgr::Clear(void) -{ - TotalKills = Kills = 0; -} - -void CSecretMgr::SetCount(int nCount) -{ - Total = nCount; -} - -void CSecretMgr::Found(int nType) -{ - if (nType == 0) Founds++; - else if (nType < 0) { - viewSetSystemMessage("Invalid secret type %d triggered.", nType); - return; - } - else Super++; -} - -void CSecretMgr::Clear(void) -{ - Total = Founds = Super = 0; -} - -void SerializeGameStats(FSerializer& arc) -{ - if (arc.BeginObject("gamestats")) - { - arc("secrets", gSecretMgr.Total) - ("secretsfound", gSecretMgr.Founds) - ("super", gSecretMgr.Super) - ("totalkills", gKillMgr.TotalKills) - ("kills", gKillMgr.Kills) - .EndObject(); - } -} - - -CSecretMgr gSecretMgr; -CKillMgr gKillMgr; - END_BLD_NS diff --git a/source/games/blood/src/endgame.h b/source/games/blood/src/endgame.h index 25d532b55..3c283a9cb 100644 --- a/source/games/blood/src/endgame.h +++ b/source/games/blood/src/endgame.h @@ -1,24 +1,19 @@ -//------------------------------------------------------------------------- /* -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. -*/ + * Copyright (C) 2023 Christoph Oelckers + * + * This file is part of Raze. + * + * 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. + * + */ //------------------------------------------------------------------------- #pragma once #include "build.h" @@ -26,26 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. BEGIN_BLD_NS -class CKillMgr { -public: - int TotalKills, Kills; - void SetCount(int); - void AddKill(DBloodActor* actor); - void AddKillCount(DBloodActor* actor); - void CountTotalKills(void); - void Clear(void); -}; - -class CSecretMgr { -public: - int Total, Founds, Super; - void SetCount(int); - void Found(int); - void Clear(void); -}; - -extern CSecretMgr gSecretMgr; -extern CKillMgr gKillMgr; +bool AllowedKillType(DBloodActor* actor); +void AddKill(DBloodActor* killer, DBloodActor* killed); END_BLD_NS diff --git a/source/games/blood/src/eventq.cpp b/source/games/blood/src/eventq.cpp index 835471ab8..880a2c5ae 100644 --- a/source/games/blood/src/eventq.cpp +++ b/source/games/blood/src/eventq.cpp @@ -231,7 +231,7 @@ void evSend(EventObject& eob, int rxId, COMMAND_ID command, DBloodActor* initiat return; #endif case kChannelSetTotalSecrets: - if (command >= kCmdNumberic) gSecretMgr.SetCount(command - kCmdNumberic); + if (command >= kCmdNumberic) Level.setSecrets(command - kCmdNumberic); else viewSetSystemMessage("Invalid Total-Secrets command by %s", eob.description().GetChars()); break; case kChannelSecretFound: @@ -245,10 +245,20 @@ void evSend(EventObject& eob, int rxId, COMMAND_ID command, DBloodActor* initiat { if (command >= kCmdNumberic) { - gSecretMgr.Found(command - kCmdNumberic); - if (gGameOptions.nGameType == 0) + int nType = command - kCmdNumberic; + if (nType < 0) + Printf(PRINT_HIGH | PRINT_NOTIFY, "Invalid secret type %d triggered.\n", nType); + else { - viewSetMessage(GStrings(FStringf("TXTB_SECRET%d", Random(2))), nullptr, MESSAGE_PRIORITY_SECRET); + if (nType == 0) + Level.addSecret(-1); + else + Level.addSuperSecret(-1); + + if (gGameOptions.nGameType == 0) + { + viewSetMessage(GStrings(FStringf("TXTB_SECRET%d", Random(2))), nullptr, MESSAGE_PRIORITY_SECRET); + } } } else viewSetSystemMessage("Invalid Trigger-Secret command by %s", eob.description().GetChars()); diff --git a/source/games/blood/src/loadsave.cpp b/source/games/blood/src/loadsave.cpp index e3c35645b..13c08ba61 100644 --- a/source/games/blood/src/loadsave.cpp +++ b/source/games/blood/src/loadsave.cpp @@ -714,7 +714,6 @@ void SerializeSequences(FSerializer& arc); void SerializeWarp(FSerializer& arc); void SerializeTriggers(FSerializer& arc); void SerializeActor(FSerializer& arc); -void SerializeGameStats(FSerializer& arc); void SerializePlayers(FSerializer& arc); void SerializeView(FSerializer& arc); void SerializeNNExts(FSerializer& arc); @@ -741,7 +740,6 @@ void GameInterface::SerializeGameState(FSerializer& arc) SerializeActor(arc); SerializePlayers(arc); SerializeEvents(arc); - SerializeGameStats(arc); SerializeSequences(arc); SerializeWarp(arc); SerializeTriggers(arc); diff --git a/source/games/blood/src/nnexts.cpp b/source/games/blood/src/nnexts.cpp index e3524b387..bfc513aae 100644 --- a/source/games/blood/src/nnexts.cpp +++ b/source/games/blood/src/nnexts.cpp @@ -308,7 +308,7 @@ static DBloodActor* nnExtSpawnDude(DBloodActor* sourceactor, DBloodActor* origin aiInitSprite(pDudeActor); - gKillMgr.AddKillCount(pDudeActor); + if (AllowedKillType(pDudeActor)) Level.addKillCount(); bool burning = IsBurningDude(pDudeActor); if (burning) { @@ -3893,10 +3893,10 @@ bool condCheckGame(DBloodActor* aCond, const EVENT& event, int cmpOp, bool PUSH) case 2: return condCmp((gFrameCount / kTicsPerSec) % 60, arg1, arg2, cmpOp); // compare level seconds case 3: return condCmp(((gFrameCount % kTicsPerSec) * 33) / 10, arg1, arg2, cmpOp); // compare level mseconds case 4: return condCmp(gFrameCount, arg1, arg2, cmpOp); // compare level time (unsafe) - case 5: return condCmp(gKillMgr.Kills, arg1, arg2, cmpOp); // compare current global kills counter - case 6: return condCmp(gKillMgr.TotalKills, arg1, arg2, cmpOp); // compare total global kills counter - case 7: return condCmp(gSecretMgr.Founds, arg1, arg2, cmpOp); // compare how many secrets found - case 8: return condCmp(gSecretMgr.Total, arg1, arg2, cmpOp); // compare total secrets + case 5: return condCmp(Level.kills.got, arg1, arg2, cmpOp); // compare current global kills counter + case 6: return condCmp(Level.kills.max, arg1, arg2, cmpOp); // compare total global kills counter + case 7: return condCmp(Level.secrets.got, arg1, arg2, cmpOp); // compare how many secrets found + case 8: return condCmp(Level.secrets.max, arg1, arg2, cmpOp); // compare total secrets /*----------------------------------------------------------------------------------------------------------------------------------*/ case 20: return condCmp(gVisibility, arg1, arg2, cmpOp); // compare global visibility value /*----------------------------------------------------------------------------------------------------------------------------------*/ diff --git a/source/games/blood/src/sbar.cpp b/source/games/blood/src/sbar.cpp index 51e90bf43..89cfcd04a 100644 --- a/source/games/blood/src/sbar.cpp +++ b/source/games/blood/src/sbar.cpp @@ -80,19 +80,12 @@ void UpdateStatusBar(PLAYER* pPlayer) UpdateFrame(); } SummaryInfo sum; + Level.fillSummary(sum); if (gGameOptions.nGameType == 3) { sum.kills = pPlayer ? pPlayer->fragCount : 0; sum.maxkills = -3; } - else - { - sum.kills = gKillMgr.Kills; - sum.maxkills = gKillMgr.TotalKills; - } - sum.secrets = gSecretMgr.Founds; - sum.supersecrets = gSecretMgr.Super; - sum.maxsecrets = max(gSecretMgr.Founds, gSecretMgr.Total); // If we found more than there are, increase the total. Some levels have a bugged counter. sum.time = Scale(PlayClock, 1000, 120); sum.totaltime = STAT_GetTotalTime(); UpdateStatusBar(&sum); diff --git a/source/games/blood/src/triggers.cpp b/source/games/blood/src/triggers.cpp index 5af4b6106..41f5d523f 100644 --- a/source/games/blood/src/triggers.cpp +++ b/source/games/blood/src/triggers.cpp @@ -485,7 +485,7 @@ void OperateSprite(DBloodActor* actor, EVENT event) { auto spawned = actSpawnDude(actor, actor->xspr.data1, -1); if (spawned) { - gKillMgr.AddKillCount(spawned); + if (AllowedKillType(spawned)) Level.addKillCount(); switch (actor->xspr.data1) { case kDudeBurningInnocent: case kDudeBurningCultist: diff --git a/source/games/blood/src/view.cpp b/source/games/blood/src/view.cpp index acd7427fa..131bf133c 100644 --- a/source/games/blood/src/view.cpp +++ b/source/games/blood/src/view.cpp @@ -102,7 +102,7 @@ void viewDrawText(FFont* pFont, const char* pString, int x, int y, int nShade, i GameStats GameInterface::getStats() { - return { gKillMgr.Kills, gKillMgr.TotalKills, gSecretMgr.Founds, gSecretMgr.Total, gFrameCount / kTicsPerSec, gPlayer[myconnectindex].fragCount }; + return { Level.kills.got, Level.kills.max, Level.secrets.got, Level.secrets.max, gFrameCount / kTicsPerSec, gPlayer[myconnectindex].fragCount }; } //---------------------------------------------------------------------------