mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
529 lines
11 KiB
C++
529 lines
11 KiB
C++
#include "ht.h"
|
|
|
|
namespace wi {
|
|
|
|
Level::Level()
|
|
{
|
|
m_fInitialized = false;
|
|
m_pfogm = NULL;
|
|
m_ptrmap = NULL;
|
|
m_ptmap = NULL;
|
|
m_ppal = NULL;
|
|
m_mpiclriclrShadow = NULL;
|
|
m_nPlayersMin = 1;
|
|
m_nPlayersMax = 1;
|
|
m_szTitle[0] = 0;
|
|
m_szFileLevel[0] = 0;
|
|
m_nVersion = 0;
|
|
m_dwRevision = 0;
|
|
}
|
|
|
|
Level::~Level()
|
|
{
|
|
if (m_fInitialized) {
|
|
// Reset gobm state
|
|
|
|
ggobm.Reset();
|
|
|
|
// Clear replicator points
|
|
|
|
ReplicatorGob::ClearReplicatorCount();
|
|
|
|
delete m_ptmap;
|
|
delete m_pfogm;
|
|
delete m_ptrmap;
|
|
|
|
if (m_ppal != NULL)
|
|
gpakr.UnmapFile(&m_fmapPalette);
|
|
if (m_mpiclriclrShadow != NULL)
|
|
gpakr.UnmapFile(&m_fmapShadowMap);
|
|
|
|
// Free up any cached paths
|
|
|
|
MobileUnitGob::FreeCachedPaths();
|
|
}
|
|
}
|
|
|
|
bool Level::LoadLevelInfo(const char *pszLevelName, IniReader *piniLoaded)
|
|
{
|
|
IniReader *pini;
|
|
|
|
if (piniLoaded == NULL) {
|
|
pini = LoadIniFile(gpakr, pszLevelName);
|
|
if (pini == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
} else {
|
|
pini = piniLoaded;
|
|
}
|
|
|
|
// Get level version
|
|
|
|
if (pini->GetPropertyValue("General", "Version", "%d", &m_nVersion) == 0)
|
|
m_nVersion = 0;
|
|
|
|
// Get revision #
|
|
|
|
if (pini->GetPropertyValue("General", "Revision", "%d", &m_dwRevision) == 0)
|
|
m_dwRevision = 0;
|
|
|
|
// Can't run if the level version is newer than what the game supports
|
|
|
|
bool fSuccess = true;
|
|
if (m_nVersion > knVersionLevelSupported) {
|
|
fSuccess = false;
|
|
HtMessageBox(kfMbWhiteBorder, "Error", "Newer version of Hostile Takeover required to run this mission.");
|
|
} else {
|
|
// Load General level data
|
|
|
|
if (!pini->GetPropertyValue("General", "Title", m_szTitle, sizeof(m_szTitle)))
|
|
strcpy(m_szTitle, "<untitled>");
|
|
|
|
m_nPlayersMin = m_nPlayersMax = 0;
|
|
pini->GetPropertyValue("General", "MinPlayers", "%d", &m_nPlayersMin);
|
|
pini->GetPropertyValue("General", "MaxPlayers", "%d", &m_nPlayersMax);
|
|
|
|
// Normalize
|
|
if (pszLevelName[0] == 'm' && pszLevelName[1] == '_') {
|
|
if (m_nPlayersMin < 2) {
|
|
m_nPlayersMin = 2;
|
|
}
|
|
if (m_nPlayersMax > 4) {
|
|
m_nPlayersMax = 4;
|
|
}
|
|
if (m_nPlayersMax < m_nPlayersMin) {
|
|
m_nPlayersMax = m_nPlayersMin;
|
|
}
|
|
} else {
|
|
m_nPlayersMin = 1;
|
|
m_nPlayersMax = 1;
|
|
}
|
|
|
|
// Read in the side info for each side
|
|
|
|
Side side;
|
|
for (side = ksideNeutral; side < kcSides; side++) {
|
|
if (side == ksideNeutral) {
|
|
LoadSideInfo(pini, "sideNeutral", &m_asidi[side]);
|
|
} else {
|
|
char szSideName[] = "side1";
|
|
szSideName[4] = '0' + side;
|
|
LoadSideInfo(pini, szSideName, &m_asidi[side]);
|
|
}
|
|
}
|
|
|
|
// If there is no human side, assume it is side 1
|
|
// because gpplrLocal has to be set to something (because our code is intolerant)
|
|
|
|
bool fHumanFound = false;
|
|
for (side = ksideNeutral; side < kcSides; side++) {
|
|
if (m_asidi[side].nIntelligence == knIntelligenceHuman)
|
|
fHumanFound = true;
|
|
}
|
|
if (!fHumanFound)
|
|
m_asidi[kside1].nIntelligence = knIntelligenceHuman;
|
|
}
|
|
|
|
if (piniLoaded == NULL)
|
|
delete pini;
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
bool Level::LoadSideInfo(IniReader *pini, char *pszSideName, SideInfo *psidi)
|
|
{
|
|
memset(psidi, 0, sizeof(*psidi));
|
|
|
|
// Include some good defaults in case this level contains no SideInfo for this side
|
|
|
|
psidi->nInitialCredits = 5000;
|
|
pini->GetPropertyValue(pszSideName, "InitialCredits", "%d", &psidi->nInitialCredits);
|
|
|
|
int tx = 0;
|
|
int ty = 0;
|
|
pini->GetPropertyValue(pszSideName, "InitialView", "%d,%d", &tx, &ty);
|
|
psidi->wptInitialView.wx = WcFromTc(tx);
|
|
psidi->wptInitialView.wy = WcFromTc(ty);
|
|
psidi->nIntelligence = knIntelligenceComputer;
|
|
pini->GetPropertyValue(pszSideName, "Intelligence", "%d", &psidi->nIntelligence);
|
|
pini->GetPropertyValue(pszSideName, "InitialStructureCount", "%d", &psidi->cStructuresInitial);
|
|
pini->GetPropertyValue(pszSideName, "InitialMobileUnitCount", "%d", &psidi->cMobileUnitsInitial);
|
|
return true;
|
|
}
|
|
|
|
bool Level::Init(const char *pszLevel, bool fConstantsOnly)
|
|
{
|
|
// Save level filename
|
|
|
|
strncpyz(m_szFileLevel, (char *)pszLevel, sizeof(m_szFileLevel));
|
|
|
|
// Init reads all useful level data and then deletes the IniReader
|
|
// to save memory.
|
|
|
|
Status("Level::Init LoadIniFile...");
|
|
IniReader *pini = LoadIniFile(gpakr, pszLevel);
|
|
if (pini == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!LoadLevelConstants(pszLevel, pini)) {
|
|
delete pini;
|
|
return false;
|
|
}
|
|
|
|
// Establish unit count deltas
|
|
|
|
ggame.CalcUnitCountDeltas(this);
|
|
|
|
// Load variables
|
|
|
|
if (!fConstantsOnly) {
|
|
if (!LoadLevelVariables(pini)) {
|
|
Assert(false);
|
|
delete pini;
|
|
return false;
|
|
}
|
|
}
|
|
delete pini;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Level::LoadLevelConstants(const char *pszLevelName, IniReader *pini)
|
|
{
|
|
if (!LoadLevelInfo(pszLevelName, pini))
|
|
return false;
|
|
|
|
// m_fInitialized is to distinguish between just having the level parameters
|
|
// read and having been initialized enough to require some cleanup.
|
|
|
|
m_fInitialized = true;
|
|
|
|
// Load map
|
|
|
|
Status("Load Tile Map...");
|
|
char szT[kcbFilename];
|
|
if (!pini->GetPropertyValue("General", "TileMap", szT, sizeof(szT))) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
Size sizPlayfield;
|
|
ggame.GetPlayfieldSize(&sizPlayfield);
|
|
m_ptmap = LoadTileMap(szT, &sizPlayfield);
|
|
Assert(m_ptmap != NULL);
|
|
if (m_ptmap == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Init GobMgr
|
|
|
|
Size sizMap;
|
|
m_ptmap->GetMapSize(&sizMap);
|
|
Size sizTile;
|
|
m_ptmap->GetTileSize(&sizTile);
|
|
if (!ggobm.Init(sizMap.cx / sizTile.cx, sizMap.cy / sizTile.cy, kcpgobMax)) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Load terrain map
|
|
|
|
Status("Load Terrain Map...");
|
|
if (!pini->GetPropertyValue("General", "TerrainMap", szT, sizeof(szT))) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
m_ptrmap = new TerrainMap;
|
|
if (m_ptrmap == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
if (!m_ptrmap->Init(szT)) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Create fog map.
|
|
|
|
Status("Create Fog Map...");
|
|
m_pfogm = new FogMap;
|
|
if (m_pfogm == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
if (!m_pfogm->Init(&sizTile, &sizMap)) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Load palette
|
|
|
|
if (!pini->GetPropertyValue("General", "Palette", szT, sizeof(szT))) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
m_ppal = (Palette *)gpakr.MapFile(szT, &m_fmapPalette);
|
|
if (m_ppal == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
strcat(szT, ".shadowmap");
|
|
m_mpiclriclrShadow = (byte *)gpakr.MapFile(szT, &m_fmapShadowMap);
|
|
|
|
// Instantiate an OvermindGob for each Computer Player
|
|
|
|
Player *pplr = gplrm.GetNextPlayer(NULL);
|
|
for (; pplr != NULL; pplr = gplrm.GetNextPlayer(pplr)) {
|
|
#ifdef STRESS
|
|
// Instantiate an Overmind for the human player if we're running stress
|
|
|
|
if (gfStress) {
|
|
if ((pplr->GetFlags() & (kfPlrComputer | kfPlrComputerOvermind)) == kfPlrComputer)
|
|
continue;
|
|
} else {
|
|
if (!(pplr->GetFlags() & kfPlrComputerOvermind))
|
|
continue;
|
|
}
|
|
#else
|
|
if (!(pplr->GetFlags() & kfPlrComputerOvermind))
|
|
continue;
|
|
#endif
|
|
|
|
// Instantiate and initialize an OvermindGob
|
|
|
|
OvermindGob *pgobOvermind = (OvermindGob *)CreateGob(kgtOvermind);
|
|
if (pgobOvermind == NULL) {
|
|
Assert(false);
|
|
delete pini;
|
|
return false;
|
|
}
|
|
|
|
if (!pgobOvermind->Init(NULL)) {
|
|
Assert(false);
|
|
delete pgobOvermind;
|
|
delete pini;
|
|
return false;
|
|
}
|
|
|
|
// Who's your daddy?
|
|
|
|
pgobOvermind->SetOwner(pplr);
|
|
}
|
|
|
|
// Load the Triggers
|
|
|
|
Status("Load Triggers...");
|
|
if (!m_tgrm.Init(pini)) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Load the UnitGroups
|
|
|
|
Status("Load UnitGroups...");
|
|
if (!m_ugm.Init(pini)) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Level::LoadLevelVariables(IniReader *pini)
|
|
{
|
|
// Load areas
|
|
|
|
Status("Load Areas...");
|
|
if (!ggobm.LoadAreas(pini)) {
|
|
return false;
|
|
}
|
|
|
|
// Enumerate all Gob descriptions and instantiate in-memory versions
|
|
|
|
Status("Load Gobs...");
|
|
char szName[kcbFilename];
|
|
FindProp find;
|
|
while (pini->FindNextProperty(&find, "GameObjects", szName, sizeof(szName))) {
|
|
GobType gt;
|
|
pini->GetPropertyValue(&find, "%d,", >);
|
|
|
|
Gob *pgob = CreateGob(gt);
|
|
if (pgob == NULL) {
|
|
Assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Don't waste space on Gobs that don't need names
|
|
|
|
char *pszName;
|
|
if (strcmp("nil", szName) == 0)
|
|
pszName = NULL;
|
|
else
|
|
pszName = szName;
|
|
|
|
if (!pgob->Init(pini, &find, pszName)) {
|
|
Assert(false);
|
|
delete pgob;
|
|
return false;
|
|
}
|
|
|
|
#ifdef STRESS
|
|
if (gfStress) {
|
|
// Make player's units invulnerable
|
|
|
|
if (pgob->GetOwner() == gpplrLocal) {
|
|
if (pgob->GetFlags() & kfGobUnit) {
|
|
UnitGob *punt = (UnitGob *)pgob;
|
|
punt->SetUnitFlags(punt->GetUnitFlags() | kfUnitInvulnerable);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Enumerate all Galaxite positions and add them to the Fog/Galaxite map
|
|
|
|
Status("Load Galaxite...");
|
|
FindProp find2;
|
|
while (pini->FindNextProperty(&find2, "Galaxite", szName, sizeof(szName))) {
|
|
int nGx, tx, ty;
|
|
pini->GetPropertyValue(&find2, "%d,%d,%d", &nGx, &tx, &ty);
|
|
m_pfogm->SetGalaxite(nGx, tx, ty);
|
|
}
|
|
|
|
// In terrain for the moment (forever?)
|
|
#if 0
|
|
// Enumerate all Wall positions and add them to the Fog/Galaxite/Wall map
|
|
|
|
Status("Load walls...");
|
|
FindProp find3;
|
|
while (pini->FindNextProperty(&find3, "Walls", szName, sizeof(szName))) {
|
|
int nHealth, tx, ty;
|
|
pini->GetPropertyValue(&find3, "%d,%d,%d", &nHealth, &tx, &ty);
|
|
m_pfogm->SetWallHealth(nHealth, tx, ty);
|
|
}
|
|
#endif
|
|
|
|
// Instantiate the "CreateAtLevelLoad" UnitGroups
|
|
|
|
m_ugm.CreateAtLevelLoadGroups();
|
|
|
|
return true;
|
|
}
|
|
|
|
#define knVerLevelState 2
|
|
bool Level::LoadState(Stream *pstm)
|
|
{
|
|
// Do version handling
|
|
|
|
byte nVer = pstm->ReadByte();
|
|
if (nVer != knVerLevelState)
|
|
return false;
|
|
|
|
// Get level name
|
|
|
|
char szLevel[kcbFilename];
|
|
pstm->ReadString(szLevel, sizeof(szLevel));
|
|
|
|
// Load level constants
|
|
|
|
if (!Init(szLevel, true)) {
|
|
return false;
|
|
}
|
|
|
|
// Check to see if the save game has a different mission revision number than the mission
|
|
// itself. If so, error. This is the case if a game is saved, then the level is revised
|
|
// in M and reloaded.
|
|
|
|
dword dwRevision = pstm->ReadDword();
|
|
if (dwRevision != m_dwRevision) {
|
|
HtMessageBox(kfMbWhiteBorder | kfMbClearDib, "Error", "This saved game is based on an older version of this mission!");
|
|
return false;
|
|
}
|
|
|
|
// Load gobm state
|
|
|
|
if (!ggobm.LoadState(pstm))
|
|
return false;
|
|
|
|
// Load fog and galaxite state
|
|
|
|
if (!m_pfogm->LoadState(pstm)) {
|
|
return false;
|
|
}
|
|
if (!m_ptrmap->LoadState(pstm)) {
|
|
return false;
|
|
}
|
|
|
|
// Load gobs
|
|
|
|
int cGobs = pstm->ReadWord();
|
|
while (cGobs-- != 0) {
|
|
// Get gob type
|
|
|
|
GobType gt = pstm->ReadByte();
|
|
Gob *pgob = CreateGob(gt);
|
|
if (pgob == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!pgob->LoadState(pstm)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Load triggers
|
|
|
|
if (!m_tgrm.LoadState(pstm)) {
|
|
return false;
|
|
}
|
|
|
|
// Load UnitGroups
|
|
|
|
if (!m_ugm.LoadState(pstm)) {
|
|
return false;
|
|
}
|
|
|
|
return pstm->IsSuccess();
|
|
}
|
|
|
|
bool Level::SaveState(Stream *pstm)
|
|
{
|
|
pstm->WriteByte(knVerLevelState);
|
|
pstm->WriteString(m_szFileLevel);
|
|
pstm->WriteDword(m_dwRevision);
|
|
ggobm.SaveState(pstm);
|
|
m_pfogm->SaveState(pstm);
|
|
m_ptrmap->SaveState(pstm);
|
|
|
|
int cgobs = 0;
|
|
Gob *pgobT;
|
|
for (pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
|
|
if (pgobT->IsSavable())
|
|
cgobs++;
|
|
}
|
|
pstm->WriteWord(cgobs);
|
|
|
|
for (pgobT = ggobm.GetFirstGob(); pgobT != NULL; pgobT = ggobm.GetNextGob(pgobT)) {
|
|
if (pgobT->IsSavable()) {
|
|
pstm->WriteByte(pgobT->GetType());
|
|
pgobT->SaveState(pstm);
|
|
}
|
|
}
|
|
|
|
// Save triggers
|
|
|
|
m_tgrm.SaveState(pstm);
|
|
|
|
// Save UnitGroups
|
|
|
|
m_ugm.SaveState(pstm);
|
|
|
|
return pstm->IsSuccess();
|
|
}
|
|
|
|
} // namespace wi
|