#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(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 // TODO: -mp-spawn 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 // TODO: -mp-start 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); } } // namespace wi