/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/config-manager.h" #include "common/events.h" #include "common/file.h" #include "common/memstream.h" #include "common/random.h" #include "freescape/freescape.h" #include "freescape/language/8bitDetokeniser.h" namespace Freescape { enum { kDrillerCGAPalettePinkBlue = 0, kDrillerCGAPaletteRedGreen = 1, }; static const struct CGAPalettteEntry { int areaId; int palette; } rawCGAPaletteTable[] { {1, kDrillerCGAPaletteRedGreen}, {2, kDrillerCGAPalettePinkBlue}, {3, kDrillerCGAPaletteRedGreen}, {4, kDrillerCGAPalettePinkBlue}, {5, kDrillerCGAPaletteRedGreen}, {6, kDrillerCGAPalettePinkBlue}, {7, kDrillerCGAPaletteRedGreen}, {8, kDrillerCGAPalettePinkBlue}, {9, kDrillerCGAPaletteRedGreen}, {10, kDrillerCGAPalettePinkBlue}, {11, kDrillerCGAPaletteRedGreen}, {12, kDrillerCGAPalettePinkBlue}, {14, kDrillerCGAPalettePinkBlue}, {16, kDrillerCGAPalettePinkBlue}, {19, kDrillerCGAPaletteRedGreen}, {20, kDrillerCGAPalettePinkBlue}, {21, kDrillerCGAPaletteRedGreen}, {22, kDrillerCGAPalettePinkBlue}, {23, kDrillerCGAPaletteRedGreen}, {28, kDrillerCGAPalettePinkBlue}, {32, kDrillerCGAPalettePinkBlue}, {127, kDrillerCGAPaletteRedGreen}, {0, 0} // This marks the end }; byte kDrillerCGAPalettePinkBlueData[4][3] = { {0x00, 0x00, 0x00}, {0x00, 0xaa, 0xaa}, {0xaa, 0x00, 0xaa}, {0xaa, 0xaa, 0xaa}, }; byte kDrillerCGAPaletteRedGreenData[4][3] = { {0x00, 0x00, 0x00}, {0x00, 0xaa, 0x00}, {0xaa, 0x00, 0x00}, {0xaa, 0x55, 0x00}, }; enum { kDrillerNoRig = 0, kDrillerRigInPlace = 1, kDrillerRigOutOfPlace = 2, kDrillerRigNoGas = 3 }; DrillerEngine::DrillerEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngine(syst, gd) { if (!Common::parseBool(ConfMan.get("automatic_drilling"), _useAutomaticDrilling)) error("Failed to parse bool from automatic_drilling option"); if (isDOS()) { if (_renderMode == Common::kRenderEGA) _viewArea = Common::Rect(40, 16, 280, 117); else if (_renderMode == Common::kRenderCGA) _viewArea = Common::Rect(36, 16, 284, 117); else error("Invalid or unknown render mode"); } else if (isAmiga() || isAtariST()) _viewArea = Common::Rect(36, 16, 284, 118); else if (isSpectrum()) _viewArea = Common::Rect(58, 20, 266, 124); else if (isCPC()) _viewArea = Common::Rect(36, 19, 284, 120); else if (isC64()) _viewArea = Common::Rect(32, 16, 288, 119); _playerHeightNumber = 1; _playerHeights.push_back(16); _playerHeights.push_back(48); _playerHeights.push_back(80); _playerHeights.push_back(112); _angleRotations.push_back(5); _angleRotations.push_back(10); _angleRotations.push_back(15); _angleRotations.push_back(30); _angleRotations.push_back(45); _angleRotations.push_back(90); _playerHeight = _playerHeights[_playerHeightNumber]; _playerWidth = 12; _playerDepth = 32; _initialTankEnergy = 48; _initialTankShield = 50; _initialJetEnergy = 29; _initialJetShield = 34; Math::Vector3d drillBaseOrigin = Math::Vector3d(0, 0, 0); Math::Vector3d drillBaseSize = Math::Vector3d(3, 2, 3); _drillBase = new GeometricObject(kCubeType, 0, 0, drillBaseOrigin, drillBaseSize, nullptr, nullptr, FCLInstructionVector(), ""); assert(!_drillBase->isDestroyed() && !_drillBase->isInvisible()); } DrillerEngine::~DrillerEngine() { delete _drillBase; } void DrillerEngine::titleScreen() { if (isDOS() && isDemo()) // Demo will not show any title screen return; if (isAmiga() || isAtariST()) // These releases has their own screens return; if (_title) { drawTitle(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(3000); } } void DrillerEngine::borderScreen() { if (isDOS() && isDemo()) // Demo will not show the border return; if (isAmiga() || isAtariST()) // These releases has their own screens return; if (_border) { drawBorder(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(3000); } } void DrillerEngine::gotoArea(uint16 areaID, int entranceID) { int prevAreaID = _currentArea ? _currentArea->getAreaID(): -1; debugC(1, kFreescapeDebugMove, "Jumping to area: %d, entrance: %d", areaID, entranceID); if (!_gameStateBits.contains(areaID)) _gameStateBits[areaID] = 0; if (!_areaMap.contains(areaID)) { assert(isDOS() && isDemo()); // Not included in the demo, abort area change return; } _currentArea = _areaMap[areaID]; _currentArea->show(); if (entranceID > 0 || areaID == 127) { traverseEntrance(entranceID); } else if (entranceID == 0) { int newPos = -1; // FIX: The next check will abort changing the current another // area if the player is too close to the corners if ((_position.z() < 100 && _position.x() > 3900) || (_position.z() > 3900 && _position.x() < 100) || (_position.z() < 100 && _position.x() < 100) || (_position.z() > 3900 && _position.x() > 3900)) { assert(prevAreaID > 0); _currentArea = _areaMap[prevAreaID]; return; } if (_position.z() < 200 || _position.z() >= 3800) { if (_position.z() < 200) newPos = 4000; else newPos = 100; _position.setValue(2, newPos); } else if(_position.x() < 200 || _position.x() >= 3800) { if (_position.x() < 200) newPos = 4000; else newPos = 100; _position.setValue(0, newPos); } else error("Invalid movement across areas"); assert(newPos != -1); _sensors = _currentArea->getSensors(); } _lastPosition = _position; if (areaID == _startArea && entranceID == _startEntrance) { _yaw = 280; } else if (areaID == 127) { assert(entranceID == 0); _yaw = 90; _pitch = 335; _flyMode = true; // Avoid falling // Show the number of completed areas _areaMap[127]->_name.replace(0, 3, Common::String::format("%4d", _gameStateVars[32])); } debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z()); clearTemporalMessages(); playSound(5, false); // Ignore sky/ground fields _gfx->_keyColor = 0; _gfx->setColorRemaps(&_currentArea->_colorRemaps); swapPalette(areaID); _currentArea->_skyColor = 0; _currentArea->_usualBackgroundColor = 0; if (areaID != _startArea || entranceID != _startEntrance) { g_system->warpMouse(_crossairPosition.x, _crossairPosition.y); rotate(0, 0); } } void DrillerEngine::loadGlobalObjects(Common::SeekableReadStream *file, int offset) { assert(!_areaMap.contains(255)); ObjectMap *globalObjectsByID = new ObjectMap; file->seek(offset); for (int i = 0; i < 8; i++) { if (isDOS() && isDemo()) // The DOS demo has a few missing objects if (i == 5) break; Object *gobj = load8bitObject(file); assert(gobj); assert(!globalObjectsByID->contains(gobj->getObjectID())); debugC(1, kFreescapeDebugParser, "Adding global object: %d", gobj->getObjectID()); (*globalObjectsByID)[gobj->getObjectID()] = gobj; } _areaMap[255] = new Area(255, 0, globalObjectsByID, nullptr); } void DrillerEngine::loadAssets() { if (isDemo()) loadAssetsDemo(); else loadAssetsFullGame(); // Start playing music, if any, in any supported format playMusic("Matt Gray - The Best Of Reformation - 07 Driller Theme"); } void DrillerEngine::loadAssetsDemo() { Common::File file; if (isAmiga()) { file.open("lift.neo"); if (!file.isOpen()) error("Failed to open 'lift.neo' file"); _title = loadAndConvertNeoImage(&file, 0); file.close(); file.open("console.neo"); if (!file.isOpen()) error("Failed to open 'console.neo' file"); _border = loadAndConvertNeoImage(&file, 0); file.close(); file.open("demo.cmd"); if (!file.isOpen()) error("Failed to open 'demo.cmd' file"); loadDemoData(&file, 0, 0x1000); file.close(); file.open("data"); if (!file.isOpen()) error("Failed to open 'data' file"); load8bitBinary(&file, 0x442, 16); loadPalettes(&file, 0x0); file.close(); file.open("driller"); if (!file.isOpen()) error("Failed to open 'driller' file"); loadFonts(&file, 0xa30); loadMessagesFixedSize(&file, 0x3960, 14, 20); loadGlobalObjects(&file, 0x3716); file.close(); file.open("soundfx"); if (!file.isOpen()) error("Failed to open 'soundfx' executable for Amiga"); loadSoundsFx(&file, 0, 25); } else if (isAtariST()) { file.open("lift.neo"); if (!file.isOpen()) error("Failed to open 'lift.neo' file"); _title = loadAndConvertNeoImage(&file, 0); file.close(); file.open("console.neo"); if (!file.isOpen()) error("Failed to open 'console.neo' file"); _border = loadAndConvertNeoImage(&file, 0); file.close(); file.open("demo.cmd"); if (!file.isOpen()) error("Failed to open 'demo.cmd' file"); loadDemoData(&file, 0, 0x1000); file.close(); file.open("data"); if (!file.isOpen()) error("Failed to open 'data' file"); load8bitBinary(&file, 0x442, 16); loadPalettes(&file, 0x0); file.close(); file.open("x.prg"); if (!file.isOpen()) error("Failed to open 'x.prg' file"); loadFonts(&file, 0x7bc); loadMessagesFixedSize(&file, 0x3b90, 14, 20); loadGlobalObjects(&file, 0x3946); file.close(); file.open("soundfx"); if (!file.isOpen()) error("Failed to open 'soundfx' executable for AtariST demo"); loadSoundsFx(&file, 0, 25); } else if (isDOS()) { _renderMode = Common::kRenderCGA; // DOS demos is CGA only _viewArea = Common::Rect(36, 16, 284, 117); // correct view area _gfx->_renderMode = _renderMode; loadBundledImages(); file.open("d2"); if (!file.isOpen()) error("Failed to open 'd2' file"); loadFonts(&file, 0x4eb0); loadMessagesFixedSize(&file, 0x636, 14, 20); load8bitBinary(&file, 0x55b0, 4); loadGlobalObjects(&file, 0x8c); // Fixed for a corrupted area names in the demo data _areaMap[2]->_name = "LAPIS LAZULI"; _areaMap[3]->_name = "EMERALD"; _areaMap[8]->_name = "TOPAZ"; file.close(); } else error("Unsupported demo for Driller"); _demoMode = !_disableDemoMode; _angleRotationIndex = 0; } byte *parseEDSK(const Common::String filename, int &size) { debugC(1, kFreescapeDebugParser, "Trying to parse edsk file: %s", filename.c_str()); Common::File file; file.open(filename); if (!file.isOpen()) error("Failed to open %s", filename.c_str()); int totalSize = file.size(); byte *edskBuffer = (byte *)malloc(totalSize); file.read(edskBuffer, totalSize); file.close(); // We don't know the final size, but we allocate enough byte *memBuffer = (byte *)malloc(totalSize); byte nsides = edskBuffer[49]; assert(nsides == 1); int ntracks = 0; int i = 256; int j = 0; while (i + 1 < totalSize) { byte ssize = edskBuffer[i + 0x14]; debug("i: %x ssize: %d, number: %d", i, ssize, edskBuffer[i + 0x10]); assert(ssize == 3 || edskBuffer[i + 0x0] == 'T'); assert(ssize == 3 || edskBuffer[i + 0x1] == 'r'); assert(ssize == 3 || edskBuffer[i + 0x2] == 'a'); //assert(ssize == 3 || ntracks == edskBuffer[i + 0x10]); int start = i + 0x100; debugC(1, kFreescapeDebugParser, "sector size: %d", ssize); if (ssize == 2) { i = i + 9 * 512 + 256; } else if (ssize == 5) { i = i + 8 * 512 + 256; } else if (ssize == 0) { i = totalSize - 1; } else if (ssize == 3) { break; // Not sure } else { error("ssize: %d", ssize); } int osize = i - start; debugC(1, kFreescapeDebugParser, "copying track %d start: %x size: %x, dest: %x", ntracks, start, osize, j); memcpy(memBuffer + j, edskBuffer + start, osize); j = j + osize; ntracks++; } size = j; if (0) { // Useful to debug where exactly each object is located in memory once it is parsed i = 0; while(i < j) { debugN("%05x: ", i); for (int k = 0; k <= 16; k++) { debugN("%02x ", memBuffer[i]); i++; } debugN("\n"); } } free(edskBuffer); return memBuffer; } void DrillerEngine::loadAssetsFullGame() { Common::File file; if (isAmiga()) { if (_variant & GF_AMIGA_RETAIL) { file.open("driller"); if (!file.isOpen()) error("Failed to open 'driller' executable for Amiga"); _border = loadAndConvertNeoImage(&file, 0x137f4); byte *palette = (byte *)malloc(16 * 3); for (int i = 0; i < 16; i++) { // gray scale palette palette[i * 3 + 0] = i * (255 / 16); palette[i * 3 + 1] = i * (255 / 16); palette[i * 3 + 2] = i * (255 / 16); } _title = loadAndConvertNeoImage(&file, 0x10, palette); loadFonts(&file, 0x8940); loadMessagesFixedSize(&file, 0xc66e, 14, 20); loadGlobalObjects(&file, 0xbd62); load8bitBinary(&file, 0x29c16, 16); loadPalettes(&file, 0x297d4); loadSoundsFx(&file, 0x30e80, 25); } else if (_variant & GF_AMIGA_BUDGET) { file.open("lift.neo"); if (!file.isOpen()) error("Failed to open 'lift.neo' file"); _title = loadAndConvertNeoImage(&file, 0); file.close(); file.open("console.neo"); if (!file.isOpen()) error("Failed to open 'console.neo' file"); _border = loadAndConvertNeoImage(&file, 0); file.close(); file.open("driller"); if (!file.isOpen()) error("Failed to open 'driller' executable for Amiga"); loadFonts(&file, 0xa62); loadMessagesFixedSize(&file, 0x499a, 14, 20); loadGlobalObjects(&file, 0x4098); load8bitBinary(&file, 0x21a3e, 16); loadPalettes(&file, 0x215fc); file.close(); file.open("soundfx"); if (!file.isOpen()) error("Failed to open 'soundfx' executable for Amiga"); loadSoundsFx(&file, 0, 25); } else error("Invalid or unknown Amiga release"); } else if (isAtariST()) { file.open("x.prg"); if (!file.isOpen()) error("Failed to open 'x.prg' executable for AtariST"); _border = loadAndConvertNeoImage(&file, 0x1371a); byte *palette = (byte *)malloc(16 * 3); for (int i = 0; i < 16; i++) { // gray scale palette palette[i * 3 + 0] = i * (255 / 16); palette[i * 3 + 1] = i * (255 / 16); palette[i * 3 + 2] = i * (255 / 16); } _title = loadAndConvertNeoImage(&file, 0x10, palette); loadFonts(&file, 0x8a32); loadMessagesFixedSize(&file, 0xc5d8, 14, 20); loadGlobalObjects(&file, 0xbccc); load8bitBinary(&file, 0x29b3c, 16); loadPalettes(&file, 0x296fa); loadSoundsFx(&file, 0x30da6, 25); } else if (isSpectrum()) { loadBundledImages(); file.open("driller.zx.extracted"); if (!file.isOpen()) error("Failed to open driller.zx.extracted"); loadMessagesFixedSize(&file, 0x20e4, 14, 20); if (_variant & GF_ZX_RETAIL) loadFonts(&file, 0x62ca); if (_variant & GF_ZX_BUDGET) loadFonts(&file, 0x5aa8); loadGlobalObjects(&file, 0x1c93); if (_variant & GF_ZX_RETAIL) load8bitBinary(&file, 0x642c, 4); else if (_variant & GF_ZX_BUDGET) load8bitBinary(&file, 0x5c0a, 4); else error("Unknown ZX spectrum variant"); } else if (isCPC()) { loadBundledImages(); byte *memBuffer; int memSize = 0; if (_variant & GF_CPC_VIRTUALWORLDS) { memBuffer = parseEDSK("virtualworlds.A.cpc.edsk", memSize); // Deofuscation / loader code for (int j = 0; j < 0x200; j++) { memBuffer[0x14000 + j] = memBuffer[0x14200 + j]; memBuffer[0x14200 + j] = memBuffer[0x13400 + j]; memBuffer[0x14400 + j] = memBuffer[0x13800 + j]; memBuffer[0x14600 + j] = memBuffer[0x13c00 + j]; } for (int j = 0; j < 0x200; j++) { memBuffer[0x13c00 + j] = memBuffer[0x13a00 + j]; memBuffer[0x13a00 + j] = memBuffer[0x13600 + j]; memBuffer[0x13800 + j] = memBuffer[0x13200 + j]; memBuffer[0x13600 + j] = memBuffer[0x12e00 + j]; memBuffer[0x12e00 + j] = memBuffer[0x13000 + j]; memBuffer[0x13000 + j] = memBuffer[0x12200 + j]; memBuffer[0x13200 + j] = memBuffer[0x12600 + j]; memBuffer[0x13400 + j] = memBuffer[0x12a00 + j]; } for (int i = 6; i >= 0; i--) { //debug("copying 0x200 bytes to %x from %x", 0x12000 + 0x200*i, 0x11400 + 0x400*i); for (int j = 0; j < 0x200; j++) { memBuffer[0x12000 + 0x200*i + j] = memBuffer[0x11400 + 0x400*i + j]; } } for (int j = 0; j < 0x200; j++) { memBuffer[0x11c00 + j] = memBuffer[0x11e00 + j]; memBuffer[0x11e00 + j] = memBuffer[0x11000 + j]; } } else memBuffer = parseEDSK("driller.cpc.edsk", memSize); assert(memSize > 0); Common::SeekableReadStream *stream = new Common::MemoryReadStream((const byte*)memBuffer, memSize); if (_variant & GF_CPC_RETAIL) { loadMessagesFixedSize(stream, 0xb0f7, 14, 20); loadFonts(stream, 0xeb14); load8bitBinary(stream, 0xec76, 4); loadGlobalObjects(stream, 0xacb2); } else if (_variant & GF_CPC_RETAIL2) { loadMessagesFixedSize(stream, 0xb0f7 - 0x3fab, 14, 20); loadFonts(stream, 0xeb14 - 0x3fab); load8bitBinary(stream, 0xaccb, 4); loadGlobalObjects(stream, 0xacb2 - 0x3fab); } else if (_variant & _variant & GF_CPC_VIRTUALWORLDS) { load8bitBinary(stream, 0x11acb, 4); } else if (_variant & GF_CPC_BUDGET) { loadMessagesFixedSize(stream, 0x9ef7, 14, 20); loadFonts(stream, 0xd914); load8bitBinary(stream, 0xda76, 4); loadGlobalObjects(stream, 0x9ab2); } else error("Unknown Amstrad CPC variant"); } else if (isC64()) { if (_targetName.hasPrefix("spacestationoblivion")) { loadBundledImages(); file.open("spacestationoblivion.c64.extracted"); loadMessagesFixedSize(&file, 0x167a, 14, 20); //loadFonts(&file, 0xae54); load8bitBinary(&file, 0x8e02, 4); loadGlobalObjects(&file, 0x1855); } else if (_targetName.hasPrefix("driller")) { file.open("driller.c64.extracted"); loadMessagesFixedSize(&file, 0x167a - 0x400, 14, 20); //loadFonts(&file, 0xae54); load8bitBinary(&file, 0x8e02 - 0x400, 4); loadGlobalObjects(&file, 0x1855 - 0x400); } } else if (_renderMode == Common::kRenderEGA) { loadBundledImages(); file.open("DRILLE.EXE"); if (!file.isOpen()) error("Failed to open DRILLE.EXE"); loadMessagesFixedSize(&file, 0x4135, 14, 20); loadFonts(&file, 0x99dd); loadGlobalObjects(&file, 0x3b42); load8bitBinary(&file, 0x9b40, 16); } else if (_renderMode == Common::kRenderCGA) { loadBundledImages(); file.open("DRILLC.EXE"); if (!file.isOpen()) error("Failed to open DRILLC.EXE"); loadFonts(&file, 0x07a4a); loadMessagesFixedSize(&file, 0x2585, 14, 20); load8bitBinary(&file, 0x7bb0, 4); loadGlobalObjects(&file, 0x1fa2); } else error("Invalid or unsupported render mode %s for Driller", Common::getRenderModeDescription(_renderMode)); /* We are going to inject a small script in the last area to force the game to end: IF COLLIDED? THEN IF VAR!=? (v32, 18) THEN END ENDIF GOTO (127, 0) */ FCLInstructionVector instructions; Common::Array conditionArray; conditionArray.push_back(0xb); conditionArray.push_back(0x20); conditionArray.push_back(0x12); conditionArray.push_back(0x12); conditionArray.push_back(0x7f); conditionArray.push_back(0x0); Common::String conditionSource = detokenise8bitCondition(conditionArray, instructions); debugC(1, kFreescapeDebugParser, "%s", conditionSource.c_str()); _areaMap[18]->_conditions.push_back(instructions); _areaMap[18]->_conditionSources.push_back(conditionSource); } void DrillerEngine::processBorder() { FreescapeEngine::processBorder(); if (isDOS() && _renderMode == Common::kRenderCGA) { // Replace some colors for the CGA borders uint32 color1 = _border->format.ARGBToColor(0xFF, 0xAA, 0x00, 0xAA); uint32 color2 = _border->format.ARGBToColor(0xFF, 0xAA, 0x55, 0x00); uint32 colorA = _border->format.ARGBToColor(0xFF, 0x00, 0xAA, 0xAA); uint32 colorB = _border->format.ARGBToColor(0xFF, 0x00, 0xAA, 0x00); uint32 colorX = _border->format.ARGBToColor(0xFF, 0xAA, 0xAA, 0xAA); uint32 colorY = _border->format.ARGBToColor(0xFF, 0xAA, 0x00, 0x00); Graphics::Surface *borderRedGreen = new Graphics::Surface(); borderRedGreen->create(1, 1, _border->format); borderRedGreen->copyFrom(*_border); for (int i = 0; i < _border->w; i++) { for (int j = 0; j < _border->h; j++) { if (borderRedGreen->getPixel(i, j) == color1) borderRedGreen->setPixel(i, j, color2); else if (borderRedGreen->getPixel(i, j) == colorA) borderRedGreen->setPixel(i, j, colorB); else if (borderRedGreen->getPixel(i, j) == colorX) borderRedGreen->setPixel(i, j, colorY); } } Texture *borderTextureRedGreen = _gfx->createTexture(borderRedGreen); const CGAPalettteEntry *entry = rawCGAPaletteTable; while (entry->areaId) { if (entry->palette == kDrillerCGAPaletteRedGreen) { _borderCGAByArea[entry->areaId] = borderTextureRedGreen; _paletteCGAByArea[entry->areaId] = (byte *)kDrillerCGAPaletteRedGreenData; } else if (entry->palette == kDrillerCGAPalettePinkBlue) { _borderCGAByArea[entry->areaId] = _borderTexture; _paletteCGAByArea[entry->areaId] = (byte *)kDrillerCGAPalettePinkBlueData; } else error("Invalid CGA palette to use"); entry++; } } } void DrillerEngine::drawUI() { Graphics::Surface *surface = nullptr; if (_border) { // This can be removed when all the borders are loaded uint32 gray = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0xA0, 0xA0, 0xA0); surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, gray); drawCrossair(surface); } else return; if (isDOS()) drawDOSUI(surface); else if (isC64()) drawC64UI(surface); else if (isSpectrum()) drawZXUI(surface); else if (isCPC()) drawCPCUI(surface); else if (isAmiga() || isAtariST()) drawAmigaAtariSTUI(surface); if (!_uiTexture) _uiTexture = _gfx->createTexture(surface); else _uiTexture->update(surface); _gfx->setViewport(_fullscreenViewArea); _gfx->drawTexturedRect2D(_fullscreenViewArea, _fullscreenViewArea, _uiTexture); _gfx->setViewport(_viewArea); surface->free(); delete surface; } void DrillerEngine::drawDOSUI(Graphics::Surface *surface) { uint32 color = _renderMode == Common::kRenderCGA ? 1 : 14; uint8 r, g, b; _gfx->readFromPalette(color, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); color = _currentArea->_usualBackgroundColor; if (_gfx->_colorRemaps && _gfx->_colorRemaps->contains(color)) { color = (*_gfx->_colorRemaps)[color]; } _gfx->readFromPalette(color, r, g, b); uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); int score = _gameStateVars[k8bitVariableScore]; drawStringInSurface(_currentArea->_name, 196, 185, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.x())), 150, 145, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.z())), 150, 153, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.y())), 150, 161, front, back, surface); if (_playerHeightNumber >= 0) drawStringInSurface(Common::String::format("%d", _playerHeightNumber), 57, 161, front, back, surface); else drawStringInSurface(Common::String::format("%s", "J"), 57, 161, front, back, surface); drawStringInSurface(Common::String::format("%02d", int(_angleRotations[_angleRotationIndex])), 46, 145, front, back, surface); drawStringInSurface(Common::String::format("%3d", _playerSteps[_playerStepIndex]), _renderMode == Common::kRenderCGA ? 44 : 46, 153, front, back, surface); drawStringInSurface(Common::String::format("%07d", score), 238, 129, front, back, surface); int hours = _countdown <= 0 ? 0 : _countdown / 3600; drawStringInSurface(Common::String::format("%02d", hours), 208, 8, front, back, surface); int minutes = _countdown <= 0 ? 0 : (_countdown - hours * 3600) / 60; drawStringInSurface(Common::String::format("%02d", minutes), 230, 8, front, back, surface); int seconds = _countdown <= 0 ? 0 : _countdown - hours * 3600 - minutes * 60; drawStringInSurface(Common::String::format("%02d", seconds), 254, 8, front, back, surface); Common::String message; int deadline; getLatestMessages(message, deadline); if (deadline <= _countdown) { drawStringInSurface(message, 190, 177, back, front, surface); _temporaryMessages.push_back(message); _temporaryMessageDeadlines.push_back(deadline); } else { if (_currentArea->_gasPocketRadius == 0) message = _messagesList[2]; else if (_drillStatusByArea[_currentArea->getAreaID()]) message = _messagesList[0]; else message = _messagesList[1]; drawStringInSurface(message, 191, 177, front, back, surface); } int energy = _gameStateVars[k8bitVariableEnergy]; int shield = _gameStateVars[k8bitVariableShield]; if (energy >= 0) { Common::Rect backBar(20, 185, 88 - energy, 191); surface->fillRect(backBar, back); Common::Rect energyBar(87 - energy, 185, 88, 191); surface->fillRect(energyBar, front); } if (shield >= 0) { Common::Rect backBar(20, 177, 88 - shield, 183); surface->fillRect(backBar, back); Common::Rect shieldBar(87 - shield, 177, 88, 183); surface->fillRect(shieldBar, front); } } void DrillerEngine::drawCPCUI(Graphics::Surface *surface) { uint32 color = 1; uint8 r, g, b; _gfx->selectColorFromFourColorPalette(color, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); color = 0; if (_gfx->_colorRemaps && _gfx->_colorRemaps->contains(color)) { color = (*_gfx->_colorRemaps)[color]; } _gfx->readFromPalette(color, r, g, b); uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); int score = _gameStateVars[k8bitVariableScore]; drawStringInSurface(_currentArea->_name, 200, 188, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.x())), 149, 148, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.z())), 149, 156, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.y())), 149, 164, front, back, surface); if (_playerHeightNumber >= 0) drawStringInSurface(Common::String::format("%d", _playerHeightNumber), 54, 164, front, back, surface); else drawStringInSurface(Common::String::format("%s", "J"), 54, 164, front, back, surface); drawStringInSurface(Common::String::format("%02d", int(_angleRotations[_angleRotationIndex])), 46, 148, front, back, surface); drawStringInSurface(Common::String::format("%3d", _playerSteps[_playerStepIndex]), 44, 156, front, back, surface); drawStringInSurface(Common::String::format("%07d", score), 239, 132, front, back, surface); int hours = _countdown <= 0 ? 0 : _countdown / 3600; drawStringInSurface(Common::String::format("%02d", hours), 209, 11, front, back, surface); int minutes = _countdown <= 0 ? 0 : (_countdown - hours * 3600) / 60; drawStringInSurface(Common::String::format("%02d", minutes), 232, 11, front, back, surface); int seconds = _countdown <= 0 ? 0 : _countdown - hours * 3600 - minutes * 60; drawStringInSurface(Common::String::format("%02d", seconds), 254, 11, front, back, surface); Common::String message; int deadline; getLatestMessages(message, deadline); if (deadline <= _countdown) { drawStringInSurface(message, 191, 180, back, front, surface); _temporaryMessages.push_back(message); _temporaryMessageDeadlines.push_back(deadline); } else if (_messagesList.size() > 0) { if (_currentArea->_gasPocketRadius == 0) message = _messagesList[2]; else if (_drillStatusByArea[_currentArea->getAreaID()]) message = _messagesList[0]; else message = _messagesList[1]; drawStringInSurface(message, 191, 180, front, back, surface); } int energy = _gameStateVars[k8bitVariableEnergy]; int shield = _gameStateVars[k8bitVariableShield]; if (energy >= 0) { Common::Rect backBar(25, 187, 89 - energy, 194); surface->fillRect(backBar, back); Common::Rect energyBar(88 - energy, 187, 88, 194); surface->fillRect(energyBar, front); } if (shield >= 0) { Common::Rect backBar(25, 180, 89 - shield, 186); surface->fillRect(backBar, back); Common::Rect shieldBar(88 - shield, 180, 88, 186); surface->fillRect(shieldBar, front); } } void DrillerEngine::drawC64UI(Graphics::Surface *surface) { uint32 color = 1; uint8 r, g, b; _gfx->selectColorFromFourColorPalette(color, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); color = 0; if (_gfx->_colorRemaps && _gfx->_colorRemaps->contains(color)) { color = (*_gfx->_colorRemaps)[color]; } _gfx->readFromPalette(color, r, g, b); uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); int score = _gameStateVars[k8bitVariableScore]; drawStringInSurface(_currentArea->_name, 200, 188, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.x())), 149, 148, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.z())), 149, 156, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.y())), 149, 164, front, back, surface); if (_playerHeightNumber >= 0) drawStringInSurface(Common::String::format("%d", _playerHeightNumber), 54, 164, front, back, surface); else drawStringInSurface(Common::String::format("%s", "J"), 54, 164, front, back, surface); drawStringInSurface(Common::String::format("%02d", int(_angleRotations[_angleRotationIndex])), 46, 148, front, back, surface); drawStringInSurface(Common::String::format("%3d", _playerSteps[_playerStepIndex]), 44, 156, front, back, surface); drawStringInSurface(Common::String::format("%07d", score), 240, 128, front, back, surface); int hours = _countdown <= 0 ? 0 : _countdown / 3600; drawStringInSurface(Common::String::format("%02d", hours), 209, 11, front, back, surface); int minutes = _countdown <= 0 ? 0 : (_countdown - hours * 3600) / 60; drawStringInSurface(Common::String::format("%02d", minutes), 232, 11, front, back, surface); int seconds = _countdown <= 0 ? 0 : _countdown - hours * 3600 - minutes * 60; drawStringInSurface(Common::String::format("%02d", seconds), 254, 11, front, back, surface); Common::String message; int deadline; getLatestMessages(message, deadline); if (deadline <= _countdown) { drawStringInSurface(message, 191, 180, back, front, surface); _temporaryMessages.push_back(message); _temporaryMessageDeadlines.push_back(deadline); } else { if (_currentArea->_gasPocketRadius == 0) message = _messagesList[2]; else if (_drillStatusByArea[_currentArea->getAreaID()]) message = _messagesList[0]; else message = _messagesList[1]; drawStringInSurface(message, 191, 180, front, back, surface); } int energy = _gameStateVars[k8bitVariableEnergy]; int shield = _gameStateVars[k8bitVariableShield]; if (energy >= 0) { Common::Rect backBar(25, 187, 89 - energy, 194); surface->fillRect(backBar, back); Common::Rect energyBar(88 - energy, 187, 88, 194); surface->fillRect(energyBar, front); } if (shield >= 0) { Common::Rect backBar(25, 180, 89 - shield, 186); surface->fillRect(backBar, back); Common::Rect shieldBar(88 - shield, 180, 88, 186); surface->fillRect(shieldBar, front); } } void DrillerEngine::drawZXUI(Graphics::Surface *surface) { uint32 color = 5; uint8 r, g, b; _gfx->readFromPalette(color, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); color = _currentArea->_usualBackgroundColor; if (_gfx->_colorRemaps && _gfx->_colorRemaps->contains(color)) { color = (*_gfx->_colorRemaps)[color]; } _gfx->readFromPalette(color, r, g, b); uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); uint32 white = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0xFF, 0xFF, 0xFF); int score = _gameStateVars[k8bitVariableScore]; drawStringInSurface(_currentArea->_name, 176, 188, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.x())), 152, 149, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.z())), 152, 157, front, back, surface); drawStringInSurface(Common::String::format("%04d", int(2 * _position.y())), 152, 165, front, back, surface); if (_playerHeightNumber >= 0) drawStringInSurface(Common::String::format("%d", _playerHeightNumber), 74, 165, front, back, surface); else drawStringInSurface(Common::String::format("%s", "J"), 74, 165, front, back, surface); drawStringInSurface(Common::String::format("%02d", int(_angleRotations[_angleRotationIndex])), 64, 149, front, back, surface); drawStringInSurface(Common::String::format("%3d", _playerSteps[_playerStepIndex]), 65, 157, front, back, surface); drawStringInSurface(Common::String::format("%07d", score), 217, 133, white, back, surface); int hours = _countdown <= 0 ? 0 : _countdown / 3600; drawStringInSurface(Common::String::format("%02d", hours), 187, 12, front, back, surface); int minutes = _countdown <= 0 ? 0 : (_countdown - hours * 3600) / 60; drawStringInSurface(Common::String::format("%02d", minutes), 209, 12, front, back, surface); int seconds = _countdown <= 0 ? 0 : _countdown - hours * 3600 - minutes * 60; drawStringInSurface(Common::String::format("%02d", seconds), 232, 12, front, back, surface); Common::String message; int deadline; getLatestMessages(message, deadline); if (deadline <= _countdown) { drawStringInSurface(message, 169, 181, back, front, surface); _temporaryMessages.push_back(message); _temporaryMessageDeadlines.push_back(deadline); } else { if (_currentArea->_gasPocketRadius == 0) message = _messagesList[2]; else if (_drillStatusByArea[_currentArea->getAreaID()]) message = _messagesList[0]; else message = _messagesList[1]; drawStringInSurface(message, 169, 181, front, back, surface); } int energy = _gameStateVars[k8bitVariableEnergy]; int shield = _gameStateVars[k8bitVariableShield]; if (energy >= 0) { Common::Rect backBar(45, 188, 109 - energy, 194); surface->fillRect(backBar, back); Common::Rect energyBar(108 - energy, 188, 108, 194); surface->fillRect(energyBar, front); } if (shield >= 0) { Common::Rect backBar(45, 181, 109 - shield, 187); surface->fillRect(backBar, back); Common::Rect shieldBar(108 - shield, 181, 108, 187); surface->fillRect(shieldBar, front); } } void DrillerEngine::drawAmigaAtariSTUI(Graphics::Surface *surface) { uint32 white = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0xFF, 0xFF, 0xFF); uint32 yellow = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0xFF, 0xFF, 0x55); uint32 brownish = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x9E, 0x80, 0x20); uint32 brown = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x7E, 0x60, 0x19); uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00); uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); int score = _gameStateVars[k8bitVariableScore]; Common::String coords; if (!isDemo()) { // It seems demos will not include the complete font? drawStringInSurface("x", 37, 18, white, transparent, surface, 82); coords = Common::String::format("%04d", 2 * int(_position.x())); for (int i = 0; i < 4; i++) drawStringInSurface(Common::String(coords[i]), 47 + 6*i, 18, white, transparent, surface, 112); drawStringInSurface("y", 37, 26, white, transparent, surface, 82); coords = Common::String::format("%04d", 2 * int(_position.z())); // Coords y and z are swapped! for (int i = 0; i < 4; i++) drawStringInSurface(Common::String(coords[i]), 47 + 6*i, 26, white, transparent, surface, 112); drawStringInSurface("z", 37, 34, white, transparent, surface, 82); coords = Common::String::format("%04d", 2 * int(_position.y())); // Coords y and z are swapped! for (int i = 0; i < 4; i++) drawStringInSurface(Common::String(coords[i]), 47 + 6*i, 34, white, transparent, surface, 112); } drawStringInSurface(_currentArea->_name, 188, 185, yellow, black, surface); drawStringInSurface(Common::String::format("%07d", score), 240, 129, yellow, black, surface); int hours = _countdown <= 0 ? 0 : _countdown / 3600; drawStringInSurface(Common::String::format("%02d:", hours), 208, 7, yellow, black, surface); int minutes = _countdown <= 0 ? 0 : (_countdown - hours * 3600) / 60; drawStringInSurface(Common::String::format("%02d:", minutes), 230, 7, yellow, black, surface); int seconds = _countdown <= 0 ? 0 : _countdown - hours * 3600 - minutes * 60; drawStringInSurface(Common::String::format("%02d", seconds), 254, 7, yellow, black, surface); Common::String message; int deadline; getLatestMessages(message, deadline); if (deadline <= _countdown) { drawStringInSurface(message, 188, 177, black, yellow, surface); _temporaryMessages.push_back(message); _temporaryMessageDeadlines.push_back(deadline); } else { if (_currentArea->_gasPocketRadius == 0) message = _messagesList[2]; else if (_drillStatusByArea[_currentArea->getAreaID()]) message = _messagesList[0]; else message = _messagesList[1]; drawStringInSurface(message, 188, 177, yellow, black, surface); } int energy = _gameStateVars[k8bitVariableEnergy]; int shield = _gameStateVars[k8bitVariableShield]; if (shield >= 0) { Common::Rect shieldBar; shieldBar = Common::Rect(11, 178, 76 - (k8bitMaxShield - shield), 184); surface->fillRect(shieldBar, brown); shieldBar = Common::Rect(11, 179, 76 - (k8bitMaxShield - shield), 183); surface->fillRect(shieldBar, brownish); shieldBar = Common::Rect(11, 180, 76 - (k8bitMaxShield - shield), 182); surface->fillRect(shieldBar, yellow); } if (energy >= 0) { Common::Rect energyBar; energyBar = Common::Rect(11, 186, 75 - (k8bitMaxEnergy - energy), 192); surface->fillRect(energyBar, brown); energyBar = Common::Rect(11, 187, 75 - (k8bitMaxEnergy - energy), 191); surface->fillRect(energyBar, brownish); energyBar = Common::Rect(11, 188, 75 - (k8bitMaxEnergy - energy), 190); surface->fillRect(energyBar, yellow); } } void DrillerEngine::drawInfoMenu() { _savedScreen = _gfx->getScreenshot(); uint32 color = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00); Graphics::Surface *surface = new Graphics::Surface(); surface->create(_screenW, _screenH, _gfx->_texturePixelFormat); surface->fillRect(_fullscreenViewArea, color); uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00); surface->fillRect(_viewArea, black); switch (_renderMode) { case Common::kRenderCGA: color = 1; break; case Common::kRenderZX: color = 6; break; default: color = 14; } uint8 r, g, b; _gfx->readFromPalette(color, r, g, b); uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b); drawStringInSurface(Common::String::format("%10s : %s", "sector", _currentArea->_name.c_str()), 69, 25, front, black, surface); Common::String rigStatus; Common::String gasFound; Common::String perTapped; Common::String gasTapped; switch (_drillStatusByArea[_currentArea->getAreaID()]) { case kDrillerNoRig: rigStatus = "Unpositioned"; gasFound = "-"; perTapped = "-"; gasTapped = "-"; break; case kDrillerRigInPlace: case kDrillerRigOutOfPlace: rigStatus = "Positioned"; gasFound = Common::String::format("%d CFT", _drillMaxScoreByArea[_currentArea->getAreaID()]); perTapped = Common::String::format("%d %%", _drillSuccessByArea[_currentArea->getAreaID()]); gasTapped = Common::String::format("%d", uint32(_drillSuccessByArea[_currentArea->getAreaID()] * _drillMaxScoreByArea[_currentArea->getAreaID()]) / 100); break; case kDrillerRigNoGas: rigStatus = "Positioned"; gasFound = "none"; perTapped = "none"; gasTapped = "zero"; break; default: error("Invalid drill status"); break; } drawStringInSurface(Common::String::format("%10s : %s", "rig status", rigStatus.c_str()), 69, 33, front, black, surface); drawStringInSurface(Common::String::format("%10s : %s", "gas found", gasFound.c_str()), 69, 41, front, black, surface); drawStringInSurface(Common::String::format("%10s : %s", "% tapped", perTapped.c_str()), 69, 49, front, black, surface); drawStringInSurface(Common::String::format("%10s : %s", "gas tapped", gasTapped.c_str()), 69, 57, front, black, surface); drawStringInSurface(Common::String::format("%13s : %d", "total sectors", 18), 84, 73, front, black, surface); drawStringInSurface(Common::String::format("%13s : %d", "safe sectors", _gameStateVars[32]), 84, 81, front, black, surface); if (isDOS()) { drawStringInSurface("l-load s-save esc-terminate", 53, 97, front, black, surface); drawStringInSurface("t-toggle sound on/off", 76, 105, front, black, surface); } else if (isSpectrum()) { drawStringInSurface("l-load s-save 1-abort", 53, 97, front, black, surface); drawStringInSurface("any other key-continue", 76, 105, front, black, surface); } _uiTexture->update(surface); _gfx->setViewport(_fullscreenViewArea); _gfx->drawTexturedRect2D(_fullscreenViewArea, _fullscreenViewArea, _uiTexture); _gfx->setViewport(_viewArea); _gfx->flipBuffer(); g_system->updateScreen(); Common::Event event; bool cont = true; while (!shouldQuit() && cont) { while (g_system->getEventManager()->pollEvent(event)) { // Events switch (event.type) { case Common::EVENT_KEYDOWN: if (event.kbd.keycode == Common::KEYCODE_l) { _gfx->setViewport(_fullscreenViewArea); loadGameDialog(); _gfx->setViewport(_viewArea); } else if (event.kbd.keycode == Common::KEYCODE_s) { _gfx->setViewport(_fullscreenViewArea); saveGameDialog(); _gfx->setViewport(_viewArea); } else if (isDOS() && event.kbd.keycode == Common::KEYCODE_t) { // TODO } else if (isDOS() && event.kbd.keycode == Common::KEYCODE_ESCAPE) { _forceEndGame = true; cont = false; } else if (isSpectrum() && event.kbd.keycode == Common::KEYCODE_1) { _forceEndGame = true; cont = false; } else cont = false; break; case Common::EVENT_SCREEN_CHANGED: _gfx->computeScreenViewport(); // TODO: properly refresh screen break; default: break; } } g_system->delayMillis(10); } _savedScreen->free(); delete _savedScreen; surface->free(); delete surface; } Math::Vector3d getProjectionToPlane(const Math::Vector3d &vect, const Math::Vector3d normal) { assert (normal.length() == 1); // Formula: return p - n * (n . p) Math::Vector3d result = vect; result -= normal * normal.dotProduct(vect); return result; } void DrillerEngine::pressedKey(const int keycode) { if (keycode == Common::KEYCODE_d) { if (isDOS() && isDemo()) // No support for drilling here yet return; clearTemporalMessages(); Common::Point gasPocket = _currentArea->_gasPocketPosition; uint32 gasPocketRadius = _currentArea->_gasPocketRadius; if (gasPocketRadius == 0) { insertTemporaryMessage(_messagesList[2], _countdown - 2); return; } if (_flyMode) { insertTemporaryMessage(_messagesList[8], _countdown - 2); return; } if (drillDeployed(_currentArea)) { insertTemporaryMessage(_messagesList[12], _countdown - 2); return; } if (_gameStateVars[k8bitVariableEnergy] < 5) { insertTemporaryMessage(_messagesList[7], _countdown - 2); return; } Math::Vector3d drill = drillPosition(); debugC(1, kFreescapeDebugMove, "Current position at %f %f %f", _position.x(), _position.y(), _position.z()); debugC(1, kFreescapeDebugMove, "Trying to adding drill at %f %f %f", drill.x(), drill.y(), drill.z()); debugC(1, kFreescapeDebugMove, "with pitch: %f and yaw %f", _pitch, _yaw); if (!checkDrill(drill)) { insertTemporaryMessage(_messagesList[4], _countdown - 2); return; } _gameStateVars[k8bitVariableEnergy] = _gameStateVars[k8bitVariableEnergy] - 5; const Math::Vector3d gasPocket3D(gasPocket.x, drill.y(), gasPocket.y); float distanceToPocket = (gasPocket3D - drill).length(); float success = _useAutomaticDrilling ? 100.0 : 100.0 * (1.0 - distanceToPocket / _currentArea->_gasPocketRadius); insertTemporaryMessage(_messagesList[3], _countdown - 2); addDrill(drill, success > 0); if (success <= 0) { insertTemporaryMessage(_messagesList[9], _countdown - 4); _drillStatusByArea[_currentArea->getAreaID()] = kDrillerRigNoGas; return; } Common::String maxScoreMessage = _messagesList[5]; int maxScore = _drillMaxScoreByArea[_currentArea->getAreaID()]; maxScoreMessage.replace(2, 6, Common::String::format("%d", maxScore)); insertTemporaryMessage(maxScoreMessage, _countdown - 4); Common::String successMessage = _messagesList[6]; successMessage.replace(0, 4, Common::String::format("%d", int(success))); while (successMessage.size() < 14) successMessage += " "; insertTemporaryMessage(successMessage, _countdown - 6); _drillSuccessByArea[_currentArea->getAreaID()] = uint32(success); _gameStateVars[k8bitVariableScore] += uint32(maxScore * uint32(success)) / 100; if (success >= 50.0) { _drillStatusByArea[_currentArea->getAreaID()] = kDrillerRigInPlace; _gameStateVars[32]++; } else _drillStatusByArea[_currentArea->getAreaID()] = kDrillerRigOutOfPlace; } else if (keycode == Common::KEYCODE_c) { if (isDOS() && isDemo()) // No support for drilling here yet return; uint32 gasPocketRadius = _currentArea->_gasPocketRadius; clearTemporalMessages(); if (gasPocketRadius == 0) { insertTemporaryMessage(_messagesList[2], _countdown - 2); return; } if (_flyMode) { insertTemporaryMessage(_messagesList[8], _countdown - 2); return; } if (!drillDeployed(_currentArea)) { insertTemporaryMessage(_messagesList[13], _countdown - 2); return; } if (_gameStateVars[k8bitVariableEnergy] < 5) { insertTemporaryMessage(_messagesList[7], _countdown - 2); return; } _gameStateVars[k8bitVariableEnergy] = _gameStateVars[k8bitVariableEnergy] - 5; uint16 areaID = _currentArea->getAreaID(); if (_drillStatusByArea[areaID] > 0) { if (_drillStatusByArea[areaID] == kDrillerRigInPlace) _gameStateVars[32]--; _drillStatusByArea[areaID] = kDrillerNoRig; } removeDrill(_currentArea); insertTemporaryMessage(_messagesList[10], _countdown - 2); int maxScore = _drillMaxScoreByArea[_currentArea->getAreaID()]; uint32 success = _drillSuccessByArea[_currentArea->getAreaID()]; uint32 scoreToRemove = uint32(maxScore * success) / 100; assert(scoreToRemove <= uint32(_gameStateVars[k8bitVariableScore])); _gameStateVars[k8bitVariableScore] -= scoreToRemove; } } Math::Vector3d DrillerEngine::drillPosition() { Math::Vector3d position = _position; position.setValue(1, position.y() - _playerHeight); position = position + 300 * getProjectionToPlane(_cameraFront, Math::Vector3d(0, 1, 0)); Object *obj = (GeometricObject *)_areaMap[255]->objectWithID(255); // Drill base assert(obj); position.setValue(0, position.x() - 128); position.setValue(2, position.z() - 128); return position; } bool DrillerEngine::drillDeployed(Area *area) { return (area->objectWithID(255) != nullptr); } bool DrillerEngine::checkDrill(const Math::Vector3d position) { GeometricObject *obj = nullptr; Math::Vector3d origin = position; int16 id; int heightLastObject; origin.setValue(0, origin.x() + 128); origin.setValue(1, origin.y() - 5); origin.setValue(2, origin.z() + 128); _drillBase->setOrigin(origin); if (_currentArea->checkCollisions(_drillBase->_boundingBox).empty()) return false; origin.setValue(0, origin.x() - 128); origin.setValue(2, origin.z() - 128); id = 255; obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); origin.setValue(1, origin.y() + 6); obj->setOrigin(origin); // This bounding box is too large and can result in the drill to float next to a wall if (!_currentArea->checkCollisions(obj->_boundingBox).empty()) return false; origin.setValue(1, origin.y() + 15); obj->setOrigin(origin); if (!_currentArea->checkCollisions(obj->_boundingBox).empty()) return false; origin.setValue(1, origin.y() - 10); heightLastObject = obj->getSize().y(); delete obj; id = 254; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); // Set position for object origin.setValue(0, origin.x() - obj->getSize().x() / 5); origin.setValue(1, origin.y() + heightLastObject); origin.setValue(2, origin.z() - obj->getSize().z() / 5); obj = (GeometricObject *)obj->duplicate(); obj->setOrigin(origin); if (!_currentArea->checkCollisions(obj->_boundingBox).empty()) return false; // Undo offset origin.setValue(0, origin.x() + obj->getSize().x() / 5); heightLastObject = obj->getSize().y(); origin.setValue(2, origin.z() + obj->getSize().z() / 5); delete obj; id = 253; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); origin.setValue(0, origin.x() + obj->getSize().x() / 5); origin.setValue(1, origin.y() + heightLastObject); origin.setValue(2, origin.z() + obj->getSize().z() / 5); obj->setOrigin(origin); if (!_currentArea->checkCollisions(obj->_boundingBox).empty()) return false; // Undo offset // origin.setValue(0, origin.x() - obj->getSize().x() / 5); heightLastObject = obj->getSize().y(); // origin.setValue(2, origin.z() - obj->getSize().z() / 5); delete obj; return true; } void DrillerEngine::addDrill(const Math::Vector3d position, bool gasFound) { // int drillObjectIDs[8] = {255, 254, 253, 252, 251, 250, 248, 247}; GeometricObject *obj = nullptr; Math::Vector3d origin = position; int16 id; int heightLastObject; id = 255; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); obj->setOrigin(origin); // offset.setValue(1, offset.y() + obj->getSize().y()); obj->makeVisible(); _currentArea->addObject(obj); heightLastObject = obj->getSize().y(); id = 254; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); // Set position for object origin.setValue(0, origin.x() - obj->getSize().x() / 5); origin.setValue(1, origin.y() + heightLastObject); origin.setValue(2, origin.z() - obj->getSize().z() / 5); obj = (GeometricObject *)obj->duplicate(); obj->setOrigin(origin); obj->makeVisible(); _currentArea->addObject(obj); // Undo offset origin.setValue(0, origin.x() + obj->getSize().x() / 5); heightLastObject = obj->getSize().y(); origin.setValue(2, origin.z() + obj->getSize().z() / 5); id = 253; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); origin.setValue(0, origin.x() + obj->getSize().x() / 5); origin.setValue(1, origin.y() + heightLastObject); origin.setValue(2, origin.z() + obj->getSize().z() / 5); obj->setOrigin(origin); obj->makeVisible(); _currentArea->addObject(obj); // Undo offset // origin.setValue(0, origin.x() - obj->getSize().x() / 5); heightLastObject = obj->getSize().y(); // origin.setValue(2, origin.z() - obj->getSize().z() / 5); if (gasFound) { id = 252; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); origin.setValue(0, origin.x() + obj->getSize().x()); origin.setValue(1, origin.y() + heightLastObject); origin.setValue(2, origin.z() + obj->getSize().z()); obj->setOrigin(origin); assert(obj); obj->makeVisible(); _currentArea->addObject(obj); heightLastObject = obj->getSize().y(); id = 251; debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id); obj = (GeometricObject *)_areaMap[255]->objectWithID(id); assert(obj); obj = (GeometricObject *)obj->duplicate(); origin.setValue(1, origin.y() + heightLastObject); obj->setOrigin(origin); assert(obj); obj->makeVisible(); _currentArea->addObject(obj); } } void DrillerEngine::removeDrill(Area *area) { for (int16 id = 251; id < 256; id++) { if (id > 252) assert(area->objectWithID(id)); if (area->objectWithID(id)) area->removeObject(id); } } void DrillerEngine::initGameState() { _flyMode = false; _noClipMode = false; _shootingFrames = 0; _underFireFrames = 0; _yaw = 0; _pitch = 0; for (int i = 0; i < k8bitMaxVariable; i++) // TODO: check maximum variable _gameStateVars[i] = 0; for (auto &it : _areaMap) { it._value->resetArea(); _gameStateBits[it._key] = 0; if (_drillStatusByArea[it._key] != kDrillerNoRig) removeDrill(it._value); _drillStatusByArea[it._key] = kDrillerNoRig; if (it._key != 255) { _drillMaxScoreByArea[it._key] = (10 + _rnd->getRandomNumber(89)) * 1000; } _drillSuccessByArea[it._key] = 0; } _gameStateVars[k8bitVariableEnergy] = _initialTankEnergy; _gameStateVars[k8bitVariableShield] = _initialTankShield; _gameStateVars[k8bitVariableEnergyDrillerTank] = _initialTankEnergy; _gameStateVars[k8bitVariableShieldDrillerTank] = _initialTankShield; _gameStateVars[k8bitVariableEnergyDrillerJet] = _initialJetEnergy; _gameStateVars[k8bitVariableShieldDrillerJet] = _initialJetShield; _playerHeightNumber = 1; _playerHeight = _playerHeights[_playerHeightNumber]; removeTimers(); startCountdown(_initialCountdown); _demoIndex = 0; _demoEvents.clear(); } bool DrillerEngine::checkIfGameEnded() { if (isDemo()) return (_demoData[_demoIndex + 1] == 0x5f); if (_countdown <= 0) { insertTemporaryMessage(_messagesList[14], _countdown - 2); drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(2000); gotoArea(127, 0); } if (_gameStateVars[k8bitVariableShield] == 0) { insertTemporaryMessage(_messagesList[15], _countdown - 2); drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(2000); gotoArea(127, 0); } if (_gameStateVars[k8bitVariableEnergy] == 0) { insertTemporaryMessage(_messagesList[16], _countdown - 2); drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(2000); gotoArea(127, 0); } if (_hasFallen) { _hasFallen = false; playSound(14, false); insertTemporaryMessage(_messagesList[17], _countdown - 4); drawBackground(); drawBorder(); drawUI(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(1000); gotoArea(127, 0); } if (_forceEndGame) { _forceEndGame = false; insertTemporaryMessage(_messagesList[18], _countdown - 2); drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(2000); gotoArea(127, 0); } if (_currentArea->getAreaID() == 127) { if (_gameStateVars[32] == 18) { // All areas are complete insertTemporaryMessage(_messagesList[19], _countdown - 2); _gameStateVars[32] = 0; // Avoid repeating the message } drawFrame(); _gfx->flipBuffer(); g_system->updateScreen(); g_system->delayMillis(5000); return true; } return false; } Common::Error DrillerEngine::saveGameStreamExtended(Common::WriteStream *stream, bool isAutosave) { for (auto &it : _areaMap) { // All but skip area 255 if (it._key == 255) continue; stream->writeUint16LE(it._key); stream->writeUint32LE(_drillStatusByArea[it._key]); stream->writeUint32LE(_drillMaxScoreByArea[it._key]); stream->writeUint32LE(_drillSuccessByArea[it._key]); } return Common::kNoError; } Common::Error DrillerEngine::loadGameStreamExtended(Common::SeekableReadStream *stream) { for (uint i = 0; i < _areaMap.size() - 1; i++) { // All except area 255 uint16 key = stream->readUint16LE(); assert(key != 255); assert(_areaMap.contains(key)); _drillStatusByArea[key] = stream->readUint32LE(); if (_drillStatusByArea[key] == kDrillerNoRig) if (drillDeployed(_areaMap[key])) removeDrill(_areaMap[key]); _drillMaxScoreByArea[key] = stream->readUint32LE(); _drillSuccessByArea[key] = stream->readUint32LE(); } return Common::kNoError; } } // End of namespace Freescape