diff options
39 files changed, 2447 insertions, 224 deletions
diff --git a/engines/bladerunner/actor_clues.cpp b/engines/bladerunner/actor_clues.cpp index 39fbc77d4e..e1841fe817 100644 --- a/engines/bladerunner/actor_clues.cpp +++ b/engines/bladerunner/actor_clues.cpp @@ -23,6 +23,7 @@ #include "bladerunner/actor_clues.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/game_constants.h" #include "bladerunner/game_info.h" #include "bladerunner/crimes_database.h" diff --git a/engines/bladerunner/actor_walk.cpp b/engines/bladerunner/actor_walk.cpp index 70624221f5..fccc8e22b3 100644 --- a/engines/bladerunner/actor_walk.cpp +++ b/engines/bladerunner/actor_walk.cpp @@ -25,6 +25,7 @@ #include "bladerunner/bladerunner.h" #include "bladerunner/actor.h" +#include "bladerunner/game_constants.h" #include "bladerunner/game_info.h" #include "bladerunner/obstacles.h" #include "bladerunner/scene.h" diff --git a/engines/bladerunner/archive.cpp b/engines/bladerunner/archive.cpp index 8088e1ac67..6468acf56e 100644 --- a/engines/bladerunner/archive.cpp +++ b/engines/bladerunner/archive.cpp @@ -22,6 +22,8 @@ #include "bladerunner/archive.h" +#include "bladerunner/game_constants.h" + #include "common/debug.h" namespace BladeRunner { diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp index 266a8f9c6b..efc8a4f7e7 100644 --- a/engines/bladerunner/bladerunner.cpp +++ b/engines/bladerunner/bladerunner.cpp @@ -61,6 +61,7 @@ #include "bladerunner/suspects_database.h" #include "bladerunner/text_resource.h" #include "bladerunner/ui/elevator.h" +#include "bladerunner/ui/esper.h" #include "bladerunner/ui/kia.h" #include "bladerunner/ui/spinner.h" #include "bladerunner/vqa_decoder.h" @@ -219,8 +220,6 @@ bool BladeRunnerEngine::startup(bool hasSavegames) { _actorDialogueQueue = new ActorDialogueQueue(this); - // TODO: esper script - _settings = new Settings(this); _itemPickup = new ItemPickup(this); @@ -392,11 +391,11 @@ bool BladeRunnerEngine::startup(bool hasSavegames) { for (int i = 0; i != 43; ++i) { Shape *shape = new Shape(this); - shape->readFromContainer("SHAPES.SHP", i); + shape->open("SHAPES.SHP", i); _shapes.push_back(shape); } - // TODO: Esper + _esper = new ESPER(this); // TODO: VK @@ -467,7 +466,8 @@ void BladeRunnerEngine::shutdown() { // TODO: Shutdown VK - // TODO: Shutdown Esper + delete _esper; + _esper = nullptr; delete _mouse; _mouse = nullptr; @@ -736,7 +736,7 @@ void BladeRunnerEngine::gameTick() { //probably not needed, this version of tick is just loading data from buffer //_audioMixer->tick(); - if (_kia->_currentSectionId) { + if (_kia->isOpen()) { _kia->tick(); return; } @@ -747,7 +747,11 @@ void BladeRunnerEngine::gameTick() { return; } - // TODO: Esper + if (_esper->isOpen()) { + _esper->tick(); + return; + } + // TODO: VK if (_elevator->isOpen()) { @@ -1031,32 +1035,101 @@ void BladeRunnerEngine::handleKeyUp(Common::Event &event) { _speechSkipped = true; } - // TODO(peterkohaut): + // TODO: if (!playerHasControl() /*|| ActorInWalkingLoop*/) { return; } - if (_kia->_currentSectionId) { + if (_kia->isOpen()) { _kia->handleKeyUp(event.kbd); return; } - if (event.kbd.keycode == Common::KEYCODE_TAB) { - _kia->openLastOpened(); - } else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) { - _kia->openOptions(); - } else if (event.kbd.keycode == Common::KEYCODE_SPACE) { - // TODO(peterkohaut): - // combat::switchCombatMode(&Combat); + if (_spinner->isOpen()) { + return; + } + + if (_elevator->isOpen()) { + return; + } + + if (_esper->isOpen()) { + return; + } + + if (_dialogueMenu->isOpen()) { + return; + } + + //TODO: scores + switch (event.kbd.keycode) { + case Common::KEYCODE_TAB: + _kia->openLastOpened(); + break; + case Common::KEYCODE_ESCAPE: + _kia->open(kKIASectionSettings); + break; + case Common::KEYCODE_SPACE: + // TODO: combat::switchCombatMode(&Combat); + break; + default: + break; } } void BladeRunnerEngine::handleKeyDown(Common::Event &event) { - // if ( PlayerHasControl <= 0 && ActorWalkingLoop != 1 && PlayingSpeechLine != 1 && VqaIsPlaying != 1 ) { - if (_kia->_currentSectionId) { + //TODO: + if (!playerHasControl() /* || ActorWalkingLoop || PlayingSpeechLine || VqaIsPlaying */) { + return; + } + + if (_kia->isOpen()) { _kia->handleKeyDown(event.kbd); } - // } + + if (_spinner->isOpen()) { + return; + } + + if (_elevator->isOpen()) { + return; + } + + if (_esper->isOpen()) { + return; + } + + if (_dialogueMenu->isOpen()) { + return; + } + + //TODO: scores + + switch (event.kbd.keycode) { + case Common::KEYCODE_F1: + _kia->open(kKIASectionHelp); + break; + case Common::KEYCODE_F2: + _kia->open(kKIASectionSave); + break; + case Common::KEYCODE_F3: + _kia->open(kKIASectionLoad); + break; + case Common::KEYCODE_F4: + _kia->open(kKIASectionCrimes); + break; + case Common::KEYCODE_F5: + _kia->open(kKIASectionSuspects); + break; + case Common::KEYCODE_F6: + _kia->open(kKIASectionClues); + break; + case Common::KEYCODE_F10: + _kia->open(kKIASectionQuit); + break; + default: + break; + } } void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool buttonDown) { @@ -1064,7 +1137,7 @@ void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool bu return; } - if (_kia->_currentSectionId) { + if (_kia->isOpen()) { if (buttonDown) { _kia->handleMouseDown(x, y, buttonLeft); } else { @@ -1082,6 +1155,15 @@ void BladeRunnerEngine::handleMouseAction(int x, int y, bool buttonLeft, bool bu return; } + if (_esper->isOpen()) { + if (buttonDown) { + _esper->handleMouseDown(x, y, buttonLeft); + } else { + _esper->handleMouseUp(x, y, buttonLeft); + } + return; + } + if (_elevator->isOpen()) { if (buttonDown) { _elevator->handleMouseDown(x, y); diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h index 593f414bc9..ba47f2c15e 100644 --- a/engines/bladerunner/bladerunner.h +++ b/engines/bladerunner/bladerunner.h @@ -33,11 +33,6 @@ #include "graphics/surface.h" -// remove these when game is playable -#define BLADERUNNER_DEBUG_RENDERING 0 -#define BLADERUNNER_DEBUG_CONSOLE 0 -#define BLADERUNNER_DEBUG_GAME 0 - namespace Common { struct Event; } @@ -46,22 +41,6 @@ struct ADGameDescription; namespace BladeRunner { -enum AnimationModes { - kAnimationModeIdle = 0, - kAnimationModeWalk = 1, - kAnimationModeRun = 2, - kAnimationModeCombatIdle = 4, - kAnimationModeCombatWalk = 7, - kAnimationModeCombatRun = 8 -}; - -enum SceneLoopMode { - kSceneLoopModeLoseControl = 0, - kSceneLoopModeChangeSet = 1, - kSceneLoopMode2 = 2, - kSceneLoopModeSpinner = 3 -}; - class Actor; class ActorDialogueQueue; class ScreenEffects; @@ -75,6 +54,7 @@ class CrimesDatabase; class Combat; class DialogueMenu; class Elevator; +class ESPER; class Font; class GameFlags; class GameInfo; @@ -125,6 +105,7 @@ public: Combat *_combat; DialogueMenu *_dialogueMenu; Elevator *_elevator; + ESPER *_esper; GameFlags *_gameFlags; GameInfo *_gameInfo; ItemPickup *_itemPickup; diff --git a/engines/bladerunner/combat.cpp b/engines/bladerunner/combat.cpp index 79491e702a..26b8b5d785 100644 --- a/engines/bladerunner/combat.cpp +++ b/engines/bladerunner/combat.cpp @@ -25,6 +25,7 @@ #include "bladerunner/actor.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/game_constants.h" #include "bladerunner/settings.h" namespace BladeRunner { diff --git a/engines/bladerunner/dialogue_menu.cpp b/engines/bladerunner/dialogue_menu.cpp index d23d8e0d25..9f43f5542a 100644 --- a/engines/bladerunner/dialogue_menu.cpp +++ b/engines/bladerunner/dialogue_menu.cpp @@ -42,7 +42,7 @@ DialogueMenu::DialogueMenu(BladeRunnerEngine *vm) { _shapes.reserve(8); for (int i = 0; i != 8; ++i) { _shapes.push_back(Shape(_vm)); - bool r = _shapes[i].readFromContainer("DIALOG.SHP", i); + bool r = _shapes[i].open("DIALOG.SHP", i); assert(r); (void)r; } diff --git a/engines/bladerunner/game_constants.h b/engines/bladerunner/game_constants.h index 9915bc9e1d..bf7e10113e 100644 --- a/engines/bladerunner/game_constants.h +++ b/engines/bladerunner/game_constants.h @@ -23,6 +23,11 @@ #ifndef BLADERUNNER_GAME_CONSTANTS_H #define BLADERUNNER_GAME_CONSTANTS_H +//TODO: remove these when game is playable +#define BLADERUNNER_DEBUG_RENDERING 0 +#define BLADERUNNER_DEBUG_CONSOLE 0 +#define BLADERUNNER_DEBUG_GAME 0 + namespace BladeRunner { enum Actors { @@ -471,6 +476,23 @@ enum Outtakes { kOuttakeBladeRunner = 41 }; +enum AnimationModes { + kAnimationModeIdle = 0, + kAnimationModeWalk = 1, + kAnimationModeRun = 2, + kAnimationModeCombatIdle = 4, + kAnimationModeCombatWalk = 7, + kAnimationModeCombatRun = 8 +}; + +enum SceneLoopMode { + kSceneLoopModeLoseControl = 0, + kSceneLoopModeChangeSet = 1, + kSceneLoopMode2 = 2, + kSceneLoopModeSpinner = 3 +}; + + } // End of namespace BladeRunner #endif diff --git a/engines/bladerunner/game_flags.cpp b/engines/bladerunner/game_flags.cpp index 81fe6a0a4f..f6ae7b198a 100644 --- a/engines/bladerunner/game_flags.cpp +++ b/engines/bladerunner/game_flags.cpp @@ -22,6 +22,8 @@ #include "bladerunner/game_flags.h" +#include "bladerunner/game_constants.h" + #include "common/debug.h" namespace BladeRunner { diff --git a/engines/bladerunner/image.cpp b/engines/bladerunner/image.cpp index cc11e5b956..925b0269e3 100644 --- a/engines/bladerunner/image.cpp +++ b/engines/bladerunner/image.cpp @@ -23,7 +23,6 @@ #include "bladerunner/image.h" #include "bladerunner/bladerunner.h" - #include "bladerunner/decompress_lcw.h" #include "common/rect.h" diff --git a/engines/bladerunner/light.h b/engines/bladerunner/light.h index 8ad86368ea..03be06499f 100644 --- a/engines/bladerunner/light.h +++ b/engines/bladerunner/light.h @@ -37,9 +37,7 @@ namespace BladeRunner { class Lights; class Light { -#if BLADERUNNER_DEBUG_RENDERING friend class BladeRunnerEngine; -#endif friend class Lights; friend class SliceRenderer; diff --git a/engines/bladerunner/lights.h b/engines/bladerunner/lights.h index 904a96f425..ee89fbb5be 100644 --- a/engines/bladerunner/lights.h +++ b/engines/bladerunner/lights.h @@ -32,9 +32,7 @@ namespace BladeRunner { class Lights { -#if BLADERUNNER_DEBUG_RENDERING friend class BladeRunnerEngine; -#endif friend class SliceRendererLights; BladeRunnerEngine *_vm; diff --git a/engines/bladerunner/module.mk b/engines/bladerunner/module.mk index 51251eea02..3b461d5a64 100644 --- a/engines/bladerunner/module.mk +++ b/engines/bladerunner/module.mk @@ -176,6 +176,7 @@ MODULE_OBJS = \ suspects_database.o \ text_resource.o \ ui/elevator.o \ + ui/esper.o \ ui/kia.o \ ui/kia_log.o \ ui/kia_section_base.o \ diff --git a/engines/bladerunner/scene_objects.h b/engines/bladerunner/scene_objects.h index b873980d05..f6aa528958 100644 --- a/engines/bladerunner/scene_objects.h +++ b/engines/bladerunner/scene_objects.h @@ -46,9 +46,7 @@ enum SceneObjectOffset { }; class SceneObjects { -#if BLADERUNNER_DEBUG_RENDERING friend class BladeRunnerEngine; -#endif static const int kSceneObjectCount = 115; struct SceneObject { diff --git a/engines/bladerunner/script/esper.cpp b/engines/bladerunner/script/esper.cpp index ca779d6e3d..e6c0390cbb 100644 --- a/engines/bladerunner/script/esper.cpp +++ b/engines/bladerunner/script/esper.cpp @@ -23,9 +23,28 @@ #include "bladerunner/script/esper.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/mouse.h" namespace BladeRunner { +void ESPERScript::initialize() { + _vm->_mouse->disable(); + SCRIPT_ESPER_DLL_Initialize(); + _vm->_mouse->enable(); +} + +void ESPERScript::photoSelected(int photoId) { + _vm->_mouse->disable(); + SCRIPT_ESPER_DLL_Photo_Selected(photoId); + _vm->_mouse->enable(); +} + +void ESPERScript::specialRegionSelected(int photoId, int regionId) { + _vm->_mouse->disable(); + SCRIPT_ESPER_DLL_Special_Region_Selected(photoId, regionId); + _vm->_mouse->enable(); +} + void ESPERScript::SCRIPT_ESPER_DLL_Initialize() { int v0 = 0; if (Actor_Clue_Query(kActorMcCoy, kClueRuncitersVideo)) { @@ -39,7 +58,7 @@ void ESPERScript::SCRIPT_ESPER_DLL_Initialize() { if (!Actor_Clue_Query(kActorMcCoy, kClueRuncitersViewB)) { Actor_Clue_Acquire(kActorMcCoy, kClueRuncitersViewB, 1, kActorRunciter); } - ESPER_Add_Photo("RC02_FA.IMG", 1, 1); + ESPER_Add_Photo("RC02_RA.IMG", 1, 1); } if (Actor_Clue_Query(kActorMcCoy, kClueEarlyQsClub)) { if (!Actor_Clue_Query(kActorMcCoy, kClueOuterDressingRoom)) { diff --git a/engines/bladerunner/script/esper.h b/engines/bladerunner/script/esper.h index 683ecf6998..63e30e55e0 100644 --- a/engines/bladerunner/script/esper.h +++ b/engines/bladerunner/script/esper.h @@ -31,10 +31,13 @@ class BladeRunnerEngine; class ESPERScript : ScriptBase { public: - ESPERScript(BladeRunnerEngine *vm) - : ScriptBase(vm) { - } + ESPERScript(BladeRunnerEngine *vm) : ScriptBase(vm) {} + void initialize(); + void photoSelected(int photoId); + void specialRegionSelected(int photoId, int regionId); + +private: void SCRIPT_ESPER_DLL_Initialize(); void SCRIPT_ESPER_DLL_Photo_Selected(int photo); bool SCRIPT_ESPER_DLL_Special_Region_Selected(int photo, int region); diff --git a/engines/bladerunner/script/scene/rc01.cpp b/engines/bladerunner/script/scene/rc01.cpp index dd793ad440..347f0e8dcb 100644 --- a/engines/bladerunner/script/scene/rc01.cpp +++ b/engines/bladerunner/script/scene/rc01.cpp @@ -30,8 +30,9 @@ void SceneScriptRC01::InitializeScene() { Game_Flag_Set(kFlagIntroPlayed); // force skip intro Game_Flag_Set(kFlagRC02toRC01); // no landing // Game_Flag_Set(kFlagRC01PoliceDone); - // Game_Flag_Set(249); - Game_Flag_Set(kFlagKIAPrivacyAddon); + // Game_Flag_Set(kFlagKIAPrivacyAddon); + + // ESPER_Flag_To_Activate(); #endif if (!Game_Flag_Query(kFlagIntroPlayed)) { diff --git a/engines/bladerunner/script/script.cpp b/engines/bladerunner/script/script.cpp index 91a755bb41..3c0c8e1866 100644 --- a/engines/bladerunner/script/script.cpp +++ b/engines/bladerunner/script/script.cpp @@ -50,6 +50,7 @@ #include "bladerunner/suspects_database.h" #include "bladerunner/text_resource.h" #include "bladerunner/ui/elevator.h" +#include "bladerunner/ui/esper.h" #include "bladerunner/ui/kia.h" #include "bladerunner/ui/spinner.h" #include "bladerunner/vector.h" @@ -1076,8 +1077,12 @@ int ScriptBase::Spinner_Interface_Choose_Dest(int loopId, bool immediately) { } void ScriptBase::ESPER_Flag_To_Activate() { - //TODO - warning("ESPER_Flag_To_Activate()"); + if (!_vm->_esper->isOpen()) { + _vm->_esper->open(&_vm->_surfaceBack); + while (_vm->_esper->isOpen()) { + _vm->gameTick(); + } + } } bool ScriptBase::Voight_Kampff_Activate(int a1, int a2){ @@ -1282,14 +1287,12 @@ void ScriptBase::KIA_Play_Photograph(int photographId) { _vm->_kia->playPhotograph(photographId); } -void ScriptBase::ESPER_Add_Photo(const char *fileName, int a2, int a3) { - //TODO - warning("ESPER_Add_Photo(%s, %d, %d)", fileName, a2, a3); +void ScriptBase::ESPER_Add_Photo(const char *name, int photoId, int shapeId) { + _vm->_esper->addPhoto(name, photoId, shapeId); } -void ScriptBase::ESPER_Define_Special_Region(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, const char *name) { - //TODO - warning("ESPER_Define_Special_Region(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %s)", a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, name); +void ScriptBase::ESPER_Define_Special_Region(int regionId, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, const char *name) { + _vm->_esper->defineRegion(regionId, Common::Rect(a2, a3, a4, a5), Common::Rect(a6, a7, a8, a9), Common::Rect(a10, a11, a12, a13), name); } void ScriptBase::VK_Add_Question(int a1, int a2, int a3) { diff --git a/engines/bladerunner/script/script.h b/engines/bladerunner/script/script.h index dabdec6b59..574f3a8f65 100644 --- a/engines/bladerunner/script/script.h +++ b/engines/bladerunner/script/script.h @@ -272,7 +272,7 @@ protected: void AI_Movement_Track_Append(int actorId, int waypointId, int delay); void AI_Movement_Track_Flush(int actorId); - void ESPER_Add_Photo(const char *fileName, int a2, int a3); + void ESPER_Add_Photo(const char *name, int photoId, int shapeId); void ESPER_Define_Special_Region(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13, const char *name); void KIA_Play_Actor_Dialogue(int actorId, int sentenceId); diff --git a/engines/bladerunner/set.h b/engines/bladerunner/set.h index 17d06263b9..d888bff697 100644 --- a/engines/bladerunner/set.h +++ b/engines/bladerunner/set.h @@ -37,9 +37,8 @@ class SetEffects; class SceneObjects; class Set { -#if BLADERUNNER_DEBUG_RENDERING friend class BladeRunnerEngine; -#endif + struct Object { char name[20]; BoundingBox bbox; diff --git a/engines/bladerunner/shape.cpp b/engines/bladerunner/shape.cpp index f5a68045da..45843447ff 100644 --- a/engines/bladerunner/shape.cpp +++ b/engines/bladerunner/shape.cpp @@ -43,16 +43,16 @@ Shape::~Shape() { delete[] _data; } -bool Shape::readFromContainer(const Common::String &container, int index) { +bool Shape::open(const Common::String &container, int index) { Common::ScopedPtr<Common::SeekableReadStream> stream(_vm->getResourceStream(container)); if (!stream) { - debug("Shape::readFromContainer failed to open '%s'", container.c_str()); + debug("Shape::open failed to open '%s'", container.c_str()); return false; } uint32 count = stream->readUint32LE(); if (index < 0 || (uint32)index >= count) { - debug("Shape::readFromContainer invalid index %d (count %u)", index, count); + debug("Shape::open invalid index %d (count %u)", index, count); return false; } @@ -63,7 +63,7 @@ bool Shape::readFromContainer(const Common::String &container, int index) { size = stream->readUint32LE(); if (size != width * height * 2) { - debug("Shape::readFromContainer size mismatch (w %d, h %d, sz %d)", width, height, size); + debug("Shape::open size mismatch (w %d, h %d, sz %d)", width, height, size); return false; } @@ -74,7 +74,7 @@ bool Shape::readFromContainer(const Common::String &container, int index) { // Enfoce a reasonable size limit if (width >= 2048 || height >= 2048) { - warning("Shape::readFromContainer shape too big (%d, %d)", width, height); + warning("Shape::open shape too big (%d, %d)", width, height); } _width = width; @@ -82,7 +82,7 @@ bool Shape::readFromContainer(const Common::String &container, int index) { _data = new byte[size]; if (stream->read(_data, size) != size) { - debug("Shape::readFromContainer error reading shape %d (w %d, h %d, sz %d)", index, width, height, size); + debug("Shape::open error reading shape %d (w %d, h %d, sz %d)", index, width, height, size); return false; } diff --git a/engines/bladerunner/shape.h b/engines/bladerunner/shape.h index 799826cb4b..fa286e590d 100644 --- a/engines/bladerunner/shape.h +++ b/engines/bladerunner/shape.h @@ -44,7 +44,7 @@ public: Shape(BladeRunnerEngine *vm); ~Shape(); - bool readFromContainer(const Common::String &container, int index); + bool open(const Common::String &container, int index); void draw(Graphics::Surface &surface, int x, int y) const; diff --git a/engines/bladerunner/text_resource.cpp b/engines/bladerunner/text_resource.cpp index 42d30038bc..ec5706e234 100644 --- a/engines/bladerunner/text_resource.cpp +++ b/engines/bladerunner/text_resource.cpp @@ -20,10 +20,10 @@ * */ - #include "bladerunner/text_resource.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/game_constants.h" #include "common/debug.h" #include "common/util.h" diff --git a/engines/bladerunner/ui/elevator.cpp b/engines/bladerunner/ui/elevator.cpp index 1ee4115fab..4c72be2345 100644 --- a/engines/bladerunner/ui/elevator.cpp +++ b/engines/bladerunner/ui/elevator.cpp @@ -76,7 +76,7 @@ int Elevator::activate(int elevatorId) { for (int i = 0; i != 16; ++i) { _shapes.push_back(new Shape(_vm)); - _shapes[i]->readFromContainer("ELEVATOR.SHP", i); + _shapes[i]->open("ELEVATOR.SHP", i); } _imagePicker->resetImages(); diff --git a/engines/bladerunner/ui/esper.cpp b/engines/bladerunner/ui/esper.cpp new file mode 100644 index 0000000000..ea1df6bd38 --- /dev/null +++ b/engines/bladerunner/ui/esper.cpp @@ -0,0 +1,1791 @@ +/* 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 "bladerunner/ui/esper.h" + +#include "bladerunner/actor.h" +#include "bladerunner/ambient_sounds.h" +#include "bladerunner/audio_player.h" +#include "bladerunner/bladerunner.h" +#include "bladerunner/decompress_lcw.h" +#include "bladerunner/font.h" +#include "bladerunner/game_info.h" +#include "bladerunner/game_constants.h" +#include "bladerunner/mouse.h" +#include "bladerunner/shape.h" +#include "bladerunner/script/esper.h" +#include "bladerunner/text_resource.h" +#include "bladerunner/ui/ui_image_picker.h" +#include "bladerunner/vqa_player.h" + +#include "common/rect.h" +#include "common/str.h" + +#include "graphics/surface.h" + +namespace BladeRunner { + +const Common::Rect ESPER::kScreen = Common::Rect(135, 123, 435, 387); + +ESPER::ESPER(BladeRunnerEngine *vm) { + _vm = vm; + + _isWaiting = false; + _shapeButton = nullptr; + _shapeThumbnail = nullptr; + _regionSelectedAck = false; + _isDrawingSelection = false; + + _isOpen = false; + _photoData = nullptr; + _viewportData = nullptr; + _shapeButton = nullptr; + _shapeThumbnail = nullptr; + _vqaMainPlayer = nullptr; + _vqaPhotoPlayer = nullptr; + _script = nullptr; + + reset(); + _buttons = new UIImagePicker(vm, 16); +} + +ESPER::~ESPER() { + delete _buttons; + reset(); +} + +void ESPER::open(Graphics::Surface *surface) { + // CD-changing logic has been removed + + while (!_vm->playerHasControl()) { + _vm->playerGainsControl(); + } + + while (_vm->_mouse->isDisabled()) { + _vm->_mouse->enable(); + } + + //TODO: time->lock() + _ambientVolume = _vm->_ambientSounds->getVolume(); + _vm->_ambientSounds->setVolume(_ambientVolume / 2); + + reset(); + + if (!_vm->openArchive("MODE.MIX")) { + return; + } + + _photoData = new Graphics::Surface(); + _photoData->create(kPhotoWidth, kPhotoHeight, createRGB555()); + + _viewportData = new Graphics::Surface(); + _viewportData->create(kScreen.width(), kScreen.height(), createRGB555()); + + _viewportNext = _viewport; + + _vm->_mainFont->setColor(0x001F); + + _shapeButton = new Shape(_vm); + if (!_shapeButton->open("ESPBUTTN.SHP", 0)) { + return; + } + + _shapesPhotos.resize(10); + + _vqaMainPlayer = new VQAPlayer(_vm, &_vm->_surfaceBack); + if (!_vqaMainPlayer->open("ESPER.VQA")) { + return; + } + _vqaMainPlayer->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(425), 25, 0, 0, 50, 0); + + unloadPhotos(); + _shapesPhotos.clear(); + + delete _shapeThumbnail; + _shapeThumbnail = nullptr; + + _buttons->deactivate(); + _buttons->resetImages(); + + delete _shapeButton; + _shapeButton = nullptr; + + delete _photoData; + _photoData = nullptr; + + delete _viewportData; + _viewportData = nullptr; + + if (_vqaMainPlayer) { + _vqaMainPlayer->close(); + delete _vqaMainPlayer; + _vqaMainPlayer= nullptr; + } + + _vm->closeArchive("MODE.MIX"); + + //TODO: time->unlock() + _vm->_ambientSounds->setVolume(_ambientVolume); + //TODO: _vm->_scene->resume(false); + reset(); +} + +bool ESPER::isOpen() { + 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 (kScreen.contains(x, y)) { + _isMouseDown = true; + playSound(460, 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); + tickMouse(_vm->_surfaceFront); + tickSound(); + + _vm->blitToScreen(_vm->_surfaceFront); + + // TODO: implement 60hz lock for smoother experience + _vm->_system->delayMillis(10); + + if (_statePhoto == kEsperPhotoStateVideoShow) { + if (_regionSelectedAck) { + _regionSelectedAck = false; + _script->specialRegionSelected(_photoIdSelected, _regions[_regionSelected].regionId); + } + } +} + +void ESPER::resume() { + //TODO +} + +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; + strcpy(_photos[i].name, name); + + assert((uint)shapeId < _shapesPhotos.size()); + _shapesPhotos[shapeId] = new Shape(_vm); + _shapesPhotos[shapeId]->open("ESPTHUMB.SHP", shapeId); + + _buttons->defineImage(i, + Common::Rect( + 100 * (i % 3) + kScreen.left + 3, + 66 * (i / 3) + kScreen.top + 3, + 100 * (i % 3) + kScreen.left + 100 - 3, + 66 * (i / 3) + kScreen.top + 66 - 3 + ), + _shapesPhotos[shapeId], + _shapesPhotos[shapeId], + _shapesPhotos[shapeId], + nullptr); + } + playSound(420, 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; + strncpy(_regions[i].name, name, 13); + _regions[i].name[13] = 0; + } +} + +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 + 1) { + // TODO: is it even used? + } else if (buttonId == kPhotoCount + 2) { + self->zoomOutStop(); + } else if (buttonId == kPhotoCount + 3) { + self->goBack(); + } + } +} + +void ESPER::reset() { + delete _photoData; + _photoData = nullptr; + + delete _viewportData; + _viewportData = nullptr; + + delete _shapeButton; + _shapeButton = nullptr; + + delete _shapeThumbnail; + _shapeThumbnail = nullptr; + + delete _vqaMainPlayer; + _vqaMainPlayer = nullptr; + + delete _vqaPhotoPlayer; + _vqaPhotoPlayer = nullptr; + + delete _script; + _script = nullptr; + + _isOpen = false; + + _shapesPhotos.clear(); + resetData(); +} + +void ESPER::resetData() { + if (_vqaPhotoPlayer) { + _vqaPhotoPlayer->close(); + delete _vqaPhotoPlayer; + _vqaPhotoPlayer = nullptr; + } + if (_shapeThumbnail) { + delete _shapeThumbnail; + _shapeThumbnail = nullptr; + } + + _viewport.left = 0; + _viewport.top = 0; + + _regionSelectedAck = false; + _mouseOverScroll = -1; + _isMouseDown = 0; + _viewportNext.left = 0; + _viewportNext.top = 0; + + _selectionRect.left = -1; + _selectionRect.top = -1; + _selectionRect.right = -1; + _selectionRect.bottom = -1; + + _selectionCrosshairX = -1; + _selectionCrosshairY = -1; + + _stateMain = kEsperMainStatePhoto; + _statePhoto = kEsperPhotoStateShow; + + _isDrawingSelection = false; + _flash = false; + _isScrolling = false; + + _scrollingDirection = -1; + _timeScrollNext = 0; + + 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)(kScreen.width()) / (float)kPhotoWidth; + _zoomVertical = (float)(kScreen.height()) / (float)kPhotoHeight; + _zoom = _zoomVertical; + _zoomMin = _zoom; + + _timeZoomOutNext = 0; + _viewportPositionX = kPhotoWidth / 2; + _viewportPositionY = kPhotoHeight / 2; + + updateViewport(); + + _screenHalfWidth = kScreen.width() / 2; + _screenHalfHeight = kScreen.height() / 2; +} + +void ESPER::resetSelectionRect() { + _selectionRect = kScreen; + _selectionCrosshairX = -1; + _selectionCrosshairY = -1; +} + +void ESPER::resetSelectionBlinking() { + _selectionBlinkingCounter = 0; + _selectionBlinkingStyle = 0; + _timeSelectionBlinkingNext = 0; +} + +void ESPER::resetPhotoZooming() { + _zoomStep = 0; + _timeZoomNext = 0; +} + +void ESPER::resetPhotoOpening() { + _photoOpeningWidth = kScreen.left + 1; + _photoOpeningHeight = kScreen.top + 1; + _timePhotoOpeningNext = 0; +} + +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 v50 = _zoom / _zoomHorizontal * 1.0f; + if ((_viewportPositionX > centerX + v50) || (_viewportPositionX < centerX - v50)) { + _viewportPositionX = centerX; + } + + float v51 = _zoom / _zoomVertical * 1.0f; + if ((_viewportPositionY > centerY + v51) || (_viewportPositionY < centerY - v51)) { + _viewportPositionY = centerY; + } +} + +void ESPER::activate(bool withOpening) { + _vm->_mouse->disable(); + + _buttons->resetImages(); + + if (withOpening) { + setStateMain(kEsperMainStateOpening); + playSound(413, 25); + wait(1000); + playSound(414, 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, _shapeButton, nullptr); + + playSound(415, 25); + wait(1000); + + setStateMain(kEsperMainStateList); + resetPhotos(); + _script->initialize(); + + _vm->_mouse->enable(); +} + +void ESPER::setStateMain(EsperMainStates state) { + if (_isOpen) { + _stateMain = state; + debug("ESPER main state: %d", _stateMain); + + } +} + +void ESPER::setStatePhoto(EsperPhotoStates state) { + _statePhoto = state; + debug("ESPER photo state: %d", _statePhoto); +} + +void ESPER::wait(int timeout) { + if (!_isWaiting) { + _isWaiting = true; + uint timeEnd = timeout + _vm->getTotalPlayTime(); + while (_vm->getTotalPlayTime() < timeEnd) { + _vm->gameTick(); + } + _isWaiting = false; + } +} + +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; + } + _vqaMainPlayer->update(false); + switch (_stateMain) { + case kEsperMainStateOpening: + case kEsperMainStateList: + return; + case kEsperMainStatePhotoOpening: + drawPhotoOpening(surface); + break; + case kEsperMainStateClear: + surface.fillRect(kScreen, 0x0000); + break; + case kEsperMainStatePhoto: + if (_isScrolling) { + tickScroll(); + } + switch (_statePhoto) { + case kEsperPhotoStateShow: + drawPhotoWithGrid(surface); + if (_isDrawingSelection) { + drawSelection(surface, true, 1); + } +#if BLADERUNNER_DEBUG_RENDERING + 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) + ), + 0x7FE0 + ); + surface.frameRect( + Common::Rect( + viewportXToScreenX(_regions[i].rectOuter.left), + viewportYToScreenY(_regions[i].rectOuter.top), + viewportXToScreenX(_regions[i].rectOuter.right), + viewportYToScreenY(_regions[i].rectOuter.bottom) + ), + 0x7FE0 + ); + } + } +#endif + break; + case kEsperPhotoStateScrolling: + scrollUpdate(); + drawPhotoWithGrid(surface); + break; + case kEsperPhotoStateSelectionZooming: + drawPhotoWithGrid(surface); + if (!drawSelectionZooming(surface)) { + setStatePhoto(kEsperPhotoStateSelectionBlinking); + playSound(418, 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; + int timeNow = _vm->getTotalPlayTime(); + if (timeNow >= _timePhotoOpeningNext) { + _photoOpeningWidth = MIN(_photoOpeningWidth + 8, kScreen.right - 1); + _photoOpeningHeight = MIN(_photoOpeningHeight + 7, kScreen.bottom - 1); + + if (_photoOpeningWidth == kScreen.right - 1 && _photoOpeningHeight == kScreen.bottom - 1) { + needMoreZooming = false; + } + + _timePhotoOpeningNext = timeNow + 20; + } + copyImageScale(_photoData, _viewport, &surface, Common::Rect(kScreen.left, kScreen.top, _photoOpeningWidth, _photoOpeningHeight)); + + surface.hLine(kScreen.left, _photoOpeningHeight, kScreen.right - 1, 0x03E0); + surface.vLine(_photoOpeningWidth, kScreen.top, kScreen.bottom - 1, 0x03E0); + surface.hLine(kScreen.left, _photoOpeningHeight - 1, kScreen.right - 1, 0x0240); + surface.vLine(_photoOpeningWidth - 1, kScreen.top, kScreen.bottom - 1, 0x0240); + + drawGrid(surface); + + if (!needMoreZooming) { + setStateMain(kEsperMainStatePhoto); + setStatePhoto(kEsperPhotoStateShow); + _vm->_mouse->enable(); + } +} + +bool ESPER::drawSelectionZooming(Graphics::Surface &surface) { + bool zooming = false; + bool needMoreZooming = true; + int timeNow = _vm->getTotalPlayTime(); + if (timeNow > _timeSelectionZoomNext) { + zooming = true; + _selectionRect.left += _selectionRectDelta.left; + _selectionRect.top += _selectionRectDelta.top; + _selectionRect.right += _selectionRectDelta.right; + _selectionRect.bottom += _selectionRectDelta.bottom; + ++_selectionZoomStep; + _timeSelectionZoomNext = timeNow + 150; + if (_selectionZoomStep > kSelectionZoomSteps) { + needMoreZooming = false; + _selectionRect.left = _selectionRectTarget.left; + _selectionRect.top = _selectionRectTarget.top; + _selectionRect.right = _selectionRectTarget.right; + _selectionRect.bottom = _selectionRectTarget.bottom; + } + } + drawSelection(surface, false, 1); + if (!needMoreZooming) { + _statePhoto = kEsperPhotoStatePhotoZooming; + resetPhotoZooming(); + zooming = false; + } + if (zooming) { + playSound(416, 20); + } + return needMoreZooming; +} + +bool ESPER::drawSelectionBlinking(Graphics::Surface &surface) { + bool needMoreBlinking = true; + int timeNow = _vm->getTotalPlayTime(); + if (timeNow > _timeSelectionBlinkingNext) { + _timeSelectionBlinkingNext = timeNow + 100; + _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) { + int timeNow = _vm->getTotalPlayTime(); + if ((timeNow > _timeZoomNext) && (_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(); + _timeZoomNext = timeNow + 300; + } + + if (_zoomDelta >= 0.0f) { + drawPhoto(surface); + } else { + drawPhotoWithGrid(surface); + } + drawGrid(surface); + + if ((timeNow > _timeZoomNext) && (_zoomStep >= _zoomSteps)) { + if (_regionSelectedAck) { + if (_regions[_regionSelected].name[0]) { + if (_zoomDelta < 0.0f) { + _blur = 1.0f; + _zoomDelta = (_zoom * 1.5f - _zoom) / (float)_zoomSteps; // 0.5f * _zoom ??? + } + setStatePhoto(kEsperPhotoStateVideoZooming); + _timeZoomNext += 300; + } else { + _regionSelectedAck = false; + _selectionRect.left = viewportXToScreenX(_regions[_regionSelected].rectInner.left); + _selectionRect.right = viewportXToScreenX(_regions[_regionSelected].rectInner.right); + _selectionRect.top = viewportYToScreenY(_regions[_regionSelected].rectInner.top); + _selectionRect.bottom = viewportYToScreenY(_regions[_regionSelected].rectInner.bottom); + prepareZoom(); + resetPhotoZooming(); + updateSelection(); + setStatePhoto(kEsperPhotoStatePhotoZooming); + } + } else { + setStatePhoto(kEsperPhotoStatePhotoSharpening); + } + resetPhotoOpening(); + } +} + +void ESPER::drawPhotoSharpening(Graphics::Surface &surface) { + int timeNow = _vm->getTotalPlayTime(); + bool needMoreSharpening = true; + if (timeNow >= _timePhotoOpeningNext) { + _photoOpeningWidth = MIN(_photoOpeningWidth + 8, kScreen.right - 1); + _photoOpeningHeight = MIN(_photoOpeningHeight + 7, kScreen.bottom - 1); + + if (_photoOpeningWidth == kScreen.right - 1 && _photoOpeningHeight == kScreen.bottom - 1) { + needMoreSharpening = false; + } + + _timePhotoOpeningNext = timeNow + 50; + } + + if (_regionSelectedAck && _regions[_regionSelected].name[0]) { + _vqaPhotoPlayer->update(true, false); + copyImageBlur(_viewportData, Common::Rect(0, 0, 299, 263), &surface, kScreen, _blur); + copyImageBlit(_viewportData, Common::Rect(0, 0, 0, 0), &surface, Common::Rect(kScreen.left, kScreen.top, _photoOpeningWidth, _photoOpeningHeight)); + } else { + drawPhoto(surface); + copyImageScale(_photoData, _viewport, _viewportData, Common::Rect(0, 0, kScreen.width(), kScreen.height())); + copyImageBlit(_viewportData, Common::Rect(0, 0, 0, 0), &surface, Common::Rect(kScreen.left, kScreen.top, _photoOpeningWidth, _photoOpeningHeight)); + + } + drawGrid(surface); + surface.hLine(kScreen.left, _photoOpeningHeight, kScreen.right - 1, 0x03E0); + surface.vLine(_photoOpeningWidth, kScreen.top, kScreen.bottom - 1, 0x03E0); + surface.hLine(kScreen.left, _photoOpeningHeight - 1, kScreen.right - 1, 0x0240); + surface.vLine(_photoOpeningWidth - 1, kScreen.top, kScreen.bottom - 1, 0x0240); + if (!needMoreSharpening) { + if (_regionSelectedAck && _regions[_regionSelected].name[0]){ + setStatePhoto(kEsperPhotoStateVideoShow); + } else { + setStatePhoto(kEsperPhotoStateShow); + } + resetPhotoZooming(); + resetPhotoOpening(); + _vm->_mouse->enable(); + } +} + +void ESPER::drawPhotoZoomOut(Graphics::Surface &surface) { + int timeNow = _vm->getTotalPlayTime(); + if (timeNow >= _timeZoomOutNext) { + _timeZoomOutNext = timeNow + 300; + + 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 (_vqaPhotoPlayer == nullptr) { + _vqaPhotoPlayer = new VQAPlayer(_vm, _viewportData); + if (!_vqaPhotoPlayer->open(Common::String(_regions[_regionSelected].name) + ".VQA")) { + setStatePhoto(kEsperPhotoStateShow); + _vm->_mouse->enable(); + + delete _vqaPhotoPlayer; + _vqaPhotoPlayer = nullptr; + + return; + } + + _timeZoomNext = 0; + } + + bool flash = false; + bool advanceFrame = false; + int timeNow = _vm->getTotalPlayTime(); + if (timeNow > _timeZoomNext) { + _timeZoomNext = timeNow + 300; + playSound(419, 25); + flash = true; + advanceFrame = true; + _blur += _zoomDelta * 5.0f; + } + + int frame = _vqaPhotoPlayer->update(true, advanceFrame); + if (frame == _vqaPhotoPlayer->getFrameCount() - 1) { + _vqaLastFrame = frame; + setStatePhoto(kEsperPhotoStatePhotoSharpening); + } + + if (flash) { + flashViewport(); + } + copyImageBlur(_viewportData, Common::Rect(0, 0, 299, 263), &surface, kScreen, _blur); + drawGrid(surface); +} + +void ESPER::drawVideoZoomOut(Graphics::Surface &surface) { + bool flash = false; + bool advanceFrame = false; + int timeNow = _vm->getTotalPlayTime(); + if (timeNow > _timeZoomNext && _vqaLastFrame > 0) { + _timeZoomNext = timeNow + 300; + playSound(419, 25); + //TODO: implement frame loading after seek, then advanceFrame can be removed + _vqaPhotoPlayer->seekToFrame(_vqaLastFrame); + int nextFrame = _vqaPhotoPlayer->getFrameCount() / 4; + if (nextFrame <= 0) { + nextFrame = 1; + } else if (nextFrame > 4) { + nextFrame = 4; + } + flash = true; + advanceFrame = true; + _vqaLastFrame -= nextFrame; + } + + _vqaPhotoPlayer->update(true, advanceFrame); + if (flash) { + flashViewport(); + } + copyImageBlit(_viewportData, Common::Rect(0, 0, 0, 0), &surface, kScreen); + drawGrid(surface); + if (timeNow > _timeZoomNext && _vqaLastFrame <= 0) { + _vqaPhotoPlayer->close(); + delete _vqaPhotoPlayer; + _vqaPhotoPlayer = nullptr; + + //TODO: there is code to stop zooming, but it is not working properly in original game + // if (_isMouseDown) { + // zoomOutStart(); + // } else { + // zoomOutStop(); + // } + zoomOutStart(); + } +} + +void ESPER::drawPhoto(Graphics::Surface &surface) { + copyImageBlur(_photoData, _viewport, &surface, kScreen, _blur); +} + +void ESPER::drawGrid(Graphics::Surface &surface) { + for (int i = 0; i < 7; ++i) { + surface.drawLine(kScreen.left + i * 50, kScreen.top, kScreen.left + i * 50, kScreen.bottom - 1, 0x109C); + } + + for (int i = 0; i < 7; ++i) { + surface.drawLine(kScreen.left, kScreen.top + i * 44, kScreen.right - 1, kScreen.top + i * 44, 0x109C); + } +} + +void ESPER::drawPhotoWithGrid(Graphics::Surface &surface) { + copyImageScale(_photoData, _viewport, &surface, kScreen); + drawGrid(surface); +} + +void ESPER::drawSelection(Graphics::Surface &surface, bool crosshair, int style) { + int left = CLIP(_selectionRect.left, kScreen.left, (int16)(kScreen.right - 1)); + int top = CLIP(_selectionRect.top, kScreen.top, (int16)(kScreen.bottom - 1)); + int right = CLIP(_selectionRect.right, kScreen.left, (int16)(kScreen.right - 1)); + int bottom = CLIP(_selectionRect.bottom, kScreen.top, (int16)(kScreen.bottom - 1)); + + int color = 0x0240; + if (style) { + color = 0x03E0; + } + + // 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 (_selectionRect.left < (kScreen.left + kScreen.right) / 2) { + _selectionCrosshairX = kScreen.left; + } else { + _selectionCrosshairX = kScreen.right - 1; + } + } + if (_selectionCrosshairY == -1) { + if (_selectionRect.top < (kScreen.top + kScreen.bottom) / 2) { + _selectionCrosshairY = kScreen.top; + } else { + _selectionCrosshairY = kScreen.bottom - 1; + } + } + + // ghosting + if (_selectionCrosshairX != right) { + surface.vLine(_selectionCrosshairX, kScreen.top, kScreen.bottom - 1, 0x0240); + if (abs(_selectionCrosshairX - right) <= 1) { + _selectionCrosshairX = right; + } else { + _selectionCrosshairX = (_selectionCrosshairX + right) / 2; + } + } + if (_selectionCrosshairY != bottom) { + surface.hLine(kScreen.left, _selectionCrosshairY, kScreen.right - 1, 0x0240); + if (abs(_selectionCrosshairY - bottom) <= 1) { + _selectionCrosshairY = bottom; + } else { + _selectionCrosshairY = (_selectionCrosshairY + bottom) / 2; + } + } + + surface.vLine(right, kScreen.top, kScreen.bottom - 1, 0x03E0); + surface.hLine(kScreen.left, bottom, kScreen.right - 1, 0x03E0); + } +} + +void ESPER::drawVideoFrame(Graphics::Surface &surface) { + _vqaPhotoPlayer->update(true, false); + copyImageBlit(_viewportData, Common::Rect(0, 0, 0, 0), &surface, kScreen); +} + +void ESPER::drawTextCoords(Graphics::Surface &surface) { + _vm->_mainFont->drawColor(Common::String::format("ZM %04.0f", _zoom / _zoomMin * 2.0f ), surface, 155, 364, 0x001F); + _vm->_mainFont->drawColor(Common::String::format("NS %04d", 12 * _viewport.top + 98 ), surface, 260, 364, 0x001F); + _vm->_mainFont->drawColor(Common::String::format("EW %04d", 12 * _viewport.left + 167), surface, 364, 364, 0x001F); +} + +void ESPER::flashViewport() { + uint16 *ptr = (uint16 *)_viewportData->getPixels(); + for (int i = 0; i < _viewportData->w * _viewportData->h; ++i) { + int8 r = (*ptr >> 10) & 0x1F; + int8 g = (*ptr >> 5) & 0x1F; + int8 b = (*ptr ) & 0x1F; + b = MIN(b * 2, 31); + *ptr = r << 10 | g << 5 | b; + + ++ptr; + } +} + +void ESPER::copyImageScale(Graphics::Surface *src, Common::Rect srcRect, Graphics::Surface *dst, Common::Rect dstRect) { + if (_flash) { + playSound(419, 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) { + uint16 *srcPtr = (uint16 *)src->getBasePtr(srcX, srcY); + uint16 *dstPtr = (uint16 *)dst->getBasePtr(dstX, dstY); + + if (_flash) { + int8 r = (*srcPtr >> 10) & 0x1F; + int8 g = (*srcPtr >> 5) & 0x1F; + int8 b = (*srcPtr ) & 0x1F; + // add blue-ish tint + b = MIN(b * 2, 31); + *dstPtr = r << 10 | g << 5 | b; + } else { + *dstPtr = *srcPtr; + } + + 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; + } + uint16 *srcPtr = (uint16 *)src->getBasePtr(srcX, srcY); + uint16 *dstPtr = (uint16 *)dst->getBasePtr(dstX, dstY); + + if (_flash) { + int8 r = (*srcPtr >> 10) & 0x1F; + int8 g = (*srcPtr >> 5) & 0x1F; + int8 b = (*srcPtr ) & 0x1F; + // add blue-ish tint + b = MIN(b * 2, 31); + *dstPtr = r << 10 | g << 5 | b; + } else { + *dstPtr = *srcPtr; + } + } + + 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(419, 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) { + uint16 *srcPtr = (uint16 *)src->getBasePtr(srcX, srcY); + uint16 *dstPtr = (uint16 *)dst->getBasePtr(dstX, dstY); + + if (_flash) { + int8 r = (*srcPtr >> 10) & 0x1F; + int8 g = (*srcPtr >> 5) & 0x1F; + int8 b = (*srcPtr ) & 0x1F; + // add blue-ish tint + b = MIN(b * 2, 31); + *dstPtr = r << 10 | g << 5 | b; + } else { + *dstPtr = *srcPtr; + } + + ++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 += 1; // bug in original game? Is using 1 instead of skipX as for Y + } + + uint16 *srcPtr = (uint16 *)src->getBasePtr(srcX, srcY); + uint16 *dstPtr = (uint16 *)dst->getBasePtr(dstX, dstY); + + if (_flash) { + int8 r = (*srcPtr >> 10) & 0x1F; + int8 g = (*srcPtr >> 5) & 0x1F; + int8 b = (*srcPtr ) & 0x1F; + // add blue-ish tint + b = MIN(b * 2, 31); + *dstPtr = r << 10 | g << 5 | b; + } else { + *dstPtr = *srcPtr; + } + + ++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) { + uint16 *srcPtr = (uint16 *)src->getBasePtr(srcRect.left + x, srcRect.top + y); + uint16 *dstPtr = (uint16 *)dst->getBasePtr(dstRect.left + x, dstRect.top + y); + *dstPtr = *srcPtr; + } + } +} + +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::tickMouse(Graphics::Surface &surface) { + if (_vm->_mouse->isDisabled()) { + return; + } + + int cursor = -1; + + Common::Point p = _vm->getMousePos(); + + _mouseOverScroll = 4; + if (_stateMain == kEsperMainStatePhoto) { + if (kScreen.contains(p)) { + if (_statePhoto == kEsperPhotoStateShow) { + if ( _zoom != 2.0f) { + if (_isMouseDown) { + if (_isDrawingSelection) { + _selectionRect.right = p.x; + _selectionRect.bottom = p.y; + } else { + _selectionRect.left = p.x; + _selectionRect.top = p.y; + _selectionRect.right = p.x + 1; + _selectionRect.bottom = p.y + 1; + _isDrawingSelection = true; + } + } else { + if (_isDrawingSelection) { + _selectionRect.right = p.x; + _selectionRect.bottom = p.y; + if (_selectionRect.right < _selectionRect.left) { + SWAP(_selectionRect.left, _selectionRect.right); + } + if (_selectionRect.bottom < _selectionRect.top) { + SWAP(_selectionRect.bottom, _selectionRect.top); + } + + if (_selectionRect.right >= _selectionRect.left + 3) { + updateSelection(); + _vm->_mouse->disable(); + zoomingStart(); + } else { + resetSelectionRect(); + } + } + _isDrawingSelection = false; + } + } + } + surface.vLine(p.x, p.y - 8, p.y - 1, 0x03E0); + surface.vLine(p.x, p.y + 8, p.y + 1, 0x03E0); + surface.hLine(p.x - 8, p.y, p.x - 1, 0x03E0); + surface.hLine(p.x + 8, p.y, p.x + 1, 0x03E0); + _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((kScreen.left + kScreen.right) / 2, (kScreen.top + kScreen.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::tickScroll() { + int timeNow = _vm->getTotalPlayTime(); + if (timeNow <= _timeScrollNext) { + return; + } + _timeScrollNext = timeNow + 300; + + 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<Common::SeekableReadStream> s(_vm->getResourceStream(_photos[photoId].name)); + + if (!s) { + reset(); + } + + int photoSize = _photoData->w * _photoData->h * _photoData->format.bytesPerPixel; + + s->skip(3); // not used, but there is compression type + uint width = s->readUint32LE(); + uint height = s->readUint32LE(); + int photoCompressedSize = s->size() - s->pos(); + uint8 *photoCompressed = (uint8 *)_photoData->getPixels() + photoSize - photoCompressedSize; + s->read(photoCompressed, photoCompressedSize); + + decompress_lcw(photoCompressed, photoCompressedSize, (uint8 *)_photoData->getPixels(), photoSize); + + // apply palette + for (uint j = 0; j < width * height; ++j) { + // _photoData[j] = Palette[_photoData[j]]; + } + + _shapeThumbnail = new Shape(_vm); + _shapeThumbnail->open("ESPTHUMB.SHP", _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, _shapeButton, nullptr); + + resetPhotoOpening(); + resetViewport(); + setStateMain(kEsperMainStatePhotoOpening); + setStatePhoto(kEsperPhotoStateOpening); + playSound(422, 25); + playSound(423, 25); +} + +void ESPER::unloadPhotos() { + for (int i = 0; i < kPhotoCount; ++i) { + if (_photos[i].isPresent) { + _buttons->resetImage(i); + delete _shapesPhotos[i]; + _shapesPhotos[i] = nullptr; + _photos[i].isPresent = false; + } + } +} + +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; + _timeSelectionZoomNext = 0; + + _selectionRectTarget = _selectionRect; + resetSelectionRect(); + _selectionRectDelta.left = (_selectionRectTarget.left - _selectionRect.left) / kSelectionZoomSteps; + _selectionRectDelta.top = (_selectionRectTarget.top - _selectionRect.top) / kSelectionZoomSteps; + _selectionRectDelta.right = (_selectionRectTarget.right - _selectionRect.right) / kSelectionZoomSteps; + _selectionRectDelta.bottom = (_selectionRectTarget.bottom - _selectionRect.bottom) / kSelectionZoomSteps; + + Common::Rect rect = _selectionRectTarget; + 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)kScreen.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 - kScreen.left) * _viewport.width() / kScreen.width(); + _viewportPositionYTarget = _viewport.top + ((rect.top + rect.bottom) / 2 - kScreen.top ) * _viewport.height() / kScreen.height(); + _viewportPositionXDelta = (_viewportPositionXTarget - _viewportPositionX) / (float)_zoomSteps; + _viewportPositionYDelta = (_viewportPositionYTarget - _viewportPositionY) / (float)_zoomSteps; + _viewportPositionXCurrent = _viewportPositionX; + _viewportPositionYCurrent = _viewportPositionY; +} + +void ESPER::updateSelection() { + int selectionWidth = abs(_selectionRect.right + 1 - _selectionRect.left); + int selectionHeight = abs(_selectionRect.bottom + 1 - _selectionRect.top); + + int photoSelectedWidth = _viewport.width() * selectionWidth / kScreen.width(); + if (photoSelectedWidth < _screenHalfWidth) { + // minimal width of selection + selectionWidth = kScreen.width() * _screenHalfWidth / _viewport.width(); + } + + photoSelectedWidth = _viewport.height() * selectionHeight / kScreen.height(); + if (photoSelectedWidth < _screenHalfHeight) { + // minimal height of selection + selectionHeight = kScreen.height() * _screenHalfHeight / _viewport.height(); + } + + // correct aspect ratio + if (selectionWidth / (float)kScreen.width() <= selectionHeight / (float)kScreen.height()) { + while (selectionWidth / (float)kScreen.width() <= selectionHeight / (float)kScreen.height()) { + ++selectionWidth; + } + } else { + while (selectionHeight / (float)kScreen.height() <= selectionWidth / (float)kScreen.width()) { + ++selectionHeight; + } + } + + if (selectionWidth > kScreen.width()) { + selectionWidth = kScreen.width(); + } + if (selectionHeight > kScreen.height()) { + selectionHeight = kScreen.height(); + } + + int left = _viewport.right - (kScreen.right - 1 - _selectionRect.left) * _viewport.width() / kScreen.width(); + int right = _viewport.left + (_selectionRect.right - kScreen.left ) * _viewport.width() / kScreen.width(); + int top = _viewport.bottom - (kScreen.bottom - 1 - _selectionRect.top ) * _viewport.height() / kScreen.height(); + int bottom = _viewport.top + (_selectionRect.bottom - kScreen.top ) * _viewport.height() / kScreen.height(); + + bool stop = false; + bool alternate = false; + + while (selectionWidth > abs(_selectionRect.right + 1 - _selectionRect.left)) { + if (alternate) { + --_selectionRect.left; + if (_selectionRect.left < 0) { + left = _viewport.right - (kScreen.right - 1 + 100 - _selectionRect.left) * _viewport.width() / kScreen.width(); + if (left < 0) { + left = 0; + ++_selectionRect.left; + if (stop) { + break; + } + stop = true; + alternate = false; + } + } + } else { + ++_selectionRect.right; + if (_selectionRect.right > kScreen.right - 1) { + right = _viewport.left + (_selectionRect.right - kScreen.left) * _viewport.width() / kScreen.width(); + if (right > kPhotoWidth - 1) { + right = kPhotoWidth - 1; + --_selectionRect.right; + if (stop) { + break; + } + stop = true; + alternate = true; + } + } + } + if (!stop) { + alternate = !alternate; + } + } + + alternate = false; + stop = false; + while (selectionHeight > abs(_selectionRect.bottom + 1 - _selectionRect.top)) { + if (alternate) { + --_selectionRect.top; + if (_selectionRect.top < 0) { + top = _viewport.bottom - (kScreen.bottom - 1 - _selectionRect.top) * _viewport.height() / kScreen.height(); + if (top < 0) { + top = 0; + ++_selectionRect.top; + if (stop) { + break; + } + stop = true; + alternate = false; + } + } + } else { + ++_selectionRect.bottom; + if (_selectionRect.bottom > kScreen.bottom - 1) { + bottom = _viewport.top + (_selectionRect.bottom - kScreen.top) * _viewport.height() / kScreen.height(); + if (bottom > kPhotoHeight - 1) { + bottom = kPhotoHeight - 1; + --_selectionRect.bottom; + if (stop) { + break; + } + alternate = true; + stop = true; + } + } + } + if (!stop) { + alternate = !alternate; + } + } + + _regionSelected = findRegion(Common::Rect(left, top, right, bottom)); + if (_regionSelected >= 0) { + _regionSelectedAck = true; + setStatePhoto(kEsperPhotoStatePhotoZooming); + } +} + +int ESPER::viewportXToScreenX(int x) { + return kScreen.width() * (x - _viewport.left) / _viewport.width() + kScreen.left; +} + +int ESPER::viewportYToScreenY(int y) { + return kScreen.height() * (y - _viewport.top) / _viewport.height() + kScreen.top; +} + +} // End of namespace BladeRunner diff --git a/engines/bladerunner/ui/esper.h b/engines/bladerunner/ui/esper.h new file mode 100644 index 0000000000..9d0dd6d012 --- /dev/null +++ b/engines/bladerunner/ui/esper.h @@ -0,0 +1,285 @@ +/* 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. + * + */ + +#ifndef BLADERUNNER_ESPER_H +#define BLADERUNNER_ESPER_H + +#include "common/array.h" +#include "common/rect.h" + +namespace Graphics { +struct Surface; +} + +namespace BladeRunner { + +class BladeRunnerEngine; +class Font; +class Shape; +class VQAPlayer; +class UIImagePicker; +class ESPERScript; + +// CD-changing logic has been removed + +enum EsperMainStates { + kEsperMainStateOpening = 0, + kEsperMainStateList = 1, + kEsperMainStatePhotoOpening = 2, + kEsperMainStateClear = 3, + kEsperMainStatePhoto = 5 +}; + +enum EsperPhotoStates { + kEsperPhotoStateShow = 0, + kEsperPhotoStateOpening = 1, + kEsperPhotoStateScrolling = 2, + kEsperPhotoStateSelectionZooming = 3, + kEsperPhotoStateSelectionBlinking = 4, + kEsperPhotoStatePhotoZooming = 5, + kEsperPhotoStatePhotoSharpening = 6, + kEsperPhotoStatePhotoZoomOut = 7, + kEsperPhotoStateVideoZooming = 8, + kEsperPhotoStateVideoShow = 9, + kEsperPhotoStateVideoZoomOut = 10 +}; + +class ESPER { + static const int kPhotoCount = 12; + static const int kRegionCount = 6; + static const int kPhotoWidth = 1280; + static const int kPhotoHeight = 960; + static const int kSelectionZoomSteps = 6; + + static const Common::Rect kScreen; + + struct Photo { + bool isPresent; + char name[13]; + int photoId; + int shapeId; + }; + + struct Region { + bool isPresent; + int regionId; + Common::Rect rectInner; + Common::Rect rectOuter; + Common::Rect rectSelection; + char name[13]; + }; + + BladeRunnerEngine *_vm; + ESPERScript *_script; + + bool _isWaiting; + bool _isOpen; + + UIImagePicker *_buttons; + + Graphics::Surface *_photoData; + Graphics::Surface *_viewportData; + + VQAPlayer *_vqaMainPlayer; + VQAPlayer *_vqaPhotoPlayer; + int _vqaLastFrame; + + Shape *_shapeButton; + Common::Array<Shape *> _shapesPhotos; + Shape *_shapeThumbnail; + + Photo _photos[kPhotoCount]; + int _photoIdSelected; + + Region _regions[kRegionCount]; + int _regionSelected; + bool _regionSelectedAck; + + EsperMainStates _stateMain; + EsperPhotoStates _statePhoto; + + bool _isDrawingSelection; + bool _isMouseDown; + int _mouseOverScroll; + + float _zoomHorizontal; + float _zoomVertical; + float _zoom; + float _zoomMin; + float _zoomTarget; + float _zoomDelta; + float _blur; + int _zoomSteps; + int _zoomStep; + int _timeZoomNext; + + bool _isZoomingOut; + int _timeZoomOutNext; + + Common::Rect _viewport; + Common::Rect _viewportNext; + int _viewportPositionX; + int _viewportPositionY; + float _viewportPositionXCurrent; + float _viewportPositionYCurrent; + int _viewportPositionXTarget; + int _viewportPositionYTarget; + float _viewportPositionXDelta; + float _viewportPositionYDelta; + int _viewportWidth; + int _viewportHeight; + + int _screenHalfWidth; + int _screenHalfHeight; + + int _flash; + + Common::Rect _selectionRect; + Common::Rect _selectionRectTarget; + Common::Rect _selectionRectDelta; + int _selectionCrosshairX; + int _selectionCrosshairY; + + int _selectionBlinkingCounter; + int _selectionBlinkingStyle; + int _timeSelectionBlinkingNext; + + int _selectionZoomStep; + int _timeSelectionZoomNext; + + int _photoOpeningWidth; + int _photoOpeningHeight; + int _timePhotoOpeningNext; + + bool _isScrolling; + int _scrollingDirection; + int _timeScrollNext; + + int _soundId1; + int _volume1; + int _soundId2; + int _volume2; + int _soundId3; + int _volume3; + int _ambientVolume; + +public: + ESPER(BladeRunnerEngine *vm); + ~ESPER(); + + void open(Graphics::Surface *surface); + void close(); + bool isOpen(); + + void handleMouseUp(int x, int y, bool buttonLeft); + void handleMouseDown(int x, int y, bool buttonLeft); + + void tick(); + + void resume(); + + void addPhoto(const char *name, int photoId, int shapeId); + void defineRegion(int regionId, Common::Rect inner, Common::Rect outer, Common::Rect selection, const char *name); + +private: + static void mouseDownCallback(int, void *); + static void mouseUpCallback(int, void *); + + void reset(); + void resetData(); + void resetPhotos(); + void resetRegions(); + void resetViewport(); + void resetSelectionRect(); + void resetSelectionBlinking(); + void resetPhotoZooming(); + void resetPhotoOpening(); + + void updateViewport(); + + void activate(bool withOpening); + void setStateMain(EsperMainStates state); + void setStatePhoto(EsperPhotoStates state); + + void wait(int timeout); + void playSound(int soundId, int volume); + + void draw(Graphics::Surface &surface); + + void drawPhotoOpening(Graphics::Surface &surface); + bool drawSelectionZooming(Graphics::Surface &surface); + bool drawSelectionBlinking(Graphics::Surface &surface); + void drawPhotoZooming(Graphics::Surface &surface); + void drawPhotoSharpening(Graphics::Surface &surface); + void drawPhotoZoomOut(Graphics::Surface &surface); + void drawVideoZooming(Graphics::Surface &surface); + void drawVideoZoomOut(Graphics::Surface &surface); + + void drawPhoto(Graphics::Surface &surface); + void drawGrid(Graphics::Surface &surface); + void drawPhotoWithGrid(Graphics::Surface &surface); + void drawSelection(Graphics::Surface &surface, bool crosshair, int style); + void drawVideoFrame(Graphics::Surface &surface); + void drawTextCoords(Graphics::Surface &surface); + + void flashViewport(); + + void copyImageScale(Graphics::Surface *src, Common::Rect srcRect, Graphics::Surface *dst, Common::Rect dstRect); + void copyImageBlur(Graphics::Surface *src, Common::Rect srcRect, Graphics::Surface *dst, Common::Rect dstRect, float u); + void copyImageBlit(Graphics::Surface *src, Common::Rect srcRect, Graphics::Surface *dst, Common::Rect dstRect); + + void tickSound(); + void tickMouse(Graphics::Surface &surface); + void tickScroll(); + + int findEmptyPhoto(); + void selectPhoto(int photoId); + void unloadPhotos(); + + int findEmptyRegion(); + int findRegion(Common::Rect where); + + void zoomingStart(); + void zoomOutStart(); + void zoomOutStop(); + + void scrollingStart(int direction); + void scrollingStop(); + void scrollUpdate(); + void scrollLeft(); + void scrollUp(); + void scrollRight(); + void scrollDown(); + + void goBack(); + + void prepareZoom(); + void updateSelection(); + + int viewportXToScreenX(int x); + int viewportYToScreenY(int y); + +}; + +} // End of namespace BladeRunner + +#endif diff --git a/engines/bladerunner/ui/kia.cpp b/engines/bladerunner/ui/kia.cpp index 95a66b6332..7563816f0f 100644 --- a/engines/bladerunner/ui/kia.cpp +++ b/engines/bladerunner/ui/kia.cpp @@ -59,20 +59,6 @@ namespace BladeRunner { const char *KIA::kPogo = "POGO"; -enum KIASections { - kKIASectionNone = 0, - kKIASectionCrimes = 1, - kKIASectionSuspects = 2, - kKIASectionClues = 3, - kKIASectionSettings = 4, - kKIASectionHelp = 5, - kKIASectionSave = 6, - kKIASectionLoad = 7, - kKIASectionQuit = 8, - kKIASectionDiagnostic = 9, - kKIASectionPogo = 10 -}; - KIA::KIA(BladeRunnerEngine *vm) { _vm = vm; @@ -137,12 +123,81 @@ void KIA::openLastOpened() { open(_lastSectionIdKIA); } -void KIA::openOptions() { - open(kKIASectionSettings); +void KIA::open(KIASections sectionId) { + if (_currentSectionId == sectionId) { + return; + } + + if (!sectionId) { + unload(); + return; + } + + if (!isOpen()) { + init(); + } + + switch (_currentSectionId) { + case kKIASectionCrimes: + _crimesSection->saveToLog(); + break; + case kKIASectionSuspects: + _suspectsSection->saveToLog(); + break; + case kKIASectionClues: + _cluesSection->saveToLog(); + break; + default: + break; + } + + if (sectionId != kKIASectionCrimes && sectionId != kKIASectionSuspects && sectionId != kKIASectionClues) { + playerReset(); + } + + _transitionId = getTransitionId(_currentSectionId, sectionId); + const char *name = getSectionVqaName(sectionId); + if (getSectionVqaName(_currentSectionId) != name) { + if (_mainVqaPlayer) { + _mainVqaPlayer->close(); + delete _mainVqaPlayer; + _mainVqaPlayer = nullptr; + } + + _mainVqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceBack); + _mainVqaPlayer->open(name); + } + + if (_transitionId) { + playTransitionSound(_transitionId); + _mainVqaPlayer->setLoop(getVqaLoopTransition(_transitionId), -1, kLoopSetModeImmediate, nullptr, nullptr); + _mainVqaPlayer->setLoop(getVqaLoopMain(sectionId), -1, kLoopSetModeEnqueue, loopEnded, this); + } else { + int loopId = getVqaLoopMain(sectionId); + _mainVqaPlayer->setLoop(loopId, -1, kLoopSetModeImmediate, nullptr, nullptr); + _mainVqaPlayer->setLoop(loopId + 1, -1, kLoopSetModeJustStart, nullptr, nullptr); + } + + _buttons->resetImages(); + createButtons(sectionId); + switchSection(sectionId); + _currentSectionId = sectionId; + + if (sectionId == kKIASectionCrimes || sectionId == kKIASectionSuspects || sectionId == kKIASectionClues) { + _lastSectionIdKIA = _currentSectionId; + } + + if (sectionId == kKIASectionSettings || sectionId == kKIASectionHelp || sectionId == kKIASectionSave || sectionId == kKIASectionLoad) { + _lastSectionIdOptions = _currentSectionId; + } +} + +bool KIA::isOpen() { + return _currentSectionId != kKIASectionNone; } void KIA::tick() { - if (!_currentSectionId) { + if (!isOpen()) { return; } @@ -235,8 +290,9 @@ void KIA::tick() { _shapes->get(39)->draw(_vm->_surfaceFront, 583, 342); } } + //TODO: implement frame loading after seek, then advanceFrame can be removed _playerVqaPlayer->seekToFrame(_playerVqaFrame); - _playerVqaPlayer->update(true); //_vm->_surfaceFront, 3 + _playerVqaPlayer->update(true, true); _playerSliceModelAngle += (float)(timeDiff) * 1.0f/400.0f; while (_playerSliceModelAngle >= 2 * M_PI) { @@ -296,11 +352,24 @@ void KIA::tick() { _vm->_mouse->draw(_vm->_surfaceFront, mouse.x, mouse.y); _vm->blitToScreen(_vm->_surfaceFront); + _vm->_system->delayMillis(10); + _timeLast = timeNow; } +void KIA::resume() { + // vqaPlayer::clear(this->_vqaPlayerMain); + if (_transitionId) { + _mainVqaPlayer->setLoop(getVqaLoopTransition(_transitionId), -1, kLoopSetModeImmediate, nullptr, nullptr); + _mainVqaPlayer->setLoop(getVqaLoopMain(_currentSectionId), -1, kLoopSetModeEnqueue, loopEnded, this); + } else { + _mainVqaPlayer->setLoop(getVqaLoopMain(_currentSectionId), -1, kLoopSetModeImmediate, nullptr, nullptr); + _mainVqaPlayer->setLoop(getVqaLoopMain(_currentSectionId) + 1, -1, kLoopSetModeJustStart, nullptr, nullptr); + } +} + void KIA::handleMouseDown(int mouseX, int mouseY, bool mainButton) { - if (!_currentSectionId) { + if (!isOpen()) { return; } if (mainButton) { @@ -312,7 +381,7 @@ void KIA::handleMouseDown(int mouseX, int mouseY, bool mainButton) { } void KIA::handleMouseUp(int mouseX, int mouseY, bool mainButton) { - if (!_currentSectionId) { + if (!isOpen()) { return; } if (mainButton) { @@ -339,7 +408,7 @@ void KIA::handleMouseUp(int mouseX, int mouseY, bool mainButton) { } void KIA::handleKeyUp(const Common::KeyState &kbd) { - if (!_currentSectionId) { + if (!isOpen()) { return; } @@ -370,7 +439,7 @@ void KIA::handleKeyUp(const Common::KeyState &kbd) { } void KIA::handleKeyDown(const Common::KeyState &kbd) { - if (!_currentSectionId) { + if (!isOpen()) { return; } switch (kbd.keycode) { @@ -473,7 +542,7 @@ void KIA::playPhotograph(int photographId) { } _playerPhotographId = photographId; _playerPhotograph = new Shape(_vm); - _playerPhotograph->readFromContainer("photos.shp", photographId); + _playerPhotograph->open("photos.shp", photographId); } void KIA::mouseDownCallback(int buttonId, void *callbackData) { @@ -544,73 +613,6 @@ void KIA::loopEnded(void *callbackData, int frame, int loopId) { self->_transitionId = 0; } -void KIA::open(int sectionId) { - if (_currentSectionId == sectionId) { - return; - } - - if (!sectionId) { - unload(); - return; - } - - if (!_currentSectionId) { - init(); - } - - switch (_currentSectionId) { - case kKIASectionCrimes: - _crimesSection->saveToLog(); - break; - case kKIASectionSuspects: - _suspectsSection->saveToLog(); - break; - case kKIASectionClues: - _cluesSection->saveToLog(); - break; - } - - if (sectionId != kKIASectionCrimes && sectionId != kKIASectionSuspects && sectionId != kKIASectionClues) { - playerReset(); - } - - _transitionId = getTransitionId(_currentSectionId, sectionId); - const char *name = getSectionVqaName(sectionId); - if (getSectionVqaName(_currentSectionId) != name) { - if (_mainVqaPlayer) { - _mainVqaPlayer->close(); - delete _mainVqaPlayer; - _mainVqaPlayer = nullptr; - } - - _mainVqaPlayer = new VQAPlayer(_vm, &_vm->_surfaceBack); - _mainVqaPlayer->open(name); - } - - if (_transitionId) { - playTransitionSound(_transitionId); - _mainVqaPlayer->setLoop(getVqaLoopTransition(_transitionId), -1, kLoopSetModeImmediate, nullptr, nullptr); - _mainVqaPlayer->setLoop(getVqaLoopMain(sectionId), -1, kLoopSetModeEnqueue, loopEnded, this); - } else { - int loopId = getVqaLoopMain(sectionId); - _mainVqaPlayer->setLoop(loopId, -1, kLoopSetModeImmediate, nullptr, nullptr); - _mainVqaPlayer->setLoop(loopId + 1, -1, kLoopSetModeJustStart, nullptr, nullptr); - } - - _buttons->resetImages(); - createButtons(sectionId); - switchSection(sectionId); - _currentSectionId = sectionId; - - if (sectionId == kKIASectionCrimes || sectionId == kKIASectionSuspects || sectionId == kKIASectionClues) { - _lastSectionIdKIA = _currentSectionId; - } - - if (sectionId == kKIASectionSettings || sectionId == kKIASectionHelp || sectionId == kKIASectionSave || sectionId == kKIASectionLoad) { - _lastSectionIdOptions = _currentSectionId; - } -} - void KIA::init() { if (!_vm->openArchive("MODE.MIX")) { return; @@ -641,7 +643,7 @@ void KIA::init() { } void KIA::unload() { - if (!_currentSectionId) { + if (!isOpen()) { return; } @@ -671,7 +673,7 @@ void KIA::unload() { _vm->closeArchive("MODE.MIX"); - _currentSectionId = 0; + _currentSectionId = kKIASectionNone; // TODO: Unfreeze game time @@ -846,7 +848,7 @@ void KIA::createButtons(int sectionId) { void KIA::buttonClicked(int buttonId) { int soundId = 0; - if (!_currentSectionId) { + if (!isOpen()) { return; } switch (buttonId) { diff --git a/engines/bladerunner/ui/kia.h b/engines/bladerunner/ui/kia.h index 0452230382..0ac7a22600 100644 --- a/engines/bladerunner/ui/kia.h +++ b/engines/bladerunner/ui/kia.h @@ -54,6 +54,21 @@ class Shape; class UIImagePicker; class VQAPlayer; +enum KIASections { + kKIASectionNone = 0, + kKIASectionCrimes = 1, + kKIASectionSuspects = 2, + kKIASectionClues = 3, + kKIASectionSettings = 4, + kKIASectionHelp = 5, + kKIASectionSave = 6, + kKIASectionLoad = 7, + kKIASectionQuit = 8, + kKIASectionDiagnostic = 9, + kKIASectionPogo = 10 +}; + + class KIA { static const char *kPogo; static const int kPlayerActorDialogueQueueCapacity = 31; @@ -68,9 +83,6 @@ class KIA { int _forceOpen; int _transitionId; - int _lastSectionIdKIA; - int _lastSectionIdOptions; - int _playerVqaTimeLast; VQAPlayer *_playerVqaPlayer; int _playerVqaFrame; @@ -86,7 +98,11 @@ class KIA { int _playerActorDialogueQueueSize; int _playerActorDialogueState; + KIASections _currentSectionId; + KIASections _lastSectionIdKIA; + KIASections _lastSectionIdOptions; KIASectionBase *_currentSection; + KIASectionClues *_cluesSection; KIASectionCrimes *_crimesSection; KIASectionDiagnostic *_diagnosticSection; @@ -104,20 +120,22 @@ class KIA { int _pogoPos; public: - int _currentSectionId; - KIALog *_log; - KIAScript *_script; - KIAShapes *_shapes; + KIALog *_log; + KIAScript *_script; + KIAShapes *_shapes; public: KIA(BladeRunnerEngine *vm); ~KIA(); void openLastOpened(); - void openOptions(); + void open(KIASections sectionId); + bool isOpen(); void tick(); + void resume(); + void handleMouseDown(int mouseX, int mouseY, bool mainButton); void handleMouseUp(int mouseX, int mouseY, bool mainButton); void handleKeyUp(const Common::KeyState &kbd); @@ -133,7 +151,6 @@ private: static void mouseUpCallback(int buttonId, void *callbackData); static void loopEnded(void *callbackData, int frame, int loopId); - void open(int sectionId); void init(); void unload(); void switchSection(int sectionId); diff --git a/engines/bladerunner/ui/kia_section_crimes.cpp b/engines/bladerunner/ui/kia_section_crimes.cpp index b0b3d7c234..a5ff221250 100644 --- a/engines/bladerunner/ui/kia_section_crimes.cpp +++ b/engines/bladerunner/ui/kia_section_crimes.cpp @@ -409,7 +409,7 @@ void KIASectionCrimes::updateSuspectPhoto() { if (_suspectPhotoShapeId != -1) { _suspectPhotoShape = new Shape(_vm); - _suspectPhotoShape->readFromContainer("photos.shp", _suspectPhotoShapeId); + _suspectPhotoShape->open("photos.shp", _suspectPhotoShapeId); } } diff --git a/engines/bladerunner/ui/kia_section_suspects.cpp b/engines/bladerunner/ui/kia_section_suspects.cpp index ce13243f76..053705c9da 100644 --- a/engines/bladerunner/ui/kia_section_suspects.cpp +++ b/engines/bladerunner/ui/kia_section_suspects.cpp @@ -498,7 +498,7 @@ void KIASectionSuspects::updateSuspectPhoto() { if (_suspectPhotoShapeId != -1) { _suspectPhotoShape = new Shape(_vm); - _suspectPhotoShape->readFromContainer("photos.shp", _suspectPhotoShapeId); + _suspectPhotoShape->open("photos.shp", _suspectPhotoShapeId); } } diff --git a/engines/bladerunner/ui/kia_shapes.cpp b/engines/bladerunner/ui/kia_shapes.cpp index d992ec7045..ce5c8a7e5e 100644 --- a/engines/bladerunner/ui/kia_shapes.cpp +++ b/engines/bladerunner/ui/kia_shapes.cpp @@ -45,7 +45,7 @@ void KIAShapes::load() { for (uint i = 0; i < kShapeCount; ++i) { Shape *shape = new Shape(_vm); - shape->readFromContainer("kiaopt.shp", i); + shape->open("kiaopt.shp", i); _shapes[i] = shape; } diff --git a/engines/bladerunner/ui/spinner.cpp b/engines/bladerunner/ui/spinner.cpp index 72c8b19070..2df9390afe 100644 --- a/engines/bladerunner/ui/spinner.cpp +++ b/engines/bladerunner/ui/spinner.cpp @@ -23,6 +23,7 @@ #include "bladerunner/ui/spinner.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/game_constants.h" #include "bladerunner/mouse.h" #include "bladerunner/scene.h" #include "bladerunner/shape.h" @@ -123,7 +124,7 @@ int Spinner::chooseDestination(int loopId, bool immediately) { for (int j = 0; j != shapeCount; ++j) { _shapes.push_back(new Shape(_vm)); - _shapes[j]->readFromContainer("SPINNER.SHP", firstShapeId + j); + _shapes[j]->open("SPINNER.SHP", firstShapeId + j); } _imagePicker->resetImages(); diff --git a/engines/bladerunner/ui/ui_image_picker.cpp b/engines/bladerunner/ui/ui_image_picker.cpp index 6c46880e02..ca61a4d989 100644 --- a/engines/bladerunner/ui/ui_image_picker.cpp +++ b/engines/bladerunner/ui/ui_image_picker.cpp @@ -24,6 +24,7 @@ #include "bladerunner/bladerunner.h" #include "bladerunner/font.h" +#include "bladerunner/game_constants.h" #include "bladerunner/mouse.h" #include "bladerunner/shape.h" @@ -207,6 +208,7 @@ void UIImagePicker::draw(Graphics::Surface &surface) { } #if BLADERUNNER_DEBUG_RENDERING surface.frameRect(img.rect, 0x7fff); + _vm->_mainFont->drawColor(Common::String::format("%d", i), surface, (img.rect.left + img.rect.right) / 2, (img.rect.top + img.rect.bottom) / 2, 0x7fff); #endif } } @@ -252,11 +254,11 @@ void UIImagePicker::drawTooltip(Graphics::Surface &surface, int x, int y) { _vm->_mainFont->drawColor(tooltip, surface, rect.left + 2, rect.top, 0x7FFF); } -void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ignore) { +bool UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ignore) { if (!_isVisible || ignore) { - return; + return false; } - + bool actionHandled = false; int hoveredImageIndex = -1; for (int i = 0; i != _imageCount; ++i) { if (_images[i].rect.contains(x, y)) { @@ -286,9 +288,10 @@ void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ign if (down && !_isButtonDown) { _isButtonDown = true; _pressedImageIndex = _hoveredImageIndex; - if (_hoveredImageIndex != 1) { + if (_hoveredImageIndex != -1) { if (_mouseDownCallback) { _mouseDownCallback(_hoveredImageIndex, _callbackData); + actionHandled = true; } } } @@ -297,13 +300,17 @@ void UIImagePicker::handleMouseAction(int x, int y, bool down, bool up, bool ign if (up) { if (_isButtonDown) { if (_hoveredImageIndex == _pressedImageIndex && _pressedImageIndex != -1) { - if (_mouseUpCallback) + if (_mouseUpCallback) { _mouseUpCallback(_hoveredImageIndex, _callbackData); + actionHandled = true; + } } } _isButtonDown = false; _pressedImageIndex = -1; } + + return actionHandled; } void UIImagePicker::resetImage(int i) { diff --git a/engines/bladerunner/ui/ui_image_picker.h b/engines/bladerunner/ui/ui_image_picker.h index 00f2f3dfd9..d5b18b2f0a 100644 --- a/engines/bladerunner/ui/ui_image_picker.h +++ b/engines/bladerunner/ui/ui_image_picker.h @@ -91,7 +91,7 @@ public: void draw(Graphics::Surface &surface); void drawTooltip(Graphics::Surface &surface, int x, int y); - void handleMouseAction(int x, int y, bool down, bool up, bool ignore = false); + bool handleMouseAction(int x, int y, bool down, bool up, bool ignore = false); void resetImage(int i); bool hasHoveredImage(); diff --git a/engines/bladerunner/vqa_decoder.cpp b/engines/bladerunner/vqa_decoder.cpp index aec4d18e5d..7dc7616fe9 100644 --- a/engines/bladerunner/vqa_decoder.cpp +++ b/engines/bladerunner/vqa_decoder.cpp @@ -25,6 +25,7 @@ #include "bladerunner/bladerunner.h" #include "bladerunner/decompress_lcw.h" #include "bladerunner/decompress_lzo.h" +#include "bladerunner/game_info.h" #include "bladerunner/lights.h" #include "bladerunner/screen_effects.h" #include "bladerunner/view.h" @@ -116,9 +117,8 @@ const char *strTag(uint32 tag) { return s; } -VQADecoder::VQADecoder(Graphics::Surface *surface) : +VQADecoder::VQADecoder() : _s(nullptr), - _surface(surface), _frameInfo(nullptr), _videoTrack(nullptr), _audioTrack(nullptr), @@ -179,7 +179,7 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *s) { } } while (chd.id != kFINF); - _videoTrack = new VQAVideoTrack(this, _surface); + _videoTrack = new VQAVideoTrack(this); _audioTrack = new VQAAudioTrack(this); #if BLADERUNNER_DEBUG_CONSOLE @@ -194,9 +194,9 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *s) { return true; } -void VQADecoder::decodeVideoFrame(int frame, bool forceDraw) { +void VQADecoder::decodeVideoFrame(Graphics::Surface *surface, int frame, bool forceDraw) { _decodingFrame = frame; - _videoTrack->decodeVideoFrame(forceDraw); + _videoTrack->decodeVideoFrame(surface, forceDraw); } void VQADecoder::decodeZBuffer(ZBuffer *zbuffer) { @@ -573,9 +573,8 @@ bool VQADecoder::readMFCI(Common::SeekableReadStream *s, uint32 size) { return true; } -VQADecoder::VQAVideoTrack::VQAVideoTrack(VQADecoder *vqaDecoder, Graphics::Surface *surface) { +VQADecoder::VQAVideoTrack::VQAVideoTrack(VQADecoder *vqaDecoder) { _vqaDecoder = vqaDecoder; - _surface = surface; _hasNewFrame = false; VQADecoder::Header *header = &vqaDecoder->_header; @@ -635,9 +634,10 @@ Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const { return _frameRate; } -void VQADecoder::VQAVideoTrack::decodeVideoFrame(bool forceDraw) { +void VQADecoder::VQAVideoTrack::decodeVideoFrame(Graphics::Surface *surface, bool forceDraw) { if (_hasNewFrame || forceDraw) { - decodeFrame((uint16 *)_surface->getPixels()); + assert(surface); + decodeFrame(surface); _hasNewFrame = false; } } @@ -807,11 +807,12 @@ bool VQADecoder::VQAVideoTrack::readVPTR(Common::SeekableReadStream *s, uint32 s return true; } -void VQADecoder::VQAVideoTrack::VPTRWriteBlock(uint16 *frame, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha) { - uint16 frame_width = _width; - uint32 frame_stride = 640; - uint16 block_width = _blockW; - uint16 block_height = _blockH; +void VQADecoder::VQAVideoTrack::VPTRWriteBlock(Graphics::Surface *surface, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha) { + uint16 *frame = (uint16 *)surface->getPixels(); + uint16 frame_width = _width; + uint32 frame_stride = surface->w; + uint16 block_width = _blockW; + uint16 block_height = _blockH; const uint8 *const block_src = &_codebook[2 * srcBlock * block_width * block_height]; @@ -845,7 +846,7 @@ void VQADecoder::VQAVideoTrack::VPTRWriteBlock(uint16 *frame, unsigned int dstBl } while (--count); } -bool VQADecoder::VQAVideoTrack::decodeFrame(uint16 *frame) { +bool VQADecoder::VQAVideoTrack::decodeFrame(Graphics::Surface *surface) { CodebookInfo &codebookInfo = _vqaDecoder->codebookInfoForFrame(_vqaDecoder->_decodingFrame); if (!codebookInfo.data) { @@ -876,19 +877,19 @@ bool VQADecoder::VQAVideoTrack::decodeFrame(uint16 *frame) { count = 2 * (((command >> 8) & 0x1f) + 1); srcBlock = command & 0x00ff; - VPTRWriteBlock(frame, dstBlock, srcBlock, count); + VPTRWriteBlock(surface, dstBlock, srcBlock, count); dstBlock += count; break; case 2: count = 2 * (((command >> 8) & 0x1f) + 1); srcBlock = command & 0x00ff; - VPTRWriteBlock(frame, dstBlock, srcBlock, 1); + VPTRWriteBlock(surface, dstBlock, srcBlock, 1); ++dstBlock; for (int i = 0; i < count; ++i) { srcBlock = *src++; - VPTRWriteBlock(frame, dstBlock, srcBlock, 1); + VPTRWriteBlock(surface, dstBlock, srcBlock, 1); ++dstBlock; } break; @@ -897,7 +898,7 @@ bool VQADecoder::VQAVideoTrack::decodeFrame(uint16 *frame) { count = 1; srcBlock = command & 0x1fff; - VPTRWriteBlock(frame, dstBlock, srcBlock, count, prefix == 4); + VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 4); ++dstBlock; break; case 5: @@ -905,7 +906,7 @@ bool VQADecoder::VQAVideoTrack::decodeFrame(uint16 *frame) { count = *src++; srcBlock = command & 0x1fff; - VPTRWriteBlock(frame, dstBlock, srcBlock, count, prefix == 6); + VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 6); dstBlock += count; break; default: diff --git a/engines/bladerunner/vqa_decoder.h b/engines/bladerunner/vqa_decoder.h index 8ab6be5c7e..fdc5b53d19 100644 --- a/engines/bladerunner/vqa_decoder.h +++ b/engines/bladerunner/vqa_decoder.h @@ -54,14 +54,14 @@ enum VQADecoderSkipFlags { class VQADecoder { public: - VQADecoder(Graphics::Surface *surface); + VQADecoder(); ~VQADecoder(); bool loadStream(Common::SeekableReadStream *s); void readFrame(int frame, uint readFlags = kVQAReadAll); - void decodeVideoFrame(int frame, bool forceDraw = false); + void decodeVideoFrame(Graphics::Surface *surface, int frame, bool forceDraw = false); void decodeZBuffer(ZBuffer *zbuffer); Audio::SeekableAudioStream *decodeAudioFrame(); void decodeView(View *view); @@ -139,7 +139,7 @@ private: class VQAAudioTrack; Common::SeekableReadStream *_s; - Graphics::Surface *_surface; + // Graphics::Surface *_surface; Header _header; int _readingFrame; @@ -172,7 +172,7 @@ private: class VQAVideoTrack { public: - VQAVideoTrack(VQADecoder *vqaDecoder, Graphics::Surface *surface); + VQAVideoTrack(VQADecoder *vqaDecoder); ~VQAVideoTrack(); uint16 getWidth() const; @@ -180,7 +180,7 @@ private: int getFrameCount() const; - void decodeVideoFrame(bool forceDraw); + void decodeVideoFrame(Graphics::Surface *surface, bool forceDraw); void decodeZBuffer(ZBuffer *zbuffer); void decodeView(View *view); void decodeScreenEffects(ScreenEffects *aesc); @@ -202,7 +202,7 @@ private: private: VQADecoder *_vqaDecoder; - Graphics::Surface *_surface; + bool _hasNewFrame; uint16 _numFrames; @@ -233,8 +233,8 @@ private: uint8 *_screenEffectsData; uint32 _screenEffectsDataSize; - void VPTRWriteBlock(uint16 *frame, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha = false); - bool decodeFrame(uint16 *frame); + void VPTRWriteBlock(Graphics::Surface *surface, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha = false); + bool decodeFrame(Graphics::Surface *surface); }; class VQAAudioTrack { diff --git a/engines/bladerunner/vqa_player.cpp b/engines/bladerunner/vqa_player.cpp index 1616459d36..7e41913042 100644 --- a/engines/bladerunner/vqa_player.cpp +++ b/engines/bladerunner/vqa_player.cpp @@ -23,6 +23,7 @@ #include "bladerunner/vqa_player.h" #include "bladerunner/bladerunner.h" +#include "bladerunner/game_constants.h" #include "audio/decoders/raw.h" @@ -71,7 +72,7 @@ void VQAPlayer::close() { _s = nullptr; } -int VQAPlayer::update(bool forceDraw) { +int VQAPlayer::update(bool forceDraw, bool advanceFrame, Graphics::Surface *customSurface) { uint32 now = 60 * _vm->_system->getMillis(); int result = -1; @@ -104,14 +105,14 @@ int VQAPlayer::update(bool forceDraw) { } result = -1; - } else if (_frameNext > _frameEnd) { + } else if (_frameNext > _frameEnd) { result = -3; } else if (now < _frameNextTime) { result = -1; - } else { + } else if (advanceFrame) { _frame = _frameNext; _decoder.readFrame(_frameNext, kVQAReadVideo); - _decoder.decodeVideoFrame(_frameNext); + _decoder.decodeVideoFrame(customSurface != nullptr ? customSurface : _surface, _frameNext); int audioPreloadFrames = 14; @@ -140,8 +141,9 @@ int VQAPlayer::update(bool forceDraw) { _frameNext++; result = _frame; } + if (result < 0 && forceDraw && _frame != -1) { - _decoder.decodeVideoFrame(_frame, true); + _decoder.decodeVideoFrame(customSurface != nullptr ? customSurface : _surface, _frame, true); result = _frame; } return result; @@ -240,6 +242,10 @@ int VQAPlayer::getLoopEndFrame(int loop) { return end; } +int VQAPlayer::getFrameCount() { + return _decoder.numFrames(); +} + void VQAPlayer::queueAudioFrame(Audio::AudioStream *audioStream) { int n = _audioStream->numQueuedStreams(); if (n == 0) diff --git a/engines/bladerunner/vqa_player.h b/engines/bladerunner/vqa_player.h index d276b9111c..d95f8b36a2 100644 --- a/engines/bladerunner/vqa_player.h +++ b/engines/bladerunner/vqa_player.h @@ -50,6 +50,7 @@ class VQAPlayer { Common::SeekableReadStream *_s; VQADecoder _decoder; Audio::QueuingAudioStream *_audioStream; + Graphics::Surface *_surface; int _frame; int _frameNext; @@ -77,7 +78,8 @@ public: VQAPlayer(BladeRunnerEngine *vm, Graphics::Surface *surface) : _vm(vm), _s(nullptr), - _decoder(surface), + _surface(surface), + _decoder(), _audioStream(nullptr), _frame(-1), _frameNext(-1), @@ -102,7 +104,7 @@ public: bool open(const Common::String &name); void close(); - int update(bool forceDraw = false); + int update(bool forceDraw = false, bool advanceFrame = true, Graphics::Surface *customSurface = nullptr); void updateZBuffer(ZBuffer *zbuffer); void updateView(View *view); void updateScreenEffects(ScreenEffects *screenEffects); @@ -116,6 +118,8 @@ public: int getLoopBeginFrame(int loop); int getLoopEndFrame(int loop); + int getFrameCount(); + private: void queueAudioFrame(Audio::AudioStream *audioStream); }; diff --git a/engines/bladerunner/waypoints.h b/engines/bladerunner/waypoints.h index 5a2a1fce6a..183c0895b1 100644 --- a/engines/bladerunner/waypoints.h +++ b/engines/bladerunner/waypoints.h @@ -31,9 +31,7 @@ namespace BladeRunner { class Waypoints { -#if BLADERUNNER_DEBUG_RENDERING friend class BladeRunnerEngine; -#endif struct Waypoint { int setId; |