/* 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 "bladerunner/ui/esper.h"
#include "bladerunner/actor.h"
#include "bladerunner/ambient_sounds.h"
#include "bladerunner/audio_player.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/debugger.h"
#include "bladerunner/decompress_lcw.h"
#include "bladerunner/font.h"
#include "bladerunner/game_info.h"
#include "bladerunner/mouse.h"
#include "bladerunner/scene.h"
#include "bladerunner/shape.h"
#include "bladerunner/script/esper_script.h"
#include "bladerunner/text_resource.h"
#include "bladerunner/time.h"
#include "bladerunner/ui/ui_image_picker.h"
#include "bladerunner/vqa_player.h"
#include "bladerunner/subtitles.h"
#include "common/rect.h"
#include "common/str.h"
namespace BladeRunner {
ESPER::ESPER(BladeRunnerEngine *vm) {
_vm = vm;
_screen = Common::Rect(135, 123, 435, 387);
_isWaiting = false;
_regionSelectedAck = false;
_isDrawingSelection = false;
_isOpen = false;
_shapesButtons = new Shapes(vm);
_shapesPhotos = new Shapes(vm);
_shapeThumbnail = nullptr;
_vqaPlayerMain = nullptr;
_vqaPlayerPhoto = nullptr;
_script = nullptr;
_buttons = new UIImagePicker(vm, kPhotoCount + 4);
reset();
}
ESPER::~ESPER() {
reset();
delete _buttons;
delete _shapesPhotos;
delete _shapesButtons;
}
void ESPER::open(Graphics::Surface *surface) {
// CD-changing logic has been removed
while (!_vm->playerHasControl()) {
_vm->playerGainsControl();
}
while (_vm->_mouse->isDisabled()) {
_vm->_mouse->enable();
}
_vm->_time->pause();
// NOTE It's probably intentional that music level is not adjusted when entering ESPER mode,
// since it is possible to use ESPER in McCoy's apartment while the moody Blade Runner Blues is playing,
// and it would not be desirable to have that one's volume lowered further.
_ambientVolumeFactorOutsideEsper = _vm->_ambientSounds->getVolume();
_vm->_ambientSounds->setVolume(_ambientVolumeFactorOutsideEsper / 2);
reset();
if (!_vm->openArchive("MODE.MIX")) {
return;
}
_surfacePhoto.create(kPhotoWidth, kPhotoHeight, gameDataPixelFormat());
_surfaceViewport.create(_screen.width(), _screen.height(), screenPixelFormat());
_viewportNext = _viewport;
if (!_shapesButtons->load("ESPBUTTN.SHP")) {
return;
}
if (!_shapesPhotos->load("ESPTHUMB.SHP")) {
return;
}
_vqaPlayerMain = new VQAPlayer(_vm, &_vm->_surfaceBack, "ESPER.VQA");
if (!_vqaPlayerMain->open()) {
return;
}
_vqaPlayerMain->setLoop(2, -1, kLoopSetModeJustStart, nullptr, nullptr);
_isOpen = true;
_flash = false;
_script = new ESPERScript(_vm);
activate(true);
}
void ESPER::close() {
// CD-changing logic has been removed
delete _script;
_script = nullptr;
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(kSfxBR035_7B), 25, 0, 0, 50, 0);
unloadPhotos();
_buttons->deactivate();
_buttons->resetImages();
_shapesButtons->unload();
_shapesPhotos->unload();
_surfacePhoto.free();
_surfaceViewport.free();
delete _vqaPlayerMain;
_vqaPlayerMain = nullptr;
_vm->closeArchive("MODE.MIX");
_vm->_time->resume();
_vm->_ambientSounds->setVolume(_ambientVolumeFactorOutsideEsper);
_vm->_scene->resume();
reset();
}
bool ESPER::isOpen() const {
return _isOpen;
}
void ESPER::handleMouseUp(int x, int y, bool mainButton) {
bool actionHandled = _buttons->handleMouseAction(x, y, false, true, false);
if (mainButton) {
_isMouseDown = false;
if (!actionHandled) {
if (_isScrolling) {
scrollingStop();
} else if (_isDrawingSelection && _mouseOverScroll == 4) {
_isDrawingSelection = false;
resetSelectionRect();
}
}
} else if (_statePhoto == kEsperPhotoStatePhotoZoomOut) {
zoomOutStop();
}
}
void ESPER::handleMouseDown(int x, int y, bool mainButton) {
bool actionHandled = _buttons->handleMouseAction(x, y, true, false, false);
if (actionHandled || _vm->_mouse->isDisabled()) {
return;
}
if (mainButton) {
if (_statePhoto != kEsperPhotoStateVideoZoomOut) {
if (_screen.contains(x, y)) {
_isMouseDown = true;
playSound(kSfxBRTARGET, 100);
}
if (_mouseOverScroll >= 0 && _mouseOverScroll <= 3 && !_isScrolling) {
scrollingStart(_mouseOverScroll);
}
tick();
}
} else {
if (_statePhoto == kEsperPhotoStateShow || _statePhoto == kEsperPhotoStateVideoShow) {
zoomOutStart();
}
}
}
void ESPER::tick() {
if (!_vm->_windowIsActive) {
return;
}
tickSound();
blit(_vm->_surfaceBack, _vm->_surfaceFront);
int mouseX, mouseY;
_vm->_mouse->getXY(&mouseX, &mouseY);
if (!_vm->_mouse->isDisabled()) {
_buttons->handleMouseAction(mouseX, mouseY, false, false, false);
}
if (!_isOpen) {
return;
}
draw(_vm->_surfaceFront);
_buttons->draw(_vm->_surfaceFront);
drawMouse(_vm->_surfaceFront);
tickSound();
_vm->_subtitles->tick(_vm->_surfaceFront);
_vm->blitToScreen(_vm->_surfaceFront);
if (_statePhoto == kEsperPhotoStateVideoShow) {
if (_regionSelectedAck) {
_regionSelectedAck = false;
_script->specialRegionSelected(_photoIdSelected, _regions[_regionSelected].regionId);
}
}
}
void ESPER::addPhoto(const char *name, int photoId, int shapeId) {
int i = findEmptyPhoto();
if (i >= 0) {
_photos[i].shapeId = shapeId;
_photos[i].isPresent = true;
_photos[i].photoId = photoId;
_photos[i].name = name;
_buttons->defineImage(i,
Common::Rect(
100 * (i % 3) + _screen.left + 3,
66 * (i / 3) + _screen.top + 3,
100 * (i % 3) + _screen.left + 100 - 3,
66 * (i / 3) + _screen.top + 66 - 3
),
_shapesPhotos->get(shapeId),
_shapesPhotos->get(shapeId),
_shapesPhotos->get(shapeId),
nullptr);
}
playSound(kSfxBR028_2A, 25);
wait(300);
tick();
}
void ESPER::defineRegion(int regionId, Common::Rect inner, Common::Rect outer, Common::Rect selection, const char *name) {
int i = findEmptyRegion();
if (i >= 0) {
_regions[i].isPresent = true;
_regions[i].regionId = regionId;
_regions[i].rectInner = inner;
_regions[i].rectOuter = outer;
_regions[i].rectSelection = selection;
_regions[i].name = name;
}
}
void ESPER::mouseDownCallback(int buttonId, void *callbackData) {
ESPER *self = ((ESPER *)callbackData);
if (self->_statePhoto != kEsperPhotoStateVideoZoomOut && buttonId == kPhotoCount + 2) {
self->zoomOutStart();
}
}
void ESPER::mouseUpCallback(int buttonId, void *callbackData) {
ESPER *self = (ESPER *)callbackData;
if (buttonId < kPhotoCount) {
self->selectPhoto(buttonId);
} else if (self->_statePhoto != kEsperPhotoStateVideoZoomOut) {
if (buttonId == kPhotoCount + 2) {
self->zoomOutStop();
} else if (buttonId == kPhotoCount + 3) {
self->goBack();
}
}
}
void ESPER::reset() {
_surfacePhoto.free();
_surfaceViewport.free();
_shapesButtons->unload();
_shapesPhotos->unload();
delete _vqaPlayerMain;
_vqaPlayerMain = nullptr;
delete _vqaPlayerPhoto;
_vqaPlayerPhoto = nullptr;
delete _script;
_script = nullptr;
_isOpen = false;
resetData();
}
void ESPER::resetData() {
delete _vqaPlayerPhoto;
_vqaPlayerPhoto = nullptr;
_shapeThumbnail = nullptr;
_viewport = Common::Rect();
_viewportNext = Common::Rect();
_selection = Common::Rect(-1, -1, -1, -1);
_regionSelectedAck = false;
_mouseOverScroll = -1;
_scrollingDirection = -1;
_selectionCrosshairX = -1;
_selectionCrosshairY = -1;
_stateMain = kEsperMainStatePhoto;
_statePhoto = kEsperPhotoStateShow;
_isMouseDown = false;
_isDrawingSelection = false;
_flash = false;
_isScrolling = false;
_timeScrollNextStart = 0u;
resetPhotos();
resetRegions();
resetViewport();
resetSelectionBlinking();
prepareZoom();
resetPhotoZooming();
resetPhotoOpening();
_soundId1 = -1;
_soundId2 = -1;
_soundId3 = -1;
}
void ESPER::resetPhotos() {
for (int i = 0; i < kPhotoCount; ++i) {
_photos[i].isPresent = false;
_photos[i].photoId = -1;
}
}
void ESPER::resetRegions() {
for (int i = 0; i < kRegionCount; ++i) {
_regions[i].isPresent = false;
_regions[i].regionId = -1;
}
}
void ESPER::resetViewport() {
_zoomHorizontal = (float)(_screen.width()) / (float)kPhotoWidth;
_zoomVertical = (float)(_screen.height()) / (float)kPhotoHeight;
_zoom = _zoomVertical;
_zoomMin = _zoom;
_timeZoomOutNextStart = 0u;
_viewportPositionX = kPhotoWidth / 2;
_viewportPositionY = kPhotoHeight / 2;
updateViewport();
_screenHalfWidth = _screen.width() / 2;
_screenHalfHeight = _screen.height() / 2;
}
void ESPER::resetSelectionRect() {
_selection = _screen;
_selectionCrosshairX = -1;
_selectionCrosshairY = -1;
}
void ESPER::resetSelectionBlinking() {
_selectionBlinkingCounter = 0;
_selectionBlinkingStyle = 0;
_timeSelectionBlinkingNextStart = 0u;
}
void ESPER::resetPhotoZooming() {
_zoomStep = 0;
_timeZoomNextDiff = 0u;
_timeZoomNextStart = 0u;
}
void ESPER::resetPhotoOpening() {
_photoOpeningWidth = _screen.left + 1;
_photoOpeningHeight = _screen.top + 1;
_timePhotoOpeningNextDiff = 0u;
_timePhotoOpeningNextStart = 0u;
}
void ESPER::updateViewport() {
float halfWidth = (1.0f / 2.0f) * ((float)kPhotoWidth * (_zoomHorizontal / _zoom));
_viewport.left = _viewportPositionX - halfWidth;
_viewport.right = _viewportPositionX + halfWidth;
if (_viewport.left < 0) {
_viewport.right -= _viewport.left;
_viewport.left = 0;
}
if (_viewport.right >= kPhotoWidth) {
_viewport.left -= _viewport.right - (kPhotoWidth - 1);
if (_viewport.left < 0) {
_viewport.left = 0;
}
_viewport.right = kPhotoWidth - 1;
}
float halfHeight = 1.0f / 2.0f * ((float)kPhotoHeight * (_zoomVertical / _zoom));
_viewport.top = _viewportPositionY - halfHeight;
_viewport.bottom = _viewportPositionY + halfHeight;
if (_viewport.top < 0) {
_viewport.bottom -= _viewport.top;
_viewport.top = 0;
}
if (_viewport.bottom >= kPhotoHeight) {
_viewport.top -= _viewport.bottom - (kPhotoHeight - 1);
if (_viewport.top < 0) {
_viewport.top = 0;
}
_viewport.bottom = kPhotoHeight - 1;
}
_viewportWidth = _viewport.right + 1 - _viewport.left;
_viewportHeight = _viewport.bottom + 1 - _viewport.top;
int centerX = (_viewport.left + _viewport.right ) / 2;
int centerY = (_viewport.top + _viewport.bottom) / 2;
float range = _zoom / _zoomHorizontal * 1.0f;
if ((_viewportPositionX > centerX + range) || (_viewportPositionX < centerX - range)) {
_viewportPositionX = centerX;
}
range = _zoom / _zoomVertical * 1.0f;
if ((_viewportPositionY > centerY + range) || (_viewportPositionY < centerY - range)) {
_viewportPositionY = centerY;
}
}
void ESPER::activate(bool withOpening) {
_vm->_mouse->disable();
_buttons->resetImages();
if (withOpening) {
setStateMain(kEsperMainStateOpening);
playSound(kSfxBR025_5A, 25);
wait(1000);
playSound(kSfxBR027_1P, 25);
wait(2000);
} else {
_buttons->deactivate();
setStateMain(kEsperMainStateClear);
}
_buttons->activate(nullptr, nullptr, mouseDownCallback, mouseUpCallback, this);
_buttons->defineImage(kPhotoCount + 3, Common::Rect(42, 403, 76, 437), nullptr, nullptr, _shapesButtons->get(0), nullptr);
playSound(kSfxBR024_4B, 25);
wait(1000);
setStateMain(kEsperMainStateList);
resetPhotos();
_script->initialize();
_vm->_mouse->enable();
}
void ESPER::setStateMain(EsperMainStates state) {
if (_isOpen) {
_stateMain = state;
}
}
void ESPER::setStatePhoto(EsperPhotoStates state) {
_statePhoto = state;
}
void ESPER::wait(uint32 timeout) {
if (!_isWaiting) {
_isWaiting = true;
// int timeEnd = timeout + _vm->_time->current();
uint32 timeStart = _vm->_time->current();
// unsigned difference is intentional
while (_vm->_gameIsRunning && (_vm->_time->current() - timeStart < timeout)) {
_vm->gameTick();
}
_isWaiting = false;
}
}
// volume should be in [0, 100]
void ESPER::playSound(int soundId, int volume) {
if (_soundId1 == -1) {
_soundId1 = soundId;
_volume1 = volume;
} else if (_soundId2 == -1) {
_soundId2 = soundId;
_volume2 = volume;
} else if (_soundId3 == -1) {
_soundId3 = soundId;
_volume3 = volume;
}
}
void ESPER::draw(Graphics::Surface &surface) {
if (!_isOpen) {
return;
}
_vqaPlayerMain->update(false);
switch (_stateMain) {
case kEsperMainStateOpening:
case kEsperMainStateList:
return;
case kEsperMainStatePhotoOpening:
drawPhotoOpening(surface);
break;
case kEsperMainStateClear:
surface.fillRect(_screen, surface.format.RGBToColor(0, 0, 0));
break;
case kEsperMainStatePhoto:
if (_isScrolling) {
tickScroll();
}
switch (_statePhoto) {
case kEsperPhotoStateShow:
drawPhotoWithGrid(surface);
if (_isDrawingSelection) {
drawSelection(surface, true, 1);
}
if (_vm->_debugger->_viewUI) {
for (int i = 0; i < kRegionCount; ++i) {
if (_regions[i].isPresent) {
surface.frameRect(
Common::Rect(
viewportXToScreenX(_regions[i].rectInner.left),
viewportYToScreenY(_regions[i].rectInner.top),
viewportXToScreenX(_regions[i].rectInner.right),
viewportYToScreenY(_regions[i].rectInner.bottom)
),
surface.format.RGBToColor(248, 248, 0)
);
surface.frameRect(
Common::Rect(
viewportXToScreenX(_regions[i].rectOuter.left),
viewportYToScreenY(_regions[i].rectOuter.top),
viewportXToScreenX(_regions[i].rectOuter.right),
viewportYToScreenY(_regions[i].rectOuter.bottom)
),
surface.format.RGBToColor(248, 248, 0)
);
}
}
}
break;
case kEsperPhotoStateScrolling:
scrollUpdate();
drawPhotoWithGrid(surface);
break;
case kEsperPhotoStateSelectionZooming:
drawPhotoWithGrid(surface);
if (!drawSelectionZooming(surface)) {
setStatePhoto(kEsperPhotoStateSelectionBlinking);
playSound(kSfxBR030_3A, 25);
}
break;
case kEsperPhotoStateSelectionBlinking:
drawPhotoWithGrid(surface);
if (!drawSelectionBlinking(surface)) {
setStatePhoto(kEsperPhotoStatePhotoZooming);
}
break;
case kEsperPhotoStatePhotoZooming:
drawPhotoZooming(surface);
break;
case kEsperPhotoStatePhotoSharpening:
drawPhotoSharpening(surface);
break;
case kEsperPhotoStatePhotoZoomOut:
drawPhotoZoomOut(surface);
break;
case kEsperPhotoStateVideoZooming:
drawVideoZooming(surface);
break;
case kEsperPhotoStateVideoShow:
drawVideoFrame(surface);
drawGrid(surface);
break;
case kEsperPhotoStateVideoZoomOut:
drawVideoZoomOut(surface);
break;
default:
break;
}
drawTextCoords(surface);
break;
}
}
void ESPER::drawPhotoOpening(Graphics::Surface &surface) {
bool needMoreZooming = true;
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timePhotoOpeningNextStart >= _timePhotoOpeningNextDiff) {
_photoOpeningWidth = MIN(_photoOpeningWidth + 8, _screen.right - 1);
_photoOpeningHeight = MIN(_photoOpeningHeight + 7, _screen.bottom - 1);
if (_photoOpeningWidth == _screen.right - 1 && _photoOpeningHeight == _screen.bottom - 1) {
needMoreZooming = false;
}
_timePhotoOpeningNextDiff = 20u;
_timePhotoOpeningNextStart = timeNow;
}
copyImageScale(_surfacePhoto, _viewport, surface, Common::Rect(_screen.left, _screen.top, _photoOpeningWidth, _photoOpeningHeight));
surface.hLine(_screen.left, _photoOpeningHeight, _screen.right - 1, surface.format.RGBToColor(0, 248, 0));
surface.vLine(_photoOpeningWidth, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 248, 0));
surface.hLine(_screen.left, _photoOpeningHeight - 1, _screen.right - 1, surface.format.RGBToColor(0, 144, 0));
surface.vLine(_photoOpeningWidth - 1, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 144, 0));
drawGrid(surface);
if (!needMoreZooming) {
setStateMain(kEsperMainStatePhoto);
setStatePhoto(kEsperPhotoStateShow);
_vm->_mouse->enable();
}
}
bool ESPER::drawSelectionZooming(Graphics::Surface &surface) {
bool zooming = false;
bool needMoreZooming = true;
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeSelectionZoomNextStart > 150u) {
zooming = true;
_selection.left += _selectionDelta.left;
_selection.top += _selectionDelta.top;
_selection.right += _selectionDelta.right;
_selection.bottom += _selectionDelta.bottom;
++_selectionZoomStep;
_timeSelectionZoomNextStart = timeNow;
if (_selectionZoomStep > kSelectionZoomSteps) {
needMoreZooming = false;
_selection.left = _selectionTarget.left;
_selection.top = _selectionTarget.top;
_selection.right = _selectionTarget.right;
_selection.bottom = _selectionTarget.bottom;
}
}
drawSelection(surface, false, 1);
if (!needMoreZooming) {
_statePhoto = kEsperPhotoStatePhotoZooming;
resetPhotoZooming();
zooming = false;
}
if (zooming) {
playSound(kSfxBR029_3A, 20);
}
return needMoreZooming;
}
bool ESPER::drawSelectionBlinking(Graphics::Surface &surface) {
bool needMoreBlinking = true;
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeSelectionBlinkingNextStart > 100u) {
_timeSelectionBlinkingNextStart = timeNow;
_selectionBlinkingStyle ^= 1;
++_selectionBlinkingCounter;
if (_selectionBlinkingCounter > 10) {
needMoreBlinking = false;
_selectionBlinkingStyle = 0;
}
}
drawSelection(surface, false, _selectionBlinkingStyle);
if (!needMoreBlinking) {
resetSelectionBlinking();
}
return needMoreBlinking;
}
void ESPER::drawPhotoZooming(Graphics::Surface &surface) {
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if ((timeNow - _timeZoomNextStart > _timeZoomNextDiff) && (_zoomStep < _zoomSteps)) {
_flash = true;
_viewportPositionXCurrent += _viewportPositionXDelta;
_viewportPositionYCurrent += _viewportPositionYDelta;
_viewportPositionX = _viewportPositionXCurrent;
_viewportPositionY = _viewportPositionYCurrent;
_zoom += _zoomDelta;
if (_zoomDelta > 0.0f) {
if (_zoom > _zoomTarget) {
_zoom = _zoomTarget;
_zoomStep = _zoomSteps;
} else {
_blur += _zoomDelta * 2.0f;
}
} else if (_zoomDelta < 0.0f) {
if (_zoom < _zoomTarget) {
_zoom = _zoomTarget;
_zoomStep = _zoomSteps;
}
}
++_zoomStep;
if (_zoomStep >= _zoomSteps) {
_zoom = _zoomTarget;
_viewportPositionX = _viewportPositionXTarget;
_viewportPositionY = _viewportPositionYTarget;
}
updateViewport();
_timeZoomNextDiff = 300u;
_timeZoomNextStart = timeNow;
}
if (_zoomDelta >= 0.0f) {
drawPhoto(surface);
} else {
drawPhotoWithGrid(surface);
}
drawGrid(surface);
// unsigned difference is intentional
if ((timeNow - _timeZoomNextStart > _timeZoomNextDiff) && (_zoomStep >= _zoomSteps)) {
if (_regionSelectedAck) {
if (!_regions[_regionSelected].name.empty()) {
if (_zoomDelta < 0.0f) {
_blur = 1.0f;
_zoomDelta = (_zoom * 1.5f - _zoom) / (float)_zoomSteps; // 0.5f * _zoom ???
}
setStatePhoto(kEsperPhotoStateVideoZooming);
_timeZoomNextDiff += 300u;
} else {
_regionSelectedAck = false;
_selection.left = viewportXToScreenX(_regions[_regionSelected].rectInner.left);
_selection.right = viewportXToScreenX(_regions[_regionSelected].rectInner.right);
_selection.top = viewportYToScreenY(_regions[_regionSelected].rectInner.top);
_selection.bottom = viewportYToScreenY(_regions[_regionSelected].rectInner.bottom);
prepareZoom();
resetPhotoZooming();
updateSelection();
setStatePhoto(kEsperPhotoStatePhotoZooming);
}
} else {
setStatePhoto(kEsperPhotoStatePhotoSharpening);
}
resetPhotoOpening();
}
}
void ESPER::drawPhotoSharpening(Graphics::Surface &surface) {
uint32 timeNow = _vm->_time->current();
bool needMoreSharpening = true;
// unsigned difference is intentional
if (timeNow - _timePhotoOpeningNextStart >= _timePhotoOpeningNextDiff) {
_photoOpeningWidth = MIN(_photoOpeningWidth + 8, _screen.right - 1);
_photoOpeningHeight = MIN(_photoOpeningHeight + 7, _screen.bottom - 1);
if (_photoOpeningWidth == _screen.right - 1 && _photoOpeningHeight == _screen.bottom - 1) {
needMoreSharpening = false;
}
_timePhotoOpeningNextDiff = 50u;
_timePhotoOpeningNextStart = timeNow;
}
if (_regionSelectedAck && !_regions[_regionSelected].name.empty()) {
_vqaPlayerPhoto->update(true, false);
copyImageBlur(_surfaceViewport, Common::Rect(0, 0, 299, 263), surface, _screen, _blur);
copyImageBlit(_surfaceViewport, Common::Rect(0, 0, 0, 0), surface, Common::Rect(_screen.left, _screen.top, _photoOpeningWidth, _photoOpeningHeight));
} else {
drawPhoto(surface);
copyImageScale(_surfacePhoto, _viewport, _surfaceViewport, Common::Rect(0, 0, _screen.width(), _screen.height()));
copyImageBlit(_surfaceViewport, Common::Rect(0, 0, 0, 0), surface, Common::Rect(_screen.left, _screen.top, _photoOpeningWidth, _photoOpeningHeight));
}
drawGrid(surface);
surface.hLine(_screen.left, _photoOpeningHeight, _screen.right - 1, surface.format.RGBToColor(0, 248, 0));
surface.vLine(_photoOpeningWidth, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 248, 0));
surface.hLine(_screen.left, _photoOpeningHeight - 1, _screen.right - 1, surface.format.RGBToColor(0, 144, 0));
surface.vLine(_photoOpeningWidth - 1, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 144, 0));
if (!needMoreSharpening) {
if (_regionSelectedAck && !_regions[_regionSelected].name.empty()) {
setStatePhoto(kEsperPhotoStateVideoShow);
} else {
setStatePhoto(kEsperPhotoStateShow);
}
resetPhotoZooming();
resetPhotoOpening();
_vm->_mouse->enable();
}
}
void ESPER::drawPhotoZoomOut(Graphics::Surface &surface) {
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeZoomOutNextStart >= 300u) {
_timeZoomOutNextStart = timeNow;
if (_zoom > _zoomMin) {
_zoom /= 1.3f;
_flash = true;
if (_zoomHorizontal <= _zoomVertical) {
if (_zoom < _zoomVertical) {
_zoom = _zoomVertical;
}
} else {
if (_zoom < _zoomHorizontal) {
_zoom = _zoomHorizontal;
}
}
updateViewport();
} else {
_statePhoto = kEsperPhotoStateShow;
}
}
drawPhotoWithGrid(surface);
}
void ESPER::drawVideoZooming(Graphics::Surface &surface) {
if (_vqaPlayerPhoto == nullptr) {
_vqaPlayerPhoto = new VQAPlayer(_vm, &_surfaceViewport, Common::String(_regions[_regionSelected].name) + ".VQA");
if (!_vqaPlayerPhoto->open()) {
setStatePhoto(kEsperPhotoStateShow);
_vm->_mouse->enable();
delete _vqaPlayerPhoto;
_vqaPlayerPhoto = nullptr;
return;
}
_timeZoomNextDiff = 0u;
_timeZoomNextStart = 0u;
}
bool flash = false;
bool advanceFrame = false;
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeZoomNextStart > _timeZoomNextDiff) {
_timeZoomNextDiff = 300u;
_timeZoomNextStart = timeNow;
playSound(kSfxBR031_1P, 25);
flash = true;
advanceFrame = true;
_blur += _zoomDelta * 5.0f;
}
int frame = _vqaPlayerPhoto->update(true, advanceFrame);
if (frame == _vqaPlayerPhoto->getFrameCount() - 1) {
_vqaLastFrame = frame;
setStatePhoto(kEsperPhotoStatePhotoSharpening);
} else if (flash) {
// TODO? Temporary workaround for very noticeable blue tint in the first frame during zoom-out:
// Don't flash for the last frame of the photo (which is the starting frame when zooming out)
flashViewport();
}
copyImageBlur(_surfaceViewport, Common::Rect(0, 0, 299, 263), surface, _screen, _blur);
drawGrid(surface);
}
void ESPER::drawVideoZoomOut(Graphics::Surface &surface) {
bool flash = false;
bool advanceFrame = false;
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeZoomNextStart > _timeZoomNextDiff && _vqaLastFrame > 0) {
_timeZoomNextDiff = 300u;
_timeZoomNextStart = timeNow;
playSound(kSfxBR031_1P, 25);
_vqaPlayerPhoto->seekToFrame(_vqaLastFrame);
int nextFrame = _vqaPlayerPhoto->getFrameCount() / 4;
if (nextFrame <= 0) {
nextFrame = 1;
} else if (nextFrame > 4) {
nextFrame = 4;
}
if (_vqaLastFrame < _vqaPlayerPhoto->getFrameCount() - 1) {
// TODO? Temporary workaround for persistent blue tint in the last frame:
// Don't flash for the last frame of the photo (starting frame when zooming out)
flash = true;
}
advanceFrame = true;
_vqaLastFrame -= nextFrame;
}
_vqaPlayerPhoto->update(true, advanceFrame);
if (flash) {
flashViewport();
}
copyImageBlit(_surfaceViewport, Common::Rect(0, 0, 0, 0), surface, _screen);
drawGrid(surface);
// unsigned difference is intentional
if (timeNow - _timeZoomNextStart > _timeZoomNextDiff && _vqaLastFrame <= 0) {
_vqaPlayerPhoto->close();
delete _vqaPlayerPhoto;
_vqaPlayerPhoto = nullptr;
if (_vm->isMouseButtonDown()) {
zoomOutStart();
} else {
zoomOutStop();
}
}
}
void ESPER::drawPhoto(Graphics::Surface &surface) {
copyImageBlur(_surfacePhoto, _viewport, surface, _screen, _blur);
}
void ESPER::drawGrid(Graphics::Surface &surface) {
for (int i = 0; i < 7; ++i) {
surface.drawLine(_screen.left + i * 50, _screen.top, _screen.left + i * 50, _screen.bottom - 1, surface.format.RGBToColor(32, 32, 224));
}
for (int i = 0; i < 7; ++i) {
surface.drawLine(_screen.left, _screen.top + i * 44, _screen.right - 1, _screen.top + i * 44, surface.format.RGBToColor(32, 32, 224));
}
}
void ESPER::drawPhotoWithGrid(Graphics::Surface &surface) {
copyImageScale(_surfacePhoto, _viewport, surface, _screen);
drawGrid(surface);
}
void ESPER::drawSelection(Graphics::Surface &surface, bool crosshair, int style) {
int left = CLIP(_selection.left, _screen.left, (int16)(_screen.right - 1));
int top = CLIP(_selection.top, _screen.top, (int16)(_screen.bottom - 1));
int right = CLIP(_selection.right, _screen.left, (int16)(_screen.right - 1));
int bottom = CLIP(_selection.bottom, _screen.top, (int16)(_screen.bottom - 1));
int color = surface.format.RGBToColor(0, 144, 0);
if (style) {
color = surface.format.RGBToColor(0, 248, 0);
}
// selection rectangle
Common::Rect selectedRect(MIN(left, right), MIN(top, bottom), MAX(left, right) + 1, MAX(top, bottom) + 1);
Common::Rect selectedRect2 = selectedRect;
selectedRect2.grow(-1);
surface.frameRect(selectedRect, color);
surface.frameRect(selectedRect2, color);
if (crosshair) {
if (_selectionCrosshairX == -1) {
if (_selection.left < (_screen.left + _screen.right) / 2) {
_selectionCrosshairX = _screen.left;
} else {
_selectionCrosshairX = _screen.right - 1;
}
}
if (_selectionCrosshairY == -1) {
if (_selection.top < (_screen.top + _screen.bottom) / 2) {
_selectionCrosshairY = _screen.top;
} else {
_selectionCrosshairY = _screen.bottom - 1;
}
}
// ghosting
if (_selectionCrosshairX != right) {
surface.vLine(_selectionCrosshairX, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 144, 0));
if (abs(_selectionCrosshairX - right) <= 1) {
_selectionCrosshairX = right;
} else {
_selectionCrosshairX = (_selectionCrosshairX + right) / 2;
}
}
if (_selectionCrosshairY != bottom) {
surface.hLine(_screen.left, _selectionCrosshairY, _screen.right - 1, surface.format.RGBToColor(0, 144, 0));
if (abs(_selectionCrosshairY - bottom) <= 1) {
_selectionCrosshairY = bottom;
} else {
_selectionCrosshairY = (_selectionCrosshairY + bottom) / 2;
}
}
surface.vLine(right, _screen.top, _screen.bottom - 1, surface.format.RGBToColor(0, 248, 0));
surface.hLine(_screen.left, bottom, _screen.right - 1, surface.format.RGBToColor(0, 248, 0));
}
}
void ESPER::drawVideoFrame(Graphics::Surface &surface) {
_vqaPlayerPhoto->update(true, false);
copyImageBlit(_surfaceViewport, Common::Rect(0, 0, 0, 0), surface, _screen);
}
void ESPER::drawTextCoords(Graphics::Surface &surface) {
const char *zm = "ZM %04.0f";
const char *ns = "NS %04d";
const char *ew = "EW %04d";
if (_vm->_language == Common::RU_RUS) {
// ПР, ВР, ГР
if (_vm->_russianCP1251) {
// Patched transalation by Siberian Studio is using Windows-1251 encoding
zm = "\xcf\xd0 %04.0f";
ns = "\xc2\xd0 %04d";
ew = "\xc3\xd0 %04d";
} else {
// Original release uses custom encoding
zm = "gh %04.0f";
ns = "dh %04d";
ew = "uh %04d";
}
}
_vm->_mainFont->drawString(&surface, Common::String::format(zm, _zoom / _zoomMin * 2.0f ), 155, 364, surface.w, surface.format.RGBToColor(0, 0, 255));
_vm->_mainFont->drawString(&surface, Common::String::format(ns, 12 * _viewport.top + 98), 260, 364, surface.w, surface.format.RGBToColor(0, 0, 255));
_vm->_mainFont->drawString(&surface, Common::String::format(ew, 12 * _viewport.left + 167), 364, 364, surface.w, surface.format.RGBToColor(0, 0, 255));
}
void ESPER::drawMouse(Graphics::Surface &surface) {
if (_vm->_mouse->isDisabled()) {
return;
}
int cursor = -1;
Common::Point p = _vm->getMousePos();
_mouseOverScroll = 4;
if (_stateMain == kEsperMainStatePhoto) {
if (_screen.contains(p)) {
if ((_statePhoto == kEsperPhotoStateShow) && ( _zoom != 2.0f)) {
if (_isMouseDown) {
if (_isDrawingSelection) {
_selection.right = p.x;
_selection.bottom = p.y;
} else {
_selection.left = p.x;
_selection.top = p.y;
_selection.right = p.x + 1;
_selection.bottom = p.y + 1;
_isDrawingSelection = true;
}
} else {
if (_isDrawingSelection) {
_selection.right = p.x;
_selection.bottom = p.y;
if (_selection.right < _selection.left) {
SWAP(_selection.left, _selection.right);
}
if (_selection.bottom < _selection.top) {
SWAP(_selection.bottom, _selection.top);
}
if (_selection.right >= _selection.left + 3) {
updateSelection();
_vm->_mouse->disable();
zoomingStart();
} else {
resetSelectionRect();
}
}
_isDrawingSelection = false;
}
}
surface.vLine(p.x, p.y - 8, p.y - 1, surface.format.RGBToColor(0, 248, 0));
surface.vLine(p.x, p.y + 8, p.y + 1, surface.format.RGBToColor(0, 248, 0));
surface.hLine(p.x - 8, p.y, p.x - 1, surface.format.RGBToColor(0, 248, 0));
surface.hLine(p.x + 8, p.y, p.x + 1, surface.format.RGBToColor(0, 248, 0));
_mouseOverScroll = -1;
} else if (p.x >= 85 && p.y >= 73 && p.x <= 484 && p.y <= 436) {
if (!_isDrawingSelection && _statePhoto != kEsperPhotoStateVideoShow && _zoom != 2.0f) {
_mouseOverScroll = (angle_1024((_screen.left + _screen.right) / 2, (_screen.top + _screen.bottom) / 2, p.x, p.y) + 128) / 256;
if (_mouseOverScroll >= 4) {
_mouseOverScroll = 0;
}
if (_mouseOverScroll == 0 && this->_viewport.top == 0) {
_mouseOverScroll = 4;
} else if (_mouseOverScroll == 1 && this->_viewport.right == kPhotoWidth - 1) {
_mouseOverScroll = 4;
} else if (_mouseOverScroll == 2 && this->_viewport.bottom == kPhotoHeight - 1) {
_mouseOverScroll = 4;
} else if (_mouseOverScroll == 3 && this->_viewport.left == 0) {
_mouseOverScroll = 4;
}
if (_mouseOverScroll != 4) {
cursor = _mouseOverScroll + 2;
}
}
}
}
if (_mouseOverScroll == 4) {
cursor = _buttons->hasHoveredImage() ? 1 : 0;
}
if (cursor != -1) {
_vm->_mouse->setCursor(cursor);
_vm->_mouse->draw(surface, p.x, p.y);
}
}
void ESPER::flashViewport() {
for (int y = 0; y < _surfaceViewport.h; ++y) {
for (int x = 0; x < _surfaceViewport.w; ++x) {
uint8 r, g, b;
void *ptr = _surfaceViewport.getBasePtr(x, y);
_surfaceViewport.format.colorToRGB(READ_UINT32(ptr), r, g, b);
b *= 2;
drawPixel(_surfaceViewport, ptr, _surfaceViewport.format.RGBToColor(r, g, b));
}
}
}
void ESPER::copyImageScale(Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, Common::Rect dstRect) {
if (_flash) {
playSound(kSfxBR031_1P, 25);
}
int srcDstWidthRatio = srcRect.width() / dstRect.width();
int srcDstWidthRest = srcRect.width() % dstRect.width();
int srcDstHeightRatio = srcRect.height() / dstRect.height();
int srcDstHeightRest = srcRect.height() % dstRect.height();
if (srcRect.width() > dstRect.width() && srcRect.height() > dstRect.height()) {
// reduce
int srcY = srcRect.top;
int srcYCounter = 0;
for (int dstY = dstRect.top; dstY < dstRect.bottom; ++dstY) {
int srcX = srcRect.left;
int srcXCounter = 0;
for (int dstX = dstRect.left; dstX < dstRect.right; ++dstX) {
srcX = CLIP(srcX, 0, src.w - 1);
srcY = CLIP(srcY, 0, src.h - 1);
dstX = CLIP(dstX, 0, dst.w - 1);
dstY = CLIP(dstY, 0, dst.h - 1);
uint8 r, g, b;
src.format.colorToRGB(READ_UINT32(src.getBasePtr(srcX, srcY)), r, g, b);
if (_flash) {
// add blue-ish tint
b *= 2;
}
drawPixel(dst, dst.getBasePtr(dstX, dstY), dst.format.RGBToColor(r, g, b));
srcX += srcDstWidthRatio;
srcXCounter += srcDstWidthRest;
if (srcXCounter >= dstRect.width()) {
srcXCounter -= dstRect.width();
++srcX;
}
}
srcY += srcDstHeightRatio;
srcYCounter += srcDstHeightRest;
if (srcYCounter >= dstRect.height()) {
srcYCounter -= dstRect.height();
++srcY;
}
}
} else {
// enlarge
int srcY = srcRect.top;
int srcYCounter = 0;
for (int dstY = dstRect.top; dstY < dstRect.bottom; ++dstY) {
int srcX = srcRect.left;
int srcXCounter = 0;
for (int dstX = dstRect.left; dstX < dstRect.right; ++dstX) {
srcXCounter += srcRect.width();
if (srcXCounter >= dstRect.width()) {
srcXCounter -= dstRect.width();
++srcX;
}
srcX = CLIP(srcX, 0, src.w - 1);
srcY = CLIP(srcY, 0, src.h - 1);
dstX = CLIP(dstX, 0, dst.w - 1);
dstY = CLIP(dstY, 0, dst.h - 1);
uint8 r, g, b;
src.format.colorToRGB(READ_UINT32(src.getBasePtr(srcX, srcY)), r, g, b);
if (_flash) {
// add blue-ish tint
b *= 2;
}
drawPixel(dst, dst.getBasePtr(dstX, dstY), dst.format.RGBToColor(r, g, b));
}
srcYCounter += srcRect.height();
if (srcYCounter >= dstRect.height()) {
srcYCounter -= dstRect.height();
++srcY;
}
}
}
_flash = false;
}
void ESPER::copyImageBlur(Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, Common::Rect dstRect, float blur) {
if (_flash) {
playSound(kSfxBR031_1P, 25);
}
int srcDstWidthRatio = srcRect.width() / dstRect.width();
int srcDstWidthRest = srcRect.width() % dstRect.width();
int srcDstHeightRatio = srcRect.height() / dstRect.height();
int srcDstHeightRest = srcRect.height() % dstRect.height();
int skipStep = (blur - (int)blur) * 1000.0f;
if (srcRect.width() > dstRect.width() && srcRect.height() > dstRect.height()) {
// reduce
int srcY = srcRect.top;
int dstY = dstRect.top;
int srcYCounter = 0;
int skipYMaxCounter = 0;
while (dstY < dstRect.bottom) {
skipYMaxCounter += skipStep;
int skipYMax = blur;
if (skipYMaxCounter >= 1000) {
skipYMaxCounter -= 1000;
++skipYMax;
}
int skipY = 0;
while (dstY < dstRect.bottom && skipY < skipYMax) {
int srcX = srcRect.left;
int dstX = dstRect.left;
int srcXCounter = 0;
int skipXMaxCounter = 0;
while (dstX < dstRect.right) {
skipXMaxCounter += skipStep;
int skipXMax = blur;
if (skipXMaxCounter >= 1000) {
skipXMaxCounter -= 1000;
++skipXMax;
}
int skipX = 0;
while (dstX < dstRect.right && skipX < skipXMax) {
srcX = CLIP(srcX, 0, src.w - 1);
srcY = CLIP(srcY, 0, src.h - 1);
dstX = CLIP(dstX, 0, dst.w - 1);
dstY = CLIP(dstY, 0, dst.h - 1);
uint8 r, g, b;
src.format.colorToRGB(READ_UINT32(src.getBasePtr(srcX, srcY)), r, g, b);
if (_flash) {
// add blue-ish tint
b *= 2;
}
drawPixel(dst, dst.getBasePtr(dstX, dstY), dst.format.RGBToColor(r, g, b));
++dstX;
++skipX;
}
srcXCounter += srcDstWidthRest;
srcX += srcDstWidthRatio * skipX;
if (srcXCounter >= dstRect.width()) {
srcXCounter -= dstRect.width();
srcX += skipX;
}
}
++dstY;
++skipY;
}
srcYCounter += srcDstHeightRest;
srcY += srcDstHeightRatio * skipY;
if (srcYCounter >= dstRect.height()) {
srcYCounter -= dstRect.height();
srcY += skipY;
}
}
} else {
// enlarge
int srcY = srcRect.top;
int dstY = dstRect.top;
int srcYCounter = srcRect.height(); // TODO: look at this again because in original source this is 0, but then first line is doubled
int skipYMaxCounter = 0;
while (dstY < dstRect.bottom) {
skipYMaxCounter += skipStep;
int skipYMax = blur;
if (skipYMaxCounter >= 1000) {
skipYMaxCounter -= 1000;
++skipYMax;
}
int skipY = 0;
while (dstY < dstRect.bottom && skipY < skipYMax) {
int srcX = srcRect.left;
int dstX = dstRect.left;
int srcXCounter = 0;
int skipXMaxCounter = 0;
while (dstX < dstRect.right) {
skipXMaxCounter += skipStep;
int skipXMax = blur;
if (skipXMaxCounter >= 1000) {
skipXMaxCounter -= 1000;
++skipXMax;
}
int skipX = 0;
while (dstX < dstRect.right && skipX < skipXMax) {
srcXCounter += srcRect.width();
if (srcXCounter >= dstRect.width()) {
srcXCounter -= dstRect.width();
++srcX; // bug in original game? Advancing by 1 instead of skipX as for Y
}
srcX = CLIP(srcX, 0, src.w - 1);
srcY = CLIP(srcY, 0, src.h - 1);
dstX = CLIP(dstX, 0, dst.w - 1);
dstY = CLIP(dstY, 0, dst.h - 1);
uint8 r, g, b;
src.format.colorToRGB(READ_UINT32(src.getBasePtr(srcX, srcY)), r, g, b);
if (_flash) {
// add blue-ish tint
b *= 2;
}
drawPixel(dst, dst.getBasePtr(dstX, dstY), dst.format.RGBToColor(r, g, b));
++dstX;
++skipX;
}
}
++dstY;
++skipY;
}
srcYCounter += srcRect.height();
if (srcYCounter >= dstRect.height()) {
srcYCounter -= dstRect.height();
srcY += skipY;
}
}
}
_flash = false;
}
void ESPER::copyImageBlit(Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, Common::Rect dstRect) {
for (int y = 0; y < dstRect.height(); ++y) {
for (int x = 0; x < dstRect.width(); ++x) {
uint8 r, g, b;
src.format.colorToRGB(READ_UINT32(src.getBasePtr(CLIP(srcRect.left + x, 0, src.w - 1), CLIP(srcRect.top + y, 0, src.h - 1))), r, g, b);
drawPixel(dst, dst.getBasePtr(CLIP(dstRect.left + x, 0, dst.w - 1), CLIP(dstRect.top + y, 0, dst.h - 1)), dst.format.RGBToColor(r, g, b));
}
}
}
void ESPER::tickSound() {
if (_soundId1 != -1) {
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(_soundId1), _volume1, 0, 0, 50, 0);
_soundId1 = -1;
}
if (_soundId2 != -1) {
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(_soundId2), _volume2, 0, 0, 50, 0);
_soundId2 = -1;
}
if (_soundId3 != -1) {
_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(_soundId3), _volume3, 0, 0, 50, 0);
_soundId3 = -1;
}
}
void ESPER::tickScroll() {
uint32 timeNow = _vm->_time->current();
// unsigned difference is intentional
if (timeNow - _timeScrollNextStart <= 300u) {
return;
}
_timeScrollNextStart = timeNow;
if (_scrollingDirection == 0) {
scrollUp();
} else if (_scrollingDirection == 1) {
scrollRight();
} else if (_scrollingDirection == 2) {
scrollDown();
} else if (_scrollingDirection == 3) {
scrollLeft();
}
}
int ESPER::findEmptyPhoto() {
for (int i = 0; i < kPhotoCount; ++i) {
if (!_photos[i].isPresent) {
return i;
}
}
return -1;
}
void ESPER::selectPhoto(int photoId) {
_vm->_mouse->disable();
_photoIdSelected = _photos[photoId].photoId;
unloadPhotos();
_script->photoSelected(_photoIdSelected);
Common::ScopedPtr s(_vm->getResourceStream(_photos[photoId].name));
uint photoSize = _surfacePhoto.w * _surfacePhoto.h * _surfacePhoto.format.bytesPerPixel;
s->skip(3); // not used, but there is compression type
uint width = s->readUint32LE();
uint height = s->readUint32LE();
uint photoCompressedSize = s->size() - s->pos();
uint8 *photoCompressed = (uint8 *)_surfacePhoto.getPixels() + photoSize - photoCompressedSize;
s->read(photoCompressed, photoCompressedSize);
decompress_lcw(photoCompressed, photoCompressedSize, (uint8 *)_surfacePhoto.getPixels(), photoSize);
#ifdef SCUMM_BIG_ENDIAN
// As the compression is working with 8-bit data, on big-endian architectures we have to switch order of bytes in uncompressed data
uint8 *rawData = (uint8 *)_surfacePhoto.getPixels();
for (size_t i = 0; i < photoSize - 1; i += 2) {
SWAP(rawData[i], rawData[i + 1]);
}
#endif
// apply palette
for (uint j = 0; j < width * height; ++j) {
// _surfacePhoto[j] = Palette[_surfacePhoto[j]];
}
_shapeThumbnail = _shapesPhotos->get(_photos[photoId].shapeId);
_buttons->resetImages();
_buttons->defineImage(kPhotoCount + 2, Common::Rect(480, 350, 578, 413), _shapeThumbnail, _shapeThumbnail, _shapeThumbnail, nullptr);
_buttons->defineImage(kPhotoCount + 3, Common::Rect(42, 403, 76, 437), nullptr, nullptr, _shapesButtons->get(0), nullptr);
resetPhotoOpening();
resetViewport();
setStateMain(kEsperMainStatePhotoOpening);
setStatePhoto(kEsperPhotoStateOpening);
playSound(kSfxBR032_7B, 25);
playSound(kSfxBR033_4B, 25);
}
void ESPER::unloadPhotos() {
for (int i = 0; i < kPhotoCount; ++i) {
if (_photos[i].isPresent) {
_photos[i].isPresent = false;
_buttons->resetImage(i);
}
}
}
int ESPER::findEmptyRegion() {
for (int i = 0; i < kRegionCount; ++i) {
if (!_regions[i].isPresent) {
return i;
}
}
return -1;
}
int ESPER::findRegion(Common::Rect where) {
for (int i = 0; i < kRegionCount; ++i) {
if (_regions[i].isPresent && _regions[i].rectOuter.contains(where) && where.contains(_regions[i].rectInner)) {
return i;
}
}
return -1;
}
void ESPER::zoomingStart() {
prepareZoom();
setStatePhoto(kEsperPhotoStateSelectionZooming);
}
void ESPER::zoomOutStart() {
if (_statePhoto == kEsperPhotoStateVideoShow) {
resetPhotoZooming();
setStatePhoto(kEsperPhotoStateVideoZoomOut);
} else {
zoomOutStop();
if (_zoomMin < _zoom) {
_isZoomingOut = true;
setStatePhoto(kEsperPhotoStatePhotoZoomOut);
}
}
}
void ESPER::zoomOutStop() {
_isZoomingOut = false;
_statePhoto = kEsperPhotoStateShow;
}
void ESPER::scrollingStart(int direction) {
scrollingStop();
if ((direction != 3 || _viewport.left > 0)
&& (direction != 0 || _viewport.top > 0)
&& (direction != 1 || _viewport.right != kPhotoWidth - 1)
&& (direction != 2 || _viewport.bottom != kPhotoHeight - 1)) {
_isScrolling = true;
_scrollingDirection = direction;
}
}
void ESPER::scrollingStop() {
_isScrolling = false;
_scrollingDirection = -1;
}
void ESPER::scrollUpdate() {
if ((_viewport.left == _viewportNext.left) && (_viewportNext.top == _viewport.top)) {
setStatePhoto(kEsperPhotoStateShow);
return;
}
if (_viewport.left != _viewportNext.left) {
_viewport.left = _viewportNext.left;
_viewport.right = _viewportNext.right;
_viewportPositionX = (_viewportNext.left + _viewportNext.right) / 2;
}
if (_viewport.top != _viewportNext.top) {
_viewport.top = _viewportNext.top;
_viewport.bottom = _viewportNext.bottom;
_viewportPositionY = (_viewportNext.top + _viewportNext.bottom) / 2;
}
}
void ESPER::scrollLeft() {this->_flash = 1;
_flash = true;
setStatePhoto(kEsperPhotoStateScrolling);
_viewportNext.left = _viewport.left - 40;
_viewportNext.right = _viewport.right - 40;
if (_viewportNext.left < 0) {
_viewportNext.right -= _viewportNext.left;
_viewportNext.left = 0;
scrollingStop();
}
_viewportNext.top = _viewport.top;
_viewportNext.bottom = _viewport.bottom;
}
void ESPER::scrollUp() {
_flash = true;
setStatePhoto(kEsperPhotoStateScrolling);
_viewportNext.top = _viewport.top - 40;
_viewportNext.bottom = _viewport.bottom - 40;
if (_viewportNext.top < 0) {
_viewportNext.bottom -= _viewportNext.top;
_viewportNext.top = 0;
scrollingStop();
}
_viewportNext.left = _viewport.left;
_viewportNext.right = _viewport.right;
}
void ESPER::scrollRight() {
if (_viewport.right < kPhotoWidth - 1) {
_flash = true;
setStatePhoto(kEsperPhotoStateScrolling);
_viewportNext.left = _viewport.left + 40;
_viewportNext.right = _viewport.right + 40;
_viewportNext.top = _viewport.top;
_viewportNext.bottom = _viewport.bottom;
if (_viewportNext.right > kPhotoWidth - 1) {
_viewportNext.left -= _viewportNext.right - (kPhotoWidth - 1);
_viewportNext.right = kPhotoWidth - 1;
scrollingStop();
}
}
}
void ESPER::scrollDown() {
if (_viewport.bottom < kPhotoHeight - 1) {
_flash = true;
setStatePhoto(kEsperPhotoStateScrolling);
_viewportNext.top = _viewport.top + 40;
_viewportNext.bottom = _viewport.bottom + 40;
_viewportNext.left = _viewport.left;
_viewportNext.right = _viewport.right;
if (_viewportNext.bottom > kPhotoHeight - 1) {
_viewportNext.top -= _viewportNext.bottom - (kPhotoHeight - 1);
_viewportNext.bottom = kPhotoHeight - 1;
scrollingStop();
}
}
}
void ESPER::goBack() {
// CD-changing logic has been removed
if (_stateMain == kEsperMainStateList) {
close();
} else {
resetData();
activate(false);
}
}
void ESPER::prepareZoom() {
_selectionZoomStep = 0;
_timeSelectionZoomNextStart = 0u;
_selectionTarget = _selection;
resetSelectionRect();
_selectionDelta.left = (_selectionTarget.left - _selection.left) / kSelectionZoomSteps;
_selectionDelta.top = (_selectionTarget.top - _selection.top) / kSelectionZoomSteps;
_selectionDelta.right = (_selectionTarget.right - _selection.right) / kSelectionZoomSteps;
_selectionDelta.bottom = (_selectionTarget.bottom - _selection.bottom) / kSelectionZoomSteps;
Common::Rect rect = _selectionTarget;
if (_regionSelectedAck) {
rect.left = viewportXToScreenX(_regions[_regionSelected].rectSelection.left);
rect.top = viewportYToScreenY(_regions[_regionSelected].rectSelection.top);
rect.right = viewportXToScreenX(_regions[_regionSelected].rectSelection.right);
rect.bottom = viewportYToScreenY(_regions[_regionSelected].rectSelection.bottom);
}
_zoomSteps = 10;
float ratio = (rect.width() + 1.0f) / (float)_screen.width();
if (ratio == 0.0f) {
_zoomTarget = ratio;
_zoomDelta = 0.0f;
} else {
_zoomTarget = CLIP(_zoom / ratio, _zoomMin, 2.0f);
_zoomSteps = CLIP((int)(_zoomTarget / _zoom) - 1, 0, 5) + 5;
_zoomDelta = (_zoomTarget - _zoom) / (float)_zoomSteps;
}
_blur = 1.0f;
_viewportPositionXTarget = _viewport.left + ((rect.left + rect.right) / 2 - _screen.left) * _viewport.width() / _screen.width();
_viewportPositionYTarget = _viewport.top + ((rect.top + rect.bottom) / 2 - _screen.top ) * _viewport.height() / _screen.height();
_viewportPositionXDelta = (_viewportPositionXTarget - _viewportPositionX) / (float)_zoomSteps;
_viewportPositionYDelta = (_viewportPositionYTarget - _viewportPositionY) / (float)_zoomSteps;
_viewportPositionXCurrent = _viewportPositionX;
_viewportPositionYCurrent = _viewportPositionY;
}
void ESPER::updateSelection() {
int selectionWidth = abs(_selection.right + 1 - _selection.left);
int selectionHeight = abs(_selection.bottom + 1 - _selection.top);
int photoSelectedWidth = _viewport.width() * selectionWidth / _screen.width();
if (photoSelectedWidth < _screenHalfWidth) {
// minimal width of selection
selectionWidth = _screen.width() * _screenHalfWidth / _viewport.width();
}
photoSelectedWidth = _viewport.height() * selectionHeight / _screen.height();
if (photoSelectedWidth < _screenHalfHeight) {
// minimal height of selection
selectionHeight = _screen.height() * _screenHalfHeight / _viewport.height();
}
// correct aspect ratio
if (selectionWidth / (float)_screen.width() <= selectionHeight / (float)_screen.height()) {
while (selectionWidth / (float)_screen.width() <= selectionHeight / (float)_screen.height()) {
++selectionWidth;
}
} else {
while (selectionHeight / (float)_screen.height() <= selectionWidth / (float)_screen.width()) {
++selectionHeight;
}
}
if (selectionWidth > _screen.width()) {
selectionWidth = _screen.width();
}
if (selectionHeight > _screen.height()) {
selectionHeight = _screen.height();
}
int left = _viewport.right - (_screen.right - 1 - _selection.left) * _viewport.width() / _screen.width();
int right = _viewport.left + (_selection.right - _screen.left ) * _viewport.width() / _screen.width();
int top = _viewport.bottom - (_screen.bottom - 1 - _selection.top ) * _viewport.height() / _screen.height();
int bottom = _viewport.top + (_selection.bottom - _screen.top ) * _viewport.height() / _screen.height();
bool stop = false;
bool alternate = false;
while (selectionWidth > abs(_selection.right + 1 - _selection.left)) {
if (alternate) {
--_selection.left;
if (_selection.left < 0) {
left = _viewport.right - (_screen.right - 1 + 100 - _selection.left) * _viewport.width() / _screen.width();
if (left < 0) {
left = 0;
++_selection.left;
if (stop) {
break;
}
stop = true;
alternate = false;
}
}
} else {
++_selection.right;
if (_selection.right > _screen.right - 1) {
right = _viewport.left + (_selection.right - _screen.left) * _viewport.width() / _screen.width();
if (right > kPhotoWidth - 1) {
right = kPhotoWidth - 1;
--_selection.right;
if (stop) {
break;
}
stop = true;
alternate = true;
}
}
}
if (!stop) {
alternate = !alternate;
}
}
alternate = false;
stop = false;
while (selectionHeight > abs(_selection.bottom + 1 - _selection.top)) {
if (alternate) {
--_selection.top;
if (_selection.top < 0) {
top = _viewport.bottom - (_screen.bottom - 1 - _selection.top) * _viewport.height() / _screen.height();
if (top < 0) {
top = 0;
++_selection.top;
if (stop) {
break;
}
stop = true;
alternate = false;
}
}
} else {
++_selection.bottom;
if (_selection.bottom > _screen.bottom - 1) {
bottom = _viewport.top + (_selection.bottom - _screen.top) * _viewport.height() / _screen.height();
if (bottom > kPhotoHeight - 1) {
bottom = kPhotoHeight - 1;
--_selection.bottom;
if (stop) {
break;
}
alternate = true;
stop = true;
}
}
}
if (!stop) {
alternate = !alternate;
}
}
if (left > right) {
SWAP(left, right);
}
if (top > bottom) {
SWAP(top, bottom);
}
_regionSelected = findRegion(Common::Rect(left, top, right, bottom));
if (_regionSelected >= 0) {
_regionSelectedAck = true;
setStatePhoto(kEsperPhotoStatePhotoZooming);
}
}
int ESPER::viewportXToScreenX(int x) {
return _screen.width() * (x - _viewport.left) / _viewport.width() + _screen.left;
}
int ESPER::viewportYToScreenY(int y) {
return _screen.height() * (y - _viewport.top) / _viewport.height() + _screen.top;
}
} // End of namespace BladeRunner