hostile-takeover/game/game.cpp
2016-08-31 23:55:30 -04:00

2695 lines
66 KiB
C++

#include "game/ht.h"
#include "game/wistrings.h"
#include "mpshared/netmessage.h"
#include "game/httppackmanager.h"
#include "game/completemanager.h"
#include "game/chatter.h"
#if 0
//temp
#include "base/bytebuffer.h"
#include "game/serviceurls.h"
#include "game/uploader.h"
#include "game/httpservice.h"
#endif
namespace wi {
Display *gpdisp;
Game ggame;
Simulation gsim;
TimerMgr gtimm;
IniReader *gpiniForms;
IniReader *gpiniGame;
Font *gapfnt[kcFonts];
GobStateMachineMgr gsmm;
GobMgr ggobm;
byte *gpbScratch;
word gcbScratch;
byte *gmpiclriclrShadow;
int gimmReinitialize = -1;
bool gfLoadReinitializeSave = false;
int gcxTile;
int gcyTile;
int gcxFullScreenFormDiv10;
int gcxyBorder;
bool gfGrayscale = false;
#ifdef DRAW_UPDATERECTS
bool gfDrawUpdateRects;
#endif
short *gmpPcFromWc;
WCoord *gmpWcFromPc;
DibBitmap *gpbmClip;
FormMgr *gpfrmmSim;
FormMgr *gpfrmmInput;
MultiFormMgr *gpmfrmm;
int gnHueOffset = 0;
int gnSatMultiplier = 0;
int gnLumOffset = 0;
int gnDemoRank = 0;
double gnScrollSpeed = 1.0;
char gszAskURL[512];
char gszDeviceId[34];
Stream *gpstmSavedGame;
UpdateMap *gpupdSim;
int gtGameSpeed = kcmsUpdate / 10; // 80 ms/frame or 12.5 FPS (1000/80)
Color *gaclrFixed;
bool gfClearFog;
word gwfPerfOptions = kfPerfAll;
word gwfHandicap = kfHcapDefault;
int gcStructGobsHumanLimitSP;
int gcStructGobsComputerLimitSP;
int gcMuntGobsHumanLimitSP;
int gcMuntGobsComputerLimitSP;
int gcStructGobsComputerDeltaSP;
int gcStructGobsHumanDeltaSP;
int gcStructGobsLimitMP;
int gcMuntGobsLimitMP;
int gcSceneryGobsLimit;
int gcScorchGobsLimit;
int gcSupportGobsLimit;
bool gfMultiplayer;
bool gfIgnoreBluetoothWarning;
SpriteManager *gpsprm;
#ifdef STRESS
bool gfStress = false;
long gtStressTimeout;
#endif
#ifndef PIL
#if (defined(IPHONE) || defined(SDL)) && (!defined(DEV_BUILD) && !defined(BETA_TIMEOUT))
char *gszVersion = "1.6";
#else
char *gszVersion = "+++VERSION+++";
#endif
#endif
// Forward declarations
bool AllocComputerPlayers() secGame;
void FreeComputerPlayers() secGame;
//
//
//
void GameMain(char *pszCmds)
{
#if defined(DEBUG) && !defined(CE) && !defined(PIL) && !defined(IPHONE) && !defined(SDL)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
char *pszLevel = NULL;
char szLevel[kcbFilename];
#ifdef WIN
char szData[_MAX_PATH];
#endif
// Parse command line
int nchAbs = 0;
int nchRel;
int imm = -1;
while (true) {
char szT[80];
int c = IniScanf(&pszCmds[nchAbs], "%s %+", szT, &nchRel);
nchAbs += nchRel;
if (c == 0)
break;
// -m %d (mode / data match: index)
if (strcmp(szT, "-m") == 0) {
c = IniScanf(&pszCmds[nchAbs], "%d%+", &imm, &nchRel);
nchAbs += nchRel;
if (c == 0)
return;
// -l %s (level: level.lvl)
} else if (strcmp(szT, "-l") == 0) {
c = IniScanf(&pszCmds[nchAbs], "%s%+", szLevel, &nchRel);
nchAbs += nchRel;
if (c == 0)
return;
pszLevel = szLevel;
#ifdef STRESS
// -s
} else if (strcmp(szT, "-s") == 0) {
gfStress = true;
#endif
} else if (strcmp(szT, "-nofog") == 0) {
gfClearFog = true;
#if defined(MP_STRESS) && !defined(PIL) && !defined(CE) && !defined(IPHONE)
// -mp-spawn <count>
// TODO: -mp-spawn <client|server> side arg_string
} else if (strcmp(szT, "-mp-spawn") == 0) {
int cSpawn;
c = IniScanf(&pszCmds[nchAbs], "%d %+", &cSpawn, &nchRel);
nchAbs += nchRel;
if (c != 2)
return;
for (int nSpawn = 0; nSpawn < cSpawn; nSpawn++) {
char szModule[MAX_PATH];
GetModuleFileName(NULL, szModule, sizeof(szModule) - 1);
char szCmdLine[MAX_PATH];
sprintf(szCmdLine, "%s -mp-start %d", szModule, nSpawn + 1);
WinExec(szCmdLine, SW_SHOW);
}
// -mp-start <pos>
// TODO: -mp-start <client|server> side arg_string
} else if (strcmp(szT, "-mp-start") == 0) {
int nSpawn;
c = IniScanf(&pszCmds[nchAbs], "%d %+", &nSpawn, &nchRel);
nchAbs += nchRel;
if (c != 2)
return;
gfMPStress = true;
gnMPPos = nSpawn;
#endif
}
}
#ifdef DEBUG_HELPERS
InitDebugHelpers();
#endif
if (!HostInit()) {
return;
}
while (true) {
if (!ggame.Init(imm)) {
ggame.Exit();
return;
}
#if 0
//for testing sync error uploading
//temp
base::ByteBuffer *bb = new base::ByteBuffer();
bb->WriteString("hello world");
const char *url = base::Format::ToString("%s?gameid=1&pid=2",
kszSyncErrorUploadUrl);
UploadByteBuffer(gphttp, bb, url);
ggame.Exit();
return;
#endif
if (gfLoadReinitializeSave) {
gfLoadReinitializeSave = false;
gshl.Launch(true);
} else {
gshl.Launch(false);
}
if (gimmReinitialize != -1) {
imm = gimmReinitialize;
gimmReinitialize = -1;
ggame.Exit();
continue;
}
break;
}
// Demo mode only nag screen
// Will mean you'll need to press the applications silkscreen button twice
// in order to exit from demo
if (gfDemo) {
gevm.ClearAppStopping();
DoRegisterNowForm();
}
ggame.Exit();
HostExit();
#ifdef DEBUG_HELPERS
ExitDebugHelpers();
#endif
#if defined(CHECK_OLD_ALLOCS) && defined(DEBUG) && !defined(CE) && !defined(PIL)
_CrtDumpMemoryLeaks();
#endif
}
Game::Game()
{
m_wf = 0;
m_pfrmSimUI = NULL;
m_fSimUninitialized = true;
m_cmm = 0;
m_cmmAlloc = 0;
m_immCurrent = -1;
m_immBest = -1;
m_szNextLevel[0] = 0;
m_fSkipSaveReinitialize = false;
m_fUpdateTriggers = false;
m_gameid = 0;
}
Game::~Game()
{
Assert(m_pfrmSimUI == NULL);
}
bool Game::Init(int imm)
{
// Haven't initialized yet
m_wf &= ~kfGameInitDone;
// Check Dynamic Memory size
if (!CheckMemoryAvailable())
return false;
// Load prefs first
LoadPreferences();
// Suck what we can from preferences
gtGameSpeed = gprefsInit.ctGameSpeed;
gfLassoSelection = gprefsInit.fLassoSelection != 0;
gnHueOffset = gprefsInit.nHueOffset;
gnSatMultiplier = gprefsInit.nSatMultiplier;
gnLumOffset = gprefsInit.nLumOffset;
strncpyz(gszUsername, gprefsInit.szUsername, sizeof(gszUsername));
strncpyz(gszPassword, gprefsInit.szPassword, sizeof(gszPassword));
strncpyz(gszToken, gprefsInit.szToken, sizeof(gszToken));
gfAnonymous = (gprefsInit.fAnonymous != 0);
gwfPerfOptions = gprefsInit.wfPerfOptions;
gwfHandicap = gprefsInit.wfHandicap;
gkey = gprefsInit.key;
gnDemoRank = gprefsInit.nDemoRank;
gnScrollSpeed = gprefsInit.nScrollSpeed;
gfIgnoreBluetoothWarning = (gprefsInit.wfPrefs & kfPrefIgnoreBluetoothWarning) ? true : false;
strncpyz(gszAskURL, gprefsInit.szAskURL, sizeof(gszAskURL));
strncpyz(gszDeviceId, gprefsInit.szDeviceId, sizeof(gszDeviceId));
// Temp buffer used for several things, including decompression, TBitmap compiling.
gcbScratch = 10 * 1024;
gpbScratch = new byte[gcbScratch];
if (gpbScratch == NULL)
return false;
// Requires gpbScratch to work
#if defined(PIL) && defined(TRACE_TO_DB_LOG)
DbLogInit();
#endif
// Init event manager
gevm.Init();
// Match display with data
if (!InitDisplay(imm)) {
HostOutputDebugString("Display didn't initialize.");
return false;
}
if (!LoadGameData()) {
HostOutputDebugString("Game data didn't load.");
return false;
}
// Init memory manager *after* setting screen mode so we know how much dyn ram is left after
// allocating back buffers (potentially from main memory).
if (!InitMemMgr()) {
HostOutputDebugString("Memory manager didn't initialize.");
return false;
}
// Init cache mgr
if (!gcam.Init()) {
HostOutputDebugString("Cache manager didn't initialize.");
return false;
}
// Init mapping tables after mem mgr since it uses mem mgr to store the tables
if (!ggame.InitCoordMappingTables()) {
HostMessageBox(TEXT("Out of memory, could not initialize game!"));
return false;
}
// Init MultiFormMgr
if (!InitMultiFormMgr()) {
HostOutputDebugString("Multi form manager didn't initialize.");
return false;
}
// Initialize the Shell
if (!gshl.Init()) {
HostOutputDebugString("Shell didn't initialize.");
return false;
}
// Make Shell palette and shadow map active
ClearDisplay();
gshl.SetPalette();
// Init TBitmapCode
TBitmap::InitClass();
m_wf |= kfGameInitBitmap;
// Init form / control requirements
ButtonControl::InitClass();
CheckBoxControl::InitClass();
PipMeterControl::InitClass();
ListControl::InitClass();
DamageMeterControl::InitClass();
// Save around game.ini
gpiniGame = LoadIniFile(gpakr, "game.ini");
if (gpiniGame == NULL)
return false;
// Save around the form.ini
gpiniForms = LoadIniFile(gpakr, "forms.ini");
if (gpiniForms == NULL)
return false;
// Load fonts
for (int ifnt = 0; ifnt < kcFonts; ifnt++) {
char szProp[2];
szProp[0] = ifnt + '0';
szProp[1] = 0;
char szT[kcbFilename];
if (gpiniGame->GetPropertyValue("Fonts", szProp, szT, sizeof(szT))) {
gapfnt[ifnt] = LoadFont(szT);
if (gapfnt[ifnt] == NULL) {
Assert("LoadFont failed");
return false;
}
// HACK: shadow font glyphs need to overlap by one pixel when drawn
if (strcmp(szT, "shadowfont.fnt") == 0) {
gapfnt[ifnt]->SetGlyphOverlap(1);
gapfnt[ifnt]->SetLineOverlap(2);
}
}
}
m_wf |= kfGameInitFonts;
// Load string table
gpstrtbl = new StringTable();
Assert(gpstrtbl != NULL, "out of memory!");
if (gpstrtbl == NULL)
return false;
if (!gpstrtbl->Init("strings.bin"))
return false;
// Init sound system
gsndm.Init();
if (gprefsInit.nVolume != (word)-1)
gsndm.SetVolume(gprefsInit.nVolume);
gsndm.Enable(true);
// Clear out stale save games
DeleteStaleSaveGames();
// We're done with game initialization
m_wf |= kfGameInitDone;
return true;
}
bool Game::CheckMemoryAvailable()
{
// The following constants have been calculated based on actual device measurements,
// scenario tests and gob count partitioning as of 8/16/03. Actual data is in \ht\docs\size.xls.
// The "min" scenario is the minimum playable scenario. If kcbDynMemMin is available, the following
// are the unit counts. If "max" scenario is the maximum scenario. If kcbDynMemMax is available, the
// following are the unit counts. This is partitioned by the gob count maximum and not the memory
// available (once over kcbDynMemMax it's gravy). The mem sizes are what is available at this
// point in time: PalmOS allocs globals and calls PilotMain.
// Note also the computer unit counts are all shared between all computer sides.
// Note that some of the computer units counts in certain levels may already be exceeded; that is ok.
// TODO: All gob limits should be validated in M.
// Non-unit gob limits.
#define kcSceneryGobsLimit 100
#define kcScorchGobsLimit 30
#define kcSupportGobsLimit 50
// min limits
#define kcbDynMemMin 130000
#define kcStructGobsHumanMinSP 39
#define kcStructGobsComputerMinSP 52
#define kcMuntGobsHumanMinSP 60
#define kcMuntGobsComputerMinSP 80
// med limits
#define kcbDynMemMed 140000
#define kcStructGobsHumanMedSP 49
#define kcStructGobsComputerMedSP 65
#define kcMuntGobsHumanMedSP 79
#define kcMuntGobsComputerMedSP 105
// max limits
#define kcbDynMemMax 150000
#define kcStructGobsHumanMaxSP 55
#define kcMuntGobsHumanMaxSP 88
#define kcStructGobsComputerMaxSP 72
#define kcMuntGobsComputerMaxSP 117
#if (kcSceneryGobsLimit+kcScorchGobsLimit+kcSupportGobsLimit+kcStructGobsHumanMaxSP+kcStructGobsComputerMaxSP+kcMuntGobsHumanMaxSP+kcMuntGobsComputerMaxSP > kcpgobMax)
#error Gob count limit error!
#endif
// mp limits
#define kcStructGobsMaxMP 55 // 33
#define kcMuntGobsMaxMP 88 // 66
#ifndef __CPU_68K
#if (kcSceneryGobsLimit+kcScorchGobsLimit+kcSupportGobsLimit+kcStructGobsMaxMP*4+kcMuntGobsMaxMP*4 > kcpgobMax)
#error Gob count limit error!
#endif
#endif
// Assume max. Works for all platforms except lowest end Palms.
gcStructGobsHumanLimitSP = kcStructGobsHumanMaxSP;
gcStructGobsComputerLimitSP = kcStructGobsComputerMaxSP;
gcMuntGobsHumanLimitSP = kcMuntGobsHumanMaxSP;
gcMuntGobsComputerLimitSP = kcMuntGobsComputerMaxSP;
gcStructGobsLimitMP = kcStructGobsMaxMP;
gcMuntGobsLimitMP = kcMuntGobsMaxMP;
gcSceneryGobsLimit = kcSceneryGobsLimit;
gcScorchGobsLimit = kcScorchGobsLimit;
gcSupportGobsLimit = kcSupportGobsLimit;
#ifdef PIL
#if 0
// Limit test
gcStructGobsHumanLimitSP = kcStructGobsHumanMinSP;
gcStructGobsComputerLimitSP = kcStructGobsComputerMinSP;
gcMuntGobsHumanLimitSP = kcMuntGobsHumanMinSP;
gcMuntGobsComputerLimitSP = kcMuntGobsComputerMinSP;
#else
// Max scenario?
UInt32 cbFree, cbMax;
MemHeapFreeBytes(0, &cbFree, &cbMax);
if (cbFree < kcbDynMemMax) {
if (cbFree >= kcbDynMemMed) {
gcStructGobsHumanLimitSP = kcStructGobsHumanMedSP;
gcStructGobsComputerLimitSP = kcStructGobsComputerMedSP;
gcMuntGobsHumanLimitSP = kcMuntGobsHumanMedSP;
gcMuntGobsComputerLimitSP = kcMuntGobsComputerMedSP;
}
else if (cbFree >= kcbDynMemMin) {
gcStructGobsHumanLimitSP = kcStructGobsHumanMinSP;
gcStructGobsComputerLimitSP = kcStructGobsComputerMinSP;
gcMuntGobsHumanLimitSP = kcMuntGobsHumanMinSP;
gcMuntGobsComputerLimitSP = kcMuntGobsComputerMinSP;
}
else {
HostNotEnoughMemory(false, cbFree, kcbDynMemMin);
return false;
}
}
#endif
#endif
return true;
}
void Game::CalcUnitCountDeltas(Level *plvl)
{
// After the level is loaded this gets called. It calcs how many initial
// units there are and recalcs unit deltas.
//
// NOTE: For now all this does is take the computer's extra structure count
// and gives it to the human player.
int cComputerStructuresInitial = 0;
for (int side = ksideNeutral; side < kcSides; side++) {
Player *pplr = gplrm.GetPlayer(side);
if (pplr->GetFlags() & kfPlrComputer)
cComputerStructuresInitial += plvl->GetSideInfo(side)->cStructuresInitial;
}
// HACK ALERT: Give the computer room to build 5 structures
int cStructuresAvailable = gcStructGobsComputerLimitSP - cComputerStructuresInitial - 5;
// Give the rest to the human player
if (cStructuresAvailable > 0) {
gcStructGobsComputerDeltaSP = -cStructuresAvailable;
gcStructGobsHumanDeltaSP = cStructuresAvailable;
} else {
gcStructGobsComputerDeltaSP = 0;
gcStructGobsHumanDeltaSP = 0;
}
}
bool Game::LoadGameData()
{
// We've choosen the best mode / data combo. Load the matching data
Assert(m_immCurrent != -1);
char szPdb[20];
sprintf(szPdb, "htdata%d%d.pdb", m_amm[m_immCurrent].nDepthData, m_amm[m_immCurrent].nSizeData);
const char *pszMainDataDir = HostGetMainDataDir();
if (!gpakr.Push(pszMainDataDir, szPdb))
return false;
// Load sound effects
if (!gpakr.Push(pszMainDataDir, "htsfx.pdb")){
HostMessageBox(TEXT("Required sound effects file missing"));
return false;
}
#if 0
// Enum and push addon data
Enum enm;
char szAddon[64];
char szDir[1024];
while (HostEnumAddonFiles(&enm, szDir, sizeof(szDir),
szAddon, sizeof(szAddon))) {
if (CheckDatabaseVersion(szDir, szAddon, true)) {
gpakr.Push(szDir, szAddon);
}
}
#endif
// Set global tile dimensions
// Perform hack for size 20 which really reuses 24x24 tiles but sizes other graphics proportionally
gcxTile = m_amm[m_immCurrent].nSizeData;
if (gcxTile == 20)
gcxTile = 24;
gcyTile = gcxTile;
// If the tile size is 24, then gpbScratch is ~2.25 times bigger to accomodate TBitmap compiling size
// increase
if (gcxTile > 16) {
if (gcbScratch <= 10 * 1024) {
delete[] gpbScratch;
if (gcxTile == 32) {
gcbScratch = 35 * 1024;
} else {
gcbScratch = 23 * 1024;
}
gpbScratch = new byte[gcbScratch];
if (gpbScratch == NULL)
return false;
}
}
return true;
}
bool Game::InitCoordMappingTables()
{
// Initialize world -> pixel coord mapping table
#define knSplit 32
//#define knSplit 4
gmpPcFromWc = (short *)gmmgr.AllocPtr(2 * kwcMax);
if (gmpPcFromWc == NULL)
return false;
Assert(kwcMax / knSplit <= gcbScratch);
Assert((kwcMax / knSplit) * knSplit == kwcMax);
short s = 0;
short nOverflow = 0;
for (int j = 0; j < knSplit; j++) {
short *psT = (short *)gpbScratch;
for (int i = 0; i < kwcMax / knSplit; i++) {
// Duplicate the behavior of gmpWcFromPc so that
// gmpPcFromWc[gmpWcFromPc[2]] == 2
*psT++ = s;
nOverflow += gcxTile;
if (nOverflow == 256) {
nOverflow = 0;
s++;
} else if (nOverflow > 256) {
nOverflow -= 256;
} else if (nOverflow + gcxTile - 1 >= 256) {
s++;
}
}
gmmgr.WritePtr(gmpPcFromWc, j * 2 * (kwcMax / knSplit), gpbScratch, 2 * (kwcMax / knSplit));
}
// Initialize pixel -> world coord mapping table
WCoord *pwcT = (WCoord *)gpbScratch;
for (int i = 0; i <= 64 * 32; i++) {
*pwcT++ = (WCoord)((i * 256L) / gcxTile);
}
// These "+ 1"s are to support the mapping of inclusive/exclusive rects
// that lay at the extreme of the allowed coordinate range
gmpWcFromPc = (WCoord *)gmmgr.AllocPtr((2 * 64 * 32) + 1);
if (gmpWcFromPc == NULL)
return false;
gmmgr.WritePtr(gmpWcFromPc, 0, gpbScratch, (2 * 64 * 32) + 1);
return true;
}
bool Game::InitMemMgr()
{
// 8/30/03 settings
// There are 12 save game slots typically + 1 auto-save slot
// Reserve 20K * 13 for save games (260K ideally)
// For cache use: There is approximately 75K of fixed allocs
// Hi-res runs smoothly at 1M cache with room to spare
// Lo-res settings are (1M - 75K) * 16 / 24 (approximately)
// Should grab more than enough space if possible to run smoothly
// How much to reserve from dynamic memory for dynamic allocations - the rest
// will be used by the storage memory manager. This is more than enough; we don't get
// anywhere close this much on low-end Palms.
#ifdef __CPU_68K
// Single player only
#define kcbDynReserve ((dword)384 * 1024)
#else
// Single and multi player
#define kcbDynReserve ((dword)512 * 1024)
#endif
#define kcbSaveGame ((dword)20 * 1024)
#define kcbMaxSaveGame ((dword)13 * kcbSaveGame)
#define kcbMinSaveGame ((dword)6 * kcbSaveGame)
// Fixed allocs from storage mem
#define kcbFixed ((dword)150 * 1024)
// Min / max needed for storage memory manager
#define kcbMinHires ((dword)512 * 1024)
#define kcbWarnHires ((dword)768 * 1024)
#define kcbMaxHires ((dword)1024 * 1024)
#define kcbMinLores ((kcbMinHires - kcbFixed) * 2 / 3 + kcbFixed)
#define kcbWarnLores ((kcbWarnHires - kcbFixed) * 2 / 3 + kcbFixed)
#define kcbMaxLores ((kcbMaxHires - kcbFixed) * 2 / 3 + kcbFixed)
bool fHires = (m_amm[m_immCurrent].nSizeData > 16);
dword cbMin, cbWarn, cbMax;
if (fHires) {
cbMin = kcbMinHires;
cbWarn = kcbWarnHires;
cbMax = kcbMaxHires;
} else {
cbMin = kcbMinLores;
cbWarn = kcbWarnLores;
cbMax = kcbMaxLores;
}
// How much storage memory is available?
dword cbDyn;
dword cbStorage;
MemMgr::GetInitSize(kcbDynReserve, &cbDyn, &cbStorage);
dword cbAvail = cbDyn + cbStorage;
// Prioritize storage memory size over # of save games, but keep at least
// a min save game size
// Calc space to reserve for save games
dword cbSaveGameReserve = kcbMinSaveGame;
if (cbAvail > cbMax) {
if (cbAvail - cbMax >= kcbMaxSaveGame)
cbSaveGameReserve = kcbMaxSaveGame;
}
// Calc what is left over
dword cbAlloc = 0;
if (cbAvail > cbSaveGameReserve)
cbAlloc = cbAvail - cbSaveGameReserve;
// If there is space left over, take it up to cbMax * 1 1/2
dword cbCeiling = cbMax * 3 / 2;
if (cbAlloc > cbCeiling)
cbAlloc = cbCeiling;
// Error if not enough memory
if (cbAlloc < cbMin) {
HostNotEnoughMemory(true, cbStorage, cbMin + cbSaveGameReserve - cbDyn);
return false;
}
// Warn if below threshold
if (cbAlloc < cbWarn)
HostMessageBox(TEXT("For better performance, increase the amount of memory available!"));
// Now initialize
dword cbTotal;
gmmgr.Init(kcbDynReserve, cbAlloc, &cbTotal);
return true;
}
#define kcmmGrow 32
void Game::AddModeMatches(int nDepthData, int nSizeData, int nDepthOrGreater, int cxWidthOrGreater)
{
HostOutputDebugString("AddModeMatches %d, %d, %d, %d",
nDepthData, nSizeData, nDepthOrGreater, cxWidthOrGreater);
int cmodes = gpdisp->GetModeCount();
for (int n = 0; n < cmodes; n++) {
ModeInfo mode;
gpdisp->GetModeInfo(n, &mode);
HostOutputDebugString("mode %d cx %d cy %d nDepth %d nDegree %d",
n, mode.cx, mode.cy, mode.nDepth, mode.nDegreeOrientation);
if (mode.nDepth < nDepthOrGreater)
continue;
if (mode.cx < cxWidthOrGreater)
continue;
if (mode.cy < cxWidthOrGreater)
continue;
if (m_cmm == m_cmmAlloc) {
ModeMatch *pmm = new ModeMatch[m_cmmAlloc + kcmmGrow];
if (pmm == NULL)
continue;
if (m_amm != NULL) {
memcpy(pmm, m_amm, sizeof(ModeMatch) * m_cmm);
delete[] m_amm;
}
m_amm = pmm;
m_cmmAlloc += kcmmGrow;
}
m_amm[m_cmm].imode = n;
m_amm[m_cmm].nDepthData = nDepthData;
m_amm[m_cmm].nSizeData = nSizeData;
m_cmm++;
}
}
#define kfMatchWidthBounded 1
#define kfMatchExactDepth 2
#define kfMatchNative 4
int Game::FindBestModeMatch2(int nDepthData, int nSizeData, int nDepthMode, int cxWidthModeMin, int cxWidthModeMax, byte bfMatch)
{
// Also, the largest Y is returned at a given width
int nBest = -1;
int cyLargestLast = -1;
int cxLargestLast = -1;
int nDepthClosestLast = 0x7fff;
for (int n = 0; n < m_cmm; n++) {
// Is this match for the requested data depth / data size?
ModeMatch *pmm = &m_amm[n];
if (pmm->nDepthData != nDepthData || pmm->nSizeData != nSizeData)
continue;
// Native modes only?
ModeInfo mode;
gpdisp->GetModeInfo(pmm->imode, &mode);
if (bfMatch & kfMatchNative) {
if (!mode.fNative)
continue;
}
// Is mode width compatible?
if (bfMatch & kfMatchWidthBounded) {
if (mode.cx < cxWidthModeMin || mode.cx > cxWidthModeMax)
continue;
} else {
if (mode.cx < cxWidthModeMin)
continue;
}
// Is depth compatible?
if (bfMatch & kfMatchExactDepth) {
// Must be exact depth
if (mode.nDepth != nDepthMode)
continue;
} else {
// Must be closest depth that is >= mode.nDepth
if (mode.nDepth < nDepthMode)
continue;
}
// Found a criteria match.
// Remember closest depth
if (mode.nDepth < nDepthClosestLast) {
nDepthClosestLast = mode.nDepth;
nBest = n;
}
// Remember largest width
if (mode.cx > cxLargestLast) {
cxLargestLast = mode.cx;
nBest = n;
}
// Remember largest Y
if (mode.cx == cxLargestLast && mode.cy > cyLargestLast) {
cyLargestLast = mode.cy;
nBest = n;
}
}
return nBest;
}
int Game::FindBestModeMatch(int nSizeDataAbove)
{
// Now find best match if we don't already have a match
static byte s_abfMatchOrder[] = {
kfMatchWidthBounded | kfMatchExactDepth, // width bounded exact depth
kfMatchWidthBounded, // width bounded closest depth
kfMatchExactDepth, // closest width exact depth
0, // closest width closest depth emulated
};
struct DataModePairs {
byte nDepthData;
byte nSizeData;
int nDepthMode;
int cxWidthModeMin;
int cxWidthModeMax;
};
static DataModePairs s_admp[] = {
// Color high res first
{ 8, 32, 8, 320, 9999 },
{ 8, 24, 8, 320, 9999 },
{ 8, 20, 8, 240, 319 },
{ 8, 16, 8, 160, 239 },
// Grayscale next at 8bpp (since grayscale data is stored 8bpp this is faster)
{ 4, 24, 8, 320, 9999 },
{ 4, 20, 8, 240, 319 },
{ 4, 16, 8, 160, 239 },
// Grayscale next at 4bpp
{ 4, 24, 4, 320, 9999 },
{ 4, 20, 4, 240, 319 },
{ 4, 16, 4, 160, 239 },
};
// Go through each data / mode pairing and perform searches based on the match order
// Find the best match
int immBest = -1;
for (int n = 0; n < ARRAYSIZE(s_admp); n++) {
DataModePairs *pdmp = &s_admp[n];
if (pdmp->nSizeData > nSizeDataAbove) {
for (int j = 0; j < ARRAYSIZE(s_abfMatchOrder); j++) {
immBest = FindBestModeMatch2(pdmp->nDepthData, pdmp->nSizeData, pdmp->nDepthMode, pdmp->cxWidthModeMin, pdmp->cxWidthModeMax, s_abfMatchOrder[j]);
if (immBest != -1)
break;
}
if (immBest != -1)
break;
}
}
return immBest;
}
bool Game::InitDisplay(int immRequested)
{
// Create a display.
gpdisp = HostCreateDisplay();
if (gpdisp == NULL)
return false;
gpsprm = gpdisp->GetSpriteManager();
// Find all raw data / mode matches
ddword ddwSizes8bpp = IsDataPresent(8);
ddword ddwSizes4bpp = IsDataPresent(4);
if (ddwSizes8bpp == 0 && ddwSizes4bpp == 0) {
HostMessageBox(TEXT("Incorrect game graphics version or no game graphics installed"));
return false;
}
if (ddwSizes8bpp & (((ddword)1) << 32))
AddModeMatches(8, 32, 8, 320); // 8 depth data, 24 size data, 8 or higher dst, 320 or higher dst
if (ddwSizes8bpp & (((ddword)1) << 24))
AddModeMatches(8, 24, 8, 320); // 8 depth data, 24 size data, 8 or higher dst, 320 or higher dst
if (ddwSizes8bpp & (((ddword)1) << 20))
AddModeMatches(8, 20, 8, 240);
if (ddwSizes8bpp & (((ddword)1) << 16))
AddModeMatches(8, 16, 8, 160);
if (ddwSizes4bpp & (((ddword)1) << 24))
AddModeMatches(4, 24, 4, 320); // 4 depth data, 24 size data, 4 or higher dst, 320 or higher dst
if (ddwSizes4bpp & (((ddword)1) << 20))
AddModeMatches(4, 20, 4, 240);
if (ddwSizes4bpp & (((ddword)1) << 16))
AddModeMatches(4, 16, 4, 160);
#if 0 // def DEBUG
// Print out mode matches
HostMessageBox("Mode Matches:");
ModeInfo mode;
for (int n = 0; n < m_cmm; n++) {
ModeMatch *pmm = &m_amm[n];
gpdisp->GetModeInfo(pmm->imode, &mode);
HostMessageBox("%dx%dx%d cyGraffiti=%d, depthData=%d, sizeData=%d", mode.cx, mode.cy, mode.nDepth, mode.cyGraffiti, pmm->nDepthData, pmm->nSizeData);
}
// Print out modes
HostMessageBox("Modes:");
for (n = 0; n < gpdisp->GetModeCount(); n++) {
gpdisp->GetModeInfo(n, &mode);
HostMessageBox("%dx%dx%d cyGraffiti=%d", mode.cx, mode.cy, mode.nDepth, mode.cyGraffiti);
}
#endif
// Find best match
int immBest = FindBestModeMatch(0);
// If we a not requesting a specific size, choose one
int immNew = immRequested;
if (immNew == -1) {
// If we found a best, check to see if it matches what is best in prefs. If it doesn't match best
// in prefs, then assume there has been a config change and ignore prefs, otherwise try to use
// prefs
if (immBest != -1) {
bool fMatchesPrefs = true;
ModeInfo mode;
gpdisp->GetModeInfo(m_amm[immBest].imode, &mode);
if (mode.cx != gprefsInit.cxModeBest)
fMatchesPrefs = false;
if (mode.cy != gprefsInit.cyModeBest)
fMatchesPrefs = false;
if (mode.nDepth != gprefsInit.nDepthModeBest)
fMatchesPrefs = false;
if (mode.nDegreeOrientation != gprefsInit.nDegreeOrientationBest)
fMatchesPrefs = false;
if (m_amm[immBest].nDepthData != gprefsInit.nDepthDataBest)
fMatchesPrefs = false;
if (m_amm[immBest].nSizeData != gprefsInit.nSizeDataBest)
fMatchesPrefs = false;
if (fMatchesPrefs) {
// Looks like there hasn't been a config change (new data / new device) so try
// to find the last settings.
for (int immT = 0; immT < m_cmm; immT++) {
ModeMatch *pmmT = &m_amm[immT];
ModeInfo modeT;
gpdisp->GetModeInfo(pmmT->imode, &modeT);
if (modeT.cx != gprefsInit.cxModeLast)
continue;
if (modeT.cy != gprefsInit.cyModeLast)
continue;
if (modeT.nDepth != gprefsInit.nDepthModeLast)
continue;
if (modeT.nDegreeOrientation != gprefsInit.nDegreeOrientationLast)
continue;
if (pmmT->nDepthData != gprefsInit.nDepthDataLast)
continue;
if (pmmT->nSizeData != gprefsInit.nSizeDataLast)
continue;
immNew = immT;
break;
}
if (immNew == -1) {
// Couldn't find what was requested. Use best
immNew = immBest;
}
} else {
// Doesn't match prefs, there has been a config change. Use what we think is best
// rather than prefs
immNew = immBest;
}
}
}
if (immNew == -1) {
HostMessageBox(TEXT("No suitable graphics mode found for installed graphics data"));
return false;
}
// If a mode was not explicitly chosen, bring up helpful suggestions about resolution / data size
if (immRequested == -1) {
#if 0
//doesn't work since if the higher res data isn't in memory then the option to use it won't be considered
// See if there is a mode that'll support higher-res graphics
int immBetter = FindBestModeMatch(m_amm[immNew].nSizeData);
if (immBetter != -1 && immBetter != immNew)
HostMessageBox(TEXT("This device supports higher resolution game graphics."));
#endif
// Give a message if grayscale data is being shown on a color device
if (m_amm[immNew].nDepthData < 8) {
for (int n = 0; n < gpdisp->GetModeCount(); n++) {
ModeInfo mode;
gpdisp->GetModeInfo(n, &mode);
if (mode.nDepth >= 8) {
HostMessageBox(TEXT("The game graphics database is grayscale and your device supports color. You may wish to install a color game graphics database."));
break;
}
}
}
}
// Try to set this mode
#if defined(WIN) && !defined(CE)
// If changing modes, use the default scale (-1)
int nScale = gprefsInit.nScale;
if (immRequested != -1)
nScale = -1;
if (!gpdisp->SetMode(m_amm[immNew].imode, nScale))
return false;
#else
if (!gpdisp->SetMode(m_amm[immNew].imode))
return false;
#endif
// Worked, remember new and best
m_immCurrent = immNew;
m_immBest = immBest;
// Get clipping dib
gpbmClip = gpdisp->GetClippingDib();
// Set the global fixed colors table
switch (m_amm[m_immCurrent].nDepthData) {
case 4:
gaclrFixed = gaclr4bpp;
gfGrayscale = true;
break;
case 8:
gaclrFixed = gaclr8bpp;
gfGrayscale = false;
break;
default:
Assert("Whoa! Unsupported color depth");
break;
}
return true;
}
void Game::RequestModeChange(int immNew)
{
// If using this already, nothing to do
if (immNew == m_immCurrent)
return;
// Stop the app; this'll cause it to restart with the new selection
gevm.SetAppStopping();
gimmReinitialize = immNew;
}
bool Game::InitMultiFormMgr()
{
// Create the MultiFormMgr
gpmfrmm = new MultiFormMgr();
Assert(gpmfrmm != NULL, "out of memory!");
if (gpmfrmm == NULL)
return false;
if (!gpmfrmm->Init(gpdisp->GetBackDib(), false))
return false;
// Grab dib size
DibBitmap *pbm = gpdisp->GetBackDib();
if (pbm == NULL)
return false;
Size sizDib;
pbm->GetSize(&sizDib);
// Initialize some global metrics used for drawing things as appropriate
// for the data size being used.
switch (m_amm[m_immCurrent].nSizeData) {
case 16:
gcxFullScreenFormDiv10 = 160 / 10;
break;
case 20:
gcxFullScreenFormDiv10 = 240 / 10;
break;
case 24:
case 32:
gcxFullScreenFormDiv10 = 320 / 10;
break;
}
gcxyBorder = PcFromFc(1);
// HT runs on known and future unknown screen modes. Because of this the main game form
// is dynamically sized and repositioned based on knowledge of how the game form is
// layed out.
TBitmap *ptbmMenuButton = LoadTBitmap("menuup.tbm");
if (ptbmMenuButton == NULL) {
return false;
}
Size sizMenuButton;
ptbmMenuButton->GetSize(&sizMenuButton);
delete ptbmMenuButton;
// cySim is the simulation area
// cyInput is the control stip at the bottom
// cyInputForm is present for capturing input in the graffiti area
int cyInput = sizMenuButton.cy;
int cySim = sizDib.cy - cyInput;
ModeInfo mode;
gpdisp->GetMode(&mode);
int cyInputForm = cyInput + mode.cyGraffiti;
// We know the playfield size
m_sizPlayfield.cx = sizDib.cx;
m_sizPlayfield.cy = cySim;
// Add two partitions
DibBitmap *pbmSim = pbm->Suballoc(0, cySim);
if (pbmSim == NULL)
return false;
gpfrmmSim = CreateFormMgr(pbmSim);
if (gpfrmmSim == NULL) {
delete pbmSim;
return false;
}
gpupdSim = gpfrmmSim->GetUpdateMap();
Rect rc;
rc.Set(0, 0, m_sizPlayfield.cx, cySim);
gpmfrmm->AddFormMgr(gpfrmmSim, &rc);
DibBitmap *pbmInput = pbm->Suballoc(cySim, cyInput);
if (pbmInput == NULL)
return false;
gpfrmmInput = CreateFormMgr(pbmInput);
if (gpfrmmInput == NULL) {
delete pbmInput;
return false;
}
gpfrmmInput->SetFlags(gpfrmmInput->GetFlags() | kfFrmmNoScroll);
rc.Set(0, cySim, m_sizPlayfield.cx, cySim + cyInputForm);
gpmfrmm->AddFormMgr(gpfrmmInput, &rc);
// Tell the display about the two formmgr's that make
// up the screen. Some displays will want this for managing
// the front dib specially.
gpdisp->SetFormMgrs(gpfrmmSim, gpfrmmInput);
return true;
}
void Game::ParseVersion(char *pszVersion, int *pnShipVersion, int *pnMajorVersion, char *pchMinorVersion)
{
// Parse the version.
// A.B[c], for example 1.0a
// A is ship version
// B is major version
// C is minor version
// Scan for ship version. Allow 0., or no ship version specified
int ichAfterDot = 0;
int c = IniScanf(pszVersion, "%d.%+", pnShipVersion, &ichAfterDot);
if (c == 0) {
*pnShipVersion = 0;
ichAfterDot = 0;
}
// Scan for major version. Do it this way to support 1.01 as less than 1.10
*pnMajorVersion = 0;
int ichAfterMajor = ichAfterDot;
if (pszVersion[ichAfterMajor] >= '0' && pszVersion[ichAfterMajor] <= '9') {
*pnMajorVersion += (pszVersion[ichAfterMajor] - '0') * 10;
ichAfterMajor++;
}
if (pszVersion[ichAfterMajor] >= '0' && pszVersion[ichAfterMajor] <= '9') {
*pnMajorVersion += (pszVersion[ichAfterMajor] - '0');
ichAfterMajor++;
}
// Scan for minor version. Allow none
char *pszAfterMajor = &pszVersion[ichAfterMajor];
if (*pszAfterMajor >= 'a' && *pszAfterMajor <= 'z') {
*pchMinorVersion = *pszAfterMajor;
} else {
*pchMinorVersion = 0;
}
}
bool Game::GetFormattedVersionString(char *pszVersion, char *pszOut)
{
int nShipVersion, nMajorVersion;
char chMinorVersion;
ggame.ParseVersion(pszVersion, &nShipVersion, &nMajorVersion, &chMinorVersion);
if (nShipVersion == 0 && nMajorVersion == 0 && chMinorVersion == 0)
return false;
if (nMajorVersion % 10 == 0) {
if (chMinorVersion == 0) {
sprintf(pszOut, "v%d.%d", nShipVersion, nMajorVersion / 10);
} else {
sprintf(pszOut, "v%d.%d%c", nShipVersion, nMajorVersion / 10, chMinorVersion);
}
} else {
if (chMinorVersion == 0) {
sprintf(pszOut, "v%d.%d%d", nShipVersion, nMajorVersion / 10, nMajorVersion % 10);
} else {
sprintf(pszOut, "v%d.%d%d%c", nShipVersion, nMajorVersion / 10, nMajorVersion % 10, chMinorVersion);
}
}
return true;
}
bool Game::CheckDatabaseVersion(const char *pszDir, char *pszPdb,
bool fUpwardCompatOK)
{
if (!gpakr.Push(pszDir, pszPdb))
return false;
#ifdef DEV_BUILD
// If DEV_BUILD, assume all database versions are compatible so that we can run in
// mismatched situations on purpose
gpakr.Pop();
return true;
#else
FileMap fmap;
char *pszDataVersion = (char *)gpakr.MapFile("version.txt", &fmap);
if (pszDataVersion == NULL) {
gpakr.Pop();
return false;
}
// Parse the version
int nVersionDataShip;
int nVersionDataMajor;
char chVersionDataMinor;
ParseVersion(pszDataVersion, &nVersionDataShip, &nVersionDataMajor, &chVersionDataMinor);
gpakr.UnmapFile(&fmap);
gpakr.Pop();
// Compare versions
return IsVersionCompatibleWithExe(nVersionDataShip, nVersionDataMajor, chVersionDataMinor, fUpwardCompatOK);
#endif
}
bool Game::IsVersionCompatibleWithExe(int nVersionCompareShip, int nVersionCompareMajor, char chVersionCompareMinor, bool fUpwardCompatOK)
{
// Unstamped EXEs can load any version database
if (stricmp(gszVersion + 1, "++VERSION+++") == 0) // this wierd +1 stuff is so the version stamper doesn't stamp this string
return true;
int nVersionShip, nVersionMajor;
char chVersionMinor;
ParseVersion(gszVersion, &nVersionShip, &nVersionMajor, &chVersionMinor);
if (fUpwardCompatOK) {
// If ship version less, not ok
if (nVersionShip < nVersionCompareShip)
return false;
// If ship version equal, check major
if (nVersionShip == nVersionCompareShip) {
if (nVersionMajor < nVersionCompareMajor)
return false;
}
} else {
// Ship versions must match
if (nVersionShip != nVersionCompareShip)
return false;
// Major versions must match too.
if (nVersionMajor != nVersionCompareMajor)
return false;
}
// Minor versions can be different.
return true;
}
ddword Game::IsDataPresent(int cBpp)
{
const char *pszMainDataDir = HostGetMainDataDir();
char szPdb[kcbFilename];
ddword ddwSizes = 0;
sprintf(szPdb, "htdata%d16.pdb", cBpp);
if (CheckDatabaseVersion(pszMainDataDir, szPdb, false))
ddwSizes |= (((ddword)1) << 16);
sprintf(szPdb, "htdata%d20.pdb", cBpp);
if (CheckDatabaseVersion(pszMainDataDir, szPdb, false))
ddwSizes |= (((ddword)1) << 20);
sprintf(szPdb, "htdata%d24.pdb", cBpp);
if (CheckDatabaseVersion(pszMainDataDir, szPdb, false))
ddwSizes |= (((ddword)1) << 24);
sprintf(szPdb, "htdata%d32.pdb", cBpp);
if (CheckDatabaseVersion(pszMainDataDir, szPdb, false))
ddwSizes |= (((ddword)1) << 32);
return ddwSizes;
}
// PlayLevel closes and deletes pstmSavedGame at its first opportunity or
// in the event of an error. Either way when it returns pstmSavedGame has
// been deleted.
int Game::PlayLevel(MissionIdentifier *pmiid, Stream *pstmSavedGame, int nRank)
{
Dictionary dictPvarsRetry;
m_szNextLevel[0] = 0;
if (pstmSavedGame != NULL) {
// Load MissionIdentifier
pstmSavedGame->Read(&m_miid.packid, sizeof(m_miid.packid));
pstmSavedGame->ReadString(m_miid.szLvlFilename,
sizeof(m_miid.szLvlFilename));
// Load next level filename
pstmSavedGame->ReadString(m_szNextLevel, sizeof(m_szNextLevel));
// Load pvars
m_dictPvars.LoadState(pstmSavedGame);
// Prep for level retry
dictPvarsRetry.Init(&m_dictPvars);
} else {
// This is the mission we want to load
m_miid = *pmiid;
// Clear pvars
m_dictPvars.Clear();
// hack for carrying forward a rank from the demo
if (nRank > 0) {
char szT[20];
itoa(nRank, szT, 10);
m_dictPvars.Set("rank", szT);
}
}
// Loop while the player retries the level or as new levels are set via SetNextLevel.
int nGo = knGoSuccess;
do {
// Randomness is good but not REAL randomness.
#ifdef STRESS
if (gfStress) {
SetRandomSeed(gtimm.GetTickCount());
} else {
SetRandomSeed(0x29a);
}
#else
SetRandomSeed(gfMultiplayer ? gtimm.GetTickCount() : 0x29a);
#endif
// Mount the pdb with the mission pack
if (!gppackm->Mount(gpakr, &m_miid.packid)) {
pstmSavedGame->Close();
delete pstmSavedGame;
nGo = knGoInitFailure;
break;
}
// The level can be changed by a SetNextMisson action. This is how
// we manage progressing from one level to the next.
if (pstmSavedGame != NULL) {
if (!gplrm.LoadState(pstmSavedGame)) {
gppackm->Unmount(gpakr, &m_miid.packid);
pstmSavedGame->Close();
delete pstmSavedGame;
nGo = knGoInitFailure;
break;
}
} else {
// Prep the Player instances that govern the local player and the
// computer players using the SideInfo contained in the level.
gplrm.Init(m_miid.szLvlFilename);
}
// Play the level until the player loses, exits, or advances to the
// next level
nGo = RunSimulation(pstmSavedGame, m_miid.szLvlFilename, 0, 0, NULL);
// Only use the saved game stream the first time through. Any
// restarts start from the beginning of the level.
pstmSavedGame = NULL;
gppackm->Unmount(gpakr, &m_miid.packid);
switch (nGo) {
case knGoAppStop:
case knGoInitFailure:
case knGoLoadSavedGame:
return nGo;
case knGoSuccess:
// If no next level, we're done.
if (m_szNextLevel[0] == 0) {
if (gfDemo &&
(stricmp(m_miid.szLvlFilename, "d_03.lvl") == 0)) {
// if player just finished D3, save rank in prefs so we
// know to start from M3 when they buy a new copy. If they
// played D3 as a challenge mission, their rank will be -1
char szT[kcbPvarValueMax];
if (ggame.GetVar("rank", szT, sizeof(szT)))
gnDemoRank = atoi(szT);
if (gnDemoRank < 0)
gnDemoRank = 0;
SavePreferences();
}
nGo = knGoAbortLevel;
} else {
// Mark this mission complete!
gpcptm->MarkComplete(&m_miid);
// Copy over new mission filename
strncpyz(m_miid.szLvlFilename, m_szNextLevel,
sizeof(m_miid.szLvlFilename));
// Successful level completion carries pvars forward
dictPvarsRetry.Init(&m_dictPvars);
m_szNextLevel[0] = 0;
}
break;
// Retrying a level reverts pvars back to the state they were at upon
// level entry
case knGoRetryLevel:
m_dictPvars.Init(&dictPvarsRetry);
break;
default:
// Clear pvars
m_dictPvars.Clear();
dictPvarsRetry.Clear();
break;
}
} while (nGo != knGoAbortLevel);
return nGo;
}
// RunSimulation will Close and delete the passed-in Stream before returning
int Game::RunSimulation(Stream *pstm, char *pszLevel, word wfRole,
dword gameid, Chatter *chatter)
{
DialogForm *pfrm = (DialogForm *)gpmfrmm->LoadForm(gpiniForms, kidfLoading, new DialogForm());
if (pfrm != NULL) {
pfrm->SetBackgroundColorIndex(kiclrBlack);
pfrm->SetTitleColor(kiclrBlack);
pfrm->SetBorderColorIndex(kiclr0CyanSide);
gpmfrmm->DrawFrame(false);
}
// Retain whether or not this is a multiplayer game. Inquiring minds will
// want to know later.
m_wf &= ~(kfGameMultiplayer | kfGameRoleServer);
if (wfRole & kfRoleMultiplayer) {
m_wf |= kfGameMultiplayer;
gfMultiplayer = true;
// Pvars shouldn't carry into multiplayer games.
m_dictPvars.Clear();
} else {
gfMultiplayer = false;
}
if (wfRole & kfRoleServer)
m_wf |= kfGameRoleServer;
gsndm.WaitSilence();
bool fSuccess = InitSimulation(pstm, pszLevel, wfRole, gameid, chatter);
if (pstm != NULL) {
if (!pstm->IsSuccess())
fSuccess = false;
pstm->Close();
delete pstm;
}
delete pfrm;
if (!fSuccess) {
gfMultiplayer = false;
return knGoInitFailure;
}
// Clear fog if asked ("-nofog" command line switch)
//temp
#if 0
gfClearFog = true;
#endif
if (gfClearFog)
gsim.GetLevel()->GetFogMap()->RevealAll(gpupdSim);
// Game loop
m_fUpdateTriggers = false;
int nGo;
Event evt;
while (true) {
if (!gevm.GetEvent(&evt, -1, false)) {
continue;
}
if (FilterEvent(&evt))
continue;
if (evt.eType == appStopEvent) {
// Warn about multiplayer if the player pressed the home button
if (m_wf & kfGameMultiplayer) {
if (!AskResignGame())
continue;
}
if (evt.dw != knAppExit)
SaveReinitializeGame();
nGo = knGoAppStop;
break;
}
if (evt.eType == gameOverEvent) {
nGo = (int)evt.dw;
break;
}
gevm.DispatchEvent(&evt);
}
ExitSimulation();
// Eat potential cancel mode messages that due to MP exit ordering got
// posted after gameOverEvent
if (gevm.PeekEvent(&evt, 0)) {
if (evt.eType == cancelModeEvent)
gevm.GetEvent(&evt, 0);
}
// Assume not multiplayer anymore
gfMultiplayer = false;
m_wf &= ~(kfGameMultiplayer | kfGameRoleServer);
return nGo;
}
bool Game::AskResignGame(bool fTellHost)
{
bool fAppStopping = gevm.IsAppStopping();
bool fAsk = true;
#if defined(IPHONE) || defined(__IPHONEOS__) || defined (__ANDROID__)
// When an iPhone or Android game exits, it exits without the opportunity to
// confirm with the user. So if iPhone and the app is stopping, just
// resign.
if (fAppStopping) {
fAsk = false;
}
#endif
Assert(m_wf & kfGameMultiplayer);
if (fAsk) {
// If app stopping is set
if (fAppStopping) {
gevm.ClearAppStopping();
}
// Ask user
char sz[128];
if (m_wf & kfGameRoleServer) {
gpstrtbl->GetString(kidsExitHost, sz, sizeof(sz));
} else {
gpstrtbl->GetString(kidsExitClient, sz, sizeof(sz));
}
// If the user wants to keep going, return with "app stopping" cleared
if (!HtMessageBox(kidfMessageBoxQuery, kfMbWhiteBorder | kfMbKeepTimersEnabled, "Warning", sz))
return false;
// If app stopping set it again
if (fAppStopping) {
gevm.SetAppStopping();
}
}
if (fTellHost && gptra != NULL) {
// Tell host
PlayerResignNetMessage prng;
prng.pid = gpplrLocal->GetId();
gptra->SendNetMessage(&prng);
}
return true;
}
bool Game::AskObserveGame()
{
bool fYes = false;
gpplrLocal->SetFlags(gpplrLocal->GetFlags() | kfPlrObserver);
SimUIForm *psui = ggame.GetSimUIForm();
psui->ClearSelection();
if (!gevm.IsAppStopping()) {
if (psui->GetRole() & kfRoleServer) {
fYes = HtMessageBox(kidfMessageBoxQuery, kfMbKeepTimersEnabled | kfMbWhiteBorder,
"GAME OVER", "Do you want to continue as an observer? Since this device hosting, answering no will stop the game.");
} else {
fYes = HtMessageBox(kidfMessageBoxQuery, kfMbKeepTimersEnabled | kfMbWhiteBorder,
"GAME OVER", "Do you want to continue as an observer?");
}
if (fYes) {
psui->SetObserving();
}
}
return fYes;
}
bool Game::SaveReinitializeGame()
{
if (m_fSkipSaveReinitialize) {
return false;
}
// We don't save "reinitialize" games when playing multiplayer
if (gfMultiplayer)
return false;
Status("** Saving Game for Return **");
Stream *pstm = HostNewSaveGameStream(knGameReinitializeSave, "Reinitialize");
Assert(pstm != NULL);
if (pstm != NULL) {
ggame.SaveGame(pstm); // SaveGame closes and deletes the stream
return true;
}
return false;
}
bool Game::InitSimulation(Stream *pstm, char *pszLevel, word wfRole,
dword gameid, Chatter *chatter) {
// Perform one-time Simulation initialization. This might seem like a
// strange place to do it but the goal is to put off the expensive stuff
// it does so it doesn't slow game launch time.
m_gameid = gameid;
if (m_fSimUninitialized) {
Status("Simulation Init (one-time)...");
if (!gsim.OneTimeInit())
return false;
m_fSimUninitialized = false;
}
Status("Simulation Init (per-level)...");
if (!gsim.PerLevelInit())
return false;
// Initialize the Simulation StateMachineMgr
if (!gsmm.Init(&gsim)) {
gsim.PerLevelExit();
return false;
}
// Either loading a saved game or a new level
if (pstm == NULL) {
// Load the first level
if (!gsim.LoadLevel(pszLevel)) {
gsim.PerLevelExit();
return false;
}
} else {
if (!gsim.LoadState(pstm)) {
gsim.PerLevelExit();
return false;
}
}
// Load the sim UI form
m_pfrmSimUI = (SimUIForm *)gpfrmmSim->LoadForm(gpiniForms, kidfSimUI, new SimUIForm(wfRole, gameid, chatter));
if (m_pfrmSimUI == NULL) {
Assert("LoadForm(SimUIForm) failed");
gsim.PerLevelExit();
return false;
}
m_pfrmSimUI->CalcLevelSpecificConstants();
// Load the input UI form
m_pfrmInputUI = (InputUIForm *)gpfrmmInput->LoadForm(gpiniForms, kidfInputUI, new InputUIForm(chatter));
if (m_pfrmInputUI == NULL) {
Assert("LoadForm(InputUIForm) failed");
delete m_pfrmSimUI;
m_pfrmSimUI = NULL;
gsim.PerLevelExit();
return false;
}
// Load minimap form
m_pfrmMiniMap = gpmfrmm->LoadForm(gpiniForms, kidfMiniMap, new Form());
if (m_pfrmMiniMap == NULL) {
Assert("LoadForm(Minimap) failed");
delete m_pfrmInputUI;
m_pfrmInputUI = NULL;
delete m_pfrmSimUI;
m_pfrmSimUI = NULL;
gsim.PerLevelExit();
return false;
}
// This form doesn't want key events
m_pfrmMiniMap->SetFlags(m_pfrmMiniMap->GetFlags() | kfFrmNoFocus);
// Position minimap
MiniMapControl *pctlMiniMap = (MiniMapControl *)m_pfrmMiniMap->GetControlPtr(kidcMiniMap);
Assert(pctlMiniMap != NULL);
pctlMiniMap->SetPosition(0, 0);
Rect rcMiniMap;
pctlMiniMap->GetRect(&rcMiniMap);
Size sizDibT;
gpmfrmm->GetDib()->GetSize(&sizDibT);
Rect rcT;
rcT.Set(sizDibT.cx - rcMiniMap.Width(), sizDibT.cy - rcMiniMap.Height(), sizDibT.cx, sizDibT.cy);
m_pfrmMiniMap->SetRect(&rcT);
// This hack is so the Simulation's first Update occurs before its first
// paint. By doing it this way the MissionLoaded trigger gets to determine
// the first image the player sees. Otherwise the map will paint first,
// then when the update timer goes off 80 milliseconds later the
// MissionLoaded trigger will go off and pop up an ecom, reposition the
// view, etc.
if ((wfRole & kfRoleMultiplayer) == 0)
m_pfrmSimUI->OnTimer(0);
// Clear the screen so the ugly palette change isn't apparent
ClearDisplay();
// Set palette
Palette *ppal = gsim.GetLevel()->GetPalette();
SetHslAdjustedPalette(ppal, gnHueOffset, gnSatMultiplier, gnLumOffset);
gmpiclriclrShadow = gsim.GetLevel()->GetShadowMap();
// Tell the sprite manager the clipping rects
Rect rcClip1;
rcClip1.left = 0;
rcClip1.top = 0;
rcClip1.right = m_sizPlayfield.cx - rcMiniMap.Width();
rcClip1.bottom = m_sizPlayfield.cy;
Rect rcClip2;
rcClip2.left = rcClip1.right;
rcClip2.top = 0;
rcClip2.right = m_sizPlayfield.cx;
rcClip2.bottom = rcT.top;
SpriteManager *psprm = gpdisp->GetSpriteManager();
if (psprm != NULL) {
psprm->SetClipRects(&rcClip1, &rcClip2);
}
return true;
}
void Game::ClearDisplay()
{
if (gpdisp == NULL)
return;
DibBitmap *pbmBack = gpdisp->GetBackDib();
if (pbmBack == NULL)
return;
pbmBack->Clear(GetColor(kiclrBlack));
if (gpmfrmm != NULL) {
gpmfrmm->InvalidateRect(NULL);
gpmfrmm->DrawFrame(false, false);
gpmfrmm->InvalidateRect(NULL);
}
}
void Game::ExitSimulation()
{
Status("Exit Simulation (per-level)...");
gsim.PerLevelExit();
gsmm.ClearDelayedMessages();
delete m_pfrmSimUI;
m_pfrmSimUI = NULL;
delete m_pfrmInputUI;
m_pfrmInputUI = NULL;
delete m_pfrmMiniMap;
m_pfrmMiniMap = NULL;
// Before the shell changes the palette...
ClearDisplay();
// Too many places are forgetting to set the palette when the
// simulation exits, so set it back to the shell palette and
// shadow map here. Note InitSimulation sets it to the level
// palette / shadowmap, so this is appropriate.
gshl.SetPalette();
}
void Game::Exit()
{
#if defined(WIN) && !defined(CE)
// If the game exits without pressing F8, stop the avi recording automatically
if (gpavir != NULL) {
delete gpavir;
gpavir = NULL;
}
#endif
// Save preferences
SavePreferences();
Status("Exit Simulation (one-time)...");
gsim.OneTimeExit();
// Clear so that when PalmOS switches palette we don't have screen trash
ClearDisplay();
m_pfrmSimUI = NULL;
delete gpmfrmm;
gpmfrmm = NULL;
delete gpiniForms;
gpiniForms = NULL;
delete gpiniGame;
gpiniGame = NULL;
if (m_wf & kfGameInitFonts) {
for (int ifnt = 0; ifnt < kcFonts; ifnt++) {
delete gapfnt[ifnt];
gapfnt[ifnt] = NULL;
}
m_wf &= ~kfGameInitFonts;
}
gsndm.Exit();
ButtonControl::ExitClass();
CheckBoxControl::ExitClass();
PipMeterControl::ExitClass();
ListControl::ExitClass();
DamageMeterControl::ExitClass();
FreeSharedTBitmaps();
if (m_wf & kfGameInitBitmap) {
TBitmap::ExitClass();
m_wf &= ~kfGameInitBitmap;
}
gshl.Exit();
delete gpstrtbl;
gpstrtbl = NULL;
// Pop off all data files
while (gpakr.Pop())
;
if (gmpWcFromPc != NULL) {
gmmgr.FreePtr(gmpWcFromPc);
gmpWcFromPc = NULL;
}
if (gmpPcFromWc != NULL) {
gmmgr.FreePtr(gmpPcFromWc);
gmpPcFromWc = NULL;
}
delete gpdisp;
gpdisp = NULL;
delete[] m_amm;
m_amm = NULL;
m_cmm = 0;
m_cmmAlloc = 0;
gplrm.Reset();
gcam.Exit();
gmmgr.Exit();
// Depends on gpbScratch so clean up before deleting
#if defined(PIL) && defined(TRACE_TO_DB_LOG)
DbLogExit();
#endif
delete[] gpbScratch;
gpbScratch = NULL;
m_fSimUninitialized = true;
}
void Game::ScheduleUpdateTriggers() {
m_fUpdateTriggers = true;
// Force GetEvent to return a message. This will cause FilterEvent
// to get called, where the check for m_fUpdateTriggers will be made,
// to update triggers immediately.
Event evt;
evt.eType = updateTriggersEvent;
gevm.PostEvent(&evt);
}
void Game::UpdateTriggers() {
// Always run triggers this way, so that trigger execution is in sync
// with multiplayer updates. This also allows triggers to go into modal
// loops, while the simulation continues (with special care).
if (m_fUpdateTriggers) {
m_fUpdateTriggers = false;
if (gsim.GetLevel() != NULL) {
if (gsim.GetLevel()->GetTriggerMgr() != NULL)
gsim.GetLevel()->GetTriggerMgr()->Update();
}
}
}
bool Game::FilterEvent(Event *pevt)
{
#ifdef INCL_VALIDATEHEAP
gmmgr.Validate();
#endif
UpdateTriggers();
switch (pevt->eType) {
case checkGameOverEvent:
if (m_pfrmSimUI != NULL) {
m_pfrmSimUI->CheckMultiplayerGameOver((Pid)pevt->dw);
}
break;
case gamePaintEvent:
// Keep track of # of paints for fps stat
extern short gcPaint;
gcPaint++;
// Draw
gpmfrmm->DrawFrame(false);
return true;
case gameSuspendEvent:
// OS alert is showing.
Suspend();
return true;
case transportEvent:
if (gptra != NULL) {
gptra->OnEvent(pevt);
}
return true;
case mpEndMissionWinEvent:
EndMissionAction::OnMPEndMissionActionEvent(knWinLoseTypeWin,
pevt->dw);
break;
case mpEndMissionLoseEvent:
EndMissionAction::OnMPEndMissionActionEvent(knWinLoseTypeLose,
pevt->dw);
break;
case mpShowObjectivesEvent:
ShowObjectivesAction::OnMPShowObjectivesEvent(pevt->dw);
break;
case disableSoundEvent:
gsndm.SaveStateAndClear();
break;
case enableSoundEvent:
gsndm.RestoreState();
break;
}
return false;
}
char *gszPaused = "PAUSED";
void Game::Suspend()
{
// Force back buffer to be valid
gpmfrmm->DrawFrame(true);
// Darken back buffer
DibBitmap *pbmBack = gpdisp->GetBackDib();
Size sizDib;
pbmBack->GetSize(&sizDib);
pbmBack->Shadow(0, 0, sizDib.cx, sizDib.cy);
// Draw "Paused" sign in the middle
// TODO: Make prettier
Font *pfnt = gapfnt[kifntTitle];
int cxText = pfnt->GetTextExtent(gszPaused);
int cyText = pfnt->GetHeight();
int cxBox = cxText * 3;
int cyBox = cyText * 3;
int x = (sizDib.cx - cxBox) / 2;
int y = (sizDib.cy - cyBox) / 2;
pbmBack->Fill(x, y, cxBox, cyBox, GetColor(kiclrBlack));
pfnt->DrawText(pbmBack, gszPaused, x + cxText, y + cyText);
// Call host. This is a modal loop. When it returns, the game isn't
// paused anymore
gsndm.SaveStateAndClear();
HostSuspendModalLoop(pbmBack);
gsndm.RestoreState();
// Ok, invalidate everything so it all redraws
gpmfrmm->InvalidateRect(NULL);
}
void Game::GetPlayfieldSize(Size *psiz)
{
*psiz = m_sizPlayfield;
}
SimUIForm *Game::GetSimUIForm()
{
return m_pfrmSimUI;
}
InputUIForm *Game::GetInputUIForm()
{
return m_pfrmInputUI;
}
Form *Game::GetMiniMapForm()
{
return m_pfrmMiniMap;
}
#define knVerGameState 9
int Game::PlaySavedGame(Stream *pstm)
{
// Overall, look at build version first
char szVersion[32];
szVersion[0] = 0;
pstm->ReadString(szVersion, sizeof(szVersion));
// Read version
bool fStale = false;
byte nVer = pstm->ReadByte();
if (nVer != knVerGameState)
fStale = true;
// Read platform
if (!fStale) {
byte bPlatform = 0;
if (nVer >= 6)
bPlatform = pstm->ReadByte();
// Check version
if (!CheckSaveGameVersion(szVersion, bPlatform))
fStale = true;
}
// If old, bail
if (fStale) {
pstm->Close();
delete pstm;
HtMessageBox(kfMbWhiteBorder | kfMbClearDib, "Error", "Save game out of date!");
return knGoFailure;
}
// PlayLevel will close and delete pstm, guaranteed.
return PlayLevel(NULL, pstm);
}
void Game::SaveGame(Stream *pstm)
{
if (pstm == NULL) {
if (!PickSaveGameStream(&pstm))
return;
}
bool fSuccess = false;
if (pstm != NULL) {
// Write build version string first
// NOTE: these first 3 entries are assumed in CheckSaveGameVersion() and DeleteStaleSaveGames()!!
pstm->WriteString(gszVersion);
// Save version
pstm->WriteByte(knVerGameState);
// Save platform byte. Needed for platform compatibility check (68K->ARM transition)
#ifdef PIL
#ifdef PNO
// ARM saved game
pstm->WriteByte(1);
#else
// 68K saved game
pstm->WriteByte(0);
#endif
#else
// Whatever, doesn't matter since it isn't Palm
pstm->WriteByte(0);
#endif
// Save mission identifier
pstm->Write(&m_miid.packid, sizeof(m_miid.packid));
pstm->WriteString(m_miid.szLvlFilename);
// Save next level filename
pstm->WriteString(m_szNextLevel);
// Save pvars
m_dictPvars.SaveState(pstm);
// Save player info
gplrm.SaveState(pstm);
// Save simulation
gsim.SaveState(pstm);
// Check for error
fSuccess = pstm->IsSuccess();
}
if (!fSuccess)
HtMessageBox(0, "Save Game", "Error saving game! This device is too low on memory to save this game. Save over an existing same game or free some space and try again.");
if (pstm != NULL) {
pstm->Close();
delete pstm;
}
}
void Game::SetNextLevel(char *pszLevel)
{
// Perform simple lower case
char szT[sizeof(m_szNextLevel)];
strncpyz(szT, pszLevel, sizeof(szT));
char *pszT = szT;
while (*pszT != 0) {
if (*pszT >= 'A' && *pszT <= 'Z')
(*pszT) += 'a' - 'A';
pszT++;
}
// Some missions don't have correct "next level" filenames,
// in that .lvl isn't there. Append if it's not there.
int cch = (int)strlen(szT);
if (sizeof(szT) - 1 - cch >= 4) {
bool fAppend = false;
if (cch < 4) {
fAppend = true;
} else if (szT[cch - 4] != '.' ||
szT[cch - 3] != 'l' ||
szT[cch - 2] != 'v' ||
szT[cch - 1] != 'l') {
fAppend = true;
}
if (fAppend) {
strncpyz(&szT[cch], ".lvl",
sizeof(szT) - cch);
}
}
strncpyz(m_szNextLevel, szT, sizeof(m_szNextLevel));
}
char *Game::GetNextLevel()
{
return m_szNextLevel;
}
bool Game::IsMultiplayer()
{
return (m_wf & kfGameMultiplayer) != 0;
}
void Game::SetGameSpeed(int t)
{
gtGameSpeed = t;
if (m_pfrmSimUI != NULL)
gtimm.SetTimerRate(m_pfrmSimUI, gtGameSpeed);
}
// Preferences support.
//
// Note always save and load assuming a storage format of big endian.
// That way we don't have a headache when someone upgrades from a 68K Palm to a
// ARM Palm with ARM HT and then HotSyncs, restoring a little endian preferences database.
Preferences gprefsInit;
void Game::LoadPreferences()
{
// Try to load preferences. If this fails,
// initialize preferences to default values.
// gprefsInit is a global that holds the preferences that are used during initialization
// It's a global because we won't be able to process all preferences related initialization
// here.
if (!LoadPreferences2()) {
// Initialize preferences to default values
memset(&gprefsInit, 0, sizeof(gprefsInit));
Date date;
HostGetCurrentDate(&date);
gprefsInit.fAnonymous = 0;
gprefsInit.nYearLastRun = date.nYear;
gprefsInit.nMonthLastRun = date.nMonth;
gprefsInit.nDayLastRun = date.nDay;
gprefsInit.nVolume = (word)-1;
gprefsInit.wfPerfOptions = kfPerfMax;
gprefsInit.ctGameSpeed = kctUpdate / 2;
gprefsInit.wfHandicap = kfHcapDefault;
#if defined(WIN) && !defined(CE)
gprefsInit.nScale = -1;
#endif
gprefsInit.nScrollSpeed = 1.0;
strncpyz(gprefsInit.szAskURL, "http://", sizeof(gprefsInit.szAskURL));
strncpyz(gprefsInit.szDeviceId, HostGenerateDeviceId(), sizeof(gprefsInit.szDeviceId));
}
}
bool Game::LoadPreferences2()
{
Preferences prefs;
int cbRead = HostLoadPreferences(&prefs, sizeof(prefs));
if (cbRead != (int)BigDword(prefs.prefv.cbSize))
return false;
switch (BigDword(prefs.prefv.dwVersion)) {
case knVersionPreferencesV100:
if (cbRead != sizeof(PreferencesV100))
return false;
if (!LoadPreferencesV100((PreferencesV100 *)&prefs))
return false;
strncpyz(gprefsInit.szDeviceId, HostGenerateDeviceId(), sizeof(gprefsInit.szDeviceId));
return true;
case knVersionPreferencesV101:
if (cbRead != sizeof(PreferencesV101))
return false;
return LoadPreferencesV101((PreferencesV101 *)&prefs);
}
return false;
}
bool Game::LoadPreferencesV100(PreferencesV100 *pprefsV100)
{
strncpyz(gprefsInit.szUsername, pprefsV100->szUsername,
sizeof(gprefsInit.szUsername));
strncpyz(gprefsInit.szPassword, pprefsV100->szPassword,
sizeof(gprefsInit.szPassword));
strncpyz(gprefsInit.szToken, pprefsV100->szToken,
sizeof(gprefsInit.szToken));
gprefsInit.fAnonymous = BigWord(pprefsV100->fAnonymous);
gprefsInit.nYearLastRun = BigWord(pprefsV100->nYearLastRun);
gprefsInit.nMonthLastRun = BigWord(pprefsV100->nMonthLastRun);
gprefsInit.nDayLastRun = BigWord(pprefsV100->nDayLastRun);
gprefsInit.nVolume = BigWord(pprefsV100->nVolume);
gprefsInit.wfPrefs = BigWord(pprefsV100->wfPrefs);
gprefsInit.wfPerfOptions = BigWord(pprefsV100->wfPerfOptions);
gprefsInit.ctGameSpeed = BigDword(pprefsV100->ctGameSpeed);
gprefsInit.fLassoSelection = BigWord(pprefsV100->fLassoSelection);
gprefsInit.nHueOffset = BigWord(pprefsV100->nHueOffset);
gprefsInit.nSatMultiplier = BigWord(pprefsV100->nSatMultiplier);
gprefsInit.nLumOffset = BigWord(pprefsV100->nLumOffset);
gprefsInit.cxModeBest = BigWord(pprefsV100->cxModeBest);
gprefsInit.cyModeBest = BigWord(pprefsV100->cyModeBest);
gprefsInit.nDepthModeBest = BigWord(pprefsV100->nDepthModeBest);
gprefsInit.nDepthDataBest = BigWord(pprefsV100->nDepthDataBest);
gprefsInit.nSizeDataBest = BigWord(pprefsV100->nSizeDataBest);
gprefsInit.nDegreeOrientationBest = BigWord(pprefsV100->nDegreeOrientationBest);
gprefsInit.cxModeLast = BigWord(pprefsV100->cxModeLast);
gprefsInit.cyModeLast = BigWord(pprefsV100->cyModeLast);
gprefsInit.nDepthModeLast = BigWord(pprefsV100->nDepthModeLast);
gprefsInit.nDepthDataLast = BigWord(pprefsV100->nDepthDataLast);
gprefsInit.nSizeDataLast = BigWord(pprefsV100->nSizeDataLast);
gprefsInit.nDegreeOrientationLast = BigWord(pprefsV100->nDegreeOrientationLast);
gprefsInit.nDemoRank = BigWord(pprefsV100->nDemoRank);
gprefsInit.nScrollSpeed = pprefsV100->nScrollSpeed;
strncpyz(gprefsInit.szAskURL, pprefsV100->szAskURL,
sizeof(gprefsInit.szAskURL));
// Migrate obsolete handicap combinations
gprefsInit.wfHandicap = BigWord(pprefsV100->wfHandicap);
if (gprefsInit.wfHandicap == 0) // old hard
gprefsInit.wfHandicap = kfHcapHard; // new hard
else if (gprefsInit.wfHandicap == (kfHcapDecreasedTimeToBuild | kfHcapIncreasedFirePower | kfHcapIncreasedMinerLoadValue | kfHcapShowEnemyBuildProgress | kfHcapShowEnemyResourceStatus))
gprefsInit.wfHandicap = kfHcapEasy;
else if (gprefsInit.wfHandicap == (kfHcapDecreasedTimeToBuild | kfHcapShowEnemyBuildProgress | kfHcapShowEnemyResourceStatus))
gprefsInit.wfHandicap = kfHcapNormal;
else if (gprefsInit.wfHandicap != kfHcapEasy && gprefsInit.wfHandicap != kfHcapNormal && gprefsInit.wfHandicap != kfHcapHard)
gprefsInit.wfHandicap = kfHcapDefault;
gprefsInit.key = pprefsV100->key;
#if defined(WIN) && !defined(CE)
gprefsInit.nScale = BigWord(pprefsV100->nScale);
#endif
return true;
}
bool Game::LoadPreferencesV101(PreferencesV101 *pprefsV101)
{
// Since order is the same as V100 except for extra fields at the end, load V100 first
if (!LoadPreferencesV100((PreferencesV100 *)pprefsV101))
return false;
// PreferencesV101 has this new field, load it
strncpyz(gprefsInit.szDeviceId, pprefsV101->szDeviceId, sizeof(gprefsInit.szDeviceId));
return true;
}
void Game::SavePreferences()
{
// Only if we've gone through initialization ok
if (!(m_wf & kfGameInitDone))
return;
// Always save the latest version of the preferences
Preferences prefs;
prefs.prefv.dwVersion = BigDword(knVersionPreferences);
prefs.prefv.cbSize = BigDword(sizeof(prefs));
strncpyz(prefs.szUsername, gszUsername, sizeof(prefs.szUsername));
strncpyz(prefs.szPassword, gszPassword, sizeof(prefs.szPassword));
strncpyz(prefs.szToken, gszToken, sizeof(prefs.szToken));
prefs.fAnonymous = gfAnonymous ? BigWord(1) : 0;
Date date;
HostGetCurrentDate(&date);
prefs.nYearLastRun = BigWord(date.nYear);
prefs.nMonthLastRun = BigWord(date.nMonth);
prefs.nDayLastRun = BigWord(date.nDay);
prefs.nVolume = BigWord(gsndm.GetVolume());
word wfPrefs = gfIgnoreBluetoothWarning ? kfPrefIgnoreBluetoothWarning : 0;
prefs.wfPrefs = BigWord(wfPrefs);
prefs.wfPerfOptions = BigWord((gwfPerfOptions & kfPerfAll) | (kfPerfMax & ~kfPerfAll));
prefs.ctGameSpeed = BigDword((dword)gtGameSpeed);
prefs.fLassoSelection = gfLassoSelection ? BigWord(1) : 0;
prefs.nHueOffset = BigWord(gnHueOffset);
prefs.nSatMultiplier = BigWord(gnSatMultiplier);
prefs.nLumOffset = BigWord(gnLumOffset);
prefs.wfHandicap = BigWord(gwfHandicap);
prefs.nDemoRank = BigWord(gnDemoRank);
prefs.nScrollSpeed = gnScrollSpeed;
strncpyz(prefs.szAskURL, gszAskURL, sizeof(prefs.szAskURL));
strncpyz(prefs.szDeviceId, gszDeviceId, sizeof(prefs.szDeviceId));
if (gpdisp == NULL || m_immBest == -1) {
prefs.cxModeBest = 0;
prefs.cyModeBest = 0;
prefs.nDepthModeBest = 0;
prefs.nDepthDataBest = 0;
prefs.nSizeDataBest = 0;
prefs.nDegreeOrientationBest = 0;
} else {
ModeInfo mode;
gpdisp->GetModeInfo(m_amm[m_immBest].imode, &mode);
prefs.cxModeBest = BigWord(mode.cx);
prefs.cyModeBest = BigWord(mode.cy);
prefs.nDepthModeBest = BigWord(mode.nDepth);
prefs.nDepthDataBest = BigWord(m_amm[m_immBest].nDepthData);
prefs.nSizeDataBest = BigWord(m_amm[m_immBest].nSizeData);
prefs.nDegreeOrientationBest = BigWord(mode.nDegreeOrientation);
}
if (gpdisp == NULL || m_immCurrent == -1) {
prefs.cxModeLast = 0;
prefs.cyModeLast = 0;
prefs.nDepthModeLast = 0;
prefs.nDepthDataLast = 0;
prefs.nSizeDataLast = 0;
prefs.nDegreeOrientationLast = 0;
} else {
ModeInfo mode;
gpdisp->GetModeInfo(m_amm[m_immCurrent].imode, &mode);
prefs.cxModeLast = BigWord(mode.cx);
prefs.cyModeLast = BigWord(mode.cy);
prefs.nDepthModeLast = BigWord(mode.nDepth);
prefs.nDepthDataLast = BigWord(m_amm[m_immCurrent].nDepthData);
prefs.nSizeDataLast = BigWord(m_amm[m_immCurrent].nSizeData);
prefs.nDegreeOrientationLast = BigWord(mode.nDegreeOrientation);
}
prefs.key = gkey;
#if defined(WIN) && !defined(CE)
if (gpdisp == NULL) {
prefs.nScale = (word)-1;
} else {
prefs.nScale = BigWord(gpdisp->GetScale());
}
#endif
HostSavePreferences(&prefs, sizeof(prefs));
}
bool Game::GetVar(const char *pszName, char *pszBuff, int cbBuff)
{
// 'system variable'
if (*pszName == '$') {
if (stricmp(pszName, "$ranktitle") == 0) {
GetRankTitle(pszBuff, cbBuff);
} else if (stricmp(pszName, "$grayscale") == 0) {
strncpyz(pszBuff, gfGrayscale ? "1" : "0", cbBuff);
} else if (stricmp(pszName, "$iphone") == 0) {
#if defined(IPHONE) || defined(__IPHONEOS__)
strncpyz(pszBuff, "1", cbBuff);
#else
strncpyz(pszBuff, "0", cbBuff);
#endif
} else if (stricmp(pszName, "$difficulty") == 0) {
char *pszT = "0";
switch (gwfHandicap) {
case kfHcapEasy:
pszT = "1";
break;
case kfHcapNormal:
pszT = "2";
break;
case kfHcapHard:
pszT = "3";
break;
}
strncpyz(pszBuff, pszT, cbBuff);
} else if (stricmp(pszName, "$demo") == 0) {
strncpyz(pszBuff, gfDemo ? "1" : "0", cbBuff);
} else {
// Bogus request
strncpyz(pszBuff, "?var?", cbBuff);
return false;
}
// 'persistent variable'
} else {
const char *pszValue = m_dictPvars.Get(pszName);
if (pszValue == NULL) {
strncpyz(pszBuff, "?pvar?", cbBuff);
return false;
}
strncpyz(pszBuff, pszValue, cbBuff);
}
return true;
}
bool Game::SetVar(const char *pszName, const char *pszValue)
{
Assert(*pszName != '$', "Setting of sysvars not allowed.");
return m_dictPvars.Set(pszName, pszValue);
}
void Game::GamePause(bool fpause) {
if (!gfMultiplayer)
gsim.Pause(fpause);
}
} // namespace wi