/* 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 "twine/holomap.h" #include "common/algorithm.h" #include "common/debug.h" #include "common/memstream.h" #include "common/stream.h" #include "common/types.h" #include "twine/audio/sound.h" #include "twine/menu/interface.h" #include "twine/parser/anim.h" #include "twine/parser/holomap.h" #include "twine/renderer/redraw.h" #include "twine/renderer/renderer.h" #include "twine/renderer/screens.h" #include "twine/resources/hqr.h" #include "twine/resources/resources.h" #include "twine/scene/animations.h" #include "twine/scene/collision.h" #include "twine/scene/gamestate.h" #include "twine/scene/movements.h" #include "twine/scene/scene.h" #include "twine/shared.h" #include "twine/text.h" #include "twine/twine.h" namespace TwinE { #define HOLOMAP_ARROW (1 << 0) #define HOLOMAP_VISITED (1 << 1) #define HOLOMAP_UNK3 (1 << 2) #define HOLOMAP_UNK4 (1 << 3) #define HOLOMAP_UNK5 (1 << 4) #define HOLOMAP_UNK6 (1 << 5) #define HOLOMAP_UNK7 (1 << 6) #define HOLOMAP_CAN_FOCUS (1 << 7) #define HOLOMAP_RESET (HOLOMAP_VISITED | HOLOMAP_UNK3 | HOLOMAP_UNK4 | HOLOMAP_UNK5 | HOLOMAP_UNK6 | HOLOMAP_UNK7) #define HOLOMAP_ACTIVE (HOLOMAP_CAN_FOCUS | HOLOMAP_ARROW) static const float ZOOM_BIG_HOLO = 9500.0f; static const float zDistanceTrajectory = 5300.0f; static const int SIZE_CURSOR = 20; Holomap::Holomap(TwinEEngine *engine) : _engine(engine) {} int32 Holomap::distance(float distance) const { const float w = (float)_engine->width() / (float)_engine->originalWidth(); const float h = (float)_engine->height() / (float)_engine->originalHeight(); const float f = MIN(w, h); return (int32)(distance / f); } int32 Holomap::scale(float val) const { const float w = (float)_engine->width() / (float)_engine->originalWidth(); const float h = (float)_engine->height() / (float)_engine->originalHeight(); const float f = MIN(w, h); return (int32)(val * f); } bool Holomap::loadLocations() { uint8 *locationsPtr = nullptr; const int32 locationsSize = HQR::getAllocEntry(&locationsPtr, Resources::HQR_RESS_FILE, RESSHQR_HOLOARROWINFO); if (locationsSize == 0) { warning("Could not find holomap locations at index %i in %s", RESSHQR_HOLOARROWINFO, Resources::HQR_RESS_FILE); return false; } Common::MemoryReadStream stream(locationsPtr, locationsSize, DisposeAfterUse::YES); _numLocations = locationsSize / 8; if (_numLocations > NUM_LOCATIONS) { warning("Amount of locations (%i) exceeds the maximum of %i", _numLocations, NUM_LOCATIONS); return false; } _engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap); for (int32 i = 0; i < _numLocations; i++) { _locations[i].angleX = (int16)ClampAngle(stream.readSint16LE()); _locations[i].angleY = (int16)ClampAngle(stream.readSint16LE()); _locations[i].size = stream.readSint16LE(); _locations[i].textIndex = (TextId)stream.readUint16LE(); if (_engine->_text->getMenuText(_locations[i].textIndex, _locations[i].name, sizeof(_locations[i].name))) { debug(2, "Scene %i: %s", i, _locations[i].name); continue; } debug(2, "Could not get location text for index %i", i); } return true; } void Holomap::setHolomapPosition(int32 locationIdx) { assert(locationIdx >= 0 && locationIdx <= ARRAYSIZE(_engine->_gameState->_holomapFlags)); _engine->_gameState->_holomapFlags[locationIdx] = HOLOMAP_ACTIVE; if (_engine->_gameState->hasItem(InventoryItems::kiHolomap)) { _engine->_redraw->addOverlay(OverlayType::koInventoryItem, InventoryItems::kiHolomap, 0, 0, 0, OverlayPosType::koNormal, 3); } } void Holomap::clearHolomapPosition(int32 locationIdx) { assert(locationIdx >= 0 && locationIdx <= ARRAYSIZE(_engine->_gameState->_holomapFlags)); _engine->_gameState->_holomapFlags[locationIdx] &= HOLOMAP_RESET; _engine->_gameState->_holomapFlags[locationIdx] |= HOLOMAP_UNK7; } void Holomap::initHoloDatas() { constexpr TwineResource resource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPAL); _engine->_screens->loadCustomPalette(resource); int32 j = HOLOMAP_PALETTE_INDEX * 3; const int32 n = NUM_HOLOMAPCOLORS * 3; for (int32 i = 0; i < n; i += 3, j += 3) { _paletteHolomap[i + 0] = _engine->_screens->_palette[j + 0]; _paletteHolomap[i + 1] = _engine->_screens->_palette[j + 1]; _paletteHolomap[i + 2] = _engine->_screens->_palette[j + 2]; } j = HOLOMAP_PALETTE_INDEX * 3; for (int32 i = n; i < 2 * n - 3; i += 3, j += 3) { _paletteHolomap[i + 0] = _engine->_screens->_palette[j + 0]; _paletteHolomap[i + 1] = _engine->_screens->_palette[j + 1]; _paletteHolomap[i + 2] = _engine->_screens->_palette[j + 2]; } computeCoorMapping(); Common::SeekableReadStream *surfaceStream = HQR::makeReadStream(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOSURFACE)); if (surfaceStream == nullptr) { error("Failed to load holomap surface"); } computeCoorGlobe(surfaceStream); delete surfaceStream; _holomapPaletteIndex = 0; } void Holomap::computeCoorGlobe(Common::SeekableReadStream *holomapSurfaceStream) { int holomapSurfaceArrayIdx = 0; _engine->_renderer->setAngleCamera(0, 0, 0); for (int alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) { const int32 rot = holomapSurfaceStream->readByte(); holomapSurfaceStream->seek(-1, SEEK_CUR); for (int beta = 0; beta < LBAAngles::ANGLE_360; beta += LBAAngles::ANGLE_11_25) { const int32 normal = 1000 + holomapSurfaceStream->readByte() * 2; const IVec3 &rotVec = _engine->_renderer->getHolomapRotation(normal, 0, alpha); const IVec3 &rotVec2 = _engine->_renderer->getHolomapRotation(rotVec.x, 0, beta); const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y)); _holomapSurface[holomapSurfaceArrayIdx].x = rotVec3.x; _holomapSurface[holomapSurfaceArrayIdx].y = rotVec3.y; _holomapSurface[holomapSurfaceArrayIdx].z = rotVec3.z; ++holomapSurfaceArrayIdx; } const int32 normal = 1000 + rot * 2; const IVec3 &rotVec = _engine->_renderer->getHolomapRotation(normal, 0, alpha); const IVec3 &rotVec2 = _engine->_renderer->getHolomapRotation(rotVec.x, 0, 0); const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y)); _holomapSurface[holomapSurfaceArrayIdx].x = rotVec3.x; _holomapSurface[holomapSurfaceArrayIdx].y = rotVec3.y; _holomapSurface[holomapSurfaceArrayIdx].z = rotVec3.z; ++holomapSurfaceArrayIdx; } assert(holomapSurfaceStream->eos()); } void Holomap::computeCoorMapping() { int projectedIndex = 0; for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) { for (int32 beta = 0; beta < LBAAngles::ANGLE_360; beta += LBAAngles::ANGLE_11_25) { _projectedSurfacePositions[projectedIndex].x2 = _engine->_screens->lerp(0, 255 * LBAAngles::ANGLE_90 + 255, LBAAngles::ANGLE_360 - 1, beta); if (alpha == LBAAngles::ANGLE_90) { _projectedSurfacePositions[projectedIndex].y2 = 255 * LBAAngles::ANGLE_90 + 255; } else { _projectedSurfacePositions[projectedIndex].y2 = ((alpha + LBAAngles::ANGLE_90) * LBAAngles::ANGLE_90) / 2; } ++projectedIndex; } _projectedSurfacePositions[projectedIndex].x2 = 255 * LBAAngles::ANGLE_90 + 255; if (alpha == LBAAngles::ANGLE_90) { _projectedSurfacePositions[projectedIndex].y2 = 255 * LBAAngles::ANGLE_90 + 255; } else { _projectedSurfacePositions[projectedIndex].y2 = ((alpha + LBAAngles::ANGLE_90) * LBAAngles::ANGLE_90) / 2; } ++projectedIndex; } } void Holomap::computeGlobeProj() { int holomapSortArrayIdx = 0; int holomapSurfaceArrayIdx = 0; _projectedSurfaceIndex = 0; for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) { for (int32 beta = 0; beta < LBAAngles::ANGLE_11_25; ++beta) { IVec3 *vec = &_holomapSurface[holomapSurfaceArrayIdx++]; const IVec3 &destPos = _engine->_renderer->longWorldRot(vec->x, vec->y, vec->z); if (alpha != LBAAngles::ANGLE_90) { _holomapSort[holomapSortArrayIdx].z = (int16)destPos.z; _holomapSort[holomapSortArrayIdx].projectedPosIdx = _projectedSurfaceIndex; ++holomapSortArrayIdx; } const IVec3 &projPos = _engine->_renderer->projectPositionOnScreen(destPos); _projectedSurfacePositions[_projectedSurfaceIndex].x1 = projPos.x; _projectedSurfacePositions[_projectedSurfaceIndex].y1 = projPos.y; ++_projectedSurfaceIndex; } IVec3 *vec = &_holomapSurface[holomapSurfaceArrayIdx++]; const IVec3 &destPos = _engine->_renderer->longWorldRot(vec->x, vec->y, vec->z); const IVec3 &projPos = _engine->_renderer->projectPositionOnScreen(destPos); _projectedSurfacePositions[_projectedSurfaceIndex].x1 = projPos.x; _projectedSurfacePositions[_projectedSurfaceIndex].y1 = projPos.y; ++_projectedSurfaceIndex; } assert(holomapSortArrayIdx == ARRAYSIZE(_holomapSort)); assert(holomapSurfaceArrayIdx == ARRAYSIZE(_holomapSurface)); assert(_projectedSurfaceIndex == ARRAYSIZE(_projectedSurfacePositions)); Common::sort(_holomapSort, _holomapSort + ARRAYSIZE(_holomapSort), [](const HolomapSort &a, const HolomapSort &b) { return a.z < b.z; }); } #define SURFACE_POS_OFFSET ((LBAAngles::ANGLE_360 / LBAAngles::ANGLE_11_25) + 1) void Holomap::drawHoloMap(uint8 *holomapImage, uint32 holomapImageSize) { computeGlobeProj(); for (int32 i = 0; i < ARRAYSIZE(_holomapSort); ++i) { assert(_holomapSort[i].projectedPosIdx + 34 < _projectedSurfaceIndex); const HolomapProjectedPos &pos1 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0]; const HolomapProjectedPos &pos2 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0 + SURFACE_POS_OFFSET]; const HolomapProjectedPos &pos3 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1]; ComputedVertex vertexCoordinates[3]; vertexCoordinates[0].x = (int16)pos1.x1; vertexCoordinates[0].y = (int16)pos1.y1; vertexCoordinates[1].x = (int16)pos2.x1; vertexCoordinates[1].y = (int16)pos2.y1; vertexCoordinates[2].x = (int16)pos3.x1; vertexCoordinates[2].y = (int16)pos3.y1; if (isPolygonVisible(vertexCoordinates)) { ComputedVertex textureCoordinates[3]; textureCoordinates[0].x = (int16)pos1.x2; textureCoordinates[0].y = (int16)pos1.y2; textureCoordinates[1].x = (int16)pos2.x2; textureCoordinates[1].y = (int16)pos2.y2; textureCoordinates[2].x = (int16)pos3.x2; textureCoordinates[2].y = (int16)pos3.y2; _engine->_renderer->renderHolomapVertices(vertexCoordinates, textureCoordinates, holomapImage, holomapImageSize); } const HolomapProjectedPos &pos4 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0 + SURFACE_POS_OFFSET]; const HolomapProjectedPos &pos5 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1 + SURFACE_POS_OFFSET]; const HolomapProjectedPos &pos6 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1]; vertexCoordinates[0].x = (int16)pos4.x1; vertexCoordinates[0].y = (int16)pos4.y1; vertexCoordinates[1].x = (int16)pos5.x1; vertexCoordinates[1].y = (int16)pos5.y1; vertexCoordinates[2].x = (int16)pos6.x1; vertexCoordinates[2].y = (int16)pos6.y1; if (isPolygonVisible(vertexCoordinates)) { ComputedVertex textureCoordinates[3]; textureCoordinates[0].x = (int16)pos4.x2; textureCoordinates[0].y = (int16)pos4.y2; textureCoordinates[1].x = (int16)pos5.x2; textureCoordinates[1].y = (int16)pos5.y2; textureCoordinates[2].x = (int16)pos6.x2; textureCoordinates[2].y = (int16)pos6.y2; _engine->_renderer->renderHolomapVertices(vertexCoordinates, textureCoordinates, holomapImage, holomapImageSize); } } } void Holomap::drawHolomapText(int32 centerx, int32 top, const char *title) { const int32 size = _engine->_text->getTextSize(title); const int32 x = centerx - size / 2; const int32 y = top; _engine->_text->setFontColor(COLOR_WHITE); _engine->_text->drawText(x, y, title); } void Holomap::drawHoloObj(const IVec3 &angle, int32 x, int32 y) { _engine->_renderer->setAngleCamera(x, y, 0); const IVec3 &destPos = _engine->_renderer->longWorldRot(0, 0, 1000); _engine->_renderer->setPosCamera(0, 0, 0); _engine->_renderer->setBaseRotation(angle); _engine->_renderer->setCameraRotation(0, 0, distance(zDistanceTrajectory)); _engine->_interface->resetClip(); Common::Rect dirtyRect; _engine->_renderer->renderIsoModel(destPos, x, y, LBAAngles::ANGLE_0, _engine->_resources->_holomapPointModelPtr, dirtyRect); _engine->copyBlockPhys(dirtyRect); } void Holomap::renderHolomapVehicle(uint &frameNumber, ActorMoveStruct &move, AnimTimerDataStruct &animTimerData, BodyData &bodyData, AnimData &animData) { const int16 newAngle = move.getRealAngle(_engine->_lbaTime); if (move.numOfStep == 0) { _engine->_movements->initRealAngle(LBAAngles::ANGLE_0, -LBAAngles::ANGLE_90, 500, &move); } if (_engine->_animations->setModelAnimation(frameNumber, animData, bodyData, &animTimerData)) { frameNumber++; if (frameNumber >= animData.getNumKeyframes()) { frameNumber = animData.getLoopFrame(); } } const Common::Rect rect(0, _engine->height() - 280, 200, _engine->height() - 1); _engine->_renderer->setProjection(rect.width() / 2, _engine->height() - 80, 128, 900, 900); _engine->_renderer->setFollowCamera(0, 0, 0, 60, 128, 0, distance(30000)); _engine->_renderer->setLightVector(-60, 128, 0); // background of the vehicle _engine->_interface->drawFilledRect(rect, COLOR_BLACK); Common::Rect dummy; _engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, newAngle, LBAAngles::ANGLE_0, bodyData, dummy); _engine->copyBlockPhys(rect); } void Holomap::drawHolomapTrajectory(int32 trajectoryIndex) { if (_engine->isDemo()) { return; } debug("Draw trajectory index %i", trajectoryIndex); const Trajectory *data = _engine->_resources->getTrajectory(trajectoryIndex); if (data == nullptr) { warning("Failed to load trajectory data for index %i", trajectoryIndex); return; } _engine->exitSceneryView(); _engine->_interface->resetClip(); _engine->_screens->clearScreen(); initHoloDatas(); _engine->setPalette(_engine->_screens->_paletteRGBACustom); ScopedEngineFreeze timeFreeze(_engine); const int32 cameraPosX = _engine->width() / 2 + 80; const int32 cameraPosY = _engine->height() / 2; _engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024); _engine->_renderer->setFollowCamera(0, 0, 0, data->pos.x, data->pos.y, data->pos.z, distance(zDistanceTrajectory)); constexpr TwineResource holomapImageRes(Resources::HQR_RESS_FILE, RESSHQR_HOLOIMG); uint8 *holomapImagePtr = nullptr; const int32 holomapImageSize = HQR::getAllocEntry(&holomapImagePtr, holomapImageRes); if (holomapImageSize == 0) { error("Failed to load holomap image"); } drawHoloMap(holomapImagePtr, holomapImageSize); const Location &loc = _locations[data->locationIdx]; drawHoloObj(data->pos, loc.angleX, loc.angleY); ActorMoveStruct move; AnimTimerDataStruct animTimerData; AnimData animData; animData.loadFromHQR(Resources::HQR_RESS_FILE, data->getAnimation(), _engine->isLBA1()); BodyData bodyData; bodyData.loadFromHQR(Resources::HQR_RESS_FILE, data->getModel(), _engine->isLBA1()); uint frameNumber = 0; int32 frameTime = _engine->_lbaTime; int16 trajAnimFrameIdx = 0; int32 waterPaletteChangeTimer = 0; bool fadeInPalette = true; _engine->_input->enableKeyMap(holomapKeyMapId); for (;;) { FrameMarker frame(_engine); _engine->readKeys(); if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) { break; } if (!fadeInPalette && waterPaletteChangeTimer < _engine->_lbaTime) { // animate the water surface _engine->setPalette(HOLOMAP_PALETTE_INDEX, NUM_HOLOMAPCOLORS, &_paletteHolomap[3 * _holomapPaletteIndex++]); if (_holomapPaletteIndex == NUM_HOLOMAPCOLORS) { _holomapPaletteIndex = 0; } waterPaletteChangeTimer = _engine->_lbaTime + 3; } renderHolomapVehicle(frameNumber, move, animTimerData, bodyData, animData); // now render the holomap path _engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024); _engine->_renderer->setFollowCamera(0, 0, 0, data->pos.x, data->pos.y, data->pos.z, distance(zDistanceTrajectory)); _engine->_renderer->setLightVector(data->pos.x, data->pos.y, 0); // animate the path from point 1 to point 2 by rendering a point model on each position // on the globe every 40 timeunits if (frameTime + 40 <= _engine->_lbaTime) { frameTime = _engine->_lbaTime; int32 modelX; int32 modelY; if (trajAnimFrameIdx < data->numAnimFrames) { modelX = data->positions[trajAnimFrameIdx].x; modelY = data->positions[trajAnimFrameIdx].y; } else { if (data->numAnimFrames < trajAnimFrameIdx) { break; } modelX = loc.angleX; modelY = loc.angleY; } drawHoloObj(data->pos, modelX, modelY); ++trajAnimFrameIdx; } if (fadeInPalette) { fadeInPalette = false; //_engine->_screens->fadeToPal(_engine->_screens->_paletteRGBACustom); } ++_engine->_lbaTime; } _engine->_screens->clearScreen(); _engine->setPalette(_engine->_screens->_paletteRGBA); _engine->_gameState->init3DGame(); _engine->_interface->loadClip(); _engine->_text->initSceneTextBank(); _engine->_input->enableKeyMap(mainKeyMapId); free(holomapImagePtr); } int32 Holomap::searchNextArrow(int32 currentLocation, int32 dir) const { const int32 idx = currentLocation; for (int32 i = currentLocation + dir; i != idx; i += dir) { if (i < 0) { i = NUM_LOCATIONS - 1; } else { i %= NUM_LOCATIONS; } if (i == _engine->_scene->_currentSceneIdx || (_engine->_gameState->_holomapFlags[i] & HOLOMAP_ACTIVE) != 0u) { return i; } } return -1; } void Holomap::drawListPos(int xRot, int yRot, int zRot, bool lower) { int n = 0; DrawListStruct drawListArray[NUM_LOCATIONS]; for (int locationIdx = 0; locationIdx < NUM_LOCATIONS; ++locationIdx) { if (!(_engine->_gameState->_holomapFlags[locationIdx] & HOLOMAP_CAN_FOCUS) && locationIdx != _engine->_scene->_currentSceneIdx) { continue; } const Location &loc = _locations[locationIdx]; const IVec3 &cameraRot = _engine->_renderer->setAngleCamera(loc.angleX, loc.angleY, 0); IVec3 m = _engine->_renderer->worldRotatePoint(IVec3(0, 0, 1000 + loc.size)); m.x = cameraRot.x; const IVec3 &m1 = _engine->_renderer->worldRotatePoint(IVec3(0, 0, 1500)); _engine->_renderer->setInverseAngleCamera(xRot, yRot, zRot); _engine->_renderer->setCameraRotation(0, 0, distance(ZOOM_BIG_HOLO)); const IVec3 &destPos3 = _engine->_renderer->worldRotatePoint(m); const IVec3 &destPos4 = _engine->_renderer->worldRotatePoint(m1); bool visible; if (lower) { visible = destPos3.z <= destPos4.z; } else { visible = destPos4.z <= destPos3.z; } if (!visible) { continue; } uint8 flags = _engine->_gameState->_holomapFlags[locationIdx] & HOLOMAP_ARROW; if (locationIdx == _engine->_scene->_currentSceneIdx) { flags |= 2u; // model type } DrawListStruct &drawList = drawListArray[n]; drawList.posValue = destPos3.z; drawList.actorIdx = locationIdx; drawList.type = flags; drawList.x = m.x; drawList.y = m.y; drawList.z = m.z; ++n; } _engine->_redraw->sortDrawingList(drawListArray, n); for (int i = 0; i < n; ++i) { const DrawListStruct &drawList = drawListArray[i]; const uint16 flags = drawList.type; const BodyData *bodyData = nullptr; if (flags == 1u) { bodyData = &_engine->_resources->_holomapArrowPtr; } else if (flags == 2u) { bodyData = &_engine->_resources->_holomapTwinsenModelPtr; } else if (flags == 3u) { bodyData = &_engine->_resources->_holomapTwinsenArrowPtr; } if (bodyData != nullptr) { const int32 angleX = _locations[drawList.actorIdx].angleX; const int32 angleY = _locations[drawList.actorIdx].angleY; Common::Rect dummy; _engine->_renderer->affObjetIso(drawList.x, drawList.y, drawList.z, angleX, angleY, LBAAngles::ANGLE_0, *bodyData, dummy); } } } void Holomap::holoMap() { ScopedEngineFreeze freeze(_engine); const int32 alphaLightTmp = _engine->_scene->_alphaLight; const int32 betaLightTmp = _engine->_scene->_betaLight; _engine->exitSceneryView(); _engine->_gameState->init3DGame(); _engine->_screens->fadeToBlack(_engine->_screens->_paletteRGBA); _engine->_sound->stopSamples(); _engine->_interface->resetClip(); _engine->_screens->clearScreen(); _engine->_screens->fadeToBlack(_engine->_screens->_paletteRGBA); initHoloDatas(); const int32 cameraPosX = _engine->width() / 2; const int32 cameraPosY = scale(190); _engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024); _engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap); _engine->_text->setFontCrossColor(COLOR_9); constexpr TwineResource holomapImageRes(Resources::HQR_RESS_FILE, RESSHQR_HOLOIMG); uint8 *holomapImagePtr = nullptr; const int32 holomapImageSize = HQR::getAllocEntry(&holomapImagePtr, holomapImageRes); if (holomapImageSize == 0) { error("Failed to load holomap image"); } int32 currentLocation = _engine->_scene->_currentSceneIdx; _engine->_text->drawHolomapLocation(_locations[currentLocation].textIndex); int32 time = _engine->_lbaTime; int32 xRot = _locations[currentLocation].angleX; int32 yRot = _locations[currentLocation].angleY; bool automove = false; bool redraw = true; int waterPaletteChangeTimer = 0; bool fadeInPalette = true; _engine->_input->enableKeyMap(holomapKeyMapId); for (;;) { FrameMarker frame(_engine); _engine->_input->readKeys(); if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) { break; } if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapPrev)) { const int32 nextLocation = searchNextArrow(currentLocation, -1); if (nextLocation != -1 && currentLocation != nextLocation) { currentLocation = nextLocation; _engine->_text->drawHolomapLocation(_locations[currentLocation].textIndex); time = _engine->_lbaTime; automove = true; } } else if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapNext)) { const int32 nextLocation = searchNextArrow(currentLocation, 1); if (nextLocation != -1 && currentLocation != nextLocation) { currentLocation = nextLocation; _engine->_text->drawHolomapLocation(_locations[currentLocation].textIndex); time = _engine->_lbaTime; automove = true; } } if (_engine->_input->isActionActive(TwinEActionType::HolomapDown)) { xRot += LBAAngles::ANGLE_2; automove = true; time = _engine->_lbaTime; } else if (_engine->_input->isActionActive(TwinEActionType::HolomapUp)) { xRot -= LBAAngles::ANGLE_2; automove = true; time = _engine->_lbaTime; } if (_engine->_input->isActionActive(TwinEActionType::HolomapRight)) { yRot += LBAAngles::ANGLE_2; automove = true; time = _engine->_lbaTime; } else if (_engine->_input->isActionActive(TwinEActionType::HolomapLeft)) { yRot -= LBAAngles::ANGLE_2; automove = true; time = _engine->_lbaTime; } if (automove) { const int32 dt = _engine->_lbaTime - time; xRot = _engine->_collision->clampedLerp(ClampAngle(xRot), _locations[currentLocation].angleX, 75, dt); yRot = _engine->_collision->clampedLerp(ClampAngle(yRot), _locations[currentLocation].angleY, 75, dt); redraw = true; } if (!fadeInPalette && waterPaletteChangeTimer < _engine->_lbaTime) { // animate the water surface _engine->setPalette(HOLOMAP_PALETTE_INDEX, NUM_HOLOMAPCOLORS, &_paletteHolomap[3 * _holomapPaletteIndex++]); if (_holomapPaletteIndex == NUM_HOLOMAPCOLORS) { _holomapPaletteIndex = 0; } waterPaletteChangeTimer = _engine->_lbaTime + 3; redraw = true; } if (redraw) { redraw = false; const Common::Rect &rect = _engine->centerOnScreenX(scale(300), 0, scale(330)); _engine->_interface->drawFilledRect(rect, COLOR_BLACK); _engine->_renderer->setInverseAngleCamera(xRot, yRot, 0); _engine->_renderer->setLightVector(xRot, yRot, 0); drawListPos(xRot, yRot, 0, false); _engine->_renderer->setInverseAngleCamera(xRot, yRot, 0); _engine->_renderer->setCameraRotation(0, 0, distance(ZOOM_BIG_HOLO)); drawHoloMap(holomapImagePtr, holomapImageSize); drawListPos(xRot, yRot, 0, true); drawHolomapText(_engine->width() / 2, 25, "HoloMap"); if (automove) { // draw cursor const Common::Rect &targetRect = _engine->centerOnScreen(SIZE_CURSOR * 2, SIZE_CURSOR * 2); _engine->_menu->drawRectBorders(targetRect.left, cameraPosY - 20, targetRect.right, cameraPosY + 20, 15, 15); } } if (automove && xRot == _locations[currentLocation].angleX && yRot == _locations[currentLocation].angleY) { automove = false; } ++_engine->_lbaTime; if (fadeInPalette) { fadeInPalette = false; _engine->_screens->fadeToPal(_engine->_screens->_paletteRGBACustom); } } _engine->_screens->clearScreen(); _engine->_text->_drawTextBoxBackground = true; _engine->setPalette(_engine->_screens->_paletteRGBA); _engine->_scene->_alphaLight = alphaLightTmp; _engine->_scene->_betaLight = betaLightTmp; _engine->_gameState->init3DGame(); _engine->_interface->loadClip(); _engine->_input->enableKeyMap(mainKeyMapId); _engine->_text->initSceneTextBank(); free(holomapImagePtr); } const char *Holomap::getLocationName(int index) const { assert(index >= 0 && index <= ARRAYSIZE(_locations)); return _locations[index].name; } } // namespace TwinE