hostile-takeover/game/game.cpp
Nathan Fulton 380d8beb25 Change default account login for new devices to not anonymous
To encourage the use of accounts, new devices should be presented with
the login screen where they can create an account. New users can still
switch to anonymous login if they choose.
2016-08-24 16:20:31 -04:00

2690 lines
66 KiB
C++

#include "game/ht.h"
#include "game/strings.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(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;
#ifdef IPHONE
// When an iPhone 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;
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) {
#ifdef IPHONE
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);
}
} // namespace wi