/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/config-manager.h" #include "common/rect.h" #include "tsage/graphics.h" #include "tsage/scenes.h" #include "tsage/tsage.h" #include "tsage/staticres.h" #include "tsage/ringworld2/ringworld2_logic.h" #include "tsage/ringworld2/ringworld2_dialogs.h" #include "tsage/ringworld2/ringworld2_scenes0.h" #include "tsage/ringworld2/ringworld2_scenes1.h" #include "tsage/ringworld2/ringworld2_scenes2.h" #include "tsage/ringworld2/ringworld2_scenes3.h" namespace TsAGE { namespace Ringworld2 { Scene *Ringworld2Game::createScene(int sceneNumber) { switch (sceneNumber) { /* Scene group #0 */ case 50: // Waking up cutscene return new Scene50(); case 100: // Quinn's room return new Scene100(); case 125: // Computer console return new Scene125(); case 150: // Empty Bedroom #1 return new Scene150(); case 160: // Credits return new Scene160(); case 175: // Empty Bedroom #2 return new Scene175(); case 180: // Title Screen return new Scene180(); case 200: // Deck #2 - By Lift return new Scene200(); case 205: // Star-field Credits return new Scene205(); case 250: // Lift return new Scene250(); case 300: // Bridge return new Scene300(); case 325: // Bridge Console return new Scene325(); case 400: // Science Lab return new Scene400(); case 500: // Lander Bay 2 Storage return new Scene500(); case 525: // Cutscene - Walking in hall return new Scene525(); case 600: // Drive Room return new Scene600(); case 700: // Lander Bay 2 return new Scene700(); case 800: // Sick bay return new Scene800(); case 825: // Autodoc return new Scene825(); case 850: // Deck #5 - By Lift return new Scene850(); case 900: // Lander Bay 2 - Crane Controls return new Scene900(); /* Scene group #1 */ // case 1000: // Cutscene scene return new Scene1000(); case 1010: // Cutscene - trip in space return new Scene1010(); case 1020: // Cutscene - trip in space 2 return new Scene1020(); case 1100: // Canyon return new Scene1100(); case 1200: // ARM Base - Air Ducts Maze return new Scene1200(); case 1337: case 1330: // Card Game return new Scene1337(); case 1500: // Cutscene: Ship landing return new Scene1500(); case 1525: // Cutscene - Ship return new Scene1525(); case 1530: // Cutscene - Crashing on Rimwall return new Scene1530(); case 1550: // Spaceport return new Scene1550(); case 1575: // Spaceport - unused ship scene return new Scene1575(); case 1580: // Inside wreck return new Scene1580(); case 1625: // Miranda being questioned return new Scene1625(); case 1700: // Rim return new Scene1700(); case 1750: // Rim Transport Vechile return new Scene1750(); case 1800: // Rim Lift Exterior return new Scene1800(); case 1850: // Rim Lift Interior return new Scene1850(); case 1875: // Rim Lift Computer return new Scene1875(); case 1900: // Spill Mountains Elevator Exit return new Scene1900(); case 1925: // Spill Mountains Elevator Shaft return new Scene1925(); case 1945: // Spill Mountains Shaft Bottom return new Scene1945(); case 1950: // Flup Tube Corridor Maze return new Scene1950(); /* Scene group #2 */ case 2000: // Spill Mountains return new Scene2000(); case 2350: // Spill Mountains: Balloon Launch Platform return new Scene2350(); case 2400: // Spill Mountains: Unused large empty room return new Scene2400(); case 2425: // Spill Mountains: The Hall of Records return new Scene2425(); case 2430: // Spill Mountains: Bedroom return new Scene2430(); case 2435: // Spill Mountains: Throne room return new Scene2435(); case 2440: // Spill Mountains: Another bedroom return new Scene2440(); case 2445: // Spill Mountains: return new Scene2445(); case 2450: // Spill Mountains: Another bedroom return new Scene2450(); case 2455: // Spill Mountains: Inside crevasse return new Scene2455(); case 2500: // Spill Mountains: Large Ledge return new Scene2500(); case 2525: // Spill Mountains: Furnace room return new Scene2525(); case 2530: // Spill Mountains: Well return new Scene2530(); case 2535: // Spill Mountains: Tannery return new Scene2535(); case 2600: // Spill Mountains: Exit return new Scene2600(); case 2700: // Outer Forest return new Scene2700(); case 2750: // Inner Forest return new Scene2750(); case 2800: // Guard post return new Scene2800(); case 2900: // Balloon Cutscene return new Scene2900(); /* Scene group #3 */ // ARM Base Hanager case 3100: return new Scene3100(); case 3125: // Ghouls dormitory return new Scene3125(); case 3150: // Jail return new Scene3150(); case 3175: // Autopsy room return new Scene3175(); case 3200: // Cutscene : Guards - Discussion return new Scene3200(); case 3210: // Cutscene : Captain and Private - Discussion return new Scene3210(); case 3220: // Cutscene : Guards in cargo zone return new Scene3220(); case 3230: // Cutscene : Guards on duty return new Scene3230(); case 3240: // Cutscene : Teal monolog return new Scene3240(); case 3245: // Cutscene : Discussions with Dr. Tomko return new Scene3245(); case 3250: // Room with large stasis field negator return new Scene3250(); case 3255: // Guard Post return new Scene3255(); case 3260: // ARM Base - Computer room return new Scene3260(); case 3275: // ARM Base - Hall return new Scene3275(); case 3350: // Cutscene - Ship landing return new Scene3350(); case 3375: // Circular Walkway return new Scene3375(); case 3385: // Corridor return new Scene3385(); case 3395: // Walkway return new Scene3395(); case 3400: // Confrontation return new Scene3400(); case 3500: // Flub tube maze return new Scene3500(); case 3600: // Cutscene - walking at gunpoint return new Scene3600(); case 3700: // Cutscene - Teleport outside return new Scene3700(); case 3800: // Desert return new Scene3800(); case 3900: // Forest Entrance return new Scene3900(); default: error("Unknown scene number - %d", sceneNumber); break; } } /** * Returns true if it is currently okay to restore a game */ bool Ringworld2Game::canLoadGameStateCurrently() { // Don't allow a game to be loaded if a dialog is active return g_globals->_gfxManagers.size() == 1; } /** * Returns true if it is currently okay to save the game */ bool Ringworld2Game::canSaveGameStateCurrently() { // Don't allow a game to be saved if a dialog is active, or if an animation // is playing, or if an active scene prevents it return g_globals->_gfxManagers.size() == 1 && R2_GLOBALS._animationCtr == 0 && (!R2_GLOBALS._sceneManager._scene || !((SceneExt *)R2_GLOBALS._sceneManager._scene)->_preventSaving); } /*--------------------------------------------------------------------------*/ SceneExt::SceneExt(): Scene() { _stripManager._onBegin = SceneExt::startStrip; _stripManager._onEnd = SceneExt::endStrip; for (int i = 0; i < 256; i++) _shadowPaletteMap[i] = 0; _savedPlayerEnabled = false; _savedUiEnabled = false; _savedCanWalk = false; _preventSaving = false; // Reset screen clipping area R2_GLOBALS._screenSurface._clipRect = Rect(); // WORKAROUND: In the original, playing animations don't reset the global _animationCtr // counter as scene changes unless the playing animation explicitly finishes. For now, // to make inter-scene debugging easier, I'm explicitly resetting the _animationCtr // on scene start, since scene objects aren't drawn while it's non-zero R2_GLOBALS._animationCtr = 0; } void SceneExt::synchronize(Serializer &s) { Scene::synchronize(s); s.syncBytes(&_shadowPaletteMap[0], 256); _sceneAreas.synchronize(s); } void SceneExt::postInit(SceneObjectList *OwnerList) { Scene::postInit(OwnerList); // Exclude the bottom area of the screen to allow room for the UI T2_GLOBALS._interfaceY = UI_INTERFACE_Y; // Initialize fields _action = NULL; _field12 = 0; _sceneMode = 0; static_cast(R2_GLOBALS._sceneHandler)->setupPaletteMaps(); int prevScene = R2_GLOBALS._sceneManager._previousScene; int sceneNumber = R2_GLOBALS._sceneManager._sceneNumber; if (((prevScene == -1) && (sceneNumber != 180) && (sceneNumber != 205) && (sceneNumber != 50)) || (sceneNumber == 50) || ((sceneNumber == 100) && (prevScene == 0 || prevScene == 180 || prevScene == 205))) { R2_GLOBALS._uiElements._active = true; R2_GLOBALS._uiElements.show(); } else { R2_GLOBALS._uiElements.updateInventory(); } } void SceneExt::remove() { _sceneAreas.clear(); Scene::remove(); R2_GLOBALS._uiElements._active = true; if (R2_GLOBALS._events.getCursor() >= EXITCURSOR_N && R2_GLOBALS._events.getCursor() <= SHADECURSOR_DOWN) R2_GLOBALS._events.setCursor(CURSOR_WALK); } void SceneExt::process(Event &event) { if (!event.handled) Scene::process(event); } void SceneExt::dispatch() { Scene::dispatch(); } bool SceneExt::display(CursorType action, Event &event) { switch (action) { case CURSOR_CROSSHAIRS: case CURSOR_WALK: return false; case CURSOR_LOOK: SceneItem::display2(1, R2_GLOBALS._randomSource.getRandomNumber(4)); break; case CURSOR_USE: SceneItem::display2(1, R2_GLOBALS._randomSource.getRandomNumber(4) + 5); break; case CURSOR_TALK: SceneItem::display2(1, R2_GLOBALS._randomSource.getRandomNumber(4) + 10); break; case R2_NEGATOR_GUN: if (R2_GLOBALS.getFlag(1)) SceneItem::display2(2, action); else SceneItem::display2(5, 0); break; case R2_SONIC_STUNNER: if ((R2_GLOBALS._scannerFrequencies[R2_QUINN] == 2) || ((R2_GLOBALS._scannerFrequencies[R2_QUINN] == 1) && (R2_GLOBALS._scannerFrequencies[R2_SEEKER] == 2) && (R2_GLOBALS._sceneManager._previousScene == 300))) { R2_GLOBALS._sound4.stop(); R2_GLOBALS._sound3.play(46); SceneItem::display2(5, 15); R2_GLOBALS._sound4.play(45); } else { R2_GLOBALS._sound3.play(43, 0); SceneItem::display2(2, R2_SONIC_STUNNER); } break; case R2_COM_SCANNER: case R2_COM_SCANNER_2: R2_GLOBALS._sound3.play(44); SceneItem::display2(2, action); R2_GLOBALS._sound3.stop(); break; case R2_PHOTON_STUNNER: R2_GLOBALS._sound3.play(99); SceneItem::display2(2, action); break; default: SceneItem::display2(2, action); break; } event.handled = true; return true; } void SceneExt::fadeOut() { uint32 black = 0; R2_GLOBALS._scenePalette.fade((const byte *)&black, false, 100); } void SceneExt::startStrip() { SceneExt *scene = (SceneExt *)R2_GLOBALS._sceneManager._scene; scene->_savedPlayerEnabled = R2_GLOBALS._player._enabled; if (scene->_savedPlayerEnabled) { scene->_savedUiEnabled = R2_GLOBALS._player._uiEnabled; scene->_savedCanWalk = R2_GLOBALS._player._canWalk; R2_GLOBALS._player.disableControl(); /* if (!R2_GLOBALS._v50696 && R2_GLOBALS._uiElements._active) R2_GLOBALS._uiElements.hide(); */ } } void SceneExt::endStrip() { SceneExt *scene = (SceneExt *)R2_GLOBALS._sceneManager._scene; if (scene->_savedPlayerEnabled) { R2_GLOBALS._player.enableControl(); R2_GLOBALS._player._uiEnabled = scene->_savedUiEnabled; R2_GLOBALS._player._canWalk = scene->_savedCanWalk; /* if (!R2_GLOBALS._v50696 && R2_GLOBALS._uiElements._active) R2_GLOBALS._uiElements.show(); */ } } void SceneExt::clearScreen() { R2_GLOBALS._screenSurface.fillRect(R2_GLOBALS._screenSurface.getBounds(), 0); } void SceneExt::refreshBackground(int xAmount, int yAmount) { switch (_activeScreenNumber) { case 700: case 1020: case 1100: case 1700: case 2600: case 2950: case 3100: case 3101: case 3275: case 3600: // Use traditional style sectioned screen loading Scene::refreshBackground(xAmount, yAmount); return; default: // Break out to new style screen loading break; } /* New style background loading */ // Get the screen data byte *dataP = g_resourceManager->getResource(RT18, _activeScreenNumber, 0); int screenSize = g_vm->_memoryManager.getSize(dataP); // Lock the background for update Graphics::Surface s = _backSurface.lockSurface(); assert(screenSize == (s.w * s.h)); // Copy the data byte *destP = (byte *)s.getPixels(); Common::copy(dataP, dataP + (s.w * s.h), destP); _backSurface.unlockSurface(); R2_GLOBALS._screenSurface.addDirtyRect(_backSurface.getBounds()); // Free the resource data DEALLOCATE(dataP); } /** * Saves the current player position and view in the details for the specified character index */ void SceneExt::saveCharacter(int characterIndex) { R2_GLOBALS._player._characterPos[characterIndex] = R2_GLOBALS._player._position; R2_GLOBALS._player._characterStrip[characterIndex] = R2_GLOBALS._player._strip; R2_GLOBALS._player._characterFrame[characterIndex] = R2_GLOBALS._player._frame; } void SceneExt::scalePalette(int RFactor, int GFactor, int BFactor) { byte *tmpPal = R2_GLOBALS._scenePalette._palette; byte newR, newG, newB; int tmp, varC, varD = 0; for (int i = 0; i < 256; i++) { newR = (RFactor * tmpPal[(3 * i)]) / 100; newG = (GFactor * tmpPal[(3 * i) + 1]) / 100; newB = (BFactor * tmpPal[(3 * i) + 2]) / 100; varC = 769; for (int j = 255; j >= 0; j--) { tmp = abs(tmpPal[(3 * j)] - newR); if (tmp >= varC) continue; tmp += abs(tmpPal[(3 * j) + 1] - newG); if (tmp >= varC) continue; tmp += abs(tmpPal[(3 * j) + 2] - newB); if (tmp >= varC) continue; varC = tmp; varD = j; } this->_shadowPaletteMap[i] = varD; } } void SceneExt::loadBlankScene() { _backSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT * 3 / 2); _backSurface.fillRect(_backSurface.getBounds(), 0); R2_GLOBALS._screenSurface.fillRect(R2_GLOBALS._screenSurface.getBounds(), 0); } /*--------------------------------------------------------------------------*/ void SceneHandlerExt::postInit(SceneObjectList *OwnerList) { SceneHandler::postInit(OwnerList); if (!R2_GLOBALS._playStream.setFile("SND4K.RES")) warning("Could not find SND4K.RES voice file"); } void SceneHandlerExt::process(Event &event) { if (T2_GLOBALS._uiElements._active && R2_GLOBALS._player._uiEnabled) { T2_GLOBALS._uiElements.process(event); if (event.handled) return; } SceneExt *scene = static_cast(R2_GLOBALS._sceneManager._scene); if (scene && R2_GLOBALS._player._uiEnabled) { // Handle any scene areas that have been registered SynchronizedList::iterator saIter; for (saIter = scene->_sceneAreas.begin(); saIter != scene->_sceneAreas.end() && !event.handled; ++saIter) { (*saIter)->process(event); } } if (!event.handled) SceneHandler::process(event); } void SceneHandlerExt::postLoad(int priorSceneBeforeLoad, int currentSceneBeforeLoad) { // Set up the shading maps used for showing the player in shadows setupPaletteMaps(); if (currentSceneBeforeLoad == 2900) { R2_GLOBALS._gfxFontNumber = 50; R2_GLOBALS._gfxColors.background = 0; R2_GLOBALS._gfxColors.foreground = 59; R2_GLOBALS._fontColors.background = 4; R2_GLOBALS._fontColors.foreground = 15; R2_GLOBALS._frameEdgeColor = 2; R2_GLOBALS._scenePalette.loadPalette(0); R2_GLOBALS._scenePalette.setEntry(255, 0xff, 0xff, 0xff); R2_GLOBALS._fadePaletteFlag = false; setupPaletteMaps(); } } void SceneHandlerExt::setupPaletteMaps() { byte *palP = &R2_GLOBALS._scenePalette._palette[0]; // Set up the mapping table for giving faded versions of pixels at different fade percentages if (!R2_GLOBALS._fadePaletteFlag) { R2_GLOBALS._fadePaletteFlag = true; for (int idx = 0; idx < 10; ++idx) { for (int palIndex = 0; palIndex < 224; ++palIndex) { int r, g, b; // Get adjusted RGB values switch (idx) { case 7: r = palP[palIndex * 3] * 85 / 100; g = palP[palIndex * 3 + 1] * 7 / 10; b = palP[palIndex * 3 + 2] * 7 / 10; break; case 8: r = palP[palIndex * 3] * 7 / 10; g = palP[palIndex * 3 + 1] * 85 / 100; b = palP[palIndex * 3 + 2] * 7 / 10; break; case 9: r = palP[palIndex * 3] * 8 / 10; g = palP[palIndex * 3 + 1] * 5 / 10; b = palP[palIndex * 3 + 2] * 9 / 10; break; default: r = palP[palIndex * 3] * (10 - idx) / 10; g = palP[palIndex * 3 + 1] * (10 - idx) / 12; b = palP[palIndex * 3 + 2] * (10 - idx) / 10; break; } // Scan for the palette index with the closest matching color int threshold = 769; int foundIndex = -1; for (int pIndex2 = 223; pIndex2 >= 0; --pIndex2) { int diffSum = ABS(palP[pIndex2 * 3] - r); if (diffSum >= threshold) continue; diffSum += ABS(palP[pIndex2 * 3 + 1] - g); if (diffSum >= threshold) continue; diffSum += ABS(palP[pIndex2 * 3 + 2] - b); if (diffSum >= threshold) continue; threshold = diffSum; foundIndex = pIndex2; } R2_GLOBALS._fadePaletteMap[idx][palIndex] = foundIndex; } } } for (int palIndex = 0; palIndex < 224; ++palIndex) { int r = palP[palIndex * 3] >> 4; int g = palP[palIndex * 3 + 1] >> 4; int b = palP[palIndex * 3 + 2] >> 4; int v = (r << 8) | (g << 4) | b; assert(v < 0x1000); R2_GLOBALS._paletteMap[v] = palIndex; } int vdx = 0; int idx = 0; int palIndex = 224; for (int vIndex = 0; vIndex < 4096; ++vIndex) { int v = R2_GLOBALS._paletteMap[vIndex]; if (!v) { R2_GLOBALS._paletteMap[vIndex] = idx; } else { idx = v; } if (!palIndex) { vdx = palIndex; } else { int idxTemp = palIndex; palIndex = (palIndex + vdx) / 2; vdx = idxTemp; } } } /*--------------------------------------------------------------------------*/ DisplayHotspot::DisplayHotspot(int regionId, ...) { _sceneRegionId = regionId; // Load up the actions va_list va; va_start(va, regionId); int param = va_arg(va, int); while (param != LIST_END) { _actions.push_back(param); param = va_arg(va, int); } va_end(va); } bool DisplayHotspot::performAction(int action) { for (uint i = 0; i < _actions.size(); i += 3) { if (_actions[i] == action) { display(_actions[i + 1], _actions[i + 2], SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); return true; } } return false; } /*--------------------------------------------------------------------------*/ DisplayObject::DisplayObject(int firstAction, ...) { // Load up the actions va_list va; va_start(va, firstAction); int param = firstAction; while (param != LIST_END) { _actions.push_back(param); param = va_arg(va, int); } va_end(va); } bool DisplayObject::performAction(int action) { for (uint i = 0; i < _actions.size(); i += 3) { if (_actions[i] == action) { display(_actions[i + 1], _actions[i + 2], SET_WIDTH, 200, SET_EXT_BGCOLOR, 7, LIST_END); return true; } } return false; } /*--------------------------------------------------------------------------*/ Ringworld2InvObjectList::Ringworld2InvObjectList(): _none(1, 1), _optoDisk(1, 2), _reader(1, 3), _negatorGun(1, 4), _steppingDisks(1, 5), _attractorUnit(1, 6), _sensorProbe(1, 7), _sonicStunner(1, 8), _cableHarness(1, 9), _comScanner(1, 10), _spentPowerCapsule(1, 11), // 10 _chargedPowerCapsule(1, 12), _aerosol(1, 13), _remoteControl(1, 14), _opticalFibre(1, 15), _clamp(1, 16), _attractorHarness(1, 17), _fuelCell(2, 2), _gyroscope(2, 3), _airbag(2, 4), _rebreatherTank(2, 5), // 20 _reserveTank(2, 5), _guidanceModule(2, 6), _thrusterValve(2, 7), _balloonBackpack(2, 8), _radarMechanism(2, 9), _joystick(2, 10), _ignitor(2, 11), _diagnosticsDisplay(2, 12), _glassDome(2, 13), _wickLamp(2, 14), // 30 _scrithKey(2, 15), _tannerMask(2, 16), _pureGrainAlcohol(3, 2), _blueSapphire(3, 3), _ancientScrolls(3, 4), _flute(3, 5), _gunpowder(3, 6), _unused(3, 7), _comScanner2(1, 10), _superconductorWire(3, 8), // 40 _pillow(3, 9), _foodTray(3, 10), _laserHacksaw(3, 11), _photonStunner(3, 12), _battery(3, 13), _soakedFaceMask(2, 17), _lightBulb(3, 14), _alcoholLamp1(2, 14), _alcoholLamp2(3, 15), _alocholLamp3(3, 15), // 50 _brokenDisplay(3, 17), _toolbox(4, 2) { // Add the items to the list _itemList.push_back(&_none); _itemList.push_back(&_optoDisk); _itemList.push_back(&_reader); _itemList.push_back(&_negatorGun); _itemList.push_back(&_steppingDisks); _itemList.push_back(&_attractorUnit); _itemList.push_back(&_sensorProbe); _itemList.push_back(&_sonicStunner); _itemList.push_back(&_cableHarness); _itemList.push_back(&_comScanner); _itemList.push_back(&_spentPowerCapsule); // 10 _itemList.push_back(&_chargedPowerCapsule); _itemList.push_back(&_aerosol); _itemList.push_back(&_remoteControl); _itemList.push_back(&_opticalFibre); _itemList.push_back(&_clamp); _itemList.push_back(&_attractorHarness); _itemList.push_back(&_fuelCell); _itemList.push_back(&_gyroscope); _itemList.push_back(&_airbag); _itemList.push_back(&_rebreatherTank); // 20 _itemList.push_back(&_reserveTank); _itemList.push_back(&_guidanceModule); _itemList.push_back(&_thrusterValve); _itemList.push_back(&_balloonBackpack); _itemList.push_back(&_radarMechanism); _itemList.push_back(&_joystick); _itemList.push_back(&_ignitor); _itemList.push_back(&_diagnosticsDisplay); _itemList.push_back(&_glassDome); _itemList.push_back(&_wickLamp); // 30 _itemList.push_back(&_scrithKey); _itemList.push_back(&_tannerMask); _itemList.push_back(&_pureGrainAlcohol); _itemList.push_back(&_blueSapphire); _itemList.push_back(&_ancientScrolls); _itemList.push_back(&_flute); _itemList.push_back(&_gunpowder); _itemList.push_back(&_unused); _itemList.push_back(&_comScanner2); _itemList.push_back(&_superconductorWire); // 40 _itemList.push_back(&_pillow); _itemList.push_back(&_foodTray); _itemList.push_back(&_laserHacksaw); _itemList.push_back(&_photonStunner); _itemList.push_back(&_battery); _itemList.push_back(&_soakedFaceMask); _itemList.push_back(&_lightBulb); _itemList.push_back(&_alcoholLamp1); _itemList.push_back(&_alcoholLamp2); _itemList.push_back(&_alocholLamp3); // 50 _itemList.push_back(&_brokenDisplay); _itemList.push_back(&_toolbox); _selectedItem = NULL; } void Ringworld2InvObjectList::reset() { // Reset all object scene numbers SynchronizedList::iterator i; for (i = _itemList.begin(); i != _itemList.end(); ++i) { (*i)->_sceneNumber = 0; } // Set up default inventory setObjectScene(R2_OPTO_DISK, 800); setObjectScene(R2_READER, 400); setObjectScene(R2_NEGATOR_GUN, 100); setObjectScene(R2_STEPPING_DISKS, 100); setObjectScene(R2_ATTRACTOR_UNIT, 400); setObjectScene(R2_SENSOR_PROBE, 400); setObjectScene(R2_SONIC_STUNNER, 500); setObjectScene(R2_CABLE_HARNESS, 700); setObjectScene(R2_COM_SCANNER, 800); setObjectScene(R2_SPENT_POWER_CAPSULE, 100); setObjectScene(R2_CHARGED_POWER_CAPSULE, 400); setObjectScene(R2_AEROSOL, 500); setObjectScene(R2_REMOTE_CONTROL, 1550); setObjectScene(R2_OPTICAL_FIBRE, 850); setObjectScene(R2_CLAMP, 850); setObjectScene(R2_ATTRACTOR_CABLE_HARNESS, 0); setObjectScene(R2_FUEL_CELL, 1550); setObjectScene(R2_GYROSCOPE, 1550); setObjectScene(R2_AIRBAG, 1550); setObjectScene(R2_REBREATHER_TANK, 500); setObjectScene(R2_RESERVE_REBREATHER_TANK, 500); setObjectScene(R2_GUIDANCE_MODULE, 1550); setObjectScene(R2_THRUSTER_VALVE, 1580); setObjectScene(R2_BALLOON_BACKPACK, 9999); setObjectScene(R2_RADAR_MECHANISM, 1550); setObjectScene(R2_JOYSTICK, 1550); setObjectScene(R2_IGNITOR, 1580); setObjectScene(R2_DIAGNOSTICS_DISPLAY, 1550); setObjectScene(R2_GLASS_DOME, 2525); setObjectScene(R2_WICK_LAMP, 2440); setObjectScene(R2_SCRITH_KEY, 2455); setObjectScene(R2_TANNER_MASK, 2535); setObjectScene(R2_PURE_GRAIN_ALCOHOL, 2530); setObjectScene(R2_SAPPHIRE_BLUE, 1950); setObjectScene(R2_ANCIENT_SCROLLS, 1950); setObjectScene(R2_FLUTE, 9999); setObjectScene(R2_GUNPOWDER, 2430); setObjectScene(R2_NONAME, 9999); setObjectScene(R2_COM_SCANNER_2, 2); setObjectScene(R2_SUPERCONDUCTOR_WIRE, 9999); setObjectScene(R2_PILLOW, 3150); setObjectScene(R2_FOOD_TRAY, 0); setObjectScene(R2_LASER_HACKSAW, 3260); setObjectScene(R2_PHOTON_STUNNER, 2); setObjectScene(R2_BATTERY, 1550); setObjectScene(R2_SOAKED_FACEMASK, 0); setObjectScene(R2_LIGHT_BULB, 3150); setObjectScene(R2_ALCOHOL_LAMP, 2435); setObjectScene(R2_ALCOHOL_LAMP_2, 2440); setObjectScene(R2_ALCOHOL_LAMP_3, 2435); setObjectScene(R2_BROKEN_DISPLAY, 1580); setObjectScene(R2_TOOLBOX, 3260); // Set up the select item handler method T2_GLOBALS._onSelectItem = SelectItem; } void Ringworld2InvObjectList::setObjectScene(int objectNum, int sceneNumber) { // Find the appropriate object int num = objectNum; SynchronizedList::iterator i = _itemList.begin(); while (num-- > 0) ++i; (*i)->_sceneNumber = sceneNumber; // If the item is the currently active one, default back to the use cursor if (R2_GLOBALS._events.getCursor() == objectNum) R2_GLOBALS._events.setCursor(CURSOR_USE); // Update the user interface if necessary T2_GLOBALS._uiElements.updateInventory( (sceneNumber == R2_GLOBALS._player._characterIndex) ? objectNum : 0); } /** * When an inventory item is selected, in Return to Ringworld two objects can be combined */ bool Ringworld2InvObjectList::SelectItem(int objectNumber) { // If no existing item selected, don't go any further int currentItem = R2_GLOBALS._events.getCursor(); if (currentItem >= 256) return false; switch (objectNumber) { case R2_NEGATOR_GUN: switch (currentItem) { case R2_SENSOR_PROBE: if (R2_GLOBALS.getFlag(1)) SceneItem::display2(5, 1); else if (R2_INVENTORY.getObjectScene(R2_SPENT_POWER_CAPSULE) != 100) SceneItem::display2(5, 3); else { R2_GLOBALS._sound3.play(48); SceneItem::display2(5, 2); R2_INVENTORY.setObjectScene(R2_SPENT_POWER_CAPSULE, 1); } break; case R2_COM_SCANNER: R2_GLOBALS._sound3.play(44); if (R2_GLOBALS.getFlag(1)) SceneItem::display2(5, 9); else if (R2_INVENTORY.getObjectScene(R2_SPENT_POWER_CAPSULE) == 100) SceneItem::display2(5, 8); else SceneItem::display2(5, 10); R2_GLOBALS._sound3.stop(); break; case R2_CHARGED_POWER_CAPSULE: if (R2_INVENTORY.getObjectScene(R2_SPENT_POWER_CAPSULE) == 1) { R2_GLOBALS._sound3.play(49); R2_INVENTORY.setObjectScene(R2_CHARGED_POWER_CAPSULE, 100); R2_GLOBALS.setFlag(1); SceneItem::display2(5, 4); } else { SceneItem::display2(5, 5); } break; default: selectDefault(objectNumber); break; } break; case R2_STEPPING_DISKS: switch (currentItem) { case R2_SENSOR_PROBE: if (R2_INVENTORY.getObjectScene(R2_CHARGED_POWER_CAPSULE) == 400) { R2_GLOBALS._sound3.play(48); SceneItem::display2(5, 6); R2_INVENTORY.setObjectScene(R2_CHARGED_POWER_CAPSULE, 1); } else { SceneItem::display2(5, 7); } break; case R2_COM_SCANNER: R2_GLOBALS._sound3.play(44); if (R2_INVENTORY.getObjectScene(R2_CHARGED_POWER_CAPSULE) == 400) SceneItem::display2(5, 16); else SceneItem::display2(5, 17); R2_GLOBALS._sound3.stop(); break; default: selectDefault(objectNumber); break; } break; case R2_ATTRACTOR_UNIT: case R2_CABLE_HARNESS: if (currentItem == R2_CABLE_HARNESS || currentItem == R2_ATTRACTOR_UNIT) { R2_INVENTORY.setObjectScene(R2_CABLE_HARNESS, 0); R2_INVENTORY.setObjectScene(R2_ATTRACTOR_UNIT, 0); R2_INVENTORY.setObjectScene(R2_ATTRACTOR_CABLE_HARNESS, 1); } else { selectDefault(objectNumber); } break; case R2_TANNER_MASK: case R2_PURE_GRAIN_ALCOHOL: if (currentItem == R2_TANNER_MASK || currentItem == R2_PURE_GRAIN_ALCOHOL) { R2_INVENTORY.setObjectScene(R2_TANNER_MASK, 0); R2_INVENTORY.setObjectScene(R2_PURE_GRAIN_ALCOHOL, 0); R2_INVENTORY.setObjectScene(R2_SOAKED_FACEMASK, R2_SEEKER); } else { selectDefault(objectNumber); } break; default: // Standard item selection return false; } return true; } void Ringworld2InvObjectList::selectDefault(int objectNumber) { Common::String msg1 = g_resourceManager->getMessage(4, 53); Common::String msg2 = g_resourceManager->getMessage(4, R2_GLOBALS._events.getCursor()); Common::String msg3 = g_resourceManager->getMessage(4, 54); Common::String msg4 = g_resourceManager->getMessage(4, objectNumber); Common::String line = Common::String::format("%.5s%.5s%.5s%.5s%s %s %s %s.", msg1.c_str(), msg2.c_str(), msg3.c_str(), msg4.c_str(), msg1.c_str() + 5, msg2.c_str() + 5, msg3.c_str() + 5, msg4.c_str() + 5); SceneItem::display(-1, -1, line.c_str(), SET_WIDTH, 280, SET_X, 160, SET_Y, 20, SET_POS_MODE, 1, SET_EXT_BGCOLOR, 7, LIST_END); } /*--------------------------------------------------------------------------*/ void Ringworld2Game::start() { int slot = -1; if (ConfMan.hasKey("save_slot")) { slot = ConfMan.getInt("save_slot"); Common::String file = g_vm->generateSaveName(slot); Common::InSaveFile *in = g_vm->_system->getSavefileManager()->openForLoading(file); if (in) delete in; else slot = -1; } if (slot >= 0) R2_GLOBALS._sceneHandler->_loadGameSlot = slot; else { // Switch to the first title screen R2_GLOBALS._events.setCursor(CURSOR_WALK); R2_GLOBALS._uiElements._active = true; R2_GLOBALS._sceneManager.setNewScene(180); } g_globals->_events.showCursor(); } void Ringworld2Game::restartGame() { if (MessageDialog::show(Ringworld2::R2_RESTART_MSG, CANCEL_BTN_STRING, YES_MSG) == 1) restart(); } void Ringworld2Game::restart() { g_globals->_scenePalette.clearListeners(); g_globals->_soundHandler.stop(); // Reset the globals g_globals->reset(); // Clear save/load slots g_globals->_sceneHandler->_saveGameSlot = -1; g_globals->_sceneHandler->_loadGameSlot = -1; // Change to the first game scene g_globals->_sceneManager.changeScene(100); } void Ringworld2Game::endGame(int resNum, int lineNum) { g_globals->_events.setCursor(CURSOR_WALK); Common::String msg = g_resourceManager->getMessage(resNum, lineNum); bool savesExist = g_saver->savegamesExist(); if (!savesExist) { // No savegames exist, so prompt the user to restart or quit if (MessageDialog::show(msg, QUIT_BTN_STRING, RESTART_BTN_STRING) == 0) g_vm->quitGame(); else restart(); } else { // Savegames exist, so prompt for Restore/Restart bool breakFlag; do { if (g_vm->shouldQuit()) { breakFlag = true; } else if (MessageDialog::show(msg, RESTART_BTN_STRING, RESTORE_BTN_STRING) == 0) { restart(); breakFlag = true; } else { handleSaveLoad(false, g_globals->_sceneHandler->_loadGameSlot, g_globals->_sceneHandler->_saveName); breakFlag = g_globals->_sceneHandler->_loadGameSlot >= 0; } } while (!breakFlag); } g_globals->_events.setCursorFromFlag(); } void Ringworld2Game::processEvent(Event &event) { if (event.eventType == EVENT_KEYPRESS) { switch (event.kbd.keycode) { case Common::KEYCODE_F1: // F1 - Help HelpDialog::show(); break; case Common::KEYCODE_F2: // F2 - Sound Options SoundDialog::execute(); break; case Common::KEYCODE_F3: // F3 - Quit quitGame(); event.handled = false; break; case Common::KEYCODE_F4: // F4 - Restart restartGame(); R2_GLOBALS._events.setCursorFromFlag(); break; case Common::KEYCODE_F7: // F7 - Restore restoreGame(); R2_GLOBALS._events.setCursorFromFlag(); break; case Common::KEYCODE_F8: // F8 - Credits R2_GLOBALS._sceneManager.changeScene(205); break; case Common::KEYCODE_F10: // F10 - Pause GfxDialog::setPalette(); MessageDialog::show(GAME_PAUSED_MSG, OK_BTN_STRING); R2_GLOBALS._events.setCursorFromFlag(); break; default: break; } } } void Ringworld2Game::rightClick() { RightClickDialog *dlg = new RightClickDialog(); int option = dlg->execute(); delete dlg; if (option == 0) CharacterDialog::show(); else if (option == 1) HelpDialog::show(); } /*--------------------------------------------------------------------------*/ NamedHotspot::NamedHotspot() : SceneHotspot() { _resNum = 0; _lookLineNum = _useLineNum = _talkLineNum = -1; } bool NamedHotspot::startAction(CursorType action, Event &event) { switch (action) { case CURSOR_WALK: // Nothing return false; case CURSOR_LOOK: if (_lookLineNum == -1) return SceneHotspot::startAction(action, event); SceneItem::display2(_resNum, _lookLineNum); return true; case CURSOR_USE: if (_useLineNum == -1) return SceneHotspot::startAction(action, event); SceneItem::display2(_resNum, _useLineNum); return true; case CURSOR_TALK: if (_talkLineNum == -1) return SceneHotspot::startAction(action, event); SceneItem::display2(_resNum, _talkLineNum); return true; default: return SceneHotspot::startAction(action, event); } } /*--------------------------------------------------------------------------*/ void SceneActor::postInit(SceneObjectList *OwnerList) { _lookLineNum = _talkLineNum = _useLineNum = -1; SceneObject::postInit(); } void SceneActor::remove() { R2_GLOBALS._sceneItems.remove(this); _shadowMap = NULL; _linkedActor = NULL; SceneObject::remove(); } bool SceneActor::startAction(CursorType action, Event &event) { bool handled = true; switch (action) { case CURSOR_LOOK: if (_lookLineNum == -1) handled = false; else SceneItem::display2(_resNum, _lookLineNum); break; case CURSOR_USE: if (_useLineNum == -1) handled = false; else SceneItem::display2(_resNum, _useLineNum); break; case CURSOR_TALK: if (_talkLineNum == -1) handled = false; else SceneItem::display2(_resNum, _talkLineNum); break; default: handled = false; break; } if (!handled) handled = ((SceneExt *)R2_GLOBALS._sceneManager._scene)->display(action, event); return handled; } GfxSurface SceneActor::getFrame() { GfxSurface frame = SceneObject::getFrame(); return frame; } /*--------------------------------------------------------------------------*/ SceneArea::SceneArea(): SceneItem() { _enabled = true; _insideArea = false; _savedCursorNum = CURSOR_NONE; _cursorState = 0; _cursorNum = CURSOR_NONE; } void SceneArea::synchronize(Serializer &s) { EventHandler::synchronize(s); _bounds.synchronize(s); s.syncAsSint16LE(_enabled); s.syncAsSint16LE(_insideArea); s.syncAsSint32LE(_cursorNum); s.syncAsSint32LE(_savedCursorNum); s.syncAsSint16LE(_cursorState); } void SceneArea::remove() { static_cast(R2_GLOBALS._sceneManager._scene)->_sceneAreas.remove(this); } void SceneArea::process(Event &event) { Common::Point mousePos = event.mousePos; mousePos.x += R2_GLOBALS._sceneManager._scene->_sceneBounds.left; if (!R2_GLOBALS._insetUp && _enabled && R2_GLOBALS._events.isCursorVisible()) { CursorType cursor = R2_GLOBALS._events.getCursor(); if (_bounds.contains(mousePos)) { // Cursor moving in bounded area if (cursor != _cursorNum) { _savedCursorNum = cursor; _cursorState = 0; R2_GLOBALS._events.setCursor(_cursorNum); } _insideArea = true; } else if ((mousePos.y < 171) && _insideArea && (_cursorNum == cursor) && (_savedCursorNum != CURSOR_NONE)) { // Cursor moved outside bounded area R2_GLOBALS._events.setCursor(_savedCursorNum); } } } void SceneArea::setDetails(const Rect &bounds, CursorType cursor) { _bounds = bounds; _cursorNum = cursor; static_cast(R2_GLOBALS._sceneManager._scene)->_sceneAreas.push_front(this); } /*--------------------------------------------------------------------------*/ SceneExit::SceneExit(): SceneArea() { _moving = false; _destPos = Common::Point(-1, -1); _sceneNumber = 0; } void SceneExit::synchronize(Serializer &s) { SceneArea::synchronize(s); s.syncAsSint16LE(_moving); s.syncAsSint16LE(_destPos.x); s.syncAsSint16LE(_destPos.y); } void SceneExit::setDetails(const Rect &bounds, CursorType cursor, int sceneNumber) { _sceneNumber = sceneNumber; SceneArea::setDetails(bounds, cursor); } void SceneExit::changeScene() { R2_GLOBALS._sceneManager.setNewScene(_sceneNumber); } void SceneExit::process(Event &event) { Common::Point mousePos = event.mousePos; mousePos.x += R2_GLOBALS._sceneManager._scene->_sceneBounds.left; if (!R2_GLOBALS._insetUp) { SceneArea::process(event); if (_enabled) { if (event.eventType == EVENT_BUTTON_DOWN) { if (!_bounds.contains(mousePos)) _moving = false; else if (!R2_GLOBALS._player._canWalk) { _moving = false; changeScene(); event.handled = true; } else { Common::Point dest((_destPos.x == -1) ? mousePos.x : _destPos.x, (_destPos.y == -1) ? mousePos.y : _destPos.y); ADD_PLAYER_MOVER(dest.x, dest.y); _moving = true; event.handled = true; } } if (_moving && (_bounds.contains(R2_GLOBALS._player._position) || (R2_GLOBALS._player._position == _destPos))) changeScene(); } } } /*--------------------------------------------------------------------------*/ void SceneAreaObject::remove() { R2_GLOBALS._sceneItems.remove(this); _object1.remove(); SceneArea::remove(); --R2_GLOBALS._insetUp; } void SceneAreaObject::process(Event &event) { if (_insetCount == R2_GLOBALS._insetUp) { CursorType cursor = R2_GLOBALS._events.getCursor(); if (_object1._bounds.contains(event.mousePos)) { // Cursor moving in bounded area if (cursor == _cursorNum) { R2_GLOBALS._events.setCursor(_savedCursorNum); } } else if (event.mousePos.y < 168) { if (_cursorNum != cursor) { // Cursor moved outside bounded area _savedCursorNum = R2_GLOBALS._events.getCursor(); R2_GLOBALS._events.setCursor(CURSOR_INVALID); } if (event.eventType == EVENT_BUTTON_DOWN) { event.handled = true; R2_GLOBALS._events.setCursor(_savedCursorNum); remove(); } } } } void SceneAreaObject::setDetails(int visage, int strip, int frameNumber, const Common::Point &pt) { _object1.postInit(); _object1.setup(visage, strip, frameNumber); _object1.setPosition(pt); _object1.fixPriority(250); _cursorNum = CURSOR_INVALID; Scene500 *scene = (Scene500 *)R2_GLOBALS._sceneManager._scene; scene->_sceneAreas.push_front(this); _insetCount = ++R2_GLOBALS._insetUp; } void SceneAreaObject::setDetails(int resNum, int lookLineNum, int talkLineNum, int useLineNum) { _object1.setDetails(resNum, lookLineNum, talkLineNum, useLineNum, 2, (SceneItem *)NULL); } /*****************************************************************************/ MazeUI::MazeUI() { _mapData = NULL; _cellsVisible.x = _cellsVisible.y = 0; _mapCells.x = _mapCells.y = 0; _cellSize.x = _cellSize.y = 0; _mapOffset.x = _mapOffset.y = 0; _resNum = _cellsResNum = 0; _frameCount = _resCount = _mapImagePitch = 0; } MazeUI::~MazeUI() { DEALLOCATE(_mapData); } void MazeUI::synchronize(Serializer &s) { SceneObject::synchronize(s); s.syncAsSint16LE(_resNum); if (s.isLoading()) load(_resNum); s.syncAsSint16LE(_mapOffset.x); s.syncAsSint16LE(_mapOffset.y); int dummy = 0; s.syncAsSint16LE(dummy); } void MazeUI::load(int resNum) { clear(); _resNum = resNum; const byte *header = g_resourceManager->getResource(RT17, resNum, 0); _cellsResNum = resNum + 1000; _mapCells.x = READ_LE_UINT16(header + 2); _mapCells.y = READ_LE_UINT16(header + 4); _frameCount = 10; _resCount = _frameCount << 3; Visage visage; visage.setVisage(_cellsResNum, 1); GfxSurface frame = visage.getFrame(2); _cellSize.x = frame.getBounds().width(); _cellSize.y = frame.getBounds().height(); _mapData = g_resourceManager->getResource(RT17, resNum, 1); _mapOffset.y = _mapOffset.x = 0; _cellsVisible.x = (_bounds.width() + _cellSize.x - 1) / _cellSize.x; _cellsVisible.y = (_bounds.height() + _cellSize.y - 1) / _cellSize.y; _mapImagePitch = (_cellsVisible.x + 1) * _cellSize.x; _mapImage.create(_mapImagePitch, _cellSize.y); _mapBounds = Rect(0, 0, _cellSize.x * _mapCells.x, _cellSize.y * _mapCells.y); } void MazeUI::clear() { if (!_resNum) _resNum = 1; if (_mapData) DEALLOCATE(_mapData); _mapData = NULL; _mapImage.clear(); } bool MazeUI::setMazePosition(const Common::Point &pt) { bool retval = false; _mapOffset = pt; if (_mapOffset.x < _mapBounds.top) { _mapOffset.x = _mapBounds.top; retval = true; } if (_mapOffset.y < _mapBounds.left) { _mapOffset.y = _mapBounds.left; retval = true; } if (_mapOffset.x + _bounds.width() > _mapBounds.right) { _mapOffset.x = _mapBounds.right - _bounds.width(); retval = true; } if (_mapOffset.y + _bounds.height() > _mapBounds.bottom) { _mapOffset.y = _mapBounds.bottom - _bounds.height(); retval = true; } return retval; } void MazeUI::reposition() { } void MazeUI::draw() { int yPos = 0; int ySize; Visage visage; _cellsVisible.y = ((_mapOffset.y % _cellSize.y) + _bounds.height() + (_cellSize.y - 1)) / _cellSize.y; // Loop to handle the cell rows of the visible display area one at a time for (int yCtr = 0; yCtr <= _cellsVisible.y; ++yCtr, yPos += ySize) { int cellY = _mapOffset.y / _cellSize.y + yCtr; // Loop to iterate through the horizontal visible cells to build up // an entire cell high horizontal slice of the map, plus one extra cell // to allow for partial cell scrolling on-screen on the left/right sides for (int xCtr = 0; xCtr <= _cellsVisible.x; ++xCtr) { int cellX = _mapOffset.x / _cellSize.x + xCtr; // Get the type of content to display in the cell int cell = getCellFromCellXY(Common::Point(cellX, cellY)) - 1; if (cell >= 0) { int frameNum = (cell % _frameCount) + 1; int rlbNum = (cell % _resCount) / _frameCount + 1; int resNum = _cellsResNum + (cell / _resCount); visage.setVisage(resNum, rlbNum); GfxSurface frame = visage.getFrame(frameNum); _mapImage.copyFrom(frame, xCtr * _cellSize.x, 0); } else { GfxSurface emptyRect; emptyRect.create(_cellSize.x, _cellSize.y); _mapImage.copyFrom(emptyRect, xCtr * _cellSize.x, 0); } } if (yPos == 0) { // First line of the map to be displayed - only the bottom portion of that // first cell row may be visible yPos = _bounds.top; ySize = _cellSize.y - (_mapOffset.y % _cellSize.y); Rect srcBounds(_mapOffset.x % _cellSize.x, _mapOffset.y % _cellSize.y, (_mapOffset.x % _cellSize.x) + _bounds.width(), _cellSize.y); Rect destBounds(_bounds.left, yPos, _bounds.right, yPos + ySize); R2_GLOBALS.gfxManager().copyFrom(_mapImage, srcBounds, destBounds); } else { if ((yPos + _cellSize.y) < _bounds.bottom) { ySize = _cellSize.y; } else { ySize = _bounds.bottom - yPos; } Rect srcBounds(_mapOffset.x % _cellSize.x, 0, (_mapOffset.x % _cellSize.x) + _bounds.width(), ySize); Rect destBounds(_bounds.left, yPos, _bounds.right, yPos + ySize); R2_GLOBALS.gfxManager().copyFrom(_mapImage, srcBounds, destBounds); } } } int MazeUI::getCellFromPixelXY(const Common::Point &pt) { if (!_bounds.contains(pt)) return -1; int cellX = (pt.x - _bounds.left + _mapOffset.x) / _cellSize.x; int cellY = (pt.y - _bounds.top + _mapOffset.y) / _cellSize.y; if ((cellX >= 0) && (cellY >= 0) && (cellX < _mapCells.x) && (cellY < _mapCells.y)) return (int16)READ_LE_UINT16(_mapData + (_mapCells.x * cellY + cellX) * 2); return -1; } int MazeUI::getCellFromCellXY(const Common::Point &p) { if (p.x < 0 || p.y < 0 || p.x >= _mapCells.x || p.y >= _mapCells.y) { return -1; } else { return (int16)READ_LE_UINT16(_mapData + (_mapCells.x * p.y + p.x) * 2); } } int MazeUI::pixelToCellXY(Common::Point &pt) { pt.x /= _cellSize.x; pt.y /= _cellSize.y; if ((pt.x >= 0) && (pt.y >= 0) && (pt.x < _mapCells.x) && (pt.y < _mapCells.y)) { return (int16)READ_LE_UINT16(_mapData + (_mapCells.x * pt.y + pt.x) * 2); } return -1; } void MazeUI::setDisplayBounds(const Rect &r) { _bounds = r; _bounds.clip(g_globals->gfxManager()._bounds); } /*--------------------------------------------------------------------------*/ void AnimationSlice::load(Common::File &f) { f.skip(2); _sliceOffset = f.readUint16LE(); f.skip(6); _drawMode = f.readByte(); _secondaryIndex = f.readByte(); } /*--------------------------------------------------------------------------*/ AnimationSlices::AnimationSlices() { _pixelData = NULL; _dataSize = 0; _dataSize2 = 0; _slices->_sliceOffset = 0; _slices->_drawMode = 0; _slices->_secondaryIndex = 0; } AnimationSlices::~AnimationSlices() { delete[] _pixelData; } void AnimationSlices::load(Common::File &f) { f.skip(4); _dataSize = f.readUint32LE(); f.skip(8); _dataSize2 = f.readUint32LE(); f.skip(28); // Load the four slice indexes for (int idx = 0; idx < 4; ++idx) _slices[idx].load(f); } int AnimationSlices::loadPixels(Common::File &f, int slicesSize) { delete[] _pixelData; _pixelData = new byte[slicesSize]; return f.read(_pixelData, slicesSize); } /*--------------------------------------------------------------------------*/ void AnimationPlayerSubData::load(Common::File &f) { uint32 posStart = f.pos(); f.skip(6); _duration = f.readUint32LE(); _frameRate = f.readUint16LE(); _framesPerSlices = f.readUint16LE(); _drawType = f.readUint16LE(); f.skip(2); _sliceSize = f.readUint16LE(); _ySlices = f.readUint16LE(); _field16 = f.readUint32LE(); f.skip(2); _palStart = f.readUint16LE(); _palSize = f.readUint16LE(); f.read(_palData, 768); _totalSize = f.readSint32LE(); f.skip(12); _slices.load(f); uint32 posEnd = f.pos(); assert((posEnd - posStart) == 0x390); } /*--------------------------------------------------------------------------*/ AnimationPlayer::AnimationPlayer(): EventHandler() { _endAction = NULL; _animData1 = NULL; _animData2 = NULL; _screenBounds = R2_GLOBALS._gfxManagerInstance._bounds; _rect1 = R2_GLOBALS._gfxManagerInstance._bounds; _paletteMode = ANIMPALMODE_REPLACE_PALETTE; _canSkip = true; _sliceHeight = 1; _endAction = NULL; _sliceCurrent = nullptr; _sliceNext = nullptr; _animLoaded = false; _objectMode = ANIMOBJMODE_1; _dataNeeded = 0; _playbackTick = 0; _playbackTickPrior = 0; _position = 0; _nextSlicesPosition = 0; _frameDelay = 0; _gameFrame = 0; } AnimationPlayer::~AnimationPlayer() { if (!isCompleted()) close(); } void AnimationPlayer::synchronize(Serializer &s) { EventHandler::synchronize(s); // TODO: Implement saving for animation player state. Currently, I disable saving // when an animation is active, so saving it's state would a "nice to have". } void AnimationPlayer::remove() { if (_endAction) _endAction->signal(); _endAction = NULL; } void AnimationPlayer::process(Event &event) { if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE) && _canSkip) { // Move the current position to the end _position = _subData._duration; } } void AnimationPlayer::dispatch() { uint32 gameFrame = R2_GLOBALS._events.getFrameNumber(); uint32 gameDiff = gameFrame - _gameFrame; if (gameDiff >= _frameDelay) { drawFrame(_playbackTick % _subData._framesPerSlices); ++_playbackTick; _position = _playbackTick / _subData._framesPerSlices; if (_position == _nextSlicesPosition) nextSlices(); _playbackTickPrior = _playbackTick; _gameFrame = gameFrame; } } bool AnimationPlayer::load(int animId, Action *endAction) { // Open up the main resource file for access TLib &libFile = g_resourceManager->first(); if (!_resourceFile.open(libFile.getFilename())) error("Could not open resource"); // Get the offset of the given resource and seek to it in the player's file reference ResourceEntry entry; uint32 fileOffset = libFile.getResourceStart(RES_IMAGE, animId, 0, entry); _resourceFile.seek(fileOffset); // At this point, the file is pointing to the start of the resource data // Set the end action _endAction = endAction; // Load the sub data block _subData.load(_resourceFile); // Set other properties _playbackTickPrior = -1; _playbackTick = 0; _frameDelay = (60 / _subData._frameRate); _gameFrame = R2_GLOBALS._events.getFrameNumber(); // WORKAROUND: Slow down the title sequences to better match the original if (animId <= 4 || animId == 15) _frameDelay *= 8; if (_subData._totalSize) { _dataNeeded = _subData._totalSize; } else { int v = (_subData._sliceSize + 2) * _subData._ySlices * _subData._framesPerSlices; _dataNeeded = (_subData._field16 / _subData._framesPerSlices) + v + 96; } debugC(1, ktSageDebugGraphics, "Data needed %d", _dataNeeded); // Set up animation data objects _animData1 = new AnimationData(); _sliceCurrent = _animData1; if (_subData._framesPerSlices <= 1) { _animData2 = NULL; _sliceNext = _sliceCurrent; } else { _animData2 = new AnimationData(); _sliceNext = _animData2; } _position = 0; _nextSlicesPosition = 1; // Load up the first slices set _sliceCurrent->_dataSize = _subData._slices._dataSize; _sliceCurrent->_slices = _subData._slices; int slicesSize = _sliceCurrent->_dataSize - 96; int readSize = _sliceCurrent->_slices.loadPixels(_resourceFile, slicesSize); _sliceCurrent->_animSlicesSize = readSize + 96; if (_sliceNext != _sliceCurrent) { getSlices(); } // Handle starting palette switch (_paletteMode) { case ANIMPALMODE_REPLACE_PALETTE: // Use the palette provided with the animation directly _palette.getPalette(); for (int idx = _subData._palStart; idx < (_subData._palStart + _subData._palSize); ++idx) { byte r = _subData._palData[idx * 3]; byte g = _subData._palData[idx * 3 + 1]; byte b = _subData._palData[idx * 3 + 2]; R2_GLOBALS._scenePalette.setEntry(idx, r, g, b); } R2_GLOBALS._sceneManager._hasPalette = true; break; case ANIMPALMODE_NONE: break; default: // ANIMPALMODE_CURR_PALETTE // Use the closest matching colors in the currently active palette to those specified in the animation for (int idx = _subData._palStart; idx < (_subData._palStart + _subData._palSize); ++idx) { byte r = _subData._palData[idx * 3]; byte g = _subData._palData[idx * 3 + 1]; byte b = _subData._palData[idx * 3 + 2]; int palIndex = R2_GLOBALS._scenePalette.indexOf(r, g, b); _palIndexes[idx] = palIndex; } break; } ++R2_GLOBALS._animationCtr; _animLoaded = true; return true; } void AnimationPlayer::drawFrame(int sliceIndex) { assert(sliceIndex < 4); AnimationSlices &slices = _sliceCurrent->_slices; AnimationSlice &slice = _sliceCurrent->_slices._slices[sliceIndex]; byte *sliceDataStart = &slices._pixelData[slice._sliceOffset - 96]; byte *sliceData1 = sliceDataStart; Rect playerBounds = _screenBounds; int y = _screenBounds.top; R2_GLOBALS._screenSurface.addDirtyRect(playerBounds); Graphics::Surface surface = R2_GLOBALS._screenSurface.lockSurface(); // Handle different drawing modes switch (slice._drawMode) { case 0: // Draw from uncompressed source for (int sliceNum = 0; sliceNum < _subData._ySlices; ++sliceNum) { for (int yIndex = 0; yIndex < _sliceHeight; ++yIndex) { // TODO: Check of _subData._drawType was done for two different kinds of // line slice drawing in original const byte *pSrc = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, y++); Common::copy(pSrc, pSrc + _subData._sliceSize, pDest); } } break; case 1: switch (slice._secondaryIndex) { case 0xfe: // Draw from uncompressed source with optional skipped rows for (int sliceNum = 0; sliceNum < _subData._ySlices; ++sliceNum) { for (int yIndex = 0; yIndex < _sliceHeight; ++yIndex, playerBounds.top++) { int offset = READ_LE_UINT16(sliceData1 + sliceNum * 2); if (offset) { const byte *pSrc = (const byte *)sliceDataStart + offset; byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, playerBounds.top); //Common::copy(pSrc, pSrc + playerBounds.width(), pDest); rleDecode(pSrc, pDest, playerBounds.width()); } } } break; case 0xff: // Draw from RLE compressed source for (int sliceNum = 0; sliceNum < _subData._ySlices; ++sliceNum) { for (int yIndex = 0; yIndex < _sliceHeight; ++yIndex, playerBounds.top++) { // TODO: Check of _subData._drawType was done for two different kinds of // line slice drawing in original const byte *pSrc = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, playerBounds.top); rleDecode(pSrc, pDest, _subData._sliceSize); } } break; default: { // Draw from two slice sets simultaneously AnimationSlice &slice2 = _sliceCurrent->_slices._slices[slice._secondaryIndex]; byte *sliceData2 = &slices._pixelData[slice2._sliceOffset - 96]; for (int sliceNum = 0; sliceNum < _subData._ySlices; ++sliceNum) { for (int yIndex = 0; yIndex < _sliceHeight; ++yIndex) { const byte *pSrc1 = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData2 + sliceNum * 2); const byte *pSrc2 = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, y++); if (slice2._drawMode == 0) { // Uncompressed background, foreground compressed Common::copy(pSrc1, pSrc1 + _subData._sliceSize, pDest); rleDecode(pSrc2, pDest, _subData._sliceSize); } else { // Both background and foreground is compressed rleDecode(pSrc1, pDest, _subData._sliceSize); rleDecode(pSrc2, pDest, _subData._sliceSize); } } } break; } } default: break; } // Unlock the screen surface R2_GLOBALS._screenSurface.unlockSurface(); if (_objectMode == ANIMOBJMODE_42) { _screenBounds.expandPanes(); // Copy the drawn frame to the back surface Rect srcRect = R2_GLOBALS._screenSurface.getBounds(); Rect destRect = srcRect; destRect.translate(-g_globals->_sceneOffset.x, -g_globals->_sceneOffset.y); R2_GLOBALS._sceneManager._scene->_backSurface.copyFrom(R2_GLOBALS._screenSurface, srcRect, destRect); // Draw any objects into the scene R2_GLOBALS._sceneObjects->draw(); } else { if (R2_GLOBALS._sceneManager._hasPalette) { R2_GLOBALS._sceneManager._hasPalette = false; R2_GLOBALS._scenePalette.refresh(); } } } /** * Read the next frame's slice set */ void AnimationPlayer::nextSlices() { _position = _nextSlicesPosition++; _playbackTick = _position * _subData._framesPerSlices; _playbackTickPrior = _playbackTick - 1; if (_sliceNext == _sliceCurrent) { int dataSize = _sliceCurrent->_slices._dataSize2; _sliceCurrent->_dataSize = dataSize; debugC(1, ktSageDebugGraphics, "Next frame size = %xh", dataSize); if (dataSize == 0) return; dataSize -= 96; assert(dataSize >= 0); _sliceCurrent->_slices.load(_resourceFile); _sliceCurrent->_animSlicesSize = _sliceCurrent->_slices.loadPixels(_resourceFile, dataSize); } else { SWAP(_sliceCurrent, _sliceNext); getSlices(); } } bool AnimationPlayer::isCompleted() { return (_position >= _subData._duration); } void AnimationPlayer::close() { if (_animLoaded) { switch (_paletteMode) { case 0: R2_GLOBALS._scenePalette.replace(&_palette); changePane(); R2_GLOBALS._sceneManager._hasPalette = true; break; case 2: closing(); break; default: changePane(); break; } } // Close the resource file _resourceFile.close(); if (_objectMode != ANIMOBJMODE_42) { // flip screen in original } // Free animation objects delete _animData1; delete _animData2; _animData1 = NULL; _animData2 = NULL; _animLoaded = false; if (g_globals != NULL) R2_GLOBALS._animationCtr = MAX(R2_GLOBALS._animationCtr - 1, 0); } void AnimationPlayer::rleDecode(const byte *pSrc, byte *pDest, int size) { while (size > 0) { byte v = *pSrc++; if (!(v & 0x80)) { // Following uncompressed set of bytes Common::copy(pSrc, pSrc + v, pDest); pSrc += v; pDest += v; size -= v; } else { int count = v & 0x3F; size -= count; if (!(v & 0x40)) { // Skip over a number of bytes pDest += count; } else { // Replicate a number of bytes Common::fill(pDest, pDest + count, *pSrc++); pDest += count; } } } } void AnimationPlayer::getSlices() { assert((_sliceNext == _animData1) || (_sliceNext == _animData2)); assert((_sliceCurrent == _animData1) || (_sliceCurrent == _animData2)); _sliceNext->_dataSize = _sliceCurrent->_slices._dataSize2; if (_sliceNext->_dataSize) { if (_sliceNext->_dataSize >= _dataNeeded) error("Bogus dataNeeded == %d / %d", _sliceNext->_dataSize, _dataNeeded); } int dataSize = _sliceNext->_dataSize - 96; _sliceNext->_slices.load(_resourceFile); _sliceNext->_animSlicesSize = _sliceNext->_slices.loadPixels(_resourceFile, dataSize); } /*--------------------------------------------------------------------------*/ AnimationPlayerExt::AnimationPlayerExt(): AnimationPlayer() { _isActive = false; _canSkip = false; } void AnimationPlayerExt::synchronize(Serializer &s) { AnimationPlayer::synchronize(s); s.syncAsSint16LE(_isActive); } /*--------------------------------------------------------------------------*/ ModalWindow::ModalWindow() { _insetCount = 0; } void ModalWindow::remove() { R2_GLOBALS._sceneItems.remove(&_object1); _object1.remove(); SceneArea::remove(); --R2_GLOBALS._insetUp; } void ModalWindow::synchronize(Serializer &s) { SceneArea::synchronize(s); s.syncAsByte(_insetCount); } void ModalWindow::process(Event &event) { if (_insetCount != R2_GLOBALS._insetUp) return; CursorType cursor = R2_GLOBALS._events.getCursor(); if (_object1._bounds.contains(event.mousePos.x + g_globals->gfxManager()._bounds.left , event.mousePos.y)) { if (cursor == _cursorNum) { R2_GLOBALS._events.setCursor(_savedCursorNum); } } else if (event.mousePos.y < 168) { if (cursor != _cursorNum) { _savedCursorNum = cursor; R2_GLOBALS._events.setCursor(CURSOR_INVALID); } if (event.eventType == EVENT_BUTTON_DOWN) { event.handled = true; R2_GLOBALS._events.setCursor(_savedCursorNum); remove(); } } } void ModalWindow::setup2(int visage, int stripFrameNum, int frameNum, int posX, int posY) { Scene1200 *scene = (Scene1200 *)R2_GLOBALS._sceneManager._scene; _object1.postInit(); _object1.setup(visage, stripFrameNum, frameNum); _object1.setPosition(Common::Point(posX, posY)); _object1.fixPriority(250); _cursorNum = CURSOR_INVALID; scene->_sceneAreas.push_front(this); ++R2_GLOBALS._insetUp; _insetCount = R2_GLOBALS._insetUp; } void ModalWindow::setup3(int resNum, int lookLineNum, int talkLineNum, int useLineNum) { _object1.setDetails(resNum, lookLineNum, talkLineNum, useLineNum, 2, (SceneItem *) NULL); } /*--------------------------------------------------------------------------*/ ScannerDialog::Button::Button() { _buttonId = 0; _buttonDown = false; } void ScannerDialog::Button::setup(int buttonId) { _buttonId = buttonId; _buttonDown = false; SceneActor::postInit(); SceneObject::setup(4, 2, 2); fixPriority(255); if (_buttonId == 1) setPosition(Common::Point(141, 99)); else if (_buttonId == 2) setPosition(Common::Point(141, 108)); static_cast(R2_GLOBALS._sceneManager._scene)->_sceneAreas.push_front(this); } void ScannerDialog::Button::synchronize(Serializer &s) { SceneActor::synchronize(s); s.syncAsSint16LE(_buttonId); } void ScannerDialog::Button::process(Event &event) { if (event.eventType == EVENT_BUTTON_DOWN && R2_GLOBALS._events.getCursor() == CURSOR_USE && _bounds.contains(event.mousePos) && !_buttonDown) { setFrame(3); _buttonDown = true; event.handled = true; } if (event.eventType == EVENT_BUTTON_UP && _buttonDown) { setFrame(2); _buttonDown = false; event.handled = true; reset(); } } bool ScannerDialog::Button::startAction(CursorType action, Event &event) { if (action == CURSOR_USE) return false; return startAction(action, event); } void ScannerDialog::Button::reset() { Scene *scene = R2_GLOBALS._sceneManager._scene; ScannerDialog &scanner = *R2_GLOBALS._scannerDialog; switch (_buttonId) { case 1: // Talk button switch (R2_GLOBALS._sceneManager._sceneNumber) { case 1550: scene->_sceneMode = 80; scene->signal(); break; case 1700: scene->_sceneMode = 30; scene->signal(); remove(); break; default: break; } break; case 2: // Scan button switch (R2_GLOBALS._sceneManager._sceneNumber) { case 1550: scanner._obj4.setup(4, 3, 1); scanner._obj5.postInit(); scanner._obj5.setup(4, 4, 1); scanner._obj5.setPosition(Common::Point(R2_GLOBALS._s1550PlayerArea[R2_QUINN].x + 145, R2_GLOBALS._s1550PlayerArea[R2_QUINN].y + 59)); scanner._obj5.fixPriority(257); scanner._obj6.postInit(); scanner._obj6.setup(4, 4, 2); scanner._obj6.setPosition(Common::Point(R2_GLOBALS._s1550PlayerArea[R2_SEEKER].x + 145, R2_GLOBALS._s1550PlayerArea[R2_SEEKER].y + 59)); scanner._obj6.fixPriority(257); break; case 1700: case 1800: if (R2_GLOBALS._rimLocation < 1201) scanner._obj4.setup(4, 3, 3); else if (R2_GLOBALS._rimLocation < 1201) scanner._obj4.setup(4, 3, 4); else scanner._obj4.setup(4, 3, 5); break; case 3800: case 3900: if ((R2_GLOBALS._desertWrongDirCtr + 1) == 0 && R2_GLOBALS._desertCorrectDirection == 0) { do { R2_GLOBALS._desertCorrectDirection = R2_GLOBALS._randomSource.getRandomNumber(3) + 1; } while (R2_GLOBALS._desertCorrectDirection == R2_GLOBALS._desertPreviousDirection); } scanner._obj4.setup(4, 7, R2_GLOBALS._desertCorrectDirection); if (!R2_GLOBALS.getFlag(46)) R2_GLOBALS.setFlag(46); break; default: scanner._obj4.setup(4, 3, 2); break; } break; default: break; } } /*--------------------------------------------------------------------------*/ ScannerDialog::Slider::Slider() { _initial = _xStart = _yp = 0; _width = _xInc = 0; _sliderDown = false; } void ScannerDialog::Slider::synchronize(Serializer &s) { SceneActor::synchronize(s); s.syncAsSint16LE(_initial); s.syncAsSint16LE(_xStart); s.syncAsSint16LE(_yp); s.syncAsSint16LE(_width); s.syncAsSint16LE(_xInc); } void ScannerDialog::Slider::remove() { static_cast(R2_GLOBALS._sceneManager._scene)->_sceneAreas.remove(this); SceneActor::remove(); } void ScannerDialog::Slider::process(Event &event) { if (event.eventType == EVENT_BUTTON_DOWN && R2_GLOBALS._events.getCursor() == CURSOR_USE && _bounds.contains(event.mousePos)) { _sliderDown = true; } if (event.eventType == EVENT_BUTTON_UP && _sliderDown) { _sliderDown = false; event.handled = true; update(); } if (_sliderDown) { event.handled = true; if (event.mousePos.x < _xStart) { setPosition(Common::Point(_xStart, _yp)); } else if (event.mousePos.x >= (_xStart + _width)) { setPosition(Common::Point(_xStart + _width, _yp)); } else { setPosition(Common::Point(event.mousePos.x, _yp)); } } } bool ScannerDialog::Slider::startAction(CursorType action, Event &event) { if (action == CURSOR_USE) return false; return startAction(action, event); } void ScannerDialog::Slider::update() { int incHalf = (_width / (_xInc - 1)) / 2; int newFrequency = ((_position.x - _xStart + incHalf) * _xInc) / (_width + incHalf * 2); setPosition(Common::Point(_xStart + ((_width * newFrequency) / (_xInc - 1)), _yp)); R2_GLOBALS._scannerFrequencies[R2_GLOBALS._player._characterIndex] = newFrequency + 1; switch (newFrequency) { case 0: R2_GLOBALS._sound4.stop(); break; case 1: R2_GLOBALS._sound4.play(45); break; case 2: R2_GLOBALS._sound4.play(4); break; case 3: R2_GLOBALS._sound4.play(5); break; case 4: R2_GLOBALS._sound4.play(6); break; default: break; } } void ScannerDialog::Slider::setup(int initial, int xStart, int yp, int width, int xInc) { _initial = initial; _xStart = xStart; _yp = yp; _width = width; _xInc = xInc; _sliderDown = false; SceneActor::postInit(); SceneObject::setup(4, 2, 1); fixPriority(255); setPosition(Common::Point(_width * (_initial - 1) / (_xInc - 1) + _xStart, yp)); static_cast(R2_GLOBALS._sceneManager._scene)->_sceneAreas.push_front(this); } /*--------------------------------------------------------------------------*/ ScannerDialog::ScannerDialog() { } void ScannerDialog::remove() { switch (R2_GLOBALS._sceneManager._sceneNumber) { case 1550: case 1700: R2_GLOBALS._events.setCursor(R2_GLOBALS._player._canWalk ? CURSOR_WALK : CURSOR_USE); break; case 3800: case 3900: { Scene *scene = R2_GLOBALS._sceneManager._scene; scene->_sceneMode = 3806; scene->signal(); break; } default: break; } SceneExt *scene = static_cast(R2_GLOBALS._sceneManager._scene); scene->_sceneAreas.remove(&_talkButton); scene->_sceneAreas.remove(&_scanButton); _talkButton.remove(); _scanButton.remove(); _slider.remove(); _obj4.remove(); _obj5.remove(); _obj6.remove(); _obj7.remove(); ModalWindow::remove(); } void ScannerDialog::setup2(int visage, int stripFrameNum, int frameNum, int posX, int posY) { // Stop player moving if currently doing so if (R2_GLOBALS._player._mover) R2_GLOBALS._player.addMover(NULL); R2_GLOBALS._events.setCursor(CURSOR_USE); ModalWindow::setup2(visage, stripFrameNum, frameNum, posX, posY); setup3(100, -1, -1, -1); _talkButton.setup(1); _scanButton.setup(2); _slider.setup(R2_GLOBALS._scannerFrequencies[R2_GLOBALS._player._characterIndex], 142, 124, 35, 5); _obj4.postInit(); _obj4.setup(4, 3, 2); _obj4.setPosition(Common::Point(160, 83)); _obj4.fixPriority(256); if (R2_GLOBALS._sceneManager._sceneNumber == 3800 || R2_GLOBALS._sceneManager._sceneNumber == 3900) { Scene *scene = R2_GLOBALS._sceneManager._scene; scene->_sceneMode = 3805; scene->signal(); } } /*--------------------------------------------------------------------------*/ } // End of namespace Ringworld2 } // End of namespace TsAGE