Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "engines/util.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_resources.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/people.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) :
SherlockEngine(syst, gameDesc), _darts(this), _foolscapWidget(this) {
_runningProlog = false;
_fastMode = false;
_allowFastMode = true;
_transparentMenus = true;
_textWindowsOn = true;
}
TattooEngine::~TattooEngine() {
}
void TattooEngine::showOpening() {
// No implementation - opening is done using in-game scenes
}
void TattooEngine::initialize() {
initGraphics(640, 480);
// Initialize the base engine
SherlockEngine::initialize();
// Initialise the global flags
_flags.resize(3200);
_flags[1] = _flags[4] = _flags[76] = true;
_runningProlog = true;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-prolog")->setEnabled(true);
// Add some more files to the cache
_res->addToCache("walk.lib");
// Set up list of people
TattooFixedText &fixedText = *(TattooFixedText *)_fixedText;
const char *peopleNamePtr = nullptr;
for (int idx = 0; idx < TATTOO_MAX_PEOPLE; ++idx) {
peopleNamePtr = fixedText.getText(PEOPLE_DATA[idx].fixedTextId);
_people->_characters.push_back(PersonData(
peopleNamePtr,
PEOPLE_DATA[idx].portrait, nullptr, nullptr));
}
// Load the inventory
loadInventory();
// Starting scene
_scene->_goToScene = STARTING_INTRO_SCENE;
// Load an initial palette
loadInitialPalette();
}
void TattooEngine::startScene() {
TattooUserInterface &ui = *(TattooUserInterface *)_ui;
if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) {
// Show the map
_scene->_currentScene = OVERHEAD_MAP;
_scene->_goToScene = _map->show();
_people->_savedPos = Common::Point(-1, -1);
_people->_savedPos._facing = -1;
}
switch (_scene->_goToScene) {
case 7:
case 8:
case 18:
case 53:
case 68:
// Load overlay mask(s) for the scene
ui._mask = _res->load(Common::Path(Common::String::format("res%02d.msk", _scene->_goToScene)));
if (_scene->_goToScene == 8)
ui._mask1 = _res->load("res08a.msk");
else if (_scene->_goToScene == 18 || _scene->_goToScene == 68)
ui._mask1 = _res->load("res08a.msk");
break;
case STARTING_INTRO_SCENE:
// Disable input so that the intro can't be skipped until the game's logo has been shown
ui._lockoutTimer = STARTUP_KEYS_DISABLED_DELAY;
break;
case 101:
// Darts Board minigame
_darts.playDarts(GAME_CRICKET);
break;
case 102:
// Darts Board minigame
_darts.playDarts(GAME_301);
break;
case 103:
// Darts Board minigame
_darts.playDarts(GAME_501);
break;
default:
break;
}
_events->setCursor(ARROW);
}
void TattooEngine::loadInitialPalette() {
byte palette[768];
Common::SeekableReadStream *stream = _res->load("room.pal");
stream->read(palette, Graphics::PALETTE_SIZE);
_screen->translatePalette(palette);
_screen->setPalette(palette);
delete stream;
}
void TattooEngine::loadInventory() {
Inventory &inv = *_inventory;
Common::String inv1 = _fixedText->getText(kFixedText_Inv1);
Common::String inv2 = _fixedText->getText(kFixedText_Inv2);
Common::String inv3 = _fixedText->getText(kFixedText_Inv3);
Common::String inv4 = _fixedText->getText(kFixedText_Inv4);
Common::String inv5 = _fixedText->getText(kFixedText_Inv5);
Common::String inv6 = _fixedText->getText(kFixedText_Inv6);
Common::String inv7 = _fixedText->getText(kFixedText_Inv7);
Common::String inv8 = _fixedText->getText(kFixedText_Inv8);
Common::String invDesc1 = _fixedText->getText(kFixedText_InvDesc1);
Common::String invDesc2 = _fixedText->getText(kFixedText_InvDesc2);
Common::String invDesc3 = _fixedText->getText(kFixedText_InvDesc3);
Common::String invDesc4 = _fixedText->getText(kFixedText_InvDesc4);
Common::String invDesc5 = _fixedText->getText(kFixedText_InvDesc5);
Common::String invDesc6 = _fixedText->getText(kFixedText_InvDesc6);
Common::String invDesc7 = _fixedText->getText(kFixedText_InvDesc7);
Common::String invDesc8 = _fixedText->getText(kFixedText_InvDesc8);
Common::String solve = _fixedText->getText(kFixedText_Solve);
// Initial inventory
inv._holdings = 5;
inv.push_back(InventoryItem(0, inv1, invDesc1, "_ITEM01A"));
inv.push_back(InventoryItem(0, inv2, invDesc2, "_ITEM02A"));
inv.push_back(InventoryItem(0, inv3, invDesc3, "_ITEM03A"));
inv.push_back(InventoryItem(0, inv4, invDesc4, "_ITEM04A"));
inv.push_back(InventoryItem(0, inv5, invDesc5, "_ITEM05A"));
// Hidden items
inv.push_back(InventoryItem(295, inv6, invDesc6, "_PAP212D", solve));
inv.push_back(InventoryItem(294, inv7, invDesc7, "_PAP212I"));
inv.push_back(InventoryItem(818, inv8, invDesc8, "_LANT02I"));
}
void TattooEngine::doFoolscapPuzzle() {
_foolscapWidget.show();
}
void TattooEngine::loadConfig() {
SherlockEngine::loadConfig();
_transparentMenus = ConfMan.getBool("transparent_windows");
_textWindowsOn = ConfMan.getBool("subtitles") || !_sound->_speechOn;
}
void TattooEngine::saveConfig() {
SherlockEngine::saveConfig();
ConfMan.setBool("transparent_windows", _transparentMenus);
ConfMan.setBool("subtitles", _textWindowsOn);
ConfMan.flushToDisk();
}
bool TattooEngine::canLoadGameStateCurrently(Common::U32String *msg) {
TattooUserInterface &ui = *(TattooUserInterface *)_ui;
return _canLoadSave && !ui._creditsWidget.active() && !_runningProlog;
}
bool TattooEngine::canSaveGameStateCurrently(Common::U32String *msg) {
TattooUserInterface &ui = *(TattooUserInterface *)_ui;
return _canLoadSave && !ui._creditsWidget.active() && !_runningProlog;
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,119 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_H
#define SHERLOCK_TATTOO_H
#include "sherlock/sherlock.h"
#include "sherlock/tattoo/tattoo_darts.h"
#include "sherlock/tattoo/widget_foolscap.h"
namespace Sherlock {
namespace Tattoo {
enum {
INV_FOREGROUND = 167,
INV_BACKGROUND = 1,
INFO_FOREGROUND = 233,
INFO_BACKGROUND = 239,
INFO_TOP = 185,
INFO_MIDDLE = 186,
INFO_BOTTOM = 188,
MENU_BACKGROUND = 225,
COMMAND_FOREGROUND = 15,
COMMAND_HIGHLIGHTED = 254,
COMMAND_NULL = 193,
PEN_COLOR = 248,
PEN_HIGHLIGHT_COLOR = 129
};
enum {
FLAG_PLAYER_IS_HOLMES = 76,
FLAG_ALT_MAP_MUSIC = 525
};
class TattooEngine : public SherlockEngine {
private:
Darts _darts;
WidgetFoolscap _foolscapWidget;
/**
* Loads the initial palette for the game
*/
void loadInitialPalette();
/**
* Load the initial inventory
*/
void loadInventory();
protected:
/**
* Initialize the engine
*/
void initialize() override;
void showOpening() override;
/**
* Starting a scene within the game
*/
void startScene() override;
/**
* Load configuration options
*/
void loadConfig() override;
public:
bool _runningProlog;
bool _fastMode, _allowFastMode;
bool _transparentMenus;
bool _textWindowsOn;
public:
TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
~TattooEngine() override;
/**
* Shows the foolscap puzzle
*/
void doFoolscapPuzzle();
/**
* Save the game configuration
*/
void saveConfig() override;
/**
* Returns true if a savegame can be loaded
*/
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
/**
* Returns true if the game can be saved
*/
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_DARTS_H
#define SHERLOCK_TATTOO_DARTS_H
#include "common/scummsys.h"
#include "sherlock/image_file.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
enum GameType { GAME_301, GAME_CRICKET, GAME_501 };
class Darts {
private:
SherlockEngine *_vm;
GameType _gameType;
ImageFile *_hand1, *_hand2;
ImageFile *_dartGraphics;
ImageFile *_dartsLeft;
ImageFile *_dartMap;
ImageFile *_dartBoard;
Common::Rect _dartInfo;
int _cricketScore[2][7];
int _score1, _score2;
int _roundNum;
int _roundScore;
int _level;
int _compPlay;
Common::String _opponent;
int _spacing;
bool _oldDartButtons;
int _handX;
Common::Point _handSize;
bool _escapePressed;
/**
* Initialize game variables
*/
void initDarts();
/**
* Load dartboard graphics
*/
void loadDarts();
/**
* Free loaded dart images
*/
void closeDarts();
/**
* Show the player names
*/
void showNames(int playerNum);
/**
* Show the current scores
*/
void showStatus(int playerNum);
/**
* Erases the power bars
*/
void erasePowerBars();
/**
* Returns true if a mouse button or key is pressed
*/
bool dartHit();
/**
* Shows a power bar and increments it until a key or mouse button is pressed. If the bar
* reaches the end, it will also end. The reached power bar number is returned.
* @param pt Bar position
* @param color draw color
* @param goToPower If provided, input is ignored, and the bar is increased up to the specified level
* @param orientation 0=Horizontal, 1=Vertical
*/
int doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation);
/**
* This is similar to doPowerBar, except it draws the player's hand moving across the
* bottom of the screen to indicate the positioning of the darts
*/
int drawHand(int goToPower, int computer);
/**
* Converts a passed co-ordinates from screen co-ordinates to an offset within the dartboard
*/
Common::Point convertFromScreenToScoreCoords(const Common::Point &pt) const;
/**
* Return the score a dart at the given position will get
*/
int dartScore(const Common::Point &pt);
/**
* Draw a dart travelling to the board
*/
void drawDartThrow(const Common::Point &dartPos, int computer);
/**
* Looks for the passed number on the dartboard. If it finds it, it will return
* the co-ordinates of the center of the number
*/
int findNumberOnBoard(int aim, Common::Point &pt);
/**
* Calculates a position for the comptuer wants to throw, and then calculates where they
* actually did throw. The computer will not always hit what it's aiming it.
*/
void getComputerNumber(int playerNum, Common::Point &targetPos);
/**
* Throw one dart. If computer is 1 or 2, the computer will throw the dart, and user input
* will be ignored.
* @param computer 1=1st computer player, 2=2nd computer player
*/
int throwDart(int dartNum, int computer);
/**
* This will update the number of hits for the target score, as well as updating the
* score if it's closed
*/
void doCricketScoreHits(int player, int scoreIndex, int numHits);
/**
* Updates the score based upon what the dart hit
*/
void updateCricketScore(int player, int dartVal, int multiplier);
/**
* Draw the darts left
*/
void drawDartsLeft(int dartNum, int computer);
public:
Darts(SherlockEngine *vm);
/**
* Play the darts game
*/
void playDarts(GameType gameType);
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,34 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_debugger.h"
#include "sherlock/sherlock.h"
namespace Sherlock {
namespace Tattoo {
TattooDebugger::TattooDebugger(SherlockEngine *vm) : Debugger(vm) {
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,43 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_DEBUGGER_H
#define SHERLOCK_TATTOO_DEBUGGER_H
#include "sherlock/debugger.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class TattooDebugger : public Debugger {
public:
TattooDebugger(SherlockEngine *vm);
~TattooDebugger() override {}
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif /* SHERLOCK_TATTOO_DEBUGGER_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,239 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_FIXED_TEXT_H
#define SHERLOCK_TATTOO_FIXED_TEXT_H
#include "sherlock/fixed_text.h"
namespace Sherlock {
namespace Tattoo {
enum FixedTextId {
kFixedText_Inv1,
kFixedText_InvDesc1,
kFixedText_Inv2,
kFixedText_InvDesc2,
kFixedText_Inv3,
kFixedText_InvDesc3,
kFixedText_Inv4,
kFixedText_InvDesc4,
kFixedText_Inv5,
kFixedText_InvDesc5,
kFixedText_Inv6,
kFixedText_InvDesc6,
kFixedText_Inv7,
kFixedText_InvDesc7,
kFixedText_Inv8,
kFixedText_InvDesc8,
kFixedText_Open,
kFixedText_Look,
kFixedText_Talk,
kFixedText_Use,
kFixedText_Journal,
kFixedText_Inventory,
kFixedText_Options,
kFixedText_Solve,
kFixedText_With,
kFixedText_NoEffect,
kFixedText_NothingToSay,
kFixedText_PickedUp,
kFixedText_Page,
kFixedText_CloseJournal,
kFixedText_SearchJournal,
kFixedText_SaveJournal,
kFixedText_AbortSearch,
kFixedText_SearchBackwards,
kFixedText_SearchForwards,
kFixedText_TextNotFound,
kFixedText_DartsPlayerHolmes,
kFixedText_DartsPlayerJock,
kFixedText_DartsBull,
kFixedText_DartsCurrentRound,
kFixedText_DartsCurrentTotalPoints,
kFixedText_DartsCurrentDart,
kFixedText_DartsStartPressKey1,
kFixedText_DartsStartPressKey2,
kFixedText_DartsPressKey,
kFixedText_DartsGameOver,
kFixedText_DartsBusted,
kFixedText_DartsWins,
kFixedText_DartsScoredPoint,
kFixedText_DartsScoredPoints,
kFixedText_DartsHitSingle,
kFixedText_DartsHitDouble,
kFixedText_DartsHitTriple,
kFixedText_DartsHitSingleBullseye,
kFixedText_DartsHitDoubleBullseye,
kFixedText_DartsHitTripleBullseye,
kFixedText_Apply,
kFixedText_Water,
kFixedText_Heat,
kFixedText_LoadGame,
kFixedText_SaveGame,
kFixedText_Music,
kFixedText_SoundEffects,
kFixedText_Voices,
kFixedText_TextWindows,
kFixedText_TransparentMenus,
kFixedText_ChangeFont,
kFixedText_Off,
kFixedText_On,
kFixedText_Quit,
kFixedText_AreYouSureYou,
kFixedText_WishToQuit,
kFixedText_Yes,
kFixedText_No,
kFixedText_EnterPassword,
kFixedText_CorrectPassword,
kFixedText_WatsonsJournal,
kFixedText_JournalSaved,
// SH2: People names
kFixedText_People_SherlockHolmes,
kFixedText_People_DrWatson,
kFixedText_People_MrsHudson,
kFixedText_People_StanleyForbes,
kFixedText_People_MycroftHolmes,
kFixedText_People_Wiggins,
kFixedText_People_PoliceConstableBurns,
kFixedText_People_AugustusTrimble,
kFixedText_People_PoliceConstableDaley,
kFixedText_People_Matron,
kFixedText_People_SisterGrace,
kFixedText_People_PrestonMcCabe,
kFixedText_People_BobColleran,
kFixedText_People_JonasRigby,
kFixedText_People_PoliceConstableRoach,
kFixedText_People_JamesDewar,
kFixedText_People_SergeantJeremyDuncan,
kFixedText_People_InspectorGregson,
kFixedText_People_InspectorLestrade,
kFixedText_People_JesseNeedhem,
kFixedText_People_ArthurFleming,
kFixedText_People_MrThomasPratt,
kFixedText_People_MathildaTillieMason,
kFixedText_People_AdrianRussell,
kFixedText_People_EldridgeWhitney,
kFixedText_People_Hepplethwaite,
kFixedText_People_HoraceSilverbridge,
kFixedText_People_OldSherman,
kFixedText_People_MaxwellVerner,
kFixedText_People_MillicentRedding,
kFixedText_People_VirgilSilverbridge,
kFixedText_People_GeorgeOKeeffe,
kFixedText_People_LordDenysLawton,
kFixedText_People_Jenkins,
kFixedText_People_JockMahoney,
kFixedText_People_Bartender,
kFixedText_People_LadyCordeliaLockridge,
kFixedText_People_Pettigrew,
kFixedText_People_SirAveryFanshawe,
kFixedText_People_Hodgkins,
kFixedText_People_WilburBirdyHeywood,
kFixedText_People_JacobFarthington,
kFixedText_People_PhilipBledsoe,
kFixedText_People_SidneyFowler,
kFixedText_People_ProfessorTheodoreTotman,
kFixedText_People_RoseHinchem,
kFixedText_People_Tallboy,
kFixedText_People_EthlebertStitchRumsey,
kFixedText_People_CharlesFreedman,
kFixedText_People_NigelHemmings,
kFixedText_People_FairfaxCarter,
kFixedText_People_WilhelmII,
kFixedText_People_Wachthund,
kFixedText_People_JonathanWilson,
kFixedText_People_DavidLloydJones,
kFixedText_People_EdwardHargrove,
kFixedText_People_Misteray,
kFixedText_People_TheLascar,
kFixedText_People_Parrot,
kFixedText_People_VincentScarrett,
kFixedText_People_Alexandra,
kFixedText_People_QueenVictoria,
kFixedText_People_JohnBrown,
kFixedText_People_APatient1,
kFixedText_People_APatient2,
kFixedText_People_Patron,
kFixedText_People_QueenVictoria2,
kFixedText_People_PatientInWhite,
kFixedText_People_Lush,
kFixedText_People_Drunk,
kFixedText_People_Prostitute,
kFixedText_People_Mudlark,
kFixedText_People_Grinder,
kFixedText_People_Bouncer,
kFixedText_People_AgnesRatchet,
kFixedText_People_AloysiusRatchet,
kFixedText_People_RealEstateAgent,
kFixedText_People_CandyClerk,
kFixedText_People_Beadle,
kFixedText_People_Prussian,
kFixedText_People_MrsRowbottom,
kFixedText_People_MissLloydJones,
kFixedText_People_TavernPatron,
kFixedText_People_User,
kFixedText_People_Toby,
kFixedText_People_Stationer,
kFixedText_People_LawClerk,
kFixedText_People_MinistryClerk,
kFixedText_People_Bather,
kFixedText_People_Maid,
kFixedText_People_LadyFanshawe,
kFixedText_People_SidneyRatchet,
kFixedText_People_Boy,
kFixedText_People_Patron2,
kFixedText_People_ConstableBrit,
kFixedText_People_WagonDriver
};
struct FixedTextLanguageEntry {
Common::Language language;
const char *const *fixedTextArray;
};
class TattooFixedText: public FixedText {
private:
const FixedTextLanguageEntry *_curLanguageEntry;
public:
TattooFixedText(SherlockEngine *vm);
~TattooFixedText() override {}
/**
* Gets text
*/
const char *getText(int fixedTextId) override;
/**
* Get action message
*/
const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_inventory.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
TattooInventory::TattooInventory(SherlockEngine *vm) : Inventory(vm) {
_invShapes.resize(8);
}
TattooInventory::~TattooInventory() {
}
void TattooInventory::loadInv() {
// Exit if the inventory names are already loaded
if (_names.size() > 0)
return;
// Load the inventory names
Common::SeekableReadStream *stream = _vm->_res->load("invent.txt");
int count = stream->readByte();
for (int idx = 0; idx < count; ++idx) {
Common::String name;
char c;
while ((c = stream->readByte()) != 0)
name += c;
_names.push_back(name);
}
delete stream;
loadGraphics();
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_INVENTORY_H
#define SHERLOCK_TATTOO_INVENTORY_H
#include "sherlock/inventory.h"
namespace Sherlock {
namespace Tattoo {
class TattooInventory : public Inventory {
public:
TattooInventory(SherlockEngine *vm);
~TattooInventory() override;
/**
* Load the list of names the inventory items correspond to, if not already loaded,
* and then calls loadGraphics to load the associated graphics
*/
void loadInv() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_JOURNAL_H
#define SHERLOCK_TATTOO_JOURNAL_H
#include "sherlock/journal.h"
#include "sherlock/image_file.h"
namespace Sherlock {
namespace Tattoo {
enum JournalHighlight {
JH_NONE = -1, JH_CLOSE = 0, JH_SEARCH = 1, JH_SAVE = 2,
JH_SCROLL_LEFT = 3, JH_PAGE_LEFT = 4, JH_PAGE_RIGHT = 5, JH_SCROLL_RIGHT = 6, JH_THUMBNAIL = 7
};
class TattooJournal : public Journal {
private:
ImageFile *_journalImages;
int _selector, _oldSelector;
bool _wait;
bool _exitJournal;
uint32 _scrollingTimer;
int _savedIndex, _savedSub, _savedPage;
/**
* Load the list of journal locations
*/
void loadLocations();
/**
* Displays the controls used by the journal
* @param mode 0: Normal journal buttons, 1: Search interface
*/
void drawControls(int mode);
/**
* Draw the journal controls used by the journal
*/
void highlightJournalControls(bool slamIt);
/**
* Draw the journal controls used in search mode
*/
void highlightSearchControls(bool slamIt);
void drawScrollBar();
/**
* Check for and handle any pending keyboard events
*/
void handleKeyboardEvents();
/**
* Handle mouse presses on interface buttons
*/
void handleButtons();
/**
* Disable the journal controls
*/
void disableControls();
/**
* Get in a name to search through the journal for
*/
int getFindName(bool printError);
/**
* Save the journal to file
*/
void saveJournal();
/**
* Show a message that the journal has been saved to file
*/
void showSavedDialog();
public:
TattooJournal(SherlockEngine *vm);
~TattooJournal() override {}
/**
* Show the journal
*/
void show();
public:
/**
* Draw the journal background, frame, and interface buttons
*/
void drawFrame() override;
/**
* Records statements that are said, in the order which they are said. The player
* can then read the journal to review them
*/
void record(int converseNum, int statementNum, bool replyOnly = false) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,446 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_map.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
#define MAP_NAME_COLOR 131
#define CLOSEUP_STEPS 30
#define SCROLL_SPEED 16
/*-------------------------------------------------------------------------*/
void MapEntry::clear() {
_iconNum = -1;
_description = "";
}
/*-------------------------------------------------------------------------*/
TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) {
_iconImages = nullptr;
_bgFound = _oldBgFound = 0;
loadData();
}
int TattooMap::show() {
Debugger &debugger = *_vm->_debugger;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
Resources &res = *_vm->_res;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
int result = 0;
// Check if we need to keep track of how many times player has been to the map
for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) {
SceneTripEntry &entry = scene._sceneTripCounters[idx];
if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) {
if (--entry._numTimes == 0) {
_vm->setFlagsDirect(entry._flag);
scene._sceneTripCounters.remove_at(idx);
}
}
}
if (music._musicOn) {
// See if Holmes or Watson is the active character
Common::String song;
if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES))
// Player is Holmes
song = "Cue9";
else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC))
song = "Cue8";
else
song = "Cue7";
if (music.loadSong(song)) {
music.startSong();
}
}
screen.initPaletteFade(1364485);
// Load the custom mouse cursors for the map
ImageFile cursors("omouse.vgs");
events.setCursor(cursors[0]._frame);
events.warpMouse();
// Load the data for the map
_iconImages = new ImageFile("mapicons.vgs");
loadData();
// Load the palette
Common::SeekableReadStream *stream = res.load("map.pal");
stream->read(screen._cMap, Graphics::PALETTE_SIZE);
screen.translatePalette(screen._cMap);
delete stream;
// Load the map image and draw it to the back buffer
ImageFile *map = new ImageFile("map.vgs");
screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer1.SHblitFrom((*map)[0], Common::Point(0, 0));
screen.activateBackBuffer1();
delete map;
screen.clear();
screen.setPalette(screen._cMap);
drawMapIcons();
// Copy the map drawn in the back buffer to the secondary back buffer
screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer2.SHblitFrom(screen._backBuffer1);
// Set initial scroll position, forcing the map to be displayed
_targetScroll = _bigPos;
screen._currentScroll = Common::Point(-1, -1);
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-map")->setEnabled(true);
do {
// Allow for event processing and get the current mouse position
events.pollEventsAndWait();
events.setButtonState();
Common::Point mousePos = events.screenMousePos();
if (debugger._showAllLocations == LOC_REFRESH) {
drawMapIcons();
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
}
music.checkSongProgress();
checkMapNames(true);
if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6))
_targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6))
_targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6))
_targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6))
_targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (_targetScroll.x < 0)
_targetScroll.x = 0;
if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.width())
_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
if (_targetScroll.y < 0)
_targetScroll.y = 0;
if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.height())
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
// Check the action
if (events.actionHit()) {
Common::CustomEventType action = events.getAction();
switch (action) {
case kActionTattooMapTopLeft:
_targetScroll.x = 0;
_targetScroll.y = 0;
break;
case kActionTattooMapBottomRight:
_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
break;
case kActionTattooMapUp:
_targetScroll.y -= SHERLOCK_SCREEN_HEIGHT;
if (_targetScroll.y < 0)
_targetScroll.y = 0;
break;
case kActionTattooMapDown:
_targetScroll.y += SHERLOCK_SCREEN_HEIGHT;
if (_targetScroll.y > (screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT))
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
break;
case kActionTattooMapSelect:
events._pressed = false;
events._oldButtons = 0;
events._released = true;
break;
default:
break;
}
}
// Handle any scrolling of the map
if (screen._currentScroll != _targetScroll) {
// If there is a Text description being displayed, restore the area under it
_mapTooltip.erase();
screen._currentScroll = _targetScroll;
checkMapNames(false);
screen.slamArea(_targetScroll.x, _targetScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
// Handling if a location has been clicked on
if (events._released && _bgFound != -1) {
// If there is a Text description being displayed, restore the area under it
_mapTooltip.erase();
// Save the current scroll position on the map
_bigPos = screen._currentScroll;
showCloseUp(_bgFound);
result = _bgFound + 1;
}
} while (!result && !_vm->shouldQuit());
keymapper->getKeymap("tattoo-map")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
music.stopMusic();
events.clearEvents();
_mapTooltip.banishWindow();
// Reset the back buffers back to standard size
screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
screen.activateBackBuffer1();
return result;
}
void TattooMap::loadData() {
Resources &res = *_vm->_res;
char c;
Common::SeekableReadStream *stream = res.load("map.txt");
_data.resize(100);
for (uint idx = 0; idx < _data.size(); ++idx)
_data[idx].clear();
do
{
// Find the start of the number
do {
c = stream->readByte();
if (stream->pos() >= stream->size())
break;
} while (c < '0' || c > '9');
if (stream->pos() >= stream->size())
break;
// Get the scene number
Common::String locStr;
locStr += c;
while ((c = stream->readByte()) != '.')
locStr += c;
MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1];
// Get the location name
while (stream->readByte() != '"')
;
while ((c = stream->readByte()) != '"')
mapEntry._description += c;
// Find the ( specifying the (X,Y) position of the Icon
while (stream->readByte() != '(')
;
// Get the X Position of the icon
Common::String numStr;
while ((c = stream->readByte()) != ',')
numStr += c;
mapEntry.x = atoi(numStr.c_str());
// Get the Y position of the icon
numStr = "";
while ((c = stream->readByte()) != ')')
numStr += c;
mapEntry.y = atoi(numStr.c_str());
// Find and get the location's icon number
while (stream->readByte() != '#')
;
Common::String iconStr;
while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r')
iconStr += c;
mapEntry._iconNum = atoi(iconStr.c_str()) - 1;
} while (stream->pos() < stream->size());
delete stream;
}
void TattooMap::drawMapIcons() {
Debugger &debugger = *_vm->_debugger;
Screen &screen = *_vm->_screen;
for (uint idx = 0; idx < _data.size(); ++idx) {
if (debugger._showAllLocations != LOC_DISABLED)
_vm->setFlagsDirect(idx + 1);
if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
MapEntry &mapEntry = _data[idx];
ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
screen._backBuffer1.SHtransBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2,
mapEntry.y - img._height / 2));
}
}
if (debugger._showAllLocations == LOC_REFRESH)
debugger._showAllLocations = LOC_ALL;
}
void TattooMap::checkMapNames(bool slamIt) {
Events &events = *_vm->_events;
Common::Point mapPos = events.mousePos();
// See if the mouse is pointing at any of the map locations
_bgFound = -1;
for (uint idx = 0; idx < _data.size(); ++idx) {
if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
MapEntry &mapEntry = _data[idx];
ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2,
mapEntry.x + img._width / 2, mapEntry.y + img._height / 2);
if (r.contains(mapPos)) {
_bgFound = idx;
break;
}
}
}
// Handle updating the tooltip
if (_bgFound != _oldBgFound) {
if (_bgFound == -1) {
_mapTooltip.setText("");
} else {
const Common::String &desc = _data[_bgFound]._description;
_mapTooltip.setText(desc);
}
_oldBgFound = _bgFound;
}
_mapTooltip.handleEvents();
if (slamIt)
_mapTooltip.draw();
}
void TattooMap::restoreArea(const Common::Rect &bounds) {
Screen &screen = *_vm->_screen;
Common::Rect r = bounds;
r.clip(Common::Rect(0, 0, screen._backBuffer1.width(), screen._backBuffer1.height()));
if (!r.isEmpty())
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r);
}
void TattooMap::showCloseUp(int closeUpNum) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
// Hide the cursor
events.hideCursor();
// Get the closeup images
Common::Path fname(Common::String::format("res%02d.vgs", closeUpNum + 1));
ImageFile pic(fname);
Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100);
Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS,
(SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS);
Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1);
int size = 64;
int n = 256;
int deltaVal = 512;
bool minimize = false;
int scaleVal, newSize;
do {
scaleVal = n;
newSize = pic[0].sDrawXSize(n);
if (newSize > size) {
if (minimize)
deltaVal /= 2;
n += deltaVal;
} else {
minimize = true;
deltaVal /= 2;
n -= deltaVal;
if (n < 1)
n = 1;
}
} while (deltaVal && size != newSize);
int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS;
for (int step = 0; step < CLOSEUP_STEPS; ++step) {
Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal));
Common::Point pt(screen._currentScroll.x + closeUp.x / 100 - picSize.x / 2,
screen._currentScroll.y + closeUp.y / 100 - picSize.y / 2);
restoreArea(oldBounds);
screen._backBuffer1.SHtransBlitFrom(pic[0], pt, false, scaleVal);
screen.slamRect(oldBounds);
screen.slamArea(pt.x, pt.y, picSize.x, picSize.y);
oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1);
closeUp += delta;
scaleVal += deltaScale;
events.wait(1);
}
// Handle final drawing of closeup
Common::Rect r(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2,
screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2,
screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width,
screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height);
restoreArea(oldBounds);
screen._backBuffer1.SHtransBlitFrom(pic[0], Common::Point(r.left, r.top));
screen.slamRect(oldBounds);
screen.slamRect(r);
events.wait(60);
events.showCursor();
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,92 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_MAP_H
#define SHERLOCK_TATTOO_MAP_H
#include "common/scummsys.h"
#include "sherlock/map.h"
#include "sherlock/resources.h"
#include "sherlock/surface.h"
#include "sherlock/tattoo/widget_tooltip.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
struct MapEntry : Common::Point {
int _iconNum;
Common::String _description;
MapEntry() : Common::Point(), _iconNum(-1) {}
MapEntry(int posX, int posY, int iconNum) : Common::Point(posX, posY), _iconNum(iconNum) {}
void clear();
};
class TattooMap : public Map {
private:
Common::Array<MapEntry> _data;
ImageFile *_iconImages;
int _bgFound, _oldBgFound;
WidgetMapTooltip _mapTooltip;
Common::Point _targetScroll;
/**
* Load data needed for the map
*/
void loadData();
/**
* Draws all available location icons onto the back buffer
*/
void drawMapIcons();
/**
* Draws the location names of whatever the mouse moves over on the map
*/
void checkMapNames(bool slamIt);
/**
* Restores an area of the map background
*/
void restoreArea(const Common::Rect &bounds);
/**
* This will load a specified close up and zoom it up to the middle of the screen
*/
void showCloseUp(int closeUpNum);
public:
TattooMap(SherlockEngine *vm);
~TattooMap() override {}
/**
* Show the map
*/
int show() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_PEOPLE_H
#define SHERLOCK_TATTOO_PEOPLE_H
#include "common/scummsys.h"
#include "common/stack.h"
#include "sherlock/people.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
// Animation sequence identifiers for characters
enum TattooSequences {
// Walk Sequences Numbers for NPCs
WALK_UP = 0,
WALK_UPRIGHT = 1,
WALK_RIGHT = 2,
WALK_DOWNRIGHT = 3,
WALK_DOWN = 4,
WALK_DOWNLEFT = 5,
WALK_LEFT = 6,
WALK_UPLEFT = 7,
// Stop Sequences Numbers for NPCs
STOP_UP = 8,
STOP_UPRIGHT = 9,
STOP_RIGHT = 10,
STOP_DOWNRIGHT = 11,
STOP_DOWN = 12,
STOP_DOWNLEFT = 13,
STOP_LEFT = 14,
STOP_UPLEFT = 15,
// NPC Talk Sequence Numbers
TALK_UPRIGHT = 16,
TALK_RIGHT = 17,
TALK_DOWNRIGHT = 18,
TALK_DOWNLEFT = 19,
TALK_LEFT = 20,
TALK_UPLEFT = 21,
// NPC Listen Sequence Numbers
LISTEN_UPRIGHT = 22,
LISTEN_RIGHT = 23,
LISTEN_DOWNRIGHT = 24,
LISTEN_DOWNLEFT = 25,
LISTEN_LEFT = 26,
LISTEN_UPLEFT = 27
};
enum NpcPath {
NPCPATH_SET_DEST = 1,
NPCPATH_PAUSE = 2,
NPCPATH_SET_TALK_FILE = 3,
NPCPATH_CALL_TALK_FILE = 4,
NPCPATH_TAKE_NOTES = 5,
NPCPATH_FACE_HOLMES = 6,
NPCPATH_PATH_LABEL = 7,
NPCPATH_GOTO_LABEL = 8,
NPCPATH_IFFLAG_GOTO_LABEL = 9
};
struct SavedNPCPath {
byte _path[MAX_NPC_PATH];
int _npcIndex;
int _npcPause;
Point32 _position;
int _npcFacing;
bool _lookHolmes;
SavedNPCPath();
SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Point32 &position,
int npcFacing, bool lookHolmes);
};
class TattooPerson: public Person {
private:
Point32 _nextDest;
private:
bool checkCollision() const;
/**
* Free the alternate graphics used by NPCs
*/
void freeAltGraphics();
protected:
/**
* Get the source position for a character potentially affected by scaling
*/
Common::Point getSourcePoint() const override;
public:
Common::Stack<SavedNPCPath> _pathStack;
int _npcIndex;
int _npcPause;
byte _npcPath[MAX_NPC_PATH];
bool _npcMoved;
int _npcFacing;
bool _resetNPCPath;
int _savedNpcSequence;
int _savedNpcFrame;
int _tempX;
int _tempScaleVal;
bool _updateNPCPath;
bool _lookHolmes;
public:
TattooPerson();
~TattooPerson() override;
/**
* Clear the NPC related data
*/
void clearNPC();
/**
* Called from doBgAnim to move NPCs along any set paths. If an NPC is paused in his path,
* he will remain paused until his pause timer runs out. If he is walking somewhere,
* he will continue walking there until he reaches the dest position. When an NPC stops moving,
* the next element of his path is processed.
*
* The path is an array of bytes with control codes followed by their parameters as needed.
*/
void updateNPC();
/**
* Push the NPC's path data onto the path stack for when a talk file moves the NPC that
* has some control codes.
*/
void pushNPCPath();
/**
* Pull an NPC's path data that has been previously saved on the path stack for that character.
* There are two possibilities for when the NPC was interrupted, and both are handled differently:
* 1) The NPC was paused at a position
* If the NPC didn't move, we can just restore his pause counter and exit. But if he did move,
* he must return to that position, and the path index must be reset to the pause he was executing.
* This means that the index must be decremented by 3
* 2) The NPC was in route to a position
* He must be set to walk to that position again. This is done by moving the path index
* so that it points to the code that set the NPC walking there in the first place.
* The regular calls to updateNPC will handle the rest
*/
void pullNPCPath();
/**
* Checks a sprite associated with an NPC to see if the frame sequence specified
* in the sequence number uses alternate graphics, and if so if they need to be loaded
*/
void checkWalkGraphics();
/**
* Synchronize the data for a savegame
*/
void synchronize(Serializer &s);
/**
* Walk Holmes to the NPC
*/
void walkHolmesToNPC();
/**
* Walk both the specified character and Holmes to specified destination positions
*/
void walkBothToCoords(const PositionFacing &holmesDest, const PositionFacing &npcDest);
/**
* This adjusts the sprites position, as well as its animation sequence:
*/
void adjustSprite() override;
/**
* Bring a moving character to a standing position
*/
void gotoStand() override;
/**
* Set the variables for moving a character from one poisition to another
* in a straight line
*/
void setWalking() override;
/**
* Walk to the co-ordinates passed, and then face the given direction
*/
void walkToCoords(const Point32 &destPos, int destDir) override;
/**
* Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker
* so that it points to the beginning of the sequence number's talk sequence in the object's
* sequence buffer
* @param seq Which sequence to use (if there's more than 1)
* @remarks 1: First talk seq, 2: second talk seq, etc.
*/
void setObjTalkSequence(int seq) override;
/**
* Center the visible screen so that the person is in the center of the screen
*/
void centerScreenOnPerson() override;
};
class TattooPeople : public People {
public:
TattooPeople(SherlockEngine *vm);
~TattooPeople() override {}
TattooPerson &operator[](PeopleId id) { return *(TattooPerson *)_data[id]; }
TattooPerson &operator[](int idx) { return *(TattooPerson *)_data[idx]; }
/**
* Restore any saved NPC walk path data from any of the NPCs
*/
void pullNPCPaths();
/**
* Finds the scene background object corresponding to a specified speaker
*/
int findSpeaker(int speaker) override;
/**
* Synchronize the data for a savegame
*/
void synchronize(Serializer &s) override;
/**
* Change the sequence of the scene background object associated with the specified speaker.
*/
void setTalkSequence(int speaker, int sequenceNum = 1) override;
/**
* Load the walking images for Sherlock
*/
bool loadWalk() override;
/**
* Restrict passed point to zone using Sherlock's positioning rules
*/
const Common::Point restrictToZone(int zoneId, const Common::Point &destPos) override;
/**
* If the specified speaker is a background object, it will set it so that it uses
* the Listen Sequence (specified by the sequence number). If the current sequence
* has an Allow Talk Code in it, the _gotoSeq field will be set so that the object
* begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code,
* the Listen Sequence will begin immediately.
* @param speaker Who is speaking
* @param sequenceNum Which listen sequence to use
*/
void setListenSequence(int speaker, int sequenceNum = 1) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,130 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_resources.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
namespace Sherlock {
namespace Tattoo {
const PeopleData PEOPLE_DATA[TATTOO_MAX_PEOPLE] = {
{ "HOLM", kFixedText_People_SherlockHolmes },
{ "WATS", kFixedText_People_DrWatson },
{ "HUDS", kFixedText_People_MrsHudson },
{ "FORB", kFixedText_People_StanleyForbes },
{ "MYCR", kFixedText_People_MycroftHolmes },
{ "WIGG", kFixedText_People_Wiggins },
{ "BURN", kFixedText_People_PoliceConstableBurns },
{ "TRIM", kFixedText_People_AugustusTrimble },
{ "DALE", kFixedText_People_PoliceConstableDaley },
{ "MATR", kFixedText_People_Matron },
{ "GRAC", kFixedText_People_SisterGrace },
{ "MCCA", kFixedText_People_PrestonMcCabe },
{ "COLL", kFixedText_People_BobColleran },
{ "JONA", kFixedText_People_JonasRigby },
{ "ROAC", kFixedText_People_PoliceConstableRoach },
{ "DEWA", kFixedText_People_JamesDewar },
{ "JERE", kFixedText_People_SergeantJeremyDuncan },
{ "GREG", kFixedText_People_InspectorGregson },
{ "LEST", kFixedText_People_InspectorLestrade },
{ "NEED", kFixedText_People_JesseNeedhem },
{ "FLEM", kFixedText_People_ArthurFleming },
{ "PRAT", kFixedText_People_MrThomasPratt },
{ "TILL", kFixedText_People_MathildaTillieMason },
{ "RUSS", kFixedText_People_AdrianRussell },
{ "WHIT", kFixedText_People_EldridgeWhitney },
{ "HEPP", kFixedText_People_Hepplethwaite },
{ "HORA", kFixedText_People_HoraceSilverbridge },
{ "SHER", kFixedText_People_OldSherman },
{ "VERN", kFixedText_People_MaxwellVerner },
{ "REDD", kFixedText_People_MillicentRedding },
{ "VIRG", kFixedText_People_VirgilSilverbridge },
{ "GEOR", kFixedText_People_GeorgeOKeeffe },
{ "LAWT", kFixedText_People_LordDenysLawton },
{ "JENK", kFixedText_People_Jenkins },
{ "JOCK", kFixedText_People_JockMahoney },
{ "BART", kFixedText_People_Bartender },
{ "LADY", kFixedText_People_LadyCordeliaLockridge },
{ "PETT", kFixedText_People_Pettigrew },
{ "FANS", kFixedText_People_SirAveryFanshawe },
{ "HODG", kFixedText_People_Hodgkins },
{ "WILB", kFixedText_People_WilburBirdyHeywood },
{ "JACO", kFixedText_People_JacobFarthington },
{ "BLED", kFixedText_People_PhilipBledsoe },
{ "FOWL", kFixedText_People_SidneyFowler },
{ "PROF", kFixedText_People_ProfessorTheodoreTotman },
{ "ROSE", kFixedText_People_RoseHinchem },
{ "TALL", kFixedText_People_Tallboy },
{ "STIT", kFixedText_People_EthlebertStitchRumsey },
{ "FREE", kFixedText_People_CharlesFreedman },
{ "HEMM", kFixedText_People_NigelHemmings },
{ "CART", kFixedText_People_FairfaxCarter },
{ "WILH", kFixedText_People_WilhelmII },
{ "WACH", kFixedText_People_Wachthund },
{ "WILS", kFixedText_People_JonathanWilson },
{ "DAVE", kFixedText_People_DavidLloydJones },
{ "HARG", kFixedText_People_EdwardHargrove },
{ "MORI", kFixedText_People_Misteray },
{ "LASC", kFixedText_People_TheLascar },
{ "PARR", kFixedText_People_Parrot },
{ "SCAR", kFixedText_People_VincentScarrett },
{ "ALEX", kFixedText_People_Alexandra },
{ "QUEE", kFixedText_People_QueenVictoria },
{ "JOHN", kFixedText_People_JohnBrown },
{ "PAT1", kFixedText_People_APatient1 },
{ "PAT2", kFixedText_People_APatient2 },
{ "PATR", kFixedText_People_Patron },
{ "QUEN", kFixedText_People_QueenVictoria },
{ "WITE", kFixedText_People_PatientInWhite },
{ "LUSH", kFixedText_People_Lush },
{ "DRNK", kFixedText_People_Drunk },
{ "PROS", kFixedText_People_Prostitute },
{ "MUDL", kFixedText_People_Mudlark },
{ "GRIN", kFixedText_People_Grinder },
{ "BOUN", kFixedText_People_Bouncer },
{ "RATC", kFixedText_People_AgnesRatchet },
{ "ALOY", kFixedText_People_AloysiusRatchet },
{ "REAL", kFixedText_People_RealEstateAgent },
{ "CAND", kFixedText_People_CandyClerk },
{ "BEAD", kFixedText_People_Beadle },
{ "PRUS", kFixedText_People_Prussian },
{ "ROWB", kFixedText_People_MrsRowbottom },
{ "MSLJ", kFixedText_People_MissLloydJones },
{ "TPAT", kFixedText_People_TavernPatron },
{ "USER", kFixedText_People_User },
{ "TOBY", kFixedText_People_Toby },
{ "STAT", kFixedText_People_Stationer },
{ "CLRK", kFixedText_People_LawClerk },
{ "CLER", kFixedText_People_MinistryClerk },
{ "BATH", kFixedText_People_Bather },
{ "MAID", kFixedText_People_Maid },
{ "LADF", kFixedText_People_LadyFanshawe },
{ "SIDN", kFixedText_People_SidneyRatchet },
{ "BOYO", kFixedText_People_Boy },
{ "PTR2", kFixedText_People_Patron2 },
{ "BRIT", kFixedText_People_ConstableBrit },
{ "DROV", kFixedText_People_WagonDriver }
};
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,44 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_RESOURCES_H
#define SHERLOCK_TATTOO_RESOURCES_H
#include "common/scummsys.h"
namespace Sherlock {
namespace Tattoo {
#define TATTOO_MAX_PEOPLE 96
struct PeopleData {
const char *portrait;
int fixedTextId;
};
extern const PeopleData PEOPLE_DATA[TATTOO_MAX_PEOPLE];
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,886 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/events.h"
#include "sherlock/people.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
const int FS_TRANS[8] = {
STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT
};
/*----------------------------------------------------------------*/
struct ShapeEntry {
Object *_shape;
TattooPerson *_person;
bool _isAnimation;
int _yp;
int _ordering;
ShapeEntry(TattooPerson *person, int yp, int ordering) :
_shape(nullptr), _person(person), _yp(yp), _isAnimation(false), _ordering(ordering) {}
ShapeEntry(Object *shape, int yp, int ordering) :
_shape(shape), _person(nullptr), _yp(yp), _isAnimation(false), _ordering(ordering) {}
ShapeEntry(int yp, int ordering) :
_shape(nullptr), _person(nullptr), _yp(yp), _isAnimation(true), _ordering(ordering) {}
};
typedef Common::List<ShapeEntry> ShapeList;
static bool sortImagesY(const ShapeEntry &s1, const ShapeEntry &s2) {
// Objects are order by the calculated Y position first and then, when both are equal,
// by the order in which the entries were added
return s1._yp < s2._yp || (s1._yp == s2._yp && s1._ordering < s2._ordering);
}
/*----------------------------------------------------------------*/
TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm), _labWidget(vm) {
_labTableScene = false;
}
bool TattooScene::loadScene(const Common::Path &filename) {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
// If we're going to the first game scene after the intro sequence, flag it as finished
if (vm._runningProlog && _currentScene == STARTING_GAME_SCENE) {
vm._runningProlog = false;
events.showCursor();
talk._talkToAbort = false;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-prolog")->setEnabled(false);
}
// Check if it's a scene we need to keep track of how many times we've visited
for (int idx = (int)_sceneTripCounters.size() - 1; idx >= 0; --idx) {
if (_sceneTripCounters[idx]._sceneNumber == _currentScene) {
if (--_sceneTripCounters[idx]._numTimes == 0) {
_vm->setFlags(_sceneTripCounters[idx]._flag);
_sceneTripCounters.remove_at(idx);
}
}
}
// Handle loading music for the scene
if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3)
music._nextSongName = Common::String::format("res%02d", _currentScene);
// Set the NPC paths for the scene
setNPCPath(WATSON);
// If it's a new song, then start it up
if (music._currentSongName.compareToIgnoreCase(music._nextSongName)) {
// WORKAROUND: Stop playing music after Diogenes fire scene in the intro,
// since it overlaps slightly into the next scene
if (talk._scriptName == "prol80p" && _currentScene == 80) {
music.stopMusic();
events.wait(5);
}
if (music.loadSong(music._nextSongName)) {
if (music._musicOn)
music.startSong();
}
}
bool result = Scene::loadScene(filename);
if (_currentScene != STARTING_INTRO_SCENE) {
// Set the menu/ui mode and whether we're in a lab table close-up scene
_labTableScene = _currentScene > 91 && _currentScene < 100;
ui._menuMode = _labTableScene ? LAB_MODE : STD_MODE;
if (_labTableScene)
ui.addFixedWidget(&_labWidget);
}
return result;
}
void TattooScene::drawAllShapes() {
TattooPeople &people = *(TattooPeople *)_vm->_people;
Screen &screen = *_vm->_screen;
ShapeList shapeList;
int ordering = 0;
// Draw all objects and animations that are set to behind
screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
// Draw all active shapes which are behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE && obj._misc == BEHIND) {
if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD)
screen._backBuffer1.SHblitFrom(*obj._imageFrame, obj._position);
else
screen._backBuffer1.SHtransBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, obj._scaleVal);
}
}
// Draw the animation if it is behind the person
if (_activeCAnim.active() && _activeCAnim._zPlacement == BEHIND)
screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
(_activeCAnim._flags & 4) >> 1, _activeCAnim._scaleVal);
screen.resetDisplayBounds();
// Queue drawing of all objects that are set to NORMAL.
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE && (obj._misc == NORMAL_BEHIND || obj._misc == NORMAL_FORWARD)) {
if (obj._scaleVal == SCALE_THRESHOLD)
shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->_offset.y +
obj._imageFrame->_height, ordering++));
else
shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->sDrawYOffset(obj._scaleVal) +
obj._imageFrame->sDrawYSize(obj._scaleVal), ordering++));
}
}
// Queue drawing the animation if it is NORMAL and can fall in front of, or behind the people
if (_activeCAnim.active() && (_activeCAnim._zPlacement == NORMAL_BEHIND || _activeCAnim._zPlacement == NORMAL_FORWARD)) {
if (_activeCAnim._scaleVal == SCALE_THRESHOLD)
shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame._offset.y +
_activeCAnim._imageFrame._height, ordering++));
else
shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame.sDrawYOffset(_activeCAnim._scaleVal) +
_activeCAnim._imageFrame.sDrawYSize(_activeCAnim._scaleVal), ordering++));
}
// Queue all active characters for drawing
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER && people[idx]._walkLoaded)
shapeList.push_back(ShapeEntry(&people[idx], people[idx]._position.y / FIXED_INT_MULTIPLIER, ordering++));
}
// Sort the list
Common::sort(shapeList.begin(), shapeList.end(), sortImagesY);
// Draw the list of shapes in order
for (ShapeList::iterator i = shapeList.begin(); i != shapeList.end(); ++i) {
ShapeEntry &se = *i;
if (se._shape) {
// it's a bg shape
if (se._shape->_quickDraw && se._shape->_scaleVal == SCALE_THRESHOLD)
screen._backBuffer1.SHblitFrom(*se._shape->_imageFrame, se._shape->_position);
else
screen._backBuffer1.SHtransBlitFrom(*se._shape->_imageFrame, se._shape->_position,
se._shape->_flags & OBJ_FLIPPED, se._shape->_scaleVal);
} else if (se._isAnimation) {
// It's an active animation
screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
(_activeCAnim._flags & 4) >> 1, _activeCAnim._scaleVal);
} else {
// Drawing person
TattooPerson &p = *se._person;
p._tempX = p._position.x / FIXED_INT_MULTIPLIER;
p._tempScaleVal = getScaleVal(p._position);
Common::Point adjust = p._adjust;
if (p._tempScaleVal == SCALE_THRESHOLD) {
p._tempX += adjust.x;
screen._backBuffer1.SHtransBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
- p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, p._tempScaleVal);
} else {
if (adjust.x) {
if (!p._tempScaleVal)
++p._tempScaleVal;
if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.x)
--adjust.x;
adjust.x = adjust.x * SCALE_THRESHOLD / p._tempScaleVal;
if (p._tempScaleVal >= SCALE_THRESHOLD)
++adjust.x;
p._tempX += adjust.x;
}
if (adjust.y) {
if (!p._tempScaleVal)
p._tempScaleVal++;
if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.y)
--adjust.y;
adjust.y = adjust.y * SCALE_THRESHOLD / p._tempScaleVal;
if (p._tempScaleVal >= SCALE_THRESHOLD)
++adjust.y;
}
screen._backBuffer1.SHtransBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
- p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, p._tempScaleVal);
}
}
}
// Draw all objects & canimations that are set to FORWARD.
// Draw all static and active shapes that are FORWARD
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE && obj._misc == FORWARD) {
if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD)
screen._backBuffer1.SHblitFrom(*obj._imageFrame, obj._position);
else
screen._backBuffer1.SHtransBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, obj._scaleVal);
}
}
// Draw the canimation if it is set as FORWARD
if (_activeCAnim.active() && _activeCAnim._zPlacement == FORWARD)
screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, _activeCAnim._scaleVal);
// Draw all NO_SHAPE shapes which have their flag bits clear
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == NO_SHAPE && (obj._flags & 1) == 0)
screen._backBuffer1.fillRect(obj.getNoShapeBounds(), 15);
}
}
void TattooScene::paletteLoaded() {
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ui.setupBGArea(screen._cMap);
ui.initScrollVars();
}
void TattooScene::checkBgShapes() {
// Call the base scene method to handle bg shapes
Scene::checkBgShapes();
// Check for any active playing animation
if (_activeCAnim.active() && _activeCAnim._zPlacement != REMOVE) {
switch (_activeCAnim._flags & 3) {
case 0:
_activeCAnim._zPlacement = BEHIND;
break;
case 1:
_activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame._frame.h - 1)) ?
NORMAL_FORWARD : NORMAL_BEHIND;
break;
case 2:
_activeCAnim._zPlacement = FORWARD;
break;
default:
break;
}
}
}
void TattooScene::freeScene() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Scene::freeScene();
// Delete any scene overlays that were used by the scene
delete ui._mask;
delete ui._mask1;
ui._mask = ui._mask1 = nullptr;
}
void TattooScene::doBgAnimCheckCursor() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
// If we're in Look Mode, make sure the cursor is the magnifying glass
if (ui._menuMode == LOOK_MODE && events.getCursor() != MAGNIFY)
events.setCursor(MAGNIFY);
// See if the mouse is over any of the arrow zones, and if so, change the cursor to the correct
// arrow cursor indicating the direcetion of the exit
if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) {
CursorId cursorId = ARROW;
if (ui._menuMode == STD_MODE && ui._arrowZone != -1 && _currentScene != 90) {
for (uint idx = 0; idx < _exits.size(); ++idx) {
Exit &exit = _exits[idx];
if (exit.contains(mousePos))
cursorId = (CursorId)(exit._image + EXIT_ZONES_START);
}
}
events.setCursor(cursorId);
} else {
events.animateCursorIfNeeded();
}
}
void TattooScene::doBgAnim() {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
TattooPeople &people = *(TattooPeople *)_vm->_people;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui);
doBgAnimCheckCursor();
music.checkSongProgress();
talk._talkToAbort = false;
// Check the characters and sprites for updates
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER)
people[idx].checkSprite();
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE)
_bgShapes[idx].checkObject();
}
// If one of the objects has signalled a call to a talk file, to go to another scene, exit immediately
if (_goToScene != -1)
return;
// Erase any affected background areas
ui.doBgAnimEraseBackground();
doBgAnimUpdateBgObjectsAndAnim();
doBgAnimDrawSprites();
ui.drawInterface();
if (ui._creditsWidget.active())
ui._creditsWidget.blitCredits();
if (screen._flushScreen) {
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
screen._flushScreen = false;
}
screen._flushScreen = false;
_doBgAnimDone = true;
ui._drawMenu = false;
// Handle drawing tooltips
if (ui._menuMode == STD_MODE || ui._menuMode == LAB_MODE)
ui._tooltipWidget.draw();
if (!ui._postRenderWidgets.empty()) {
for (WidgetList::iterator i = ui._postRenderWidgets.begin(); i != ui._postRenderWidgets.end(); ++i)
(*i)->draw();
ui._postRenderWidgets.clear();
}
if (!vm._fastMode)
events.wait(3);
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._updateNPCPath)
people[idx].updateNPC();
}
}
void TattooScene::doBgAnimUpdateBgObjectsAndAnim() {
People &people = *_vm->_people;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE)
obj.adjustObject();
}
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER)
people[idx].adjustSprite();
}
// Flag the bg shapes which need to be redrawn
checkBgShapes();
drawAllShapes();
ui.drawMaskArea(true);
}
void TattooScene::updateBackground() {
TattooPeople &people = *(TattooPeople *)_vm->_people;
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Scene::updateBackground();
ui.drawMaskArea(false);
screen._flushScreen = true;
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
TattooPerson &p = people[idx];
if (p._type != INVALID) {
if (_goToScene == -1 || _cAnim.size() == 0) {
if (p._type == REMOVE) {
screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y);
p._type = INVALID;
} else {
if (p._tempScaleVal == SCALE_THRESHOLD) {
screen.flushImage(p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
- p._imageFrame->_width), &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y);
} else {
int ts = p._imageFrame->sDrawYSize(p._tempScaleVal);
int ty = p._position.y / FIXED_INT_MULTIPLIER - ts;
screen.flushScaleImage(p._imageFrame, Common::Point(p._tempX, ty),
&p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y, p._tempScaleVal);
}
}
}
}
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) {
if (_goToScene == -1) {
if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
if (obj._type == REMOVE)
obj._type = INVALID;
}
}
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (_goToScene == -1) {
if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
screen.slamRect(obj.getNoShapeBounds());
screen.slamRect(obj.getOldBounds());
} else if (obj._type == HIDE_SHAPE) {
if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
obj._type = HIDDEN;
}
}
}
screen._flushScreen = false;
}
void TattooScene::doBgAnimDrawSprites() {
TattooPeople &people = *(TattooPeople *)_vm->_people;
Screen &screen = *_vm->_screen;
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
TattooPerson &person = people[idx];
if (person._type != INVALID) {
if (_goToScene == -1 || _cAnim.size() == 0) {
if (person._type == REMOVE) {
screen.slamRect(person.getOldBounds());
person._type = INVALID;
} else {
if (person._tempScaleVal == SCALE_THRESHOLD) {
screen.flushImage(person._imageFrame, Common::Point(person._tempX, person._position.y / FIXED_INT_MULTIPLIER
- person.frameHeight()), &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y);
} else {
int ts = person._imageFrame->sDrawYSize(person._tempScaleVal);
int ty = person._position.y / FIXED_INT_MULTIPLIER - ts;
screen.flushScaleImage(person._imageFrame, Common::Point(person._tempX, ty),
&person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y, person._tempScaleVal);
}
}
}
}
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) {
if (_goToScene == -1) {
if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
if (obj._type == REMOVE)
obj._type = INVALID;
}
}
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
if (_goToScene == -1) {
if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
screen.slamRect(obj.getNoShapeBounds());
screen.slamRect(obj.getOldBounds());
} else if (obj._type == HIDE_SHAPE) {
if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
obj._type = HIDDEN;
}
}
}
if (_activeCAnim.active() || _activeCAnim._zPlacement == REMOVE) {
if (_activeCAnim._zPlacement != REMOVE) {
screen.flushImage(&_activeCAnim._imageFrame, _activeCAnim._position, _activeCAnim._oldBounds, _activeCAnim._scaleVal);
} else {
screen.slamRect(_activeCAnim._removeBounds);
_activeCAnim._removeBounds = Common::Rect(0, 0, 0, 0);
_activeCAnim._zPlacement = -1; // Reset _zPlacement so we don't REMOVE again
}
}
}
int TattooScene::getScaleVal(const Point32 &pt) {
bool found = false;
int result = SCALE_THRESHOLD;
Common::Point pos(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER);
for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) {
ScaleZone &sz = _scaleZones[idx];
if (sz.contains(pos)) {
int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber;
result = 25600L / n;
// CHECKME: Shouldn't we set 'found' at this place?
}
}
// If it wasn't found, we may be off screen to the left or right, so find the scale zone
// that would apply to the y val passed in disregarding the x
if (!found) {
for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) {
ScaleZone &sz = _scaleZones[idx];
if (pos.y >= sz.top && pos.y < sz.bottom) {
int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber;
result = 25600L / n;
}
}
}
return result;
}
int TattooScene::startCAnim(int cAnimNum, int playRate) {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
TattooPeople &people = *(TattooPeople *)_vm->_people;
Resources &res = *_vm->_res;
Talk &talk = *_vm->_talk;
UserInterface &ui = *_vm->_ui;
// Exit immediately if the anim number is out of range, or the anim doesn't have a position specified
if (cAnimNum < 0 || cAnimNum >= (int)_cAnim.size() || _cAnim[cAnimNum]._position.x == -1)
// Return out of range error
return -1;
// Get the co-ordinates that the Player & NPC #1 must walk to and end on
CAnim &cAnim = _cAnim[cAnimNum];
PositionFacing goto1 = cAnim._goto[0];
PositionFacing goto2 = cAnim._goto[1];
PositionFacing teleport1 = cAnim._teleport[0];
PositionFacing teleport2 = cAnim._teleport[1];
// See if the Player must walk to a position before the animation starts
SpriteType savedPlayerType = people[HOLMES]._type;
if (goto1.x != -1 && people[HOLMES]._type == CHARACTER) {
if (people[HOLMES]._position != goto1)
people[HOLMES].walkToCoords(goto1, goto1._facing);
}
if (talk._talkToAbort)
return 1;
// See if NPC #1 must walk to a position before the animation starts
SpriteType savedNPCType = people[WATSON]._type;
if (goto2.x != -1 && people[WATSON]._type == CHARACTER) {
if (people[WATSON]._position != goto2)
people[WATSON].walkToCoords(goto2, goto2._facing);
}
if (talk._talkToAbort)
return 1;
// Turn the player (and NPC #1 if necessary) off before running the canimation
if (teleport1.x != -1 && savedPlayerType == CHARACTER)
people[HOLMES]._type = REMOVE;
if (teleport2.x != -1 && savedNPCType == CHARACTER)
people[WATSON]._type = REMOVE;
if (ui._windowOpen)
ui.banishWindow();
// Open up the room resource file and get the data for the animation
Common::SeekableReadStream *stream = res.load(_roomFilename);
stream->seek(44 + cAnimNum * 4);
stream->seek(stream->readUint32LE());
Common::SeekableReadStream *animStream = stream->readStream(cAnim._dataSize);
delete stream;
// Set up the active animation
_activeCAnim._position = cAnim._position;
_activeCAnim._oldBounds = Common::Rect(0, 0, 0, 0);
_activeCAnim._flags = cAnim._flags;
_activeCAnim._scaleVal = cAnim._scaleVal;
_activeCAnim._zPlacement = 0;
_activeCAnim.load(animStream, _compressed);
while (!_vm->shouldQuit()) {
// Get the next frame
if (!_activeCAnim.getNextFrame())
break;
// Draw the frame
doBgAnim();
// Check for skip prolog action being pressed to abort animation
events.pollEvents();
if (events.actionHit()) {
Common::CustomEventType action = events.getAction();
if (action == kActionTattooSkipProlog && vm._runningProlog) {
_vm->setFlags(-76);
_vm->setFlags(396);
_goToScene = STARTING_GAME_SCENE;
talk._talkToAbort = true;
_activeCAnim.close();
break;
}
}
}
// Turn the people back on
people[HOLMES]._type = savedPlayerType;
if (teleport2.x != -1)
people[WATSON]._type = savedNPCType;
// Teleport the Player to the ending coordinates if necessary
if (teleport1.x != -1 && savedPlayerType == CHARACTER) {
people[HOLMES]._position = teleport1;
people[HOLMES]._sequenceNumber = teleport1._facing;
people[HOLMES].gotoStand();
}
// Teleport Watson to the ending coordinates if necessary
if (teleport2.x != -1 && savedNPCType == CHARACTER) {
people[WATSON]._position = teleport2;
people[WATSON]._sequenceNumber = teleport2._facing;
people[WATSON].gotoStand();
}
// Flag the Canimation to be cleared
_activeCAnim._zPlacement = REMOVE;
_activeCAnim._removeBounds = _activeCAnim._oldBounds;
_vm->_ui->_bgFound = -1;
// Free up the animation
_activeCAnim.close();
return 1;
}
void TattooScene::setNPCPath(int npc) {
TattooPeople &people = *(TattooPeople *)_vm->_people;
SaveManager &saves = *_vm->_saves;
Talk &talk = *_vm->_talk;
// Don't do initial scene setup if a savegame has just been loaded
if (saves._justLoaded)
return;
people[npc].clearNPC();
people[npc]._npcName = Common::String::format("WATS%.2dA", _currentScene);
// If we're in the middle of a script that will continue once the scene is loaded,
// return without calling the path script
if (talk._scriptMoreFlag == 1 || talk._scriptMoreFlag == 3)
return;
// Turn off all the NPCs, since the talk script will turn them back on as needed
for (int idx = 1; idx < MAX_CHARACTERS; ++idx)
people[idx]._type = INVALID;
// Call the path script for the scene
Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene);
talk.talkTo(pathFile);
}
int TattooScene::findBgShape(const Common::Point &pt) {
People &people = *_vm->_people;
UserInterface &ui = *_vm->_ui;
if (!_doBgAnimDone)
// New frame hasn't been drawn yet
return -1;
int result = -1;
for (int idx = (int)_bgShapes.size() - 1; idx >= 0 && result == -1; --idx) {
Object &o = _bgShapes[idx];
if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN &&
(o._aType <= PERSON || (ui._menuMode == LAB_MODE && o._aType == SOLID))) {
if (o.getNewBounds().contains(pt))
result = idx;
} else if (o._type == NO_SHAPE) {
if (o.getNoShapeBounds().contains(pt))
result = idx;
}
}
// Now check for the mouse being over an NPC. If so, it overrides any found bg object
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
Person &person = people[idx];
if (person._type == CHARACTER) {
int scaleVal = getScaleVal(person._position);
Common::Rect charRect;
if (scaleVal == SCALE_THRESHOLD)
charRect = Common::Rect(person.frameWidth(), person.frameHeight());
else
charRect = Common::Rect(person._imageFrame->sDrawXSize(scaleVal), person._imageFrame->sDrawYSize(scaleVal));
charRect.moveTo(person._position.x / FIXED_INT_MULTIPLIER, person._position.y / FIXED_INT_MULTIPLIER
- charRect.height());
if (charRect.contains(pt))
result = 1000 + idx;
}
}
return result;
}
void TattooScene::synchronize(Serializer &s) {
TattooEngine &vm = *(TattooEngine *)_vm;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
uint numSceneTripCounters = 0;
Scene::synchronize(s);
// Since save version 5: sync _sceneTripCounters
if (s.isSaving())
numSceneTripCounters = _sceneTripCounters.size();
s.syncAsUint32LE(numSceneTripCounters, 5);
if (s.isLoading())
_sceneTripCounters.resize(numSceneTripCounters);
for (auto &tripCounter : _sceneTripCounters) {
s.syncAsSint32LE(tripCounter._flag, 5);
s.syncAsSint32LE(tripCounter._sceneNumber, 5);
s.syncAsSint32LE(tripCounter._numTimes, 5);
}
if (s.isLoading()) {
// In case we were showing the intro prologue or the ending credits, stop them
vm._runningProlog = false;
ui._creditsWidget.close();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-prolog")->setEnabled(false);
}
}
int TattooScene::closestZone(const Common::Point &pt) {
int zone = -1;
int dist = 9999;
for (uint idx = 0; idx < _zones.size(); ++idx) {
Common::Rect &r = _zones[idx];
// Check the distance from the point to the center of the zone
int d = ABS(r.left + (r.width() / 2) - pt.x) + ABS(r.top + (r.height() / 2) - pt.y);
if (d < dist) {
dist = d;
zone = idx;
}
// Check the distance from the point to the upper left of the zone
d = ABS((int)(r.left - pt.x)) + ABS((int)(r.top - pt.y));
if (d < dist)
{
dist = d;
zone = idx;
}
// Check the distance from the point to the upper right of the zone
d = ABS(r.left + r.width() - pt.x) + ABS(r.top - pt.y);
if (d < dist) {
dist = d;
zone = idx;
}
// Check the distance from the point to the lower left of the zone
d = ABS(r.left - pt.x) + ABS(r.top + r.height() - pt.y);
if (d < dist) {
dist = d;
zone = idx;
}
// Check the distance from the point to the lower right of the zone
d = ABS(r.left + r.width() - pt.x) + ABS(r.top + r.height() - pt.y);
if (d < dist) {
dist = d;
zone = idx;
}
}
return zone;
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,156 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_SCENE_H
#define SHERLOCK_TATTOO_SCENE_H
#include "common/scummsys.h"
#include "sherlock/scene.h"
#include "sherlock/tattoo/widget_lab.h"
namespace Sherlock {
namespace Tattoo {
extern const int FS_TRANS[8];
enum {
STARTING_GAME_SCENE = 1, WEARY_PUNT = 52, TRAIN_RIDE = 69, STARTING_INTRO_SCENE = 91, OVERHEAD_MAP2 = 90, OVERHEAD_MAP = 100
};
struct SceneTripEntry {
int _flag;
int _sceneNumber;
int _numTimes;
SceneTripEntry() : _flag(0), _sceneNumber(0), _numTimes(0) {}
SceneTripEntry(int flag, int sceneNumber, int numTimes) : _flag(flag),
_sceneNumber(sceneNumber), _numTimes(numTimes) {}
};
class TattooScene : public Scene {
private:
WidgetLab _labWidget;
void doBgAnimCheckCursor();
/**
* Update the background objects and canimations as part of doBgAnim
*/
void doBgAnimUpdateBgObjectsAndAnim();
void doBgAnimDrawSprites();
/**
* Resets the NPC path information when entering a new scene.
* @remarks The default talk file for the given NPC is set to WATS##A, where ## is
* the scene number being entered
*/
void setNPCPath(int npc);
protected:
/**
* Loads the data associated for a given scene. The room resource file's format is:
* BGHEADER: Holds an index for the rest of the file
* STRUCTS: The objects for the scene
* IMAGES: The graphic information for the structures
*
* The _misc field of the structures contains the number of the graphic image
* that it should point to after loading; _misc is then set to 0.
*/
bool loadScene(const Common::Path &filename) override;
/**
* Checks all the background shapes. If a background shape is animating,
* it will flag it as needing to be drawn. If a non-animating shape is
* colliding with another shape, it will also flag it as needing drawing
*/
void checkBgShapes() override;
/**
* Draw all the shapes, people and NPCs in the correct order
*/
void drawAllShapes() override;
/**
* Called by loadScene when the palette is loaded for Rose Tattoo
*/
void paletteLoaded() override;
/**
* Synchronize the data for a savegame
*/
void synchronize(Serializer &s) override;
/**
* Returns the index of the closest zone to a given point.
*/
int closestZone(const Common::Point &pt) override;
public:
StreamingImageFile _activeCAnim;
Common::Array<SceneTripEntry> _sceneTripCounters;
bool _labTableScene;
public:
TattooScene(SherlockEngine *vm);
/**
* Returns the scale value for the passed co-ordinates. This is taken from the scene's
* scale zones, interpolating inbetween the top and bottom values of the zones as needed
*/
int getScaleVal(const Point32 &pt);
/**
* Fres all the graphics and other dynamically allocated data for the scene
*/
void freeScene() override;
/**
* Draw all objects and characters.
*/
void doBgAnim() override;
/**
* Update the screen back buffer with all of the scene objects which need
* to be drawn
*/
void updateBackground() override;
/**
* Attempt to start a canimation sequence. It will load the requisite graphics, and
* then copy the canim object into the _canimShapes array to start the animation.
*
* @param cAnimNum The canim object within the current scene
* @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc.
* A negative playRate can also be specified to play the animation in reverse
*/
int startCAnim(int cAnimNum, int playRate = 1) override;
/**
* Attempts to find a background shape within the passed bounds. If found,
* it will return the shape number, or -1 on failure.
*/
int findBgShape(const Common::Point &pt) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,37 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_screen.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
TattooScreen::TattooScreen(SherlockEngine *vm) : Screen(vm) {
_backBuffer1.create(640, 480);
_backBuffer2.create(640, 480);
activateBackBuffer1();
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,43 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_SCREEN_H
#define SHERLOCK_TATTOO_SCREEN_H
#include "sherlock/screen.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class TattooScreen : public Screen {
public:
TattooScreen(SherlockEngine *vm);
~TattooScreen() override {}
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_TALK_H
#define SHERLOCK_TATTOO_TALK_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/serializer.h"
#include "common/stream.h"
#include "common/stack.h"
#include "sherlock/talk.h"
#include "sherlock/tattoo/widget_password.h"
#include "sherlock/tattoo/widget_talk.h"
namespace Sherlock {
namespace Tattoo {
#define TALK_SEQUENCE_STACK_SIZE 20
class WidgetTalk;
class TattooTalk : public Talk {
friend class WidgetTalk;
private:
WidgetTalk _talkWidget;
WidgetPassword _passwordWidget;
SequenceEntry _sequenceStack[TALK_SEQUENCE_STACK_SIZE];
OpcodeReturn cmdCallTalkFile(const byte *&str);
OpcodeReturn cmdSwitchSpeaker(const byte *&str);
OpcodeReturn cmdMouseOnOff(const byte *&str);
OpcodeReturn cmdGotoScene(const byte *&str);
OpcodeReturn cmdWalkHolmesToCoords(const byte *&str);
OpcodeReturn cmdNextSong(const byte *&str);
OpcodeReturn cmdPassword(const byte *&str);
OpcodeReturn cmdPlaySong(const byte *&str);
OpcodeReturn cmdRestorePeopleSequence(const byte *&str);
OpcodeReturn cmdSetNPCDescOnOff(const byte *&str);
OpcodeReturn cmdSetNPCInfoLine(const byte *&str);
OpcodeReturn cmdNPCLabelGoto(const byte *&str);
OpcodeReturn cmdNPCLabelIfFlagGoto(const byte *&str);
OpcodeReturn cmdNPCLabelSet(const byte *&str);
OpcodeReturn cmdSetNPCOff(const byte *&str);
OpcodeReturn cmdSetNPCOn(const byte *&str);
OpcodeReturn cmdSetNPCPathDest(const byte *&str);
OpcodeReturn cmdSetNPCPathPause(const byte *&str);
OpcodeReturn cmdSetNPCPathPauseTakingNotes(const byte *&str);
OpcodeReturn cmdSetNPCPathPauseLookingHolmes(const byte *&str);
OpcodeReturn cmdSetNPCPosition(const byte *&str);
OpcodeReturn cmdSetNPCTalkFile(const byte *&str);
OpcodeReturn cmdSetNPCVerb(const byte *&str);
OpcodeReturn cmdSetNPCVerbCAnimation(const byte *&str);
OpcodeReturn cmdSetNPCVerbScript(const byte *&str);
OpcodeReturn cmdSetNPCVerbTarget(const byte *&str);
OpcodeReturn cmdSetNPCWalkGraphics(const byte *&str);
OpcodeReturn cmdSetSceneEntryFlag(const byte *&str);
OpcodeReturn cmdSetTalkSequence(const byte *&str);
OpcodeReturn cmdSetWalkControl(const byte *&str);
OpcodeReturn cmdTalkInterruptsDisable(const byte *&str);
OpcodeReturn cmdTalkInterruptsEnable(const byte *&str);
OpcodeReturn cmdTurnSoundsOff(const byte *&str);
OpcodeReturn cmdWalkHolmesAndNPCToCAnimation(const byte *&str);
OpcodeReturn cmdWalkNPCToCAnimation(const byte *&str);
OpcodeReturn cmdWalkNPCToCoords(const byte *&str);
OpcodeReturn cmdWalkHomesAndNPCToCoords(const byte *&str);
protected:
/**
* Display the talk interface window
*/
void talkInterface(const byte *&str) override;
/**
* Called when a character being spoken to has no talk options to display
*/
void nothingToSay() override;
/**
* Show the talk display
*/
void showTalk() override;
public:
TattooTalk(SherlockEngine *vm);
~TattooTalk() override {}
/**
* Called whenever a conversation or item script needs to be run. For standard conversations,
* it opens up a description window similar to how 'talk' does, but shows a 'reply' directly
* instead of waiting for a statement option.
* @remarks It seems that at some point, all item scripts were set up to use this as well.
* In their case, the conversation display is simply suppressed, and control is passed on to
* doScript to implement whatever action is required.
*/
void talkTo(const Common::String &filename) override;
/**
* Push the details of a passed object onto the saved sequences stack
*/
void pushSequenceEntry(Object *obj) override;
/**
* Pulls a background object sequence from the sequence stack and restore's the
* object's sequence
*/
void pullSequence(int slot = -1) override;
/**
* Returns true if the script stack is empty
*/
bool isSequencesEmpty() const override;
/**
* Clears the stack of pending object sequences associated with speakers in the scene
*/
void clearSequences() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,984 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_journal.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
bool WidgetList::contains(const WidgetBase *item) const {
for (const_iterator i = begin(); i != end(); ++i) {
if ((*i) == item)
return true;
}
return false;
}
/*-------------------------------------------------------------------------*/
TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm),
_inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm),
_verbsWidget(vm), _creditsWidget(vm), _optionsWidget(vm), _quitWidget(vm) {
Common::fill(&_lookupTable[0], &_lookupTable[Graphics::PALETTE_COUNT], 0);
Common::fill(&_lookupTable1[0], &_lookupTable1[Graphics::PALETTE_COUNT], 0);
_scrollSize = 0;
_scrollSpeed = 16;
_drawMenu = false;
_bgShape = nullptr;
_personFound = false;
_lockoutTimer = 0;
_exitZone = -1;
_scriptZone = -1;
_arrowZone = _oldArrowZone = -1;
_activeObj = -1;
_cAnimFramePause = 0;
_scrollHighlight = SH_NONE;
_mask = _mask1 = nullptr;
_maskCounter = 0;
_interfaceImages = new ImageFile("intrface.vgs");
}
TattooUserInterface::~TattooUserInterface() {
delete _interfaceImages;
delete _mask;
delete _mask1;
}
void TattooUserInterface::initScrollVars() {
Screen &screen = *_vm->_screen;
_scrollSize = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
_targetScroll = Common::Point(0, 0);
screen._currentScroll = Common::Point(0, 0);
}
void TattooUserInterface::lookAtObject() {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Sound &sound = *_vm->_sound;
Talk &talk = *_vm->_talk;
Common::Point mousePos = events.mousePos();
Common::String desc;
_lookPos = mousePos;
_menuMode = LOOK_MODE;
if (_personFound) {
desc = people[_bgFound - 1000]._examine;
} else {
// Check if there is a Look animation
if (_bgShape->_lookcAnim != 0) {
//int cAnimSpeed = _bgShape->_lookcAnim & 0xe0;
//cAnimSpeed >>= 5;
//++cAnimSpeed;
_cAnimFramePause = _bgShape->_lookFrames;
desc = _bgShape->_examine;
int cNum = (_bgShape->_lookcAnim & 0x1f) - 1;
scene.startCAnim(cNum);
} else if (_bgShape->_lookPosition.y != 0) {
// Need to walk to object before looking at it
people[HOLMES].walkToCoords(_bgShape->_lookPosition, _bgShape->_lookPosition._facing);
}
if (!talk._talkToAbort) {
desc = _bgShape->_examine;
if (_bgShape->_lookFlag)
_vm->setFlags(_bgShape->_lookFlag);
// Find the Sound File to Play if there is one
if (!desc.hasPrefix("_")) {
for (uint idx = 0; idx < scene._objSoundList.size(); ++idx) {
// Get the object name up to the equals
const char *p = strchr(scene._objSoundList[idx].c_str(), '=');
// Form the name and remove any trailing spaces
Common::String name(scene._objSoundList[idx].c_str(), p);
while (name.hasSuffix(" "))
name.deleteLastChar();
// See if this Object Sound List entry matches the object's name
if (!_bgShape->_name.compareToIgnoreCase(name)) {
// Move forward to get the sound filename
while ((*p == ' ') || (*p == '='))
++p;
// If it's not "NONE", play the speech File
Common::String soundName(p);
if (soundName.compareToIgnoreCase("NONE")) {
soundName.toLowercase();
if (!soundName.contains('.'))
soundName += ".wav";
sound.playSound(Common::Path(soundName), WAIT_RETURN_IMMEDIATELY);
}
break;
}
}
}
}
}
// Only show the desciption if the object has one, and if no talk file interrupted while walking to it
if (!talk._talkToAbort && !desc.empty()) {
if (_cAnimFramePause == 0)
printObjectDesc(desc, true);
else
// The description was already printed by an animation
_cAnimFramePause = 0;
} else if (desc.empty()) {
// There was no description to display, so reset back to STD_MODE
_menuMode = STD_MODE;
}
}
void TattooUserInterface::printObjectDesc(const Common::String &str, bool firstTime) {
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Talk &talk = *_vm->_talk;
if (str.hasPrefix("_")) {
// The passed string specifies a talk file
_lookScriptFlag = true;
events.setCursor(MAGNIFY);
int savedSelector = _selector;
if (!_invLookFlag)
_windowOpen = false;
talk.talkTo(str.c_str() + 1);
_lookScriptFlag = false;
if (talk._talkToAbort) {
events.setCursor(ARROW);
return;
}
// See if we're looking at an inventory item
if (_invLookFlag) {
_selector = _oldSelector = savedSelector;
doInventory(0);
_invLookFlag = false;
} else {
// Nope
events.setCursor(ARROW);
_action = kActionNone;
_keyState.keycode = Common::KEYCODE_INVALID;
_menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
events._pressed = events._released = events._rightReleased = false;
events._oldButtons = 0;
}
} else {
events._pressed = events._released = events._rightReleased = false;
// Show text dialog
_textWidget.load(str);
_textWidget.summonWindow();
if (firstTime)
_selector = _oldSelector = -1;
_drawMenu = _windowOpen = true;
}
}
void TattooUserInterface::doJournal() {
TattooJournal &journal = *(TattooJournal *)_vm->_journal;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
byte lookupTable[Graphics::PALETTE_COUNT], lookupTable1[Graphics::PALETTE_COUNT];
Common::copy(&_lookupTable[0], &_lookupTable[Graphics::PALETTE_COUNT], &lookupTable[0]);
Common::copy(&_lookupTable1[0], &_lookupTable1[Graphics::PALETTE_COUNT], &lookupTable1[0]);
_menuMode = JOURNAL_MODE;
journal.show();
_menuMode = STD_MODE;
_windowOpen = false;
_action = kActionNone;
_keyState.keycode = Common::KEYCODE_INVALID;
// Restore the old screen palette and greyscale lookup table
screen.clear();
screen.setPalette(screen._cMap);
Common::copy(&lookupTable[0], &lookupTable[Graphics::PALETTE_COUNT], &_lookupTable[0]);
Common::copy(&lookupTable1[0], &lookupTable1[Graphics::PALETTE_COUNT], &_lookupTable1[0]);
// Restore the scene
screen._backBuffer1.SHblitFrom(screen._backBuffer2);
scene.updateBackground();
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
void TattooUserInterface::reset() {
UserInterface::reset();
_lookPos = Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
_tooltipWidget.setText("");
_widgets.clear();
_fixedWidgets.clear();
}
void TattooUserInterface::handleInput() {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Common::Point mousePos = events.mousePos();
_keyState.keycode = Common::KEYCODE_INVALID;
_action = kActionNone;
// Check for credits starting
if (_vm->readFlags(3000) && !_creditsWidget.active())
_creditsWidget.initCredits();
// Check the mouse positioning
if (events.isCursorVisible())
_bgFound = scene.findBgShape(mousePos);
_personFound = _bgFound >= 1000;
_bgShape = (_bgFound != -1 && _bgFound < 1000) ? &scene._bgShapes[_bgFound] : nullptr;
if (_lockoutTimer)
--_lockoutTimer;
// Action handling
if (events.actionHit()) {
_action = events.getAction();
if (_action == kActionTattooSkipProlog && vm._runningProlog && !_lockoutTimer) {
vm.setFlags(-76);
vm.setFlags(396);
scene._goToScene = STARTING_GAME_SCENE;
} else if (_menuMode == STD_MODE) {
if (_action == kActionTattooChangeSpeed && vm._allowFastMode) {
events.toggleSpeed();
} else if (_action == kActionTattooLook && _bgFound != -1) {
// Beging used for testing that Look dialogs work
lookAtObject();
}
}
}
if (events.kbHit()) {
_keyState = events.getKey();
}
if (!events.isCursorVisible()) {
_keyState.keycode = Common::KEYCODE_INVALID;
_action = kActionNone;
}
// If there's any active widgets/windows, let the most recently open one do event processing
if (!_widgets.empty())
_widgets.back()->handleEvents();
else if (!_fixedWidgets.empty())
_fixedWidgets.back()->handleEvents();
// Handle input depending on what mode we're in
switch (_menuMode) {
case STD_MODE:
doStandardControl();
break;
case LOOK_MODE:
doLookControl();
break;
default:
break;
}
}
void TattooUserInterface::drawInterface(int bufferNum) {
Screen &screen = *_vm->_screen;
// Draw any active on-screen widgets
for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i)
(*i)->draw();
for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
(*i)->draw();
// Handle drawing credits
// TODO: See if credits are only shown on a single screen. If so, _fixedWidgets could be used
if (_creditsWidget.active())
_creditsWidget.drawCredits();
// Bring the widgets to the screen
if (_mask != nullptr)
screen._flushScreen = true;
}
void TattooUserInterface::doBgAnimRestoreUI() {
TattooScene &scene = *((TattooScene *)_vm->_scene);
Screen &screen = *_vm->_screen;
// If there are any on-screen widgets, then erase them
for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
(*i)->erase();
for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i)
(*i)->erase();
// If there is a Text Tag being display, restore the area underneath it
_tooltipWidget.erase();
// If a canimation is active, restore the graphics underneath it
if (scene._activeCAnim.active())
screen.restoreBackground(scene._activeCAnim._oldBounds);
// If a canimation just ended, remove its graphics from the backbuffer
if (scene._activeCAnim._removeBounds.width() > 0)
screen.restoreBackground(scene._activeCAnim._removeBounds);
}
void TattooUserInterface::doScroll() {
Screen &screen = *_vm->_screen;
// If we're already at the target scroll position, nothing needs to be done
if (_targetScroll.x == screen._currentScroll.x)
return;
screen._flushScreen = true;
if (_targetScroll.x > screen._currentScroll.x) {
screen._currentScroll.x += _scrollSpeed;
if (screen._currentScroll.x > _targetScroll.x)
screen._currentScroll.x = _targetScroll.x;
} else if (_targetScroll.x < screen._currentScroll.x) {
screen._currentScroll.x -= _scrollSpeed;
if (screen._currentScroll.x < _targetScroll.x)
screen._currentScroll.x = _targetScroll.x;
}
// Reset the default look position to the center of the new screen area
_lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
}
void TattooUserInterface::doStandardControl() {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
People &people = *_vm->_people;
SaveManager &saves = *_vm->_saves;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Talk &talk = *_vm->_talk;
Common::Point mousePos = events.mousePos();
// Don't do any input processing whilst the prolog is running
// or the cursor is hidden (e.g. by a call to cmdMouseOnOff())
if (vm._runningProlog || !events.isCursorVisible())
return;
// When the end credits are active, any press will open the ScummVM global main menu
if (_creditsWidget.active()) {
if (_keyState.keycode || _action || events._released || events._rightReleased) {
vm._canLoadSave = true;
vm.openMainMenuDialog();
vm._canLoadSave = false;
}
return;
}
// Display the names of any Objects the cursor is pointing at
displayObjectNames();
switch (_action) {
case kActionTattooSave:
// Save game
events.warpMouse();
saveGame();
return;
case kActionTattooLoad:
// Load game
events.warpMouse();
loadGame();
return;
case kActionTattooJournal:
// Display journal
if (vm.readFlags(FLAG_PLAYER_IS_HOLMES)) {
freeMenu();
doJournal();
// See if we're in a Lab Table Room
_menuMode = (scene._labTableScene) ? LAB_MODE : STD_MODE;
return;
}
break;
case kActionTattooInv:
// Display inventory
freeMenu();
doInventory(3);
return;
case kActionTattooOptions:
// Display options
events.warpMouse();
_optionsWidget.load();
return;
case kActionTattooQuit:
// Quit menu
freeMenu();
events.warpMouse();
doQuitMenu();
return;
default:
break;
}
// See if a mouse button was released
if (events._released || events._rightReleased) {
// See if the mouse was released in an exit (Arrow) zone. Unless it's also pointing at an object
// within the zone, in which case the object gets precedence
_exitZone = -1;
if (_arrowZone != -1 && events._released)
_exitZone = _arrowZone;
// Turn any Text display off
if (_arrowZone == -1 || events._rightReleased)
freeMenu();
bool noDesc = false;
if (_personFound) {
if (people[_bgFound - 1000]._description.empty() || people[_bgFound - 1000]._description.hasPrefix(" "))
noDesc = true;
} else if (_bgFound != -1) {
if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" "))
noDesc = true;
} else {
noDesc = true;
}
if (events._rightReleased) {
// Show the verbs menu for the highlighted object
_tooltipWidget.banishWindow();
saves.createThumbnail();
_verbsWidget.load(!noDesc);
_verbsWidget.summonWindow();
_selector = _oldSelector = -1;
_activeObj = _bgFound;
_menuMode = VERB_MODE;
} else if (_personFound || (_bgFound != -1 && _bgFound < 1000 && _bgShape->_aType == PERSON)) {
// The object found is a person (the default for people is TALK)
talk.initTalk(_bgFound);
_activeObj = -1;
} else if (!noDesc) {
// Either call the code to Look at its Examine Field or call the Exit animation
// if the object is an exit, specified by the first four characters of the name being "EXIT"
Common::String name = _personFound ? people[_bgFound - 1000]._name : _bgShape->_name;
if (!name.hasPrefix("EXIT")) {
lookAtObject();
} else {
// Run the Exit animation and set which scene to go to next
for (int idx = 0; idx < 6; ++idx) {
if (!_bgShape->_use[idx]._verb.compareToIgnoreCase("Open")) {
checkAction(_bgShape->_use[idx], _bgFound);
_activeObj = -1;
}
}
}
} else {
// See if there are any Script Zones where they clicked
if (scene.checkForZones(mousePos, _scriptZone) != 0) {
// Mouse click in a script zone
events._pressed = events._released = false;
} else if (scene.checkForZones(mousePos, NOWALK_ZONE) != 0) {
events._pressed = events._released = false;
} else {
// Walk to where the mouse was clicked
people[HOLMES]._walkDest = mousePos;
people[HOLMES].goAllTheWay();
}
}
}
}
void TattooUserInterface::doLookControl() {
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
// See if a mouse button was released, a key pressed or a action pressed to close the active look dialog
if (events._released || events._rightReleased || _keyState.keycode || _action) {
// See if we were looking at an inventory object
if (!_invLookFlag) {
// See if there is any more text to display
if (!_textWidget._remainingText.empty()) {
printObjectDesc(_textWidget._remainingText, false);
} else {
// Otherwise restore the background and go back into STD_MODE
freeMenu();
_action = kActionNone;
_keyState.keycode = Common::KEYCODE_INVALID;
_menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
events.setCursor(ARROW);
events._pressed = events._released = events._rightReleased = false;
events._oldButtons = 0;
}
} else {
// We were looking at a Inventory object
// Erase the text window, and then redraw the inventory window
_textWidget.banishWindow();
doInventory(0);
_invLookFlag = false;
_action = kActionNone;
_keyState.keycode = Common::KEYCODE_INVALID;
events.setCursor(ARROW);
events._pressed = events._released = events._rightReleased = false;
events._oldButtons = 0;
}
}
}
void TattooUserInterface::displayObjectNames() {
Events &events = *_vm->_events;
Scene &scene = *_vm->_scene;
Common::Point mousePos = events.mousePos();
_arrowZone = -1;
if (_bgFound == -1 || scene._currentScene == OVERHEAD_MAP2) {
for (uint idx = 0; idx < scene._exits.size() && _arrowZone == -1; ++idx) {
Exit &exit = scene._exits[idx];
if (exit.contains(mousePos))
_arrowZone = idx;
}
}
_tooltipWidget.handleEvents();
_oldArrowZone = _arrowZone;
}
void TattooUserInterface::doInventory(int mode) {
People &people = *_vm->_people;
people[HOLMES].gotoStand();
_inventoryWidget.load(mode);
_inventoryWidget.summonWindow();
_menuMode = INV_MODE;
}
void TattooUserInterface::doControls() {
_optionsWidget.load();
}
void TattooUserInterface::pickUpObject(int objNum) {
Inventory &inv = *_vm->_inventory;
Scene &scene = *_vm->_scene;
Talk &talk = *_vm->_talk;
Object &obj = scene._bgShapes[objNum];
bool printed = false;
int verbField = -1;
// Find which Verb field to use for pick up data
for (int idx = 0; idx < 6; ++idx) {
if (!scumm_stricmp(obj._use[idx]._target.c_str(), "*PICKUP"))
verbField = idx;
}
if (verbField != -1) {
if (obj._use[verbField]._cAnimNum)
scene.startCAnim(obj._use[verbField]._cAnimNum - 1);
}
if (!talk._talkToAbort) {
if (obj._type == NO_SHAPE)
obj._type = INVALID;
else
// Erase shape
obj._type = REMOVE;
} else {
return;
}
if (verbField != -1) {
for (int idx = 0; idx < 4 && !talk._talkToAbort; ++idx) {
if (obj.checkNameForCodes(obj._use[verbField]._names[idx])) {
if (!talk._talkToAbort)
printed = true;
}
}
}
if (talk._talkToAbort)
return;
// Add the item to the player's inventory
inv.putItemInInventory(obj);
if (!printed) {
Common::String desc = obj._description;
desc.setChar(tolower(desc[0]), 0);
putMessage("%s %s", FIXED(PickedUp), desc.c_str());
}
if (_menuMode != TALK_MODE && _menuMode != MESSAGE_MODE) {
_menuMode = STD_MODE;
_keyState.keycode = Common::KEYCODE_INVALID;
_action = kActionNone;
}
}
void TattooUserInterface::doQuitMenu() {
_quitWidget.show();
}
void TattooUserInterface::putMessage(const char *formatStr, ...) {
// Create the string to display
va_list args;
va_start(args, formatStr);
Common::String str = Common::String::vformat(formatStr, args);
va_end(args);
// Open the message widget
_menuMode = MESSAGE_MODE;
_messageWidget.load(str, 25);
_messageWidget.summonWindow();
}
void TattooUserInterface::setupBGArea(const byte cMap[Graphics::PALETTE_SIZE]) {
Scene &scene = *_vm->_scene;
// This requires that there is a 16 grayscale palette sequence in the palette that goes from lighter
// to darker as the palette numbers go up. The last palette entry in that run is specified by _bgColor
byte *p = &_lookupTable[0];
for (int idx = 0; idx < Graphics::PALETTE_COUNT; ++idx)
*p++ = BG_GREYSCALE_RANGE_END - (cMap[idx * 3] * 30 + cMap[idx * 3 + 1] * 59 + cMap[idx * 3 + 2] * 11) / 480;
// If we're going to a scene with a haze special effect, initialize the translate table to lighten the colors
if (_mask != nullptr) {
p = &_lookupTable1[0];
for (int idx = 0; idx < Graphics::PALETTE_COUNT; ++idx) {
int r, g, b;
switch (scene._currentScene) {
case 8:
r = cMap[idx * 3] * 4 / 5;
g = cMap[idx * 3 + 1] * 3 / 4;
b = cMap[idx * 3 + 2] * 3 / 4;
break;
case 18:
case 68:
r = cMap[idx * 3] * 4 / 3;
g = cMap[idx * 3 + 1] * 4 / 3;
b = cMap[idx * 3 + 2] * 4 / 3;
break;
case 7:
case 53:
r = cMap[idx * 3] * 4 / 3;
g = cMap[idx * 3 + 1] * 4 / 3;
b = cMap[idx * 3 + 2] * 4 / 3;
break;
default:
r = g = b = 0;
break;
}
byte c = 0xff;
int cd = 99999;
for (int pal = 0; pal < Graphics::PALETTE_COUNT; ++pal) {
int d = (r - cMap[pal * 3]) * (r - cMap[pal * 3]) + (g - cMap[pal * 3 + 1]) * (g - cMap[pal * 3 + 1]) +
(b - cMap[pal * 3 + 2]) * (b - cMap[pal * 3 + 2]);
if (d < cd) {
c = pal;
cd = d;
if (!d)
break;
}
}
*p++ = c;
}
}
}
void TattooUserInterface::doBgAnimEraseBackground() {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 };
if (_mask != nullptr) {
// Since a mask is active, restore the screen from the secondary back buffer prior to applying the mask
screen._backBuffer1.SHblitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0,
screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
switch (scene._currentScene) {
case 7:
if (++_maskCounter == 2) {
_maskCounter = 0;
if (--_maskOffset.x < 0)
_maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1;
}
break;
case 8:
_maskOffset.x += 2;
if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH)
_maskOffset.x = 0;
break;
case 18:
case 68:
++_maskCounter;
if (_maskCounter / 4 >= 16)
_maskCounter = 0;
_maskOffset.x = OFFSETS[_maskCounter / 4];
break;
case 53:
if (++_maskCounter == 2) {
_maskCounter = 0;
if (++_maskOffset.x == screen._backBuffer1.width())
_maskOffset.x = 0;
}
break;
default:
break;
}
} else {
// Standard scene without mask, so call user interface to erase any UI elements as necessary
doBgAnimRestoreUI();
// Restore background for any areas covered by characters and shapes
for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y,
people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y));
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
Object &obj = scene._bgShapes[idx];
if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) ||
obj._type == HIDE_SHAPE || obj._type == REMOVE)
screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._oldPosition,
Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x,
obj._oldPosition.y + obj._oldSize.y));
}
// If credits are active, erase the area they cover
if (_creditsWidget.active())
_creditsWidget.eraseCredits();
}
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
Object &obj = scene._bgShapes[idx];
if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds());
obj._oldPosition = obj._position;
obj._oldSize = obj._noShapeSize;
}
}
// Adjust the Target Scroll if needed
if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) <
(SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) {
_targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
SHERLOCK_SCREEN_WIDTH / 8 - 250);
if (_targetScroll.x < 0)
_targetScroll.x = 0;
}
if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) >
(SHERLOCK_SCREEN_WIDTH / 4 * 3) && people[people._walkControl]._delta.x > 0)
_targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250);
if (_targetScroll.x > _scrollSize)
_targetScroll.x = _scrollSize;
doScroll();
}
void TattooUserInterface::drawMaskArea(bool mode) {
Scene &scene = *_vm->_scene;
int xp = mode ? _maskOffset.x : 0;
if (_mask != nullptr) {
switch (scene._currentScene) {
case 7:
maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
maskArea(*_mask, Common::Point(_maskOffset.x, 110));
maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110));
break;
case 8:
maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180));
maskArea(*_mask, Common::Point(_maskOffset.x, 180));
maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180));
if (!_vm->readFlags(880))
maskArea(*_mask1, Common::Point(940, 300));
break;
case 18:
maskArea(*_mask, Common::Point(xp, 203));
if (!_vm->readFlags(189))
maskArea(*_mask1, Common::Point(124 + xp, 239));
break;
case 53:
maskArea(*_mask, Common::Point(_maskOffset.x, 110));
if (mode)
maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
break;
case 68:
maskArea(*_mask, Common::Point(xp, 203));
maskArea(*_mask1, Common::Point(124 + xp, 239));
break;
default:
break;
}
}
}
void TattooUserInterface::maskArea(Common::SeekableReadStream &mask, const Common::Point &pt) {
Screen &screen = *_vm->_screen;
Surface &bb1 = screen._backBuffer1;
mask.seek(0);
int xSize = mask.readUint16LE();
int ySize = mask.readUint16LE();
int pixel, len, xp, yp;
for (yp = 0; yp < ySize; ++yp) {
byte *ptr = (byte *)bb1.getBasePtr(pt.x, pt.y + yp);
for (xp = 0; xp < xSize;) {
// The mask data consists of pairs of pixel/lengths, where all non-zero pixels means that the
// given pixel on the back buffer is darkened (the mask pixel value isn't otherwise used)
pixel = mask.readByte();
len = mask.readByte();
for (; len > 0; --len, ++xp, ++ptr) {
if (pixel && (pt.x + xp) >= screen._currentScroll.x && (pt.x + xp) < (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) {
*ptr = _lookupTable1[*ptr];
}
}
}
assert(xp == xSize);
}
}
void TattooUserInterface::makeBGArea(const Common::Rect &r) {
Screen &screen = *_vm->_screen;
for (int yp = r.top; yp < r.bottom; ++yp) {
byte *ptr = (byte *)screen._backBuffer1.getBasePtr(r.left, yp);
for (int xp = r.left; xp < r.right; ++xp, ++ptr)
*ptr = _lookupTable[*ptr];
}
screen.slamRect(r);
}
void TattooUserInterface::drawDialogRect(Surface &s, const Common::Rect &r, bool raised) {
if (raised) {
// Draw Left
s.vLine(r.left, r.top, r.bottom - 1, INFO_TOP);
s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_TOP);
// Draw Top
s.hLine(r.left + 2, r.top, r.right - 1, INFO_TOP);
s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_TOP);
// Draw Right
s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_BOTTOM);
s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_BOTTOM);
// Draw Bottom
s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_BOTTOM);
s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_BOTTOM);
} else {
// Draw Left
s.vLine(r.left, r.top, r.bottom - 1, INFO_BOTTOM);
s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_BOTTOM);
// Draw Top
s.hLine(r.left + 2, r.top, r.right - 1, INFO_BOTTOM);
s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_BOTTOM);
// Draw Right
s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_TOP);
s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_TOP);
// Draw Bottom
s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_TOP);
s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_TOP);
}
}
void TattooUserInterface::banishWindow(bool slideUp) {
if (!_widgets.empty())
_widgets.back()->banishWindow();
}
void TattooUserInterface::freeMenu() {
for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
(*i)->erase();
_widgets.clear();
}
void TattooUserInterface::clearWindow() {
banishWindow();
}
void TattooUserInterface::loadGame() {
WidgetFiles &files = *(WidgetFiles *)_vm->_saves;
files.show(SAVEMODE_LOAD);
}
void TattooUserInterface::saveGame() {
WidgetFiles &files = *(WidgetFiles *)_vm->_saves;
files.show(SAVEMODE_SAVE);
}
void TattooUserInterface::addFixedWidget(WidgetBase *widget) {
_fixedWidgets.push_back(widget);
widget->summonWindow();
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,264 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_UI_H
#define SHERLOCK_TATTOO_UI_H
#include "common/scummsys.h"
#include "common/list.h"
#include "sherlock/saveload.h"
#include "sherlock/screen.h"
#include "sherlock/user_interface.h"
#include "sherlock/tattoo/widget_credits.h"
#include "sherlock/tattoo/widget_files.h"
#include "sherlock/tattoo/widget_inventory.h"
#include "sherlock/tattoo/widget_options.h"
#include "sherlock/tattoo/widget_quit.h"
#include "sherlock/tattoo/widget_text.h"
#include "sherlock/tattoo/widget_tooltip.h"
#include "sherlock/tattoo/widget_verbs.h"
namespace Sherlock {
namespace Tattoo {
// Button width/height
#define BUTTON_SIZE 15
// How long to play the intro before it can be skipped
#define STARTUP_KEYS_DISABLED_DELAY 200
class WidgetBase;
enum ScrollHighlight { SH_NONE = 0, SH_SCROLL_UP = 1, SH_PAGE_UP = 2, SH_THUMBNAIL = 3, SH_PAGE_DOWN = 4, SH_SCROLL_DOWN = 5 };
class WidgetList : public Common::List <WidgetBase *> {
public:
bool contains(const WidgetBase *item) const;
};
class TattooUserInterface : public UserInterface {
friend class WidgetBase;
private:
int _scriptZone;
int _cAnimFramePause;
WidgetInventory _inventoryWidget;
WidgetMessage _messageWidget;
WidgetQuit _quitWidget;
WidgetList _fixedWidgets;
WidgetList _widgets;
byte _lookupTable[Graphics::PALETTE_COUNT];
byte _lookupTable1[Graphics::PALETTE_COUNT];
private:
/**
* Handle any input when we're in standard mode (with no windows open)
*/
void doStandardControl();
/**
* Handle input when in look mode
*/
void doLookControl();
/**
* Handle input when the File window is open
*/
void doFileControl();
/**
* Handle input while the verb menu is open
*/
void doVerbControl();
/**
* Free any active menu
*/
void freeMenu();
public:
Common::Point _targetScroll;
int _scrollSize, _scrollSpeed;
bool _drawMenu;
int _arrowZone, _oldArrowZone;
Object *_bgShape;
bool _personFound;
int _activeObj;
Common::KeyState _keyState;
Common::CustomEventType _action;
Common::Point _lookPos;
ScrollHighlight _scrollHighlight;
Common::SeekableReadStream *_mask, *_mask1;
Common::Point _maskOffset;
int _maskCounter;
int _lockoutTimer;
ImageFile *_interfaceImages;
WidgetCredits _creditsWidget;
WidgetOptions _optionsWidget;
WidgetText _textWidget;
WidgetSceneTooltip _tooltipWidget;
WidgetVerbs _verbsWidget;
WidgetList _postRenderWidgets;
public:
TattooUserInterface(SherlockEngine *vm);
~TattooUserInterface() override;
/**
* Handles restoring any areas of the back buffer that were/are covered by UI elements
*/
void doBgAnimRestoreUI();
/**
* Checks to see if the screen needs to be scrolled. If so, scrolls it towards the target position
*/
void doScroll();
/**
* Initializes scroll variables
*/
void initScrollVars();
/**
* Display the long description for an object in a window
*/
void lookAtObject();
/**
* Display the passed long description for an object. If the flag firstTime is set,
* the window will be opened to accommodate the text. Otherwise, the remaining text
* will be printed in an already open window
*/
void printObjectDesc(const Common::String &str, bool firstTime);
/**
* Handles displaying the journal
*/
void doJournal();
/**
* Put the game in inventory mode by opening the inventory dialog
*/
void doInventory(int mode);
/**
* Handle the display of the options/setup menu
*/
void doControls();
/**
* Handle the display of the quit menu
*/
void doQuitMenu();
/**
* Pick up the selected object
*/
void pickUpObject(int objNum);
/**
* This will display a text message in a dialog at the bottom of the screen
*/
void putMessage(MSVC_PRINTF const char *formatStr, ...) GCC_PRINTF(2, 3);
/**
* Makes a greyscale translation table for each palette entry in the table
*/
void setupBGArea(const byte cMap[Graphics::PALETTE_SIZE]);
/**
* Erase any background as needed before drawing frame
*/
void doBgAnimEraseBackground();
/**
* Draws overlays onto the scene. Basically, the smoke effects some scenes have
*/
void drawMaskArea(bool mode);
/**
* Takes the data passed in the image and apply it to the surface at the given position.
* The src mask data is encoded with a different color for each item. To highlight one,
the runs that do not match the highlight number will be darkened
*/
void maskArea(Common::SeekableReadStream &mask, const Common::Point &pt);
/**
* Translate a given area of the back buffer to greyscale shading
*/
void makeBGArea(const Common::Rect &r);
/**
* Draws all the dialog rectangles for any items that need them
*/
void drawDialogRect(Surface &s, const Common::Rect &r, bool raised);
/**
* If the mouse cursor is point at the cursor, then display the name of the object on the screen.
* If there is no object being pointed it, clear any previously displayed name
*/
void displayObjectNames();
/**
* Show the load game dialog, and allow the user to load a game
*/
void loadGame();
/**
* Show the save game dialog, and allow the user to save the game
*/
void saveGame();
/**
* Add a widget to the current scene to be executed if there are no active widgets in the
* main _widgets list
*/
void addFixedWidget(WidgetBase *widget);
public:
/**
* Resets the user interface
*/
void reset() override;
/**
* Main input handler for the user interface
*/
void handleInput() override;
/**
* Draw the user interface onto the screen's back buffers
*/
void drawInterface(int bufferNum = 3) override;
/**
* Clear any active text window
*/
void clearWindow() override;
/**
* Banish any active window
* @remarks Tattoo doesn't use sliding windows, but the parameter is in the base
* UserInterface class as a convenience for Scalpel UI code
*/
void banishWindow(bool slideUp = true) override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,351 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
namespace Sherlock {
namespace Tattoo {
WidgetBase::WidgetBase(SherlockEngine *vm) : _vm(vm) {
_scroll = false;
_dialogTimer = 0;
_outsideMenu = false;
}
void WidgetBase::summonWindow() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
// Double-check that the same widget isn't added twice
if (ui._widgets.contains(this))
error("Tried to add a widget multiple times");
// Add widget to the screen
if (!ui._fixedWidgets.contains(this))
ui._widgets.push_back(this);
ui._windowOpen = true;
_outsideMenu = false;
draw();
}
void WidgetBase::banishWindow() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
erase();
_surface.free();
ui._widgets.remove(this);
ui._windowOpen = false;
}
void WidgetBase::close() {
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
banishWindow();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
events.clearEvents();
}
bool WidgetBase::active() const {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
for (Common::List<WidgetBase *>::iterator i = ui._widgets.begin(); i != ui._widgets.end(); ++i) {
if ((*i) == this)
return true;
}
return false;
}
void WidgetBase::erase() {
Screen &screen = *_vm->_screen;
if (_oldBounds.width() > 0) {
// Restore the affected area from the secondary back buffer into the first one, and then copy to screen
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds);
screen.slamRect(_oldBounds);
// Reset the old bounds so it won't be erased again
_oldBounds = Common::Rect(0, 0, 0, 0);
}
}
void WidgetBase::draw() {
Screen &screen = *_vm->_screen;
// If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it
if (_oldBounds.width() > 0 && _oldBounds != _bounds)
erase();
if (_bounds.width() > 0 && !_surface.empty()) {
// Get the area to draw, adjusted for scroll position
restrictToScreen();
// Draw the background for the widget
drawBackground();
// Draw the widget onto the back buffer and then slam it to the screen
screen._backBuffer1.SHtransBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top));
screen.slamRect(_bounds);
// Store a copy of the drawn area for later erasing
_oldBounds = _bounds;
}
}
void WidgetBase::drawBackground() {
TattooEngine &vm = *(TattooEngine *)_vm;
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Rect bounds = _bounds;
bounds.grow(-3);
if (vm._transparentMenus) {
ui.makeBGArea(bounds);
} else {
screen._backBuffer1.fillRect(bounds, MENU_BACKGROUND);
}
}
Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) {
Talk &talk = *_vm->_talk;
lines.clear();
uint idx;
for (idx = 0; idx < str.size(); idx++)
if (str[idx] >= talk._opcodes[OP_SWITCH_SPEAKER] && str[idx] != talk._opcodes[OP_NULL])
break;
Common::String rest;
Common::Array<Common::String> arr = _surface.wordWrap(str.substr(0, idx), maxWidth, rest, Common::String::npos, maxLines);
lines.swap(arr);
// Return any remaining text left over
return rest + str.substr(idx);
}
void WidgetBase::restrictToScreen() {
Screen &screen = *_vm->_screen;
if (_bounds.left < screen._currentScroll.x)
_bounds.moveTo(screen._currentScroll.x, _bounds.top);
if (_bounds.top < 0)
_bounds.moveTo(_bounds.left, 0);
if (_bounds.right > (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH))
_bounds.moveTo(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH - _bounds.width(), _bounds.top);
if (_bounds.bottom > screen._backBuffer1.height())
_bounds.moveTo(_bounds.left, screen._backBuffer1.height() - _bounds.height());
}
void WidgetBase::makeInfoArea(Surface &s) {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
// Draw the four corners of the Info Box
s.SHtransBlitFrom(images[0], Common::Point(0, 0));
s.SHtransBlitFrom(images[1], Common::Point(s.width() - images[1]._width, 0));
s.SHtransBlitFrom(images[2], Common::Point(0, s.height() - images[2]._height));
s.SHtransBlitFrom(images[3], Common::Point(s.width() - images[3]._width, s.height() - images[3]._height));
// Draw the top of the Info Box
s.hLine(images[0]._width, 0, s.width() - images[1]._width, INFO_TOP);
s.hLine(images[0]._width, 1, s.width() - images[1]._width, INFO_MIDDLE);
s.hLine(images[0]._width, 2, s.width() - images[1]._width, INFO_BOTTOM);
// Draw the bottom of the Info Box
s.hLine(images[0]._width, s.height()- 3, s.width() - images[1]._width, INFO_TOP);
s.hLine(images[0]._width, s.height()- 2, s.width() - images[1]._width, INFO_MIDDLE);
s.hLine(images[0]._width, s.height()- 1, s.width() - images[1]._width, INFO_BOTTOM);
// Draw the left Side of the Info Box
s.vLine(0, images[0]._height, s.height()- images[2]._height, INFO_TOP);
s.vLine(1, images[0]._height, s.height()- images[2]._height, INFO_MIDDLE);
s.vLine(2, images[0]._height, s.height()- images[2]._height, INFO_BOTTOM);
// Draw the right Side of the Info Box
s.vLine(s.width() - 3, images[0]._height, s.height()- images[2]._height, INFO_TOP);
s.vLine(s.width() - 2, images[0]._height, s.height()- images[2]._height, INFO_MIDDLE);
s.vLine(s.width() - 1, images[0]._height, s.height()- images[2]._height, INFO_BOTTOM);
}
void WidgetBase::makeInfoArea() {
makeInfoArea(_surface);
}
void WidgetBase::drawDialogRect(const Common::Rect &r, bool raised) {
static_cast<TattooUserInterface *>(_vm->_ui)->drawDialogRect(_surface, r, raised);
}
void WidgetBase::checkTabbingKeys(int numOptions) {
}
Common::Rect WidgetBase::getScrollBarBounds() const {
Common::Rect r(BUTTON_SIZE, _bounds.height() - 6);
r.moveTo(_bounds.width() - BUTTON_SIZE - 3, 3);
return r;
}
void WidgetBase::drawScrollBar(int index, int pageSize, int count) {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
// Fill the area with transparency
Common::Rect r = getScrollBarBounds();
_surface.fillRect(r, TRANSPARENCY);
bool raised = ui._scrollHighlight != 1;
_surface.fillRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.top + BUTTON_SIZE - 2), INFO_MIDDLE);
ui.drawDialogRect(_surface, Common::Rect(r.left, r.top, r.left + BUTTON_SIZE, r.top + BUTTON_SIZE), raised);
raised = ui._scrollHighlight != 5;
_surface.fillRect(Common::Rect(r.left + 2, r.bottom - BUTTON_SIZE + 2, r.right - 2, r.bottom - 2), INFO_MIDDLE);
ui.drawDialogRect(_surface, Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom), raised);
// Draw the arrows on the scroll buttons
byte color = index ? INFO_BOTTOM + 2 : INFO_BOTTOM;
_surface.hLine(r.left + r.width() / 2, r.top - 2 + BUTTON_SIZE / 2, r.left + r.width() / 2, color);
_surface.hLine(r.left + r.width() / 2 - 1, r.top - 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color);
_surface.hLine(r.left + r.width() / 2 - 2, r.top + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color);
_surface.hLine(r.left + r.width() / 2 - 3, r.top + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color);
color = (index + pageSize) < count ? INFO_BOTTOM + 2 : INFO_BOTTOM;
_surface.hLine(r.left + r.width() / 2 - 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color);
_surface.hLine(r.left + r.width() / 2 - 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color);
_surface.hLine(r.left + r.width() / 2 - 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color);
_surface.hLine(r.left + r.width() / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2, r.left + r.width() / 2, color);
// Draw the scroll position bar
int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count;
barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2);
int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE +
(r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize);
_surface.fillRect(Common::Rect(r.left + 2, barY + 2, r.right - 2, barY + barHeight - 2), INFO_MIDDLE);
ui.drawDialogRect(_surface, Common::Rect(r.left, barY, r.right, barY + barHeight), true);
}
void WidgetBase::handleScrollbarEvents(int index, int pageSize, int count) {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
// If they're dragging the scrollbar thumb, keep it selected whilst the button is being held
if ((events._pressed || events._released) && ui._scrollHighlight == SH_THUMBNAIL)
return;
ui._scrollHighlight = SH_NONE;
if ((!events._pressed && !events._rightReleased) || !_scroll)
return;
Common::Rect r = getScrollBarBounds();
r.translate(_bounds.left, _bounds.top);
// Calculate the Scroll Position bar
int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count;
barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2);
int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE +
(r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize);
if (Common::Rect(r.left, r.top, r.right, r.top + BUTTON_SIZE).contains(mousePos))
// Mouse on scroll up button
ui._scrollHighlight = SH_SCROLL_UP;
else if (Common::Rect(r.left, r.top + BUTTON_SIZE, r.right, barY).contains(mousePos))
// Mouse on paging up area (the area of the vertical bar above the thumbnail)
ui._scrollHighlight = SH_PAGE_UP;
else if (Common::Rect(r.left, barY, r.right, barY + barHeight).contains(mousePos))
// Mouse on scrollbar thumb
ui._scrollHighlight = SH_THUMBNAIL;
else if (Common::Rect(r.left, barY + barHeight, r.right, r.bottom - BUTTON_SIZE).contains(mousePos))
// Mouse on paging down area (the area of the vertical bar below the thumbnail)
ui._scrollHighlight = SH_PAGE_DOWN;
else if (Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom).contains(mousePos))
// Mouse on scroll down button
ui._scrollHighlight = SH_SCROLL_DOWN;
}
void WidgetBase::handleScrolling(int &scrollIndex, int pageSize, int max) {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::CustomEventType action = ui._action;
Common::Point mousePos = events.mousePos();
Common::Rect r = getScrollBarBounds();
r.translate(_bounds.left, _bounds.top);
if (ui._scrollHighlight != SH_NONE || action == kActionTattooWidgetScrollStart || action == kActionTattooWidgetScrollEnd
|| action == kActionTattooWidgetScrollPageUp || action == kActionTattooWidgetScrollPageDown
|| action == kActionTattooWidgetScrollUp || action == kActionTattooWidgetScrollDown) {
// Check for the scrollbar
if (ui._scrollHighlight == SH_THUMBNAIL) {
int yp = mousePos.y;
yp = CLIP(yp, r.top + BUTTON_SIZE + 3, r.bottom - BUTTON_SIZE - 3);
// Calculate the line number that corresponds to the position that the mouse is on the scrollbar
int lineNum = (yp - r.top - BUTTON_SIZE - 3) * (max - pageSize) / (r.height() - BUTTON_SIZE * 2 - 6);
scrollIndex = CLIP(lineNum, 0, max - pageSize);
}
// Get the current frame so we can check the scroll timer against it
uint32 frameNum = events.getFrameCounter();
if (frameNum > _dialogTimer) {
// Set the timeout for the next scroll if the mouse button remains held down
_dialogTimer = (_dialogTimer == 0) ? frameNum + pageSize : frameNum + 1;
// Check for Scroll Up
if ((ui._scrollHighlight == SH_SCROLL_UP || action == kActionTattooWidgetScrollUp) && scrollIndex)
--scrollIndex;
// Check for Page Up
else if ((ui._scrollHighlight == SH_PAGE_UP || action == kActionTattooWidgetScrollPageUp) && scrollIndex)
scrollIndex -= pageSize;
// Check for Page Down
else if ((ui._scrollHighlight == SH_PAGE_DOWN || action == kActionTattooWidgetScrollPageDown)
&& (scrollIndex + pageSize < max)) {
scrollIndex += pageSize;
if (scrollIndex + pageSize >max)
scrollIndex = max - pageSize;
}
// Check for Scroll Down
else if ((ui._scrollHighlight == SH_SCROLL_DOWN || action == kActionTattooWidgetScrollDown) && (scrollIndex + pageSize < max))
++scrollIndex;
}
if (action == kActionTattooWidgetScrollEnd)
scrollIndex = max - pageSize;
if (scrollIndex < 0 || action == kActionTattooWidgetScrollStart)
scrollIndex = 0;
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,146 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_BASE_H
#define SHERLOCK_TATTOO_WIDGET_BASE_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "common/str-array.h"
#include "sherlock/surface.h"
namespace Sherlock {
class SherlockEngine;
class ImageFile;
namespace Tattoo {
class WidgetBase {
private:
uint32 _dialogTimer;
protected:
SherlockEngine *_vm;
Common::Rect _bounds;
Common::Rect _oldBounds;
Surface _surface;
bool _outsideMenu;
bool _scroll;
/**
* Used by descendent classes to split up long text for display across multiple lines
*/
Common::String splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines);
/**
* Ensure that menu is drawn entirely on-screen
*/
void restrictToScreen();
/**
* Draw a window frame around the dges of the passed surface
*/
void makeInfoArea(Surface &s);
/**
* Draw a window frame around the widget's surface
*/
void makeInfoArea();
/**
* Draw a dialog rectangle
*/
void drawDialogRect(const Common::Rect &r, bool raised = true);
/**
* Return the area of a widget that the scrollbar will be drawn in
*/
virtual Common::Rect getScrollBarBounds() const;
/**
* Draw the scrollbar for the dialog
*/
void drawScrollBar(int index, int pageSize, int count);
/**
* Handles any events when the mouse is on the scrollbar
*/
void handleScrollbarEvents(int index, int pageSize, int count);
/**
* Handle adjusting a passed scrolling index as necessary
*/
void handleScrolling(int &scrollIndex, int pageSize, int max);
/**
* Close the dialog
*/
void close();
/**
* Handle drawing the background on the area the widget is going to cover
*/
virtual void drawBackground();
public:
WidgetBase(SherlockEngine *vm);
virtual ~WidgetBase() {}
/**
* Returns true if the given widget is active in the user interface's widget list
*/
bool active() const;
/**
* Erase any previous display of the widget on the screen
*/
virtual void erase();
/**
* Update the display of the widget on the screen
*/
virtual void draw();
/**
* Used by some descendents to check for keys to mouse the mouse within the dialog
*/
void checkTabbingKeys(int numOptions);
/**
* Summon the window
*/
virtual void summonWindow();
/**
* Close a currently active menu
*/
virtual void banishWindow();
/**
* Handle event processing
*/
virtual void handleEvents() {}
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,215 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_credits.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
WidgetCredits::WidgetCredits(SherlockEngine *vm) : _vm(vm) {
_creditSpeed = 4;
_creditsActive = false;
}
void WidgetCredits::initCredits() {
Resources &res = *_vm->_res;
Screen &screen = *_vm->_screen;
Common::SeekableReadStream *stream = res.load("credits.txt");
int spacing = screen.fontHeight() * 2;
int yp = screen.height();
_creditsActive = true;
_creditLines.clear();
while (stream->pos() < stream->size()) {
Common::String line = stream->readLine();
if (line.hasPrefix("Scroll Speed")) {
const char *p = line.c_str() + 12;
while ((*p < '0') || (*p > '9'))
p++;
_creditSpeed = atoi(p);
} else if (line.hasPrefix("Y Spacing")) {
const char *p = line.c_str() + 12;
while ((*p < '0') || (*p > '9'))
p++;
spacing = atoi(p) + screen.fontHeight() + 1;
} else {
int width = screen.stringWidth(line) + 2;
_creditLines.push_back(CreditLine(line, Common::Point((screen.width() - width) / 2 + 1, yp), width));
yp += spacing;
}
}
// Post-processing for finding split lines
for (int l = 0; l < (int)_creditLines.size(); ++l) {
CreditLine &cl = _creditLines[l];
const char *p = strchr(cl._line.c_str(), '-');
if (p != nullptr && p[1] == '>') {
cl._line2 = Common::String(p + 3);
cl._line = Common::String(cl._line.c_str(), p);
int width = cl._width;
int width1 = screen.stringWidth(cl._line);
int width2 = screen.stringWidth(cl._line2);
int c = 1;
for (int l1 = l + 1; l1 < (int)_creditLines.size(); ++l1) {
if ((p = strchr(_creditLines[l1]._line.c_str(), '-')) != nullptr) {
if (p[1] == '>') {
Common::String line1 = Common::String(_creditLines[l1]._line.c_str(), p);
Common::String line2 = Common::String(p + 3);
width1 = MAX(width1, screen.stringWidth(line1));
if (screen.stringWidth(line2) > width2)
width2 = screen.stringWidth(line2);
++c;
} else {
break;
}
} else {
break;
}
}
width = width1 + width2 + screen.widestChar();
width1 += screen.widestChar();
for (int l1 = l; l1 < l + c; ++l1) {
_creditLines[l1]._width = width;
_creditLines[l1]._xOffset = width1;
}
l += c - 1;
}
}
delete stream;
}
void WidgetCredits::close() {
_creditsActive = false;
_creditLines.clear();
}
void WidgetCredits::drawCredits() {
Screen &screen = *_vm->_screen;
Common::Rect screenRect(0, 0, screen.width(), screen.height());
Surface &bb1 = screen._backBuffer1;
for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < screen.height(); ++idx) {
if (screenRect.contains(_creditLines[idx]._position)) {
if (!_creditLines[idx]._line2.empty()) {
int x1 = _creditLines[idx]._position.x;
int x2 = x1 + _creditLines[idx]._xOffset;
const Common::String &line1 = _creditLines[idx]._line;
const Common::String &line2 = _creditLines[idx]._line2;
bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y), 0);
bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y), 0);
bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y), INFO_TOP);
bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y), 0);
bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y), 0);
bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y), INFO_TOP);
} else {
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y - 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y + 1), 0);
bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y), INFO_TOP);
}
}
}
}
void WidgetCredits::blitCredits() {
Screen &screen = *_vm->_screen;
Common::Rect screenRect(0, -_creditSpeed, screen.width(), screen.height() + _creditSpeed);
for (uint idx = 0; idx < _creditLines.size(); ++idx) {
if (screenRect.contains(_creditLines[idx]._position)) {
Common::Rect r(_creditLines[idx]._width, screen.fontHeight() + 2);
r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1);
screen.slamRect(r);
}
}
}
void WidgetCredits::eraseCredits() {
Screen &screen = *_vm->_screen;
Common::Rect screenRect(0, -_creditSpeed, screen.width(), screen.height() + _creditSpeed);
for (uint idx = 0; idx < _creditLines.size(); ++idx) {
if (screenRect.contains(_creditLines[idx]._position)) {
Common::Rect r(_creditLines[idx]._width, screen.fontHeight() + 3);
r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1 + _creditSpeed);
screen.restoreBackground(r);
screen.slamRect(r);
}
_creditLines[idx]._position.y -= _creditSpeed;
}
if (_creditLines[_creditLines.size() - 1]._position.y < -_creditSpeed) {
// Completely finished credits display. Note that the credits will still remain flagged as active,
// so that the user interface knows not to allow and standard scene interaction
_creditLines.clear();
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,89 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_CREDITS_H
#define SHERLOCK_TATTOO_CREDITS_H
#include "common/array.h"
#include "common/rect.h"
#include "common/str.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
struct CreditLine {
Common::Point _position;
int _xOffset;
int _width;
Common::String _line, _line2;
CreditLine(const Common::String &line, const Common::Point &pt, int width) :
_line(line), _position(pt), _width(width), _xOffset(0) {}
};
class WidgetCredits {
private:
SherlockEngine *_vm;
Common::Array<CreditLine> _creditLines;
int _creditSpeed;
bool _creditsActive;
public:
WidgetCredits(SherlockEngine *vm);
/**
* Returns true if the credits are active
*/
bool active() const { return _creditsActive; }
/**
* Initialize and load credit data for display
*/
void initCredits();
/**
* Closes down credits display
*/
void close();
/**
* Draw credits on the screen
*/
void drawCredits();
/**
* Blit the drawn credits to the screen
*/
void blitCredits();
/**
* Erase any area of the screen covered by credits
*/
void eraseCredits();
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,457 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/translation.h"
#include "gui/saveload.h"
#include "sherlock/tattoo/widget_files.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
#define FILES_LINES_COUNT 5
WidgetFiles::WidgetFiles(SherlockEngine *vm, const Common::String &target) :
SaveManager(vm, target), WidgetBase(vm), _vm(vm) {
_fileMode = SAVEMODE_NONE;
_selector = _oldSelector = -1;
}
void WidgetFiles::show(SaveMode mode) {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
if (_vm->_showOriginalSavesDialog) {
// Render and display the file dialog
_fileMode = mode;
ui._menuMode = FILES_MODE;
_selector = _oldSelector = -1;
_scroll = true;
createSavegameList();
// Set up the display area
_bounds = Common::Rect(SHERLOCK_SCREEN_WIDTH * 2 / 3, (_surface.fontHeight() + 1) *
(FILES_LINES_COUNT + 1) + 17);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Create the surface and render its contents
_surface.create(_bounds.width(), _bounds.height());
render(RENDER_ALL);
summonWindow();
ui._menuMode = FILES_MODE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-scrolling")->setEnabled(true);
keymapper->getKeymap("tattoo-files")->setEnabled(true);
} else if (mode == SAVEMODE_LOAD) {
showScummVMRestoreDialog();
} else {
showScummVMSaveDialog();
}
}
void WidgetFiles::showScummVMSaveDialog() {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
int slot = dialog->runModalWithCurrentTarget();
if (slot >= 0) {
Common::String desc = dialog->getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didn't enter it
desc = dialog->createDefaultSaveDescription(slot);
}
_vm->saveGameState(slot, desc);
}
close();
delete dialog;
}
void WidgetFiles::showScummVMRestoreDialog() {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
int slot = dialog->runModalWithCurrentTarget();
close();
delete dialog;
if (slot >= 0) {
_vm->loadGameState(slot);
}
}
void WidgetFiles::render(FilesRenderMode mode) {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
byte color;
if (mode == RENDER_ALL) {
_surface.clear(TRANSPARENCY);
makeInfoArea();
switch (_fileMode) {
case SAVEMODE_LOAD:
_surface.writeString(FIXED(LoadGame),
Common::Point((_surface.width() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP);
break;
case SAVEMODE_SAVE:
_surface.writeString(FIXED(SaveGame),
Common::Point((_surface.width() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP);
break;
default:
break;
}
_surface.hLine(3, _surface.fontHeight() + 7, _surface.width() - 4, INFO_TOP);
_surface.hLine(3, _surface.fontHeight() + 8, _surface.width() - 4, INFO_MIDDLE);
_surface.hLine(3, _surface.fontHeight() + 9, _surface.width() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6));
_surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, _surface.fontHeight() + 6));
int xp = _surface.width() - BUTTON_SIZE - 6;
_surface.vLine(xp, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_TOP);
_surface.vLine(xp + 1, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_MIDDLE);
_surface.vLine(xp + 2, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8));
_surface.SHtransBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4));
}
int xp = _surface.stringWidth("00.") + _surface.widestChar() + 5;
int yp = _surface.fontHeight() + 14;
for (int idx = _savegameIndex; idx < (_savegameIndex + FILES_LINES_COUNT); ++idx) {
if (idx == _selector && mode != RENDER_ALL)
color = COMMAND_HIGHLIGHTED;
else
color = INFO_TOP;
if (mode == RENDER_NAMES_AND_SCROLLBAR)
_surface.fillRect(Common::Rect(4, yp, _surface.width() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY);
Common::String numStr = Common::String::format("%d.", idx + 1);
_surface.writeString(numStr, Common::Point(_surface.widestChar(), yp), color);
_surface.writeString(_savegames[idx], Common::Point(xp, yp), color);
yp += _surface.fontHeight() + 1;
}
// Draw the Scrollbar if necessary
if (mode != RENDER_NAMES)
drawScrollBar(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
}
void WidgetFiles::handleEvents() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::CustomEventType action = ui._action;
// Handle scrollbar events
ScrollHighlight oldHighlight = ui._scrollHighlight;
handleScrollbarEvents(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
int oldScrollIndex = _savegameIndex;
handleScrolling(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
// See if the mouse is pointing at any filenames in the window
if (Common::Rect(_bounds.left, _bounds.top + _surface.fontHeight() + 14,
_bounds.right - BUTTON_SIZE - 5, _bounds.bottom - 5).contains(mousePos)) {
_selector = (mousePos.y - _bounds.top - _surface.fontHeight() - 14) / (_surface.fontHeight() + 1) +
_savegameIndex;
} else {
_selector = -1;
}
// Check for the slot change actions
if (action == kActionTattooFilesNextSlot || action == kActionTattooFilesNextPageSlot) {
// If the mouse is not over any of the filenames, move the mouse so that it points to the first one
if (_selector == -1) {
events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 20,
_bounds.top + _surface.fontHeight() * 2 + 8));
} else {
// See if we're doing Next Page Slot action
if (action == kActionTattooFilesNextPageSlot) {
// We're doing Shift Tab
if (_selector == _savegameIndex)
_selector = _savegameIndex + 4;
else
--_selector;
} else {
// We're doing Next Slot action
++_selector;
if (_selector >= _savegameIndex + 5)
_selector = _savegameIndex;
}
events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() * 2
+ 8 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1)));
}
}
// Only redraw the window if the scrollbar position has changed
if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _savegameIndex || _selector != _oldSelector)
render(RENDER_NAMES_AND_SCROLLBAR);
_oldSelector = _selector;
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if (events._released || events._rightReleased || action == kActionTattooFilesSelect) {
ui._scrollHighlight = SH_NONE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
if (_outsideMenu && !_bounds.contains(mousePos)) {
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-files")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
} else {
_outsideMenu = false;
if (_selector != -1) {
if (_fileMode == SAVEMODE_LOAD) {
// We're in Load Mode
_vm->loadGameState(_selector);
} else if (_fileMode == SAVEMODE_SAVE) {
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-files")->setEnabled(false);
keymapper->getKeymap("tattoo-files-name")->setEnabled(true);
keymapper->getKeymap("tattoo-exit")->setEnabled(true);
// We're in Save Mode
if (getFilename())
_vm->saveGameState(_selector, _savegames[_selector]);
keymapper->getKeymap("tattoo-files-name")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
}
}
}
}
}
bool WidgetFiles::getFilename() {
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
int index = 0;
int done = 0;
bool blinkFlag = false;
int blinkCountdown = 0;
int cursorColor = 192;
byte color, textColor;
bool insert = true;
assert(_selector != -1);
Common::Point pt(_surface.stringWidth("00.") + _surface.widestChar() + 5,
_surface.fontHeight() + 14 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1));
Common::String numStr = Common::String::format("%d.", _selector + 1);
_surface.writeString(numStr, Common::Point(_surface.widestChar(), pt.y), COMMAND_HIGHLIGHTED);
Common::String filename = _savegames[_selector];
if (isSlotEmpty(_selector)) {
index = 0;
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
filename = "";
} else {
index = filename.size();
_surface.writeString(filename, pt, COMMAND_HIGHLIGHTED);
pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
}
do {
scene.doBgAnim();
if (talk._talkToAbort)
return false;
char currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
Common::String charString = Common::String::format("%c", currentChar);
int width = screen.charWidth(currentChar);
// Wait for keypress or action
while (!events.kbHit() && !events.actionHit()) {
events.pollEventsAndWait();
events.setButtonState();
scene.doBgAnim();
if (talk._talkToAbort)
return false;
if (--blinkCountdown <= 0) {
blinkCountdown = 3;
blinkFlag = !blinkFlag;
if (blinkFlag) {
textColor = 236;
color = cursorColor;
} else {
textColor = COMMAND_HIGHLIGHTED;
color = TRANSPARENCY;
}
_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), color);
if (currentChar != ' ')
_surface.writeString(charString, pt, textColor);
}
if (_vm->shouldQuit())
return false;
}
while (events.kbHit()) {
Common::KeyState keyState = events.getKey();
if (keyState.keycode == Common::KEYCODE_BACKSPACE && index > 0) {
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
if (insert) {
filename.deleteChar(index);
} else {
filename.setChar(' ', index);
}
_surface.fillRect(Common::Rect(pt.x, pt.y, _surface.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
} else if (keyState.keycode == Common::KEYCODE_DELETE && index < (int)filename.size()) {
filename.deleteChar(index);
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(Common::String(filename.c_str() + index), pt, COMMAND_HIGHLIGHTED);
} else if (keyState.keycode == Common::KEYCODE_RETURN) {
done = 1;
}
if ((keyState.ascii >= ' ') && (keyState.ascii <= 'z') && (index < 50)) {
if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w - BUTTON_SIZE - 20) {
if (insert)
filename.insertChar(keyState.ascii, index);
else
filename.setChar(keyState.ascii, index);
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9,
pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
pt.x += _surface.charWidth(keyState.ascii);
++index;
}
}
currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
charString = Common::String::format("%c", currentChar);
width = screen.charWidth(currentChar);
}
while (events.actionHit()) {
Common::CustomEventType action = events.getAction();
if ((action == kActionTattooFilesNameLeft && index > 0)
|| (action == kActionTattooFilesNameRight && index < (int)filename.size() && pt.x < (_bounds.right - BUTTON_SIZE - 20))
|| (action == kActionTattooFilesNameStart && index > 0)
|| (action == kActionTattooFilesNameEnd)) {
_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), TRANSPARENCY);
if (currentChar)
_surface.writeString(charString, pt, COMMAND_HIGHLIGHTED);
switch (action) {
case kActionTattooFilesNameLeft:
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
break;
case kActionTattooFilesNameRight:
pt.x += _surface.charWidth(filename[index]);
++index;
break;
case kActionTattooFilesNameStart:
pt.x = _surface.stringWidth("00.") + _surface.widestChar() + 5;
index = 0;
break;
case kActionTattooFilesNameEnd:
pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
index = filename.size();
while (filename[index - 1] == ' ' && index > 0) {
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
}
break;
default:
break;
}
} else if (action == kActionTattooFilesNameToggleInsertMode) {
insert = !insert;
if (insert)
cursorColor = 192;
else
cursorColor = 200;
} else if (action == kActionTattooExit) {
_selector = -1;
render(RENDER_NAMES_AND_SCROLLBAR);
done = -1;
}
currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
charString = Common::String::format("%c", currentChar);
width = screen.charWidth(currentChar);
}
} while (!done && !_vm->shouldQuit());
scene.doBgAnim();
if (talk._talkToAbort)
return false;
if (done == 1)
_savegames[_selector] = filename;
return done == 1;
}
Common::Rect WidgetFiles::getScrollBarBounds() const {
Common::Rect scrollRect(BUTTON_SIZE, _bounds.height() - _surface.fontHeight() - 13);
scrollRect.moveTo(_bounds.width() - BUTTON_SIZE - 3, _surface.fontHeight() + 10);
return scrollRect;
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_FILES_H
#define SHERLOCK_TATTOO_WIDGET_FILES_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/saveload.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
enum FilesRenderMode { RENDER_ALL, RENDER_NAMES, RENDER_NAMES_AND_SCROLLBAR };
class WidgetFiles: public WidgetBase, public SaveManager {
private:
SherlockEngine *_vm;
SaveMode _fileMode;
int _selector, _oldSelector;
/**
* Render the dialog
*/
void render(FilesRenderMode mode);
/**
* Show the ScummVM Save Game dialog
*/
void showScummVMSaveDialog();
/**
* Show the ScummVM Load Game dialog
*/
void showScummVMRestoreDialog();
/**
* Prompt the user for a savegame name in the currently selected slot
*/
bool getFilename();
/**
* Return the area of a widget that the scrollbar will be drawn in
*/
Common::Rect getScrollBarBounds() const override;
public:
WidgetFiles(SherlockEngine *vm, const Common::String &target);
/**
* Prompt the user whether to quit
*/
void show(SaveMode mode);
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,321 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_foolscap.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
WidgetFoolscap::WidgetFoolscap(TattooEngine *vm) : WidgetBase(vm) {
for (int idx = 0; idx < 3; ++idx) {
Common::fill(&_answers[idx][0], &_answers[idx][10], 0);
_solutions[idx] = nullptr;
}
_images = nullptr;
_numWide = 0;
_spacing = 0;
_blinkFlag = false;
_blinkCounter = 0;
_lineNum = _charNum = 0;
_solved = false;
}
WidgetFoolscap::~WidgetFoolscap() {
delete _images;
}
void WidgetFoolscap::show() {
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
switch (_vm->getLanguage()) {
case Common::FR_FRA:
_lines[0] = Common::Point(34, 210);
_lines[1] = Common::Point(72, 242);
_lines[2] = Common::Point(34, 276);
_numWide = 8;
_spacing = 19;
_images = new ImageFile("paperf.vgs");
break;
case Common::DE_DEU:
_lines[0] = Common::Point(44, 73);
_lines[1] = Common::Point(56, 169);
_lines[2] = Common::Point(47, 256);
_numWide = 7;
_spacing = 19;
_images = new ImageFile("paperg.vgs");
break;
default:
// English
_lines[0] = Common::Point(65, 84);
_lines[1] = Common::Point(65, 159);
_lines[2] = Common::Point(75, 234);
_numWide = 5;
_spacing = 20;
_images = new ImageFile("paper.vgs");
break;
}
_solved = false;
_blinkFlag = false;
_blinkCounter = 0;
_lineNum = _charNum = 0;
_cursorPos = Common::Point(_lines[0].x + 8 - screen.widestChar() / 2, _lines[0].y - screen.fontHeight() - 2);
// Set up window bounds
ImageFrame &paperFrame = (*_images)[0];
_bounds = Common::Rect(paperFrame._width, paperFrame._height);
_bounds.moveTo(screen._currentScroll.x + (SHERLOCK_SCREEN_WIDTH - paperFrame._width) / 2,
(SHERLOCK_SCREEN_HEIGHT - paperFrame._height) / 2);
// Clear answer data and set correct solution strings
for (int idx = 0; idx < 3; ++idx)
Common::fill(&_answers[idx][0], &_answers[idx][10], 0);
_solutions[0] = FIXED(Apply);
_solutions[1] = FIXED(Water);
_solutions[2] = FIXED(Heat);
// Set up the window background
_surface.create(_bounds.width(), _bounds.height());
_surface.SHblitFrom(paperFrame, Common::Point(0, 0));
// If they have already solved the puzzle, put the answer on the graphic
if (_vm->readFlags(299)) {
Common::Point cursorPos;
for (int line = 0; line < 3; ++line) {
cursorPos.y = _lines[line].y - screen.fontHeight() - 2;
for (uint idx = 0; idx < strlen(_solutions[line]); ++idx) {
cursorPos.x = _lines[line].x + 8 - screen.widestChar() / 2 + idx * _spacing;
char c = _solutions[line][idx];
Common::String str = Common::String::format("%c", c);
_surface.writeString(str, Common::Point(cursorPos.x + screen.widestChar() / 2
- screen.charWidth(c) / 2, cursorPos.y), 0);
}
}
}
// Show the window
summonWindow();
ui._menuMode = FOOLSCAP_MODE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-foolscap")->setEnabled(true);
}
void WidgetFoolscap::handleEvents() {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
byte cursorColor = 254;
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
// If they have not solved the puzzle, let them solve it here
if (!_vm->readFlags(299)) {
if (!ui._keyState.keycode && !ui._action) {
if (--_blinkCounter < 0) {
_blinkCounter = 3;
_blinkFlag = !_blinkFlag;
if (_blinkFlag) {
// Draw the caret
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + screen.widestChar() - 1,
_cursorPos.y + screen.fontHeight() - 1), cursorColor);
if (_answers[_lineNum][_charNum]) {
Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]);
_surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2
- screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0);
}
} else {
// Restore background
restoreChar();
// Draw the character at that position if there is one
if (_answers[_lineNum][_charNum]) {
Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]);
_surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2
- screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0);
}
}
}
} else {
// Handle keyboard and action events
handleKeyboardEvents();
}
}
if ((events._released || events._rightReleased) && _outsideMenu && !_bounds.contains(mousePos)) {
// Clicked outside window to close it
events.clearEvents();
close();
}
}
void WidgetFoolscap::handleKeyboardEvents() {
Screen &screen = *_vm->_screen;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::KeyState keyState = ui._keyState;
Common::CustomEventType action = ui._action;
if ((toupper(keyState.ascii) >= 'A') && (toupper(keyState.ascii) <= 'Z')) {
// Visible key pressed, set it and set the keycode to move the caret to the right
_answers[_lineNum][_charNum] = keyState.ascii;
action = kActionTattooFoolscapRight;
}
// Restore background
restoreChar();
if (_answers[_lineNum][_charNum]) {
Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]);
_surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2
- screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0);
}
switch (keyState.keycode) {
case Common::KEYCODE_BACKSPACE:
if (_charNum)
--_charNum;
else if (_lineNum) {
--_lineNum;
_charNum = strlen(_solutions[_lineNum]) - 1;
}
_answers[_lineNum][_charNum] = ' ';
break;
case Common::KEYCODE_DELETE:
_answers[_lineNum][_charNum] = ' ';
break;
default:
break;
}
switch (action) {
case kActionTattooFoolscapExit:
close();
break;
case kActionTattooFoolscapUp:
if (_lineNum) {
--_lineNum;
if (_charNum >= (int)strlen(_solutions[_lineNum]))
_charNum = (int)strlen(_solutions[_lineNum]) - 1;
}
break;
case kActionTattooFoolscapDown:
if (_lineNum < 2) {
++_lineNum;
if (_charNum >= (int)strlen(_solutions[_lineNum]))
_charNum = (int)strlen(_solutions[_lineNum]) - 1;
}
break;
case kActionTattooFoolscapLeft:
if (_charNum)
--_charNum;
else if (_lineNum) {
--_lineNum;
_charNum = strlen(_solutions[_lineNum]) - 1;
}
break;
case kActionTattooFoolscapRight:
if (_charNum < (int)strlen(_solutions[_lineNum]) - 1)
++_charNum;
else if (_lineNum < 2) {
++_lineNum;
_charNum = 0;
}
break;
default:
break;
}
_cursorPos.x = _lines[_lineNum].x + 8 - screen.widestChar() / 2 + _charNum * _spacing;
_cursorPos.y = _lines[_lineNum].y - screen.fontHeight() - 2;
// See if all of their anwers are correct
if (!scumm_stricmp(_answers[0], _solutions[0]) && !scumm_stricmp(_answers[1], _solutions[1])
&& !scumm_stricmp(_answers[2], _solutions[2])) {
_solved = true;
close();
}
}
void WidgetFoolscap::restoreChar() {
Screen &screen = *_vm->_screen;
ImageFrame &bgFrame = (*_images)[0];
_surface.SHblitFrom(bgFrame, _cursorPos, Common::Rect(_cursorPos.x, _cursorPos.y,
_cursorPos.x + screen.widestChar(), _cursorPos.y + screen.fontHeight()));
}
void WidgetFoolscap::close() {
TattooScene &scene = *(TattooScene *)_vm->_scene;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
delete _images;
_images = nullptr;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-foolscap")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
// Close the window
banishWindow();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
// Don't call the talk files if the puzzle has already been solved
if (!_vm->readFlags(299)) {
// Run the appropriate script depending on whether or not they solved the puzzle correctly
if (_solved) {
talk.talkTo("SLVE12S.TLK");
talk.talkTo("WATS12X.TLK");
_vm->setFlags(299);
} else {
talk.talkTo("HOLM12X.TLK");
}
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,81 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_FOOLSCAP_H
#define SHERLOCK_TATTOO_FOOLSCAP_H
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/image_file.h"
namespace Sherlock {
namespace Tattoo {
class TattooEngine;
class WidgetFoolscap: public WidgetBase {
private:
ImageFile *_images;
Common::Point _lines[3];
char _answers[3][10];
const char *_solutions[3];
int _numWide;
int _spacing;
Common::Point _cursorPos;
int _blinkCounter;
bool _blinkFlag;
int _lineNum, _charNum;
bool _solved;
/**
* Handle keyboard events
*/
void handleKeyboardEvents();
/**
* Restore the background for the current line/horiz position
*/
void restoreChar();
public:
WidgetFoolscap(TattooEngine *vm);
~WidgetFoolscap() override;
/**
* Show the foolscap puzzle
*/
void show();
/**
* Close the window
*/
void close();
/**
* Handle events whilst the widget is on-screen
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,802 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_inventory.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
#define INVENTORY_XSIZE 70 // Width of the box that surrounds inventory items
#define INVENTORY_YSIZE 70 // Height of the box that surrounds inventory items
#define MAX_INV_COMMANDS 10 // Maximum elements in dialog
#define NUM_INV_PER_LINE 4 // Number of inentory items per line in the dialog
WidgetInventoryTooltip::WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner) :
WidgetTooltipBase(vm), _owner(owner) {
}
void WidgetInventoryTooltip::setText(const Common::String &str) {
// If no text specified, erase any previously displayed tooltip and free its surface
if (str.empty()) {
erase();
_surface.free();
return;
}
int width = _surface.stringWidth(str) + 2;
int height = 0;
Common::String line1 = str, line2;
// See if we need to split it into two lines
if (width > 150) {
// Yes, we do
const char *s = str.c_str();
const char *space = nullptr;
int dif = 10000;
while (*s) {
s = strchr(s, ' ');
if (!s) {
if (!space) {
height = _surface.stringHeight(str) + 2;
} else {
line1 = Common::String(str.c_str(), space);
line2 = Common::String(space + 1);
height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4;
}
break;
} else {
line1 = Common::String(str.c_str(), s);
line2 = Common::String(s + 1);
int width1 = _surface.stringWidth(line1);
int width2 = _surface.stringWidth(line2);
if (ABS(width1 - width2) < dif) {
// Found a split point that results in less overall width
space = s;
dif = ABS(width1 - width2);
width = MAX(width1, width2);
}
s++;
}
}
} else {
height = _surface.stringHeight(str) + 2;
}
// Allocate a fresh surface for the new string
_bounds = Common::Rect(width, height);
_surface.create(width, height);
_surface.clear(TRANSPARENCY);
if (line2.empty()) {
_surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP);
} else {
int xp, yp;
xp = (_bounds.width() - _surface.stringWidth(line1) - 2) / 2;
_surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP);
xp = (_bounds.width() - _surface.stringWidth(line2) - 2) / 2;
yp = _surface.stringHeight(line2) + 2;
_surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP);
}
}
void WidgetInventoryTooltip::handleEvents() {
Events &events = *_vm->_events;
FixedText &fixedText = *_vm->_fixedText;
Inventory &inv = *_vm->_inventory;
TattooPeople &people = *(TattooPeople *)_vm->_people;
Scene &scene = *_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::String str;
int select = -1, oldSelect = 999;
Common::String strWith = fixedText.getText(kFixedText_With);
Common::String strUse = fixedText.getText(kFixedText_Use);
// Register the tooltip for requiring post-rendering drawing, since we draw directly to the screen if a scene
// mask is active, since the initial draw to the screen will be covered by the mask rendering
if (ui._mask) {
ui._postRenderWidgets.push_back(this);
}
// If we are using an inventory item on an object in the room, display the appropriate text above the mouse cursor
if (_owner->_invVerbMode == 3) {
select = ui._bgFound;
oldSelect = ui._oldBgFound;
if (select != -1 && (select != oldSelect || (select != -1 && _surface.empty()))) {
// See if we're pointing at a shape or a sprite
if (select < 1000) {
Object &obj = scene._bgShapes[select];
if (!obj._description.empty() && !obj._description.hasPrefix(" ")) {
if (_vm->getLanguage() == Common::EL_GRC) {
if (!_owner->_swapItems)
str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), obj._description.c_str(),
inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
else
str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
obj._description.c_str(), _owner->_verb.c_str());
} else {
if (_owner->_swapItems)
str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), obj._description.c_str(), _owner->_action.c_str(),
inv[_owner->_invSelect]._name.c_str());
else
str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), inv[_owner->_invSelect]._name.c_str(),
_owner->_action.c_str(), obj._description.c_str());
}
}
} else {
Person &person = people[ui._bgFound - 1000];
if (!person._description.empty() && !person._description.hasPrefix(" ")) {
if (_vm->getLanguage() == Common::EL_GRC) {
if (!_owner->_swapItems)
str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), person._description.c_str(),
inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
else
str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
person._description.c_str(), _owner->_verb.c_str());
} else {
if (_owner->_swapItems)
str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), person._description.c_str(),
_owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str());
else
str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(),
inv[_owner->_invSelect]._name.c_str(), _owner->_action.c_str(), person._description.c_str());
}
}
}
}
} else {
const Common::Rect &b = _owner->_bounds;
Common::Rect r(b.left + 3, b.top + 3, b.right - 3 - BUTTON_SIZE, b.bottom - 3);
if (r.contains(mousePos)) {
select = (mousePos.x - r.left) / (INVENTORY_XSIZE + 3) + NUM_INVENTORY_SHOWN / 2 *
((mousePos.y - r.top) / (INVENTORY_YSIZE + 3)) + inv._invIndex;
if (select >= inv._holdings) {
select = -1;
} else {
oldSelect = _owner->_invSelect;
if (select != _owner->_invSelect || _surface.empty()) {
if (_owner->_invMode == 1) {
// See if we were pointing at a shapre or sprite
if (ui._activeObj < 1000) {
Object &obj = scene._bgShapes[ui._activeObj];
if (!obj._description.empty() && !obj._description.hasPrefix(" "))
str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
strWith.c_str(), obj._description.c_str());
} else {
Person &person = people[ui._activeObj - 1000];
if (!person._description.empty() && !person._description.hasPrefix(" "))
str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
strWith.c_str(), person._description.c_str());
}
} else {
if (_owner->_invVerbMode == 2)
str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[_owner->_invSelect]._name.c_str(),
strWith.c_str(), inv[select]._name.c_str());
else
str = inv[select]._description.c_str();
}
}
}
}
}
// See if they are pointing at a different inventory object and we need to
// change the graphics of the Text Tag
if (select != oldSelect || (select != -1 && _surface.empty())) {
// Set the text
setText(str);
if (_owner->_invVerbMode != 3)
_owner->_invSelect = select;
else
ui._oldBgFound = select;
} else if (select == -1 && oldSelect != -1) {
setText(Common::String());
return;
}
if (_owner->_invVerbMode == 3)
// Adjust tooltip to be above the inventory item being shown above the standard cursor
mousePos.y -= events._hotspotPos.y;
// Update the position of the tooltip
int xs = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCENE_WIDTH - _bounds.width());
int ys = CLIP(mousePos.y - _bounds.height(), 0, SHERLOCK_SCREEN_HEIGHT - _bounds.height());
_bounds.moveTo(xs, ys);
}
/*----------------------------------------------------------------*/
WidgetInventoryVerbs::WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner) :
WidgetBase(vm), _owner(owner) {
_invVerbSelect = _oldInvVerbSelect = -1;
}
void WidgetInventoryVerbs::load() {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
// Make the Verb List for this Inventory Item
_inventCommands.clear();
_inventCommands.push_back(FIXED(Look));
// Default the Action word to "with"
_owner->_action = _vm->getLanguage() == Common::EL_GRC ? "" : FIXED(With);
// Search all the bgshapes for any matching Target Fields
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
Object &obj = scene._bgShapes[idx];
if (obj._type != INVALID && obj._type != HIDDEN) {
for (int useNum = 0; useNum < 6; ++useNum) {
if (!obj._use[useNum]._verb.hasPrefix("*") &&
!obj._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name)) {
// Make sure the Verb is not already in the list
bool found1 = false;
for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
if (!_inventCommands[cmdNum].compareToIgnoreCase(obj._use[useNum]._verb))
found1 = true;
}
if (!found1) {
_inventCommands.push_back(obj._use[useNum]._verb);
// Check for any Special Action commands
for (int nameNum = 0; nameNum < 4; ++nameNum) {
if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*V", 2)) {
if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*VSWAP", 6))
_owner->_swapItems = true;
else
_owner->_action = Common::String(obj._use[useNum]._names[nameNum].c_str() + 2);
}
}
}
}
}
}
}
// Search the NPCs for matches as well
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
for (int useNum = 0; useNum < 2; ++useNum) {
if (!people[idx]._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name) &&
!people[idx]._use[useNum]._verb.empty() && !people[idx]._use[useNum]._verb.hasPrefix(" ")) {
bool found1 = false;
for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
if (!_inventCommands[cmdNum].compareToIgnoreCase(people[idx]._use[cmdNum]._verb))
found1 = true;
}
if (!found1)
_inventCommands.push_back(people[idx]._use[useNum]._verb);
}
}
}
// Finally see if the item itself has a verb
if (!inv[_owner->_invSelect]._verb._verb.empty()) {
// Don't add "Solve" to the Foolscap if it's already been "Solved"
if (inv[_owner->_invSelect]._verb._verb.compareToIgnoreCase(FIXED(Solve)) || !_vm->readFlags(299))
_inventCommands.push_back(inv[_owner->_invSelect]._verb._verb);
}
// Now find the widest command in the _inventCommands array
int width = 0;
for (uint idx = 0; idx < _inventCommands.size(); ++idx)
width = MAX(width, _surface.stringWidth(_inventCommands[idx]));
// Set up bounds for the menu
_bounds = Common::Rect(width + _surface.widestChar() * 2 + 6,
(_surface.fontHeight() + 7) * _inventCommands.size() + 3);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Create the surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
makeInfoArea();
// Draw the Verb commands and the lines separating them
ImageFile &images = *ui._interfaceImages;
for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
_surface.writeString(_inventCommands[idx], Common::Point((_bounds.width() -
_surface.stringWidth(_inventCommands[idx])) / 2, (_surface.fontHeight() + 7) * idx + 5), INFO_TOP);
if (idx < (int)_inventCommands.size() - 1) {
_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.right - 4, INFO_TOP);
_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.right - 4, INFO_MIDDLE);
_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.right - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1)));
_surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width,
(_surface.fontHeight() + 7) * (idx + 1) - 1));
}
}
summonWindow();
}
void WidgetInventoryVerbs::handleEvents() {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Common::Point mousePos = events.mousePos();
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
TattooEngine &vm = *(TattooEngine *)_vm;
// Handle changing highlighted verb entry
highlightControls();
// See if they want to close the menu (by clicking outside the menu)
Common::Rect innerBounds = _bounds;
innerBounds.grow(-3);
// Flag is they are pressing outside of the menu
if (!innerBounds.contains(mousePos))
_outsideMenu = true;
if (events._released || events._rightReleased || ui._action == kActionTattooInvExit) {
ui._scrollHighlight = SH_NONE;
banishWindow();
if (_outsideMenu || ui._action == kActionTattooInvExit) {
_owner->_invVerbMode = 0;
} else if (innerBounds.contains(mousePos)) {
_outsideMenu = false;
// Check if they are trying to solve the Foolscap puzzle, or looking at the completed puzzle
bool doFoolscap = !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) &&
!_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Solve));
doFoolscap |= (!inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
&& !_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Look)) && vm.readFlags(299);
if (doFoolscap) {
// Close the entire Inventory and return to Standard Mode
_owner->_invVerbMode = 0;
_owner->_tooltipWidget.banishWindow();
_owner->banishWindow();
inv.freeInv();
events.clearEvents();
vm.doFoolscapPuzzle();
} else if (_invVerbSelect == 0) {
// They have released the mouse on the Look Verb command, so Look at the inventory item
ui._invLookFlag = true;
inv.freeInv();
ui._windowOpen = false;
ui._lookPos = mousePos;
ui.printObjectDesc(inv[_owner->_invSelect]._examine, true);
} else {
_owner->_invVerbMode = 3;
ui._oldBgFound = -1;
// See if the selected Verb with the selected Iventory Item, is to be used by itself
if (!_inventCommands[_invVerbSelect].compareToIgnoreCase(inv[_owner->_invSelect]._verb._verb) ||
!inv[_owner->_invSelect]._verb._target.compareToIgnoreCase("*SELF")) {
inv.freeInv();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
events.clearEvents();
ui.checkAction(inv[_owner->_invSelect]._verb, 2000);
} else {
_owner->_verb = _inventCommands[_invVerbSelect];
}
// If we are still in Inventory Mode, setup the graphic to float in front of the mouse cursor
if (ui._menuMode == INV_MODE) {
// Add the inventory item to the cursor
ImageFrame &imgFrame = (*inv._invShapes[_owner->_invSelect - inv._invIndex])[0];
events.setCursor(ARROW, Common::Point(-100, imgFrame._height), imgFrame._frame);
// Close the inventory dialog without banishing it, so it can keep getting events
// to handle tooltips and actually making the selection of what object to use them item on
inv.freeInv();
_owner->_surface.free();
}
}
}
}
}
void WidgetInventoryVerbs::highlightControls() {
Events &events = *_vm->_events;
Common::Point mousePos = events.mousePos();
Common::Rect innerBounds = _bounds;
innerBounds.grow(-3);
// Set the highlighted verb
_invVerbSelect = -1;
if (innerBounds.contains(mousePos))
_invVerbSelect = (mousePos.y - _bounds.top - 3) / (_surface.fontHeight() + 7);
// See if the highlighted verb has changed
if (_invVerbSelect != _oldInvVerbSelect) {
// Draw the list again, with the new highlighting
for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
byte color = (idx == _invVerbSelect) ? COMMAND_HIGHLIGHTED : INFO_TOP;
_surface.writeString(_inventCommands[idx], Common::Point(
(_bounds.width() - _surface.stringWidth(_inventCommands[idx])) / 2,
(_surface.fontHeight() + 7) * idx + 5), color);
}
_oldInvVerbSelect = _invVerbSelect;
}
}
/*----------------------------------------------------------------*/
WidgetInventory::WidgetInventory(SherlockEngine *vm) : WidgetBase(vm),
_tooltipWidget(vm, this), _verbList(vm, this) {
_invMode = 0;
_invVerbMode = 0;
_invSelect = _oldInvSelect = -1;
_selector = _oldSelector = -1;
_swapItems = false;
}
void WidgetInventory::load(int mode) {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
Screen &screen = *_vm->_screen;
Common::Point mousePos = events.mousePos();
if (mode == 3) {
mode = 2;
mousePos = Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
}
if (mode != 0)
_invMode = mode;
_invVerbMode = 0;
_invSelect = _oldInvSelect = -1;
_selector = _oldSelector = -1;
_scroll = true;
if (mode == 0) {
banishWindow();
} else {
_bounds = Common::Rect((INVENTORY_XSIZE + 3) * NUM_INVENTORY_SHOWN / 2 + BUTTON_SIZE + 6,
(INVENTORY_YSIZE + 3) * 2 + 3);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
}
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-scrolling")->setEnabled(true);
keymapper->getKeymap("tattoo-inv")->setEnabled(true);
// Ensure menu will be on-screen
restrictToScreen();
// Load the inventory data
inv.loadInv();
// Redraw the inventory menu on the widget surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
// Draw the window background and then the inventory on top of it
makeInfoArea(_surface);
drawBars();
drawInventory();
}
void WidgetInventory::drawBars() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
int x;
_surface.hLine(3, INVENTORY_YSIZE + 3, _bounds.width() - 4, INFO_TOP);
_surface.hLine(3, INVENTORY_YSIZE + 4, _bounds.width() - 4, INFO_MIDDLE);
_surface.hLine(3, INVENTORY_YSIZE + 5, _bounds.width() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2));
for (int idx = 1; idx <= NUM_INVENTORY_SHOWN / 2; ++idx) {
x = idx * (INVENTORY_XSIZE + 3);
_surface.vLine(x, 3, _bounds.height() - 4, INFO_TOP);
_surface.vLine(x + 1, 3, _bounds.height() - 4, INFO_MIDDLE);
_surface.vLine(x + 2, 3, _bounds.height() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[6], Common::Point(x - 1, 1));
_surface.SHtransBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4));
_surface.SHtransBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5));
_surface.SHtransBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2));
}
_surface.vLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM);
}
void WidgetInventory::drawInventory() {
Inventory &inv = *_vm->_inventory;
// TODO: Refactor _invIndex into this widget class
for (int idx = 0, itemId = inv._invIndex; idx < NUM_INVENTORY_SHOWN; ++idx, ++itemId) {
// Figure out the drawing position
Common::Point pt(3 + (INVENTORY_XSIZE + 3) * (idx % (NUM_INVENTORY_SHOWN / 2)),
3 + (INVENTORY_YSIZE + 3) * (idx / (NUM_INVENTORY_SHOWN / 2)));
// Draw the box to serve as the background for the item
_surface.hLine(pt.x + 1, pt.y, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
_surface.fillRect(Common::Rect(pt.x, pt.y + 1, pt.x + INVENTORY_XSIZE, pt.y + INVENTORY_YSIZE - 1), TRANSPARENCY);
_surface.hLine(pt.x + 1, pt.y + INVENTORY_YSIZE - 1, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
// Draw the item
if (itemId < inv._holdings) {
ImageFrame &img = (*inv._invShapes[idx])[0];
_surface.SHtransBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2,
pt.y + (INVENTORY_YSIZE - img._height) / 2));
}
}
drawScrollBar(inv._invIndex / NUM_INV_PER_LINE, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
}
void WidgetInventory::handleEvents() {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
People &people = *_vm->_people;
TattooScene &scene = *(TattooScene *)_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
if (_invVerbMode == 1) {
checkTabbingKeys(MAX_INV_COMMANDS);
} else if (_invVerbMode == 0) {
checkInvTabbingKeys();
// Handle scrollbar events
int oldScrollIndex = inv._invIndex / NUM_INV_PER_LINE;
int invIndex = inv._invIndex / NUM_INV_PER_LINE;
ScrollHighlight oldHighlight = ui._scrollHighlight;
handleScrollbarEvents(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
handleScrolling(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
if (oldScrollIndex != invIndex) {
// Starting visible item index has changed, so set the index and reload inventory graphics
inv._invIndex = invIndex * NUM_INV_PER_LINE;
inv.freeGraphics();
inv.loadGraphics();
}
if (ui._scrollHighlight != oldHighlight || oldScrollIndex != invIndex) {
drawInventory();
return;
}
}
if (_invVerbMode != 1)
_tooltipWidget.handleEvents();
// Flag is they started pressing outside of the menu
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if (_invVerbMode != 3)
highlightControls();
// See if they released a mouse button button
if (events._released || events._rightReleased || ui._action == kActionTattooInvExit) {
ui._scrollHighlight = SH_NONE;
// See if they have a Verb List open for an Inventry Item
if (_invVerbMode == 1)
return;
if (_invVerbMode == 3) {
// Selecting object after inventory verb has been selected
_tooltipWidget.banishWindow();
close();
if (ui._action != kActionTattooInvExit) {
// If user pointed at an item, use the selected inventory item with this item
bool found = false;
if (ui._bgFound != -1) {
if (ui._personFound) {
Person &person = people[ui._bgFound - 1000];
for (int idx = 0; idx < 2; ++idx) {
if (!person._use[idx]._verb.compareToIgnoreCase(_verb) &&
!person._use[idx]._target.compareToIgnoreCase(_invTarget)) {
ui.checkAction(person._use[idx], ui._bgFound);
found = true;
}
}
} else {
for (int idx = 0; idx < 6; ++idx) {
if (!ui._bgShape->_use[idx]._verb.compareToIgnoreCase(_verb) &&
!ui._bgShape->_use[idx]._target.compareToIgnoreCase(_invTarget)) {
ui.checkAction(ui._bgShape->_use[idx], ui._bgFound);
found = true;
}
}
}
}
if (!found)
ui.putMessage("%s", FIXED(NoEffect));
}
} else if ((_outsideMenu && !_bounds.contains(mousePos)) || ui._action == kActionTattooInvExit) {
// Want to close the window (clicked outside of it). So close the window and return to Standard
close();
} else if (_bounds.contains(mousePos)) {
// Mouse button was released inside the inventory window
_outsideMenu = false;
// See if they are pointing at one of the inventory items
if (_invSelect != -1) {
// See if they are in Use Obj with Inv. Mode (they right clicked on an item
// in the room and selected "Use with Inv.")
if (_invMode == 1) {
_tooltipWidget.banishWindow();
banishWindow();
// See if the item in the room that they started with was a person
bool found = false;
if (ui._activeObj >= 1000) {
// Object was a person, activate anything in his two verb fields
for (int idx = 0; idx < 2; ++idx) {
if (!people[ui._activeObj - 1000]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj);
found = true;
}
}
} else {
// Object was a regular object, activate anything in its verb fields
for (int idx = 0; idx < 6; ++idx) {
if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj);
found = true;
}
}
}
if (!found)
ui.putMessage("%s", FIXED(NoEffect));
} else {
// See if they right clicked on an item
if (events._rightReleased) {
_invVerbMode = 1;
_verbList._oldInvVerbSelect = -1;
_tooltipWidget.banishWindow();
// Keep track of the name of the inventory object so we can check it against the target fields
// of verbs when we activate it
_invTarget = inv[_invSelect]._name;
_swapItems = false;
_verbList.load();
} else {
// They left clicked on an inventory item, so Look at it
// Check if they are looking at the solved Foolscap
if ((!inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
&& vm.readFlags(299)) {
banishWindow();
_tooltipWidget.erase();
_invVerbMode = 0;
inv.freeInv();
events.clearEvents();
events.setCursor(ARROW);
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
scene.doBgAnim();
vm.doFoolscapPuzzle();
} else {
ui._invLookFlag = true;
inv.freeInv();
_tooltipWidget.banishWindow();
ui._windowOpen = false;
ui._lookPos = mousePos;
ui.printObjectDesc(inv[_invSelect]._examine, true);
}
}
}
}
}
}
}
void WidgetInventory::checkInvTabbingKeys() {
}
void WidgetInventory::highlightControls() {
// TODO
}
void WidgetInventory::banishWindow() {
WidgetBase::banishWindow();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-inv")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
_verbList.banishWindow();
}
void WidgetInventory::draw() {
WidgetBase::draw();
_tooltipWidget.draw();
}
void WidgetInventory::erase() {
WidgetBase::erase();
_tooltipWidget.erase();
}
void WidgetInventory::close() {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
TattooScene &scene = *(TattooScene *)_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
banishWindow();
inv.freeInv();
events.clearEvents();
events.setCursor(ARROW);
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,157 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_INVENTORY_H
#define SHERLOCK_TATTOO_WIDGET_INVENTORY_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/tattoo/widget_tooltip.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
#define NUM_INVENTORY_SHOWN 8 // Number of Inventory Items Shown
class WidgetInventory;
class WidgetInventoryTooltip: public WidgetTooltipBase {
private:
WidgetInventory *_owner;
protected:
/**
* Overriden from base class, since tooltips have a completely transparent background
*/
void drawBackground() override {}
public:
WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner);
~WidgetInventoryTooltip() override {}
/**
* Set the text for the tooltip
*/
void setText(const Common::String &str);
/**
* Handle updating the tooltip state
*/
void handleEvents() override;
};
class WidgetInventoryVerbs : public WidgetBase {
private:
WidgetInventory *_owner;
Common::StringArray _inventCommands;
void highlightControls();
public:
int _invVerbSelect, _oldInvVerbSelect;
public:
WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner);
~WidgetInventoryVerbs() override {}
void load();
/**
* Handle updating the tooltip state
*/
void handleEvents() override;
};
class WidgetInventory: public WidgetBase {
friend class WidgetInventoryTooltip;
friend class WidgetInventoryVerbs;
private:
int _invVerbMode;
int _selector, _oldSelector;
int _invSelect, _oldInvSelect;
WidgetInventoryTooltip _tooltipWidget;
WidgetInventoryVerbs _verbList;
bool _swapItems;
Surface _menuSurface;
Common::String _invTarget;
/**
* Draw the bars within the dialog
*/
void drawBars();
/**
* Check for keys to mouse the mouse within the inventory dialog
*/
void checkInvTabbingKeys();
/**
* Highlights the controls
*/
void highlightControls();
public:
int _invMode;
Common::String _action;
Common::String _verb;
public:
WidgetInventory(SherlockEngine *vm);
~WidgetInventory() override {}
/**
* Load the inventory window
*/
void load(int mode);
/**
* Draw the inventory on the surface
*/
void drawInventory();
/**
* Close the window
*/
void close();
/**
* Handle events whilst the widget is on-screen
*/
void handleEvents() override;
/**
* Close a currently active menu
*/
void banishWindow() override;
/**
* Erase any previous display of the widget on the screen
*/
void erase() override;
/**
* Update the display of the widget on the screen
*/
void draw() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,197 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_lab.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
WidgetLab::WidgetLab(SherlockEngine *vm) : WidgetBase(vm) {
_labObject = nullptr;
}
void WidgetLab::summonWindow() {
WidgetBase::summonWindow();
_labObject = nullptr;
}
void WidgetLab::handleEvents() {
Events &events = *_vm->_events;
Scene &scene = *_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
WidgetBase::handleEvents();
// Handle drawing tooltips. If the user is dragging a lab item, display a tooltip for using the item
// on another. Otherwise, fall back on showing standard tooltips
if (events.getCursor() == INVALID_CURSOR)
displayLabNames();
else
ui.displayObjectNames();
// See if they've released a mouse button to do an action
if (events._released || events._rightReleased) {
// See if the mouse was released in an exit/arrow zone (ie. the "Exit" button)
ui._exitZone = -1;
if (ui._arrowZone != -1 && events._released)
ui._exitZone = ui._arrowZone;
// Turn any current tooltip off
if (ui._arrowZone == -1 || events._rightReleased)
ui._tooltipWidget.setText("");
bool noDesc = false;
if (ui._bgFound != -1) {
if (ui._bgShape->_description.hasPrefix(" ") || ui._bgShape->_description.empty())
noDesc = true;
} else {
noDesc = true;
}
events.setCursor(ARROW);
if (events._rightReleased) {
// If the player is dragging an object around, restore it to its previous location and reset the cursor
if (_labObject) {
_labObject->toggleHidden();
// Toggle any other objects (like shadows) tied to this object
for (int idx = 0; idx < 6; ++idx) {
if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
for (int nameNum = 0; nameNum < 4; ++nameNum)
scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
}
}
events.setCursor(ARROW);
}
// Show the command list for this object
ui._verbsWidget.load(!noDesc);
} else if (!noDesc) {
// The player has released on an object, see if they had an object selected
// that will be used with this new object
if (_labObject) {
// See if the dragged object can be used with the new object
for (int idx = 0; idx < 6; ++idx) {
// See if the name of the dragged object is in any of the Target
// fields of the verbs for the new object
if (!_labObject->_name.compareToIgnoreCase(ui._bgShape->_use[idx]._target.c_str())) {
// This object can be used, so use it
ui.checkAction(ui._bgShape->_use[idx], ui._bgFound);
ui._activeObj = -1;
}
}
// Restore the dragged object to its previous location
_labObject->toggleHidden();
// Toggle any other objects (like shadows) tied to this object
for (int idx = 0; idx < 6; ++idx) {
if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
for (int nameNum = 0; nameNum < 4; ++nameNum)
scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
}
}
} else if (!ui._bgShape->_name.compareToIgnoreCase("Exit")) {
// Execute the Exit button's script, which will leave the scene
ui.lookAtObject();
}
} else {
// The player has released the mouse while NOT over an object. If theu were dragging an object
// around with the mouse, restore it to its previous location and reset the cursor
if (_labObject) {
_labObject->toggleHidden();
// Toggle any other objects (like shadows) tied to this object
for (int idx = 0; idx < 6; ++idx) {
if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
for (int nameNum = 0; nameNum < 4; ++nameNum)
scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
}
}
}
}
_labObject = nullptr;
ui._tooltipWidget._offsetY = 0;
} else if (events._pressed && !_labObject) {
// If the mouse is over an object and the object is not SOLID, then we need to pick this object
// up so the player can move it around
if (ui._bgFound != -1) {
// Check if the object is set as SOLID, you can't pick up Solid items
if (ui._bgShape->_aType != SOLID && ui._bgShape->_type != NO_SHAPE) {
// Save a reference to the object about to be dragged
_labObject = ui._bgShape;
// Set the mouse cursor to the object
Graphics::Surface &img = _labObject->_imageFrame->_frame;
Common::Point cursorOffset = mousePos - _labObject->_position;
events.setCursor(ARROW, cursorOffset, img);
ui._tooltipWidget._offsetY = cursorOffset.y;
// Hide this object until they are done with it (releasing it)
_labObject->toggleHidden();
// Toggle any other objects (like shadows) tied to this object
for (int idx = 0; idx < 6; ++idx) {
if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
for (int nameNum = 0; nameNum < 4; ++nameNum)
scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
}
}
}
}
}
}
void WidgetLab::displayLabNames() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
// See if thay are pointing at a different object and we need to change the tooltip
if (ui._bgFound != ui._oldBgFound) {
// See if there is a new object to be displayed
if (ui._bgFound == -1) {
ui._tooltipWidget.setText("");
} else {
Common::String str = Common::String::format("%s %s %s %s", FIXED(Use), _labObject->_description.c_str(),
FIXED(With), ui._bgShape->_description.c_str());
// Make sure that the Object has a name
if (!ui._bgShape->_description.empty() && !ui._bgShape->_description.hasPrefix(" ")) {
ui._tooltipWidget.setText(str);
} else {
ui._tooltipWidget.setText("");
}
}
}
ui._oldArrowZone = ui._arrowZone;
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,65 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_LAB_H
#define SHERLOCK_TATTOO_WIDGET_LAB_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
#include "sherlock/objects.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetLab: public WidgetBase {
private:
Object *_labObject;
/**
* Display tooltips of an object being dragged along with any object the dragged
* object is currently over
*/
void displayLabNames();
public:
Common::String _remainingText;
public:
WidgetLab(SherlockEngine *vm);
~WidgetLab() override {}
/**
* Summon the window
*/
void summonWindow() override;
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,395 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_options.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
WidgetOptions::WidgetOptions(SherlockEngine *vm) : WidgetBase(vm) {
_midiSliderX = _digiSliderX = 0;
_selector = _oldSelector = -1;
}
void WidgetOptions::load() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
_centerPos = events.mousePos();
render();
summonWindow();
ui._menuMode = OPTION_MODE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-options")->setEnabled(true);
keymapper->getKeymap("tattoo-exit")->setEnabled(true);
}
void WidgetOptions::handleEvents() {
TattooEngine &vm = *(TattooEngine *)_vm;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
Screen &screen = *_vm->_screen;
Sound &sound = *_vm->_sound;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::CustomEventType action = ui._action;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
if (talk._talkToAbort) {
sound.stopSound();
return;
}
// Flag if they started pressing outside the window
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if (action != kActionNone) {
// Emulate a mouse release if Enter or Space Bar is pressed
if (ui._action == kActionTattooOptionsSelect) {
events._pressed = events._oldButtons = false;
events._released = true;
} else if (ui._action == kActionTattooExit) {
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
return;
} else {
checkTabbingKeys(11);
}
}
// Check highlighting the various controls
if (_bounds.contains(mousePos)) {
_selector = (mousePos.y - _bounds.top) / (_surface.fontHeight() + 7);
// If one of the sliders has been selected, & the mouse is not pressed, reset the selector to -1
if ((_selector == 3 || _selector == 6) && !events._pressed)
_selector = -1;
} else {
_selector = -1;
if (_outsideMenu && (events._released || events._rightReleased)) {
events.clearEvents();
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
return;
}
}
// If the selected control has changed, redraw the dialog contents
if (_selector != _oldSelector)
render(OP_CONTENTS);
_oldSelector = _selector;
// Adjust the Volume Sliders (if necessary) here
switch (_selector) {
case 3: {
// Set Music Volume
_midiSliderX = mousePos.x - _bounds.left;
if (_midiSliderX < _surface.widestChar())
_midiSliderX = _surface.widestChar();
else
if (_midiSliderX > _bounds.width() - _surface.widestChar())
_midiSliderX = _bounds.width() - _surface.widestChar();
int newVolume = (_midiSliderX - _surface.widestChar()) * 255 / (_bounds.width() - _surface.widestChar() * 2);
if (newVolume != music._musicVolume) {
music.setMusicVolume(newVolume);
vm.saveConfig();
}
render(OP_NAMES);
break;
}
case 6: {
// Set Digitized Volume
_digiSliderX = mousePos.x - _bounds.left;
if (_digiSliderX < _surface.widestChar())
_digiSliderX = _surface.widestChar();
else if (_digiSliderX > _bounds.width() - _surface.widestChar())
_digiSliderX = _bounds.width() - _surface.widestChar();
int newVolume = (_digiSliderX - _surface.widestChar()) * 255 / (_bounds.width() - _surface.widestChar() * 2);
if (newVolume != sound._soundVolume) {
sound.setVolume(newVolume);
vm.saveConfig();
}
render(OP_NAMES);
break;
}
default:
break;
}
// Option selected
if (events._released || events._rightReleased) {
events.clearEvents();
_outsideMenu = false;
int temp = _selector;
_selector = -1;
switch (temp) {
case 0:
// Load Game
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
ui.loadGame();
break;
case 1:
// Save Game
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
ui.saveGame();
break;
case 2:
// Toggle Music
music._musicOn = !music._musicOn;
if (!music._musicOn)
music.stopMusic();
else
music.startSong();
render(OP_NAMES);
vm.saveConfig();
break;
case 4:
// Toggle Sound Effects
sound.stopSound();
sound._digitized = !sound._digitized;
render(OP_NAMES);
vm.saveConfig();
break;
case 5:
// Toggle Voices
sound._speechOn = !sound._speechOn;
render(OP_NAMES);
vm.saveConfig();
break;
case 7:
// Toggle Text Windows
vm._textWindowsOn = !vm._textWindowsOn;
render(OP_NAMES);
vm.saveConfig();
break;
case 8: {
// New Font Style
int fontNumber = screen.fontNumber() + 1;
if (fontNumber == 7)
fontNumber = 0;
screen.setFont(fontNumber);
render(OP_ALL);
vm.saveConfig();
break;
}
case 9:
// Toggle Transparent Menus
vm._transparentMenus = !vm._transparentMenus;
render(OP_NAMES);
vm.saveConfig();
break;
case 10:
// Quit
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
banishWindow();
ui.doQuitMenu();
break;
default:
break;
}
_oldSelector = -1;
}
}
void WidgetOptions::render(OptionRenderMode mode) {
TattooEngine &vm = *(TattooEngine *)_vm;
Music &music = *_vm->_music;
Sound &sound = *_vm->_sound;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
const char *const OFF_ON[2] = { FIXED(Off), FIXED(On) };
// Draw the border if necessary
if (mode == OP_ALL) {
// Set bounds for the dialog
Common::String widestString = Common::String::format("%s %s", FIXED(TransparentMenus), FIXED(Off));
_bounds = Common::Rect(_surface.stringWidth(widestString) + _surface.widestChar() * 2 + 6,
(_surface.fontHeight() + 7) * 11 + 3);
_bounds.moveTo(_centerPos.x - _bounds.width() / 2, _centerPos.y - _bounds.height() / 2);
// Get slider positions
_midiSliderX = music._musicVolume * (_bounds.width() - _surface.widestChar() * 2) / 255 + _surface.widestChar();
_digiSliderX = sound._soundVolume * (_bounds.width() - _surface.widestChar() * 2) / 255 + _surface.widestChar();
// Setup the dialog
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
makeInfoArea();
// Draw the lines separating options in the dialog
int yp = _surface.fontHeight() + 7;
for (int idx = 0; idx < 7; ++idx) {
_surface.SHtransBlitFrom(images[4], Common::Point(0, yp - 1));
_surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, yp - 1));
_surface.hLine(3, yp, _surface.width() - 4, INFO_TOP);
_surface.hLine(3, yp + 1, _surface.width() - 4, INFO_MIDDLE);
_surface.hLine(3, yp + 2, _surface.width() - 4, INFO_BOTTOM);
yp += _surface.fontHeight() + 7;
if (idx == 1)
yp += _surface.fontHeight() + 7;
else if (idx == 2)
yp += (_surface.fontHeight() + 7) * 2;
}
}
// Now go through and display all the items that can be highlighted
for (int idx = 0, yp = 5; idx < 11; ++idx, yp += _surface.fontHeight() + 7) {
if (mode == OP_ALL || idx == _selector || idx == _oldSelector) {
if (mode == OP_NAMES)
_surface.fillRect(Common::Rect(4, yp, _surface.width() - 5, yp + _surface.fontHeight() - 1), TRANSPARENCY);
byte color = (idx == _selector) ? COMMAND_HIGHLIGHTED : INFO_TOP;
Common::String str;
switch (idx) {
case 0:
str = FIXED(LoadGame);
break;
case 1:
str = FIXED(SaveGame);
break;
case 2:
str = Common::String::format("%s %s", FIXED(Music), OFF_ON[music._musicOn]);
break;
case 3:
drawSlider(yp, _midiSliderX);
break;
case 4:
str = Common::String::format("%s %s", FIXED(SoundEffects), OFF_ON[sound._digitized]);
break;
case 5:
str = Common::String::format("%s %s", FIXED(Voices), OFF_ON[sound._speechOn]);
break;
case 6:
drawSlider(yp, _digiSliderX);
break;
case 7:
if (!sound._voices) {
color = INFO_BOTTOM;
str = Common::String::format("%s %s", FIXED(TextWindows), FIXED(On));
} else {
str = Common::String::format("%s %s", FIXED(TextWindows), OFF_ON[vm._textWindowsOn]);
}
break;
case 8:
str = FIXED(ChangeFont);
break;
case 9:
str = Common::String::format("%s %s", FIXED(TransparentMenus), OFF_ON[vm._transparentMenus]);
break;
case 10:
str = FIXED(Quit);
break;
default:
break;
}
// Unless we're doing one of the Slider Controls, print the text for the line
if (idx != 3 && idx != 6) {
int xp = (_surface.width() - _surface.stringWidth(str)) / 2;
_surface.writeString(str, Common::Point(xp, yp), color);
}
}
}
}
void WidgetOptions::drawSlider(int yp, int sliderX) {
int num = (_surface.fontHeight() + 4) & 0xfe;
int sliderY = yp + num / 2 - 8;
_surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.width() - 5,
sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY);
_surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2,
_surface.width() - _surface.widestChar() - 1, sliderY + 4), INFO_MIDDLE);
drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.width() - _surface.widestChar(), sliderY + 6));
_surface.fillRect(Common::Rect(sliderX - 1, sliderY - (num - 6) / 2 + 2,
sliderX + 2, sliderY - (num - 6) / 2 + num - 2), INFO_MIDDLE);
drawDialogRect(Common::Rect(sliderX - 3, sliderY - (num - 6) / 2,
sliderX + 4, sliderY - (num - 6) / 2 + num));
if (sliderX - 4 > _surface.widestChar())
_surface.fillRect(Common::Rect(sliderX - 4, sliderY, sliderX - 3, sliderY + 4), INFO_BOTTOM);
if (sliderX + 4 < _surface.width() - _surface.widestChar())
_surface.fillRect(Common::Rect(sliderX + 4, sliderY, sliderX + 5, sliderY + 4), INFO_BOTTOM);
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,73 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_OPTIONS_H
#define SHERLOCK_TATTOO_WIDGET_OPTIONS_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
enum OptionRenderMode { OP_ALL = 0, OP_CONTENTS = 1, OP_NAMES = 2};
/**
* Handles displaying the options dialog
*/
class WidgetOptions : public WidgetBase {
private:
int _midiSliderX, _digiSliderX;
int _selector, _oldSelector;
Common::Point _centerPos;
/**
* Render the contents of the dialog onto the widget's surface
*/
void render(OptionRenderMode mode = OP_ALL);
/**
* Draw a slider on the widget's surface
*/
void drawSlider(int yp, int sliderX);
public:
WidgetOptions(SherlockEngine *vm);
~WidgetOptions() override {}
/**
* Load and then display the options dialog
*/
void load();
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,220 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_password.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
WidgetPassword::WidgetPassword(SherlockEngine *vm) : WidgetBase(vm) {
_blinkFlag = false;
_blinkCounter = 0;
_index = 0;
_cursorColor = 192;
_insert = true;
}
void WidgetPassword::show() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
// Set the up window to be centered on the screen
_bounds = Common::Rect(_surface.widestChar() * 20 + 6, (_surface.fontHeight() + 7) * 2 + 3);
_bounds.moveTo(SHERLOCK_SCREEN_WIDTH / 2 - _bounds.width() / 2, SHERLOCK_SCREEN_HEIGHT / 2 - _bounds.height() / 2);
// Create the surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
makeInfoArea();
// Draw the header area
_surface.writeString(FIXED(EnterPassword), Common::Point((_bounds.width() - _surface.stringWidth(FIXED(EnterPassword))) / 2, 5), INFO_TOP);
_surface.hLine(3, _surface.fontHeight() + 7, _bounds.width() - 4, INFO_TOP);
_surface.hLine(3, _surface.fontHeight() + 8, _bounds.width() - 4, INFO_MIDDLE);
_surface.hLine(3, _surface.fontHeight() + 9, _bounds.width() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 7 - 1));
_surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, _surface.fontHeight() + 7 - 1));
// Set the password entry data
_cursorPos = Common::Point(_surface.widestChar(), _surface.fontHeight() + 12);
_password = "";
_index = 0;
_cursorColor = 192;
_insert = true;
// Show the dialog
ui._menuMode = PASSWORD_MODE;
summonWindow();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-password")->setEnabled(true);
}
void WidgetPassword::handleEvents() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
const Common::KeyCode &keycode = ui._keyState.keycode;
Common::CustomEventType action = ui._action;
char currentChar = (_index == (int)_password.size()) ? ' ' : _password[_index];
int width = _surface.charWidth(currentChar);
if (!keycode && !action) {
// Nothing entered, so keep blinking the cursor
if (--_blinkCounter < 0) {
_blinkCounter = 3;
_blinkFlag = !_blinkFlag;
byte color, textColor;
if (_blinkFlag) {
textColor = 236;
color = _cursorColor;
} else {
textColor = COMMAND_HIGHLIGHTED;
color = TRANSPARENCY;
}
// Draw the cursor and the character it's over
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + width, _cursorPos.y + _surface.fontHeight()), color);
if (currentChar != ' ')
_surface.writeString(Common::String::format("%c", _password[_index]), _cursorPos, textColor);
}
} else if (keycode == Common::KEYCODE_BACKSPACE && _index) {
_cursorPos.x -= _surface.charWidth(_password[_index - 1]);
if (_insert)
_password.deleteChar(_index - 1);
else
_password.setChar(' ', _index - 1);
// Redraw the text
--_index;
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y +
_surface.fontHeight() - 1), TRANSPARENCY);
_surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED);
} else if ((action == kActionTattooPasswordLeft && _index > 0)
|| (action == kActionTattooPasswordRight && _index < (int)_password.size() && _cursorPos.x < (_bounds.width() - _surface.widestChar() - 3))
|| (action == kActionTattooPasswordStart && _index > 0)
|| (action == kActionTattooPasswordEnd)) {
// Restore character the cursor was previously over
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + width, _cursorPos.y + _surface.fontHeight()), TRANSPARENCY);
if (currentChar != ' ')
_surface.writeString(Common::String::format("%c", _password[_index]), _cursorPos, COMMAND_HIGHLIGHTED);
switch (action) {
case kActionTattooPasswordLeft:
_cursorPos.x -= _surface.charWidth(_password[_index - 1]);
--_index;
break;
case kActionTattooPasswordRight:
_cursorPos.x += _surface.charWidth(_password[_index]);
++_index;
break;
case kActionTattooPasswordStart:
_cursorPos.x = _surface.widestChar();
_index = 0;
break;
case kActionTattooPasswordEnd:
_cursorPos.x = _surface.stringWidth(_password) + _surface.widestChar();
_index = _password.size();
while (_index > 0 && _password[_index - 1] == ' ') {
_cursorPos.x -= _surface.charWidth(_password[_index - 1]);
--_index;
}
break;
default:
break;
}
} else if (action == kActionTattooPasswordToggleInsertMode) {
_insert = !_insert;
_cursorColor = _insert ? 192 : 200;
} else if (keycode == Common::KEYCODE_DELETE) {
if (_index < (int)_password.size())
_password.deleteChar(_index);
// Redraw the text
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y +
_surface.fontHeight() - 1), TRANSPARENCY);
_surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED);
} else if (keycode == Common::KEYCODE_RETURN || keycode == Common::KEYCODE_ESCAPE) {
close();
return;
} else if ((ui._keyState.ascii >= ' ') && (ui._keyState.ascii <= 'z')) {
if (_cursorPos.x + _surface.charWidth(ui._keyState.ascii) < _bounds.width() - _surface.widestChar() - 3) {
if (_insert)
_password.insertChar(ui._keyState.ascii, _index);
else
_password.setChar(ui._keyState.ascii, _index);
// Redraw the text
_surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y +
_surface.fontHeight() - 1), TRANSPARENCY);
_surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED);
_cursorPos.x += _surface.charWidth(ui._keyState.ascii);
++_index;
}
}
// Also handle clicking outside the window to abort
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if ((events._released || events._rightReleased) && _outsideMenu && !_bounds.contains(mousePos)) {
close();
}
}
void WidgetPassword::close() {
Talk &talk = *_vm->_talk;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo-password")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
banishWindow();
if (talk._talkToAbort)
return;
// See if they entered the correct password
Common::String correct1 = FIXED(CorrectPassword);
Common::String correct2 = Common::String::format("%s?", FIXED(CorrectPassword));
Common::String correct3 = Common::String::format("%s ?", FIXED(CorrectPassword));
if (!_password.compareToIgnoreCase(correct1) || !_password.compareToIgnoreCase(correct2)
|| !_password.compareToIgnoreCase(correct3))
// They got it correct
_vm->setFlags(149);
talk.talkTo("LASC52P");
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,67 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_PASSWORD_H
#define SHERLOCK_TATTOO_WIDGET_PASSWORD_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetPassword: public WidgetBase {
private:
Common::Point _cursorPos;
Common::String _password;
int _index;
bool _blinkFlag;
int _blinkCounter;
byte _cursorColor;
bool _insert;
/**
* Close the window and check if the entered password is correct
*/
void close();
public:
WidgetPassword(SherlockEngine *vm);
~WidgetPassword() override {}
/**
* Show the password entry window
*/
void show();
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,168 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_quit.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
WidgetQuit::WidgetQuit(SherlockEngine *vm) : WidgetBase(vm) {
_select = _oldSelect = -1;
}
void WidgetQuit::show() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
Common::Point mousePos = events.mousePos();
const char *YES = FIXED(Yes);
const char *NO = FIXED(No);
// Set up the display area
_bounds = Common::Rect(_surface.stringWidth(FIXED(AreYouSureYou)) + _surface.widestChar() * 2,
(_surface.fontHeight() + 7) * 4);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Create the surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
makeInfoArea();
// Draw the message text
_surface.writeString(FIXED(AreYouSureYou), Common::Point((_surface.width() - _surface.stringWidth(FIXED(AreYouSureYou))) / 2, 5), INFO_TOP);
_surface.writeString(FIXED(WishToQuit), Common::Point((_surface.width() - _surface.stringWidth(FIXED(WishToQuit))) / 2,
_surface.fontHeight() + 9), INFO_TOP);
// Draw the horizontal bars separating the commands and the message
int yp = (_surface.fontHeight() + 4) * 2 + 3;
for (int idx = 0; idx < 2; ++idx) {
_surface.SHtransBlitFrom(images[4], Common::Point(0, yp - 1));
_surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, yp - 1));
_surface.hLine(3, yp, _surface.width() - 4, INFO_TOP);
_surface.hLine(3, yp + 1, _surface.width() - 4, INFO_MIDDLE);
_surface.hLine(3, yp + 2, _surface.width() - 4, INFO_BOTTOM);
const char *btn = (idx == 0) ? YES : NO;
_surface.writeString(btn, Common::Point((_bounds.width() - _surface.stringWidth(btn)) / 2, yp + 5), INFO_TOP);
yp += _surface.fontHeight() + 7;
}
ui._menuMode = QUIT_MODE;
summonWindow();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-quit-dialog")->setEnabled(true);
}
void WidgetQuit::handleEvents() {
Events &events = *_vm->_events;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::Rect yesRect(_bounds.left, _bounds.top + (_surface.fontHeight() + 4) * 2 + 3, _bounds.right,
_bounds.top + (_surface.fontHeight() + 4) * 2 + 3 + _surface.fontHeight() + 7);
Common::Rect noRect(_bounds.left, _bounds.top + (_surface.fontHeight() + 4) * 2 + _surface.fontHeight() + 10,
_bounds.right, _bounds.top + (_surface.fontHeight() + 4) * 2 + 10 + _surface.fontHeight() * 2 + 7);
Common::CustomEventType action = ui._action;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
if (talk._talkToAbort)
return;
// Determine the highlighted item
_select = -1;
if (yesRect.contains(mousePos))
_select = 1;
else if (noRect.contains(mousePos))
_select = 0;
if (action != kActionNone) {
switch (action) {
case kActionTattooQuitDialogNextOption:
// If the mouse is not over any of the options, move the mouse so that it points to the first option
if (_select == -1)
events.warpMouse(Common::Point(_bounds.right - 10, _bounds.top + (_surface.fontHeight() + 4) * 2
+ 3 + _surface.fontHeight() + 1));
else if (_select == 1)
events.warpMouse(Common::Point(mousePos.x, _bounds.top + (_surface.fontHeight() + 4) * 2
+ 3 + _surface.fontHeight() * 2 + 11));
else
events.warpMouse(Common::Point(mousePos.x, _bounds.top + (_surface.fontHeight() + 4) * 2
+ 3 + _surface.fontHeight() + 1));
break;
case kActionTattooQuitDialogNo:
keymapper->getKeymap("tattoo-quit-dialog")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
return;
case kActionTattooQuitDialogYes:
keymapper->getKeymap("tattoo-quit-dialog")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
_vm->quitGame();
break;
default:
break;
}
}
// Check for change of the highlighted item
if (_select != _oldSelect) {
byte color = (_select == 1) ? COMMAND_HIGHLIGHTED : INFO_TOP;
int yp = (_surface.fontHeight() + 4) * 2 + 8;
_surface.writeString(FIXED(Yes), Common::Point((_surface.width() - _surface.stringWidth(FIXED(Yes))) / 2, yp), color);
color = (_select == 0) ? COMMAND_HIGHLIGHTED : INFO_TOP;
yp += (_surface.fontHeight() + 7);
_surface.writeString(FIXED(No), Common::Point((_surface.width() - _surface.stringWidth(FIXED(No))) / 2, yp), color);
}
_oldSelect = _select;
// Flag is they started pressing outside of the menu
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if (events._released || events._rightReleased) {
events.clearEvents();
keymapper->getKeymap("tattoo-options")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
if (_select == 1)
// Yes selected
_vm->quitGame();
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,56 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_QUIT_H
#define SHERLOCK_TATTOO_WIDGET_QUIT_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetQuit: public WidgetBase {
private:
int _select, _oldSelect;
public:
WidgetQuit(SherlockEngine *vm);
~WidgetQuit() override {}
/**
* Prompt the user whether to quit
*/
void show();
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,491 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_talk.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_journal.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
#define STATEMENT_NUM_X 6
#define NUM_VISIBLE_TALK_LINES 6
WidgetTalk::WidgetTalk(SherlockEngine *vm) : WidgetBase(vm) {
_talkScrollIndex = 0;
_selector = _oldSelector = -1;
_talkTextX = 0;
_dialogTimer = 0;
}
void WidgetTalk::getTalkWindowSize() {
//TattooTalk &talk = *(TattooTalk *)_vm->_talk;
int width, height;
// See how many statements are going to be available
#if 0
int numStatements = 0;
for (uint idx = 0; idx < talk._statements.size(); ++idx) {
if (talk._statements[idx]._talkMap != -1)
++numStatements;
}
#endif
width = SHERLOCK_SCREEN_WIDTH * 2 / 3;
// Split up the questions into separate strings for each line
_bounds = Common::Rect(width, 1);
setStatementLines();
// Make sure that the window does not get too big
if (_statementLines.size() < 7) {
height = (_surface.fontHeight() + 1) * _statementLines.size() + 9;
_scroll = false;
} else {
// Set up the height to a constrained amount, and add extra width for the scrollbar
width += BUTTON_SIZE + 3;
height = (_surface.fontHeight() + 1) * 6 + 9;
_scroll = true;
}
_bounds = Common::Rect(width, height);
}
void WidgetTalk::load() {
TattooPeople &people = *(TattooPeople *)_vm->_people;
TattooScene &scene = *(TattooScene *)_vm->_scene;
// Figure out the window size
getTalkWindowSize();
// Place the window centered above the player
Common::Point pt;
int scaleVal = scene.getScaleVal(people[HOLMES]._position);
pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
if (scaleVal == SCALE_THRESHOLD) {
pt.x += people[0].frameWidth() / 2;
pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()
- _bounds.height() - _surface.fontHeight();
} else {
pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2;
pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal)
- _bounds.height() - _surface.fontHeight();
}
_bounds.moveTo(pt);
// Set up the surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
// Form the background for the new window
makeInfoArea();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-scrolling")->setEnabled(true);
keymapper->getKeymap("tattoo-talk")->setEnabled(true);
keymapper->getKeymap("tattoo-exit")->setEnabled(true);
}
void WidgetTalk::handleEvents() {
Events &events = *_vm->_events;
TattooJournal &journal = *(TattooJournal *)_vm->_journal;
TattooPeople &people = *(TattooPeople *)_vm->_people;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Sound &sound = *_vm->_sound;
TattooTalk &talk = *(TattooTalk *)_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::KeyCode keycode = ui._keyState.keycode;
Common::CustomEventType action = ui._action;
bool hotkey = false;
bool callParrotFile = false;
// Handle scrollbar events
ScrollHighlight oldHighlight = ui._scrollHighlight;
handleScrollbarEvents(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
int oldScrollIndex = _talkScrollIndex;
handleScrolling(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
// Only redraw the window if the scrollbar position has changed
if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _talkScrollIndex)
render(HL_NO_HIGHLIGHTING);
// Flag if they started pressing outside of the window
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
// Check for which statement they are pointing at
_selector = -1;
if (ui._scrollHighlight == SH_NONE) {
if (Common::Rect(_bounds.left, _bounds.top + 5, _bounds.right - 3, _bounds.bottom - 5).contains(mousePos)) {
if (_scroll) {
// Disregard the scrollbar when setting the statement number
if (!Common::Rect(_bounds.right - BUTTON_SIZE, _bounds.top, _bounds.right, _bounds.bottom).contains(mousePos))
_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
} else {
_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1);
}
// Now translate the line number of the displayed line into the appropriate
// Statement number or set it to 255 to indicate no Statement selected
if (_selector >= 0 && _selector < (int)_statementLines.size())
_selector = _statementLines[_selector]._num;
else
_selector = -1;
}
}
// Check for the tab keys
if ((action == kActionTattooTalkNext || action == kActionTattooTalkPrevious) && ui._scrollHighlight == SH_NONE) {
if (_selector == -1) {
_selector = _statementLines[_scroll ? _talkScrollIndex : 0]._num;
events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 10, _bounds.top + _surface.fontHeight() + 2));
} else {
if (action == kActionTattooTalkPrevious) {
_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
if (_statementLines[_selector]._num == _statementLines[_talkScrollIndex]._num) {
_selector = (_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex;
} else {
int idx = _selector;
do {
--_selector;
} while (_selector > 0 && _statementLines[idx]._num == _statementLines[_selector]._num);
}
int idx = _selector;
while ((_statementLines[idx]._num == _statementLines[_selector - 1]._num) && (_selector > _talkScrollIndex))
--_selector;
} else {
_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
if (_statementLines[_selector]._num == _statementLines[(_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex]._num) {
_selector = _talkScrollIndex;
} else {
int idx = _selector;
do {
++_selector;
} while (_selector < (int)_statementLines.size() && _statementLines[idx]._num == _statementLines[_selector]._num);
}
}
events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() + 2 + (_surface.fontHeight() + 1)
* (_selector - _talkScrollIndex)));
_selector = _statementLines[_selector]._num;
}
}
// Handle selecting a talk entry if a numeric key has been pressed
if (keycode >= Common::KEYCODE_1 && keycode <= Common::KEYCODE_9) {
int x = 0, y = 0, t;
for (t = 0; t < (int)_statementLines.size(); ++t) {
if (t > 0 && _statementLines[x]._num != _statementLines[t]._num) {
x = t;
++y;
}
if (y == (keycode - Common::KEYCODE_1)) {
_selector = _statementLines[t]._num;
_outsideMenu = false;
hotkey = true;
break;
}
}
}
// Display the selected statement highlighted and reset the last statement.
if (_selector != _oldSelector) {
render(HL_CHANGED_HIGHLIGHTS);
_oldSelector = _selector;
}
if (events._released || events._rightReleased || action == kActionTattooExit || hotkey) {
events.clearEvents();
_dialogTimer = 0;
ui._scrollHighlight = SH_NONE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
// See if they want to close the menu (click outside the window or Escape pressed)
if ((_outsideMenu && !_bounds.contains(mousePos)) || action == kActionTattooExit) {
if (action == kActionTattooExit)
_selector = -1;
talk.freeTalkVars();
talk.pullSequence();
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER) {
while (!people[idx]._pathStack.empty())
people[idx].pullNPCPath();
}
}
banishWindow();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-talk")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
if (scene._currentScene == WEARY_PUNT)
callParrotFile = true;
}
_outsideMenu = false;
// See if they have selected a statement to say
if (_selector != -1) {
if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
journal.record(talk._converseNum, _selector);
talk._talkHistory[talk._converseNum][_selector] = true;
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-talk")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
banishWindow();
talk._speaker = _vm->readFlags(FLAG_PLAYER_IS_HOLMES) ? HOLMES : WATSON;
_scroll = false;
const byte *msg = (const byte *)talk._statements[_selector]._statement.c_str();
talk.talkInterface(msg);
if (sound._speechOn)
sound._talkSoundFile += Common::String::format("%02dA", _selector + 1);
int msgLen = MAX((int)talk._statements[_selector]._statement.size(), 160);
people.setTalkSequence(talk._speaker);
talk.waitForMore(msgLen);
if (talk._talkToAbort)
return;
people.setListenSequence(talk._speaker);
do {
talk._scriptSelect = _selector;
talk._speaker = talk._talkTo;
// Make a copy of the reply (since talkTo can reload the statements list), and call talkTo
Common::String reply = talk._statements[_selector]._reply;
talk.doScript(reply);
// Reset the misc field in case any people changed their sequences
for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
people[idx]._misc = 0;
if (!talk._talkToAbort) {
if (!talk._statements[_selector]._modified.empty()) {
for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx)
_vm->setFlags(talk._statements[_selector]._modified[idx]);
talk.setTalkMap();
}
// See if there is another talk file linked to this.
if (!talk._statements[_selector]._linkFile.empty() && !talk._scriptMoreFlag) {
Common::String linkFile = talk._statements[_selector]._linkFile;
talk.freeTalkVars();
talk.loadTalkFile(linkFile);
_talkScrollIndex = 0;
int select = -1;
_selector = _oldSelector = -1;
// Find the first statement that has all its flags set correctly
for (uint idx = 0; idx < talk._statements.size() && select == -1; ++select) {
if (!talk._statements[idx]._talkMap)
select = idx;
}
if (select == -1) {
talk.freeTalkVars();
talk.nothingToSay();
return;
}
// See is the new statement is in stealth mode
talk._talkStealth = (talk._statements[select]._statement.hasPrefix("^")) ? 2 : 0;
// See if the new file is a standard file, a reply first file, or a Stealth Mode file
if (!talk._statements[select]._statement.hasPrefix("*") && !talk._statements[select]._statement.hasPrefix("^")) {
load();
summonWindow();
setStatementLines();
render(HL_NO_HIGHLIGHTING);
break;
} else {
_selector = select;
if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
journal.record(talk._converseNum, _selector);
talk._talkHistory[talk._converseNum][_selector] = true;
}
} else {
talk.freeTalkVars();
talk.pullSequence();
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER)
while (!people[idx]._pathStack.empty())
people[idx].pullNPCPath();
}
if (ui._menuMode != PASSWORD_MODE) {
ui.banishWindow();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
events.setCursor(ARROW);
}
break;
}
} else {
break;
}
} while (!_vm->shouldQuit());
events.clearEvents();
// Now, if a script was pushed onto the script stack, restore them to allow the previous script to continue.
talk.popStack();
}
}
if (callParrotFile)
talk.talkTo("POUT52A");
}
void WidgetTalk::render(Highlight highlightMode) {
TattooTalk &talk = *(TattooTalk *)_vm->_talk;
if (highlightMode != HL_SCROLLBAR_ONLY) {
int yp = 5;
int statementNum = 1;
// Draw all the statements
// Check whether scrolling has occurred, and if so, figure out what the starting
// number for the first visible statement will be
if (_talkScrollIndex) {
for (int idx = 1; idx <= _talkScrollIndex; ++idx) {
if (_statementLines[idx - 1]._num != _statementLines[idx]._num)
++statementNum;
}
}
// Main drawing loop
for (uint idx = _talkScrollIndex; idx < _statementLines.size() && yp < (_bounds.height() - _surface.fontHeight()); ++idx) {
if (highlightMode == HL_NO_HIGHLIGHTING || _statementLines[idx]._num == _selector ||
_statementLines[idx]._num == _oldSelector) {
// Erase the line contents
_surface.fillRect(Common::Rect(3, yp, _surface.width() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY);
// Different coloring based on whether the option has been previously chosen or not
byte color = (!talk._talkHistory[talk._converseNum][_statementLines[idx]._num]) ?
INFO_TOP : INFO_BOTTOM;
if (_statementLines[idx]._num == _selector && highlightMode == HL_CHANGED_HIGHLIGHTS)
color = COMMAND_HIGHLIGHTED;
// See if it's the start of a new statement, so needs the statement number to be displayed
if (!idx || _statementLines[idx]._num != _statementLines[idx - 1]._num) {
Common::String numStr = Common::String::format("%d.", statementNum);
_surface.writeString(numStr, Common::Point(STATEMENT_NUM_X, yp), color);
}
// Display the statement line
_surface.writeString(_statementLines[idx]._line, Common::Point(_talkTextX, yp), color);
}
yp += _surface.fontHeight() + 1;
// If the next line starts a new statement, then increment the statement number
if (idx == (_statementLines.size() - 1) || _statementLines[idx]._num != _statementLines[idx + 1]._num)
++statementNum;
}
}
// See if the scroll bar needs to be drawn
if (_scroll && highlightMode != HL_CHANGED_HIGHLIGHTS)
drawScrollBar(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
}
void WidgetTalk::setStatementLines() {
TattooTalk &talk = *(TattooTalk *)_vm->_talk;
const char *numStr = "19.";
// See how many statements are going to be available
int numStatements = 0;
for (uint idx = 0; idx < talk._statements.size(); ++idx) {
if (talk._statements[idx]._talkMap != -1)
++numStatements;
}
// If there are more lines than can be displayed in the interface window at one time, adjust the allowed
// width to take into account needing a scrollbar
int xSize = _scroll ? _bounds.width() - BUTTON_SIZE - 3 : _bounds.width();
// Also adjust the width to allow room for the statement numbers at the left edge of the display
int n = (numStatements < 10) ? 1 : 0;
xSize -= _surface.stringWidth(numStr + n) + _surface.widestChar() / 2 + 9;
_talkTextX = _surface.stringWidth(numStr + n) + _surface.widestChar() / 4 + 6;
_statementLines.clear();
for (uint statementNum = 0; statementNum < talk._statements.size(); ++statementNum) {
// See if this statement meets all of its flag requirements
if (talk._statements[statementNum]._talkMap != -1) {
// Get the next statement text to process
Common::String str = talk._statements[statementNum]._statement;
Common::StringArray statementLines;
splitLines(str, statementLines, xSize, 999);
// Add the lines in
for (uint idx = 0; idx < statementLines.size(); ++idx)
_statementLines.push_back(StatementLine(statementLines[idx], statementNum));
}
}
}
void WidgetTalk::refresh() {
_talkScrollIndex = 0;
_selector = _oldSelector = -1;
setStatementLines();
render(HL_NO_HIGHLIGHTING);
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,94 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_TALK_H
#define SHERLOCK_TATTOO_WIDGET_TALK_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
enum Highlight { HL_NO_HIGHLIGHTING, HL_CHANGED_HIGHLIGHTS, HL_SCROLLBAR_ONLY };
/**
* Handles displaying a dialog with conversation options the player can select from
*/
class WidgetTalk: public WidgetBase {
struct StatementLine {
Common::String _line;
int _num;
StatementLine() : _num(0) {}
StatementLine(const Common::String &line, int num) : _line(line), _num(num) {}
};
private:
int _talkScrollIndex;
Common::Array<StatementLine> _statementLines;
int _selector, _oldSelector;
int _talkTextX;
uint32 _dialogTimer;
/**
* Get the needed size for a talk window
*/
void getTalkWindowSize();
/**
* Re-renders the contenst of the window to the widget's surface
*/
void render(Highlight highlightMode);
/**
* This initializes the _statementLines array, which contains the talk options split up line
* by line, as well as which statement a particular line is part of.
*/
void setStatementLines();
public:
WidgetTalk(SherlockEngine *vm);
~WidgetTalk() override {}
/**
* Figures out how many lines the available talk lines will take up, and opens a text window
* of appropriate size
*/
void load();
/**
* Refresh the talk display
*/
void refresh();
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,228 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_text.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
WidgetText::WidgetText(SherlockEngine *vm) : WidgetBase(vm) {
}
void WidgetText::load(const Common::String &str, int speaker) {
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::StringArray lines;
int width = SHERLOCK_SCREEN_WIDTH / 3;
int height;
for (;;) {
splitLines(str, lines, width - _surface.widestChar() * 2, 100);
height = (screen.fontHeight() + 1) * lines.size() + 9;
if ((width - _surface.widestChar() * 2 > height * 3 / 2) || (width - _surface.widestChar() * 2
> SHERLOCK_SCREEN_WIDTH * 3 / 4))
break;
width += (width / 4);
}
// See if it's only a single line long
if (height == _surface.fontHeight() + 10) {
width = _surface.widestChar() * 2 + 6;
const char *strP = str.c_str();
while (*strP && (*strP < talk._opcodes[OP_SWITCH_SPEAKER] || *strP == talk._opcodes[OP_NULL]))
width += _surface.charWidth(*strP++);
}
_bounds = Common::Rect(width, height);
if (speaker == -1) {
// No speaker specified, so center window on look position
_bounds.translate(ui._lookPos.x - width / 2, ui._lookPos.y - height / 2);
} else {
// Speaker specified, so center the window above them
centerWindowOnSpeaker(speaker);
}
render(str);
}
void WidgetText::centerWindowOnSpeaker(int speaker) {
TattooPeople &people = *(TattooPeople *)_vm->_people;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Common::Point pt;
speaker &= 0x7f;
bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES);
if (people[HOLMES]._type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag))) {
// Place the window centered above the player
pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
int scaleVal = scene.getScaleVal(people[HOLMES]._position);
if (scaleVal == SCALE_THRESHOLD) {
pt.x += people[HOLMES].frameWidth() / 2;
pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()
- _bounds.height() - _surface.fontHeight();
} else {
pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2;
pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal)
- _bounds.height() - _surface.fontHeight();
}
} else {
pt.y = -1;
// Check each NPC to see if they are the one that is talking
for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
// WORKAROUND: Fixes an original game bug where the positioning for Watson's dialogs
// during conversations at the Park Lake lake scene is in the incorrect position
if (speaker == 1 && scene._currentScene == 30)
continue;
if (people[idx]._type == CHARACTER) {
if (!scumm_strnicmp(people[idx]._npcName.c_str(), people._characters[speaker]._portrait, 4)) {
// Place the window above the player
pt.x = people[idx]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
int scaleVal = scene.getScaleVal(people[idx]._position);
if (scaleVal == SCALE_THRESHOLD) {
pt.x += people[idx].frameWidth() / 2;
pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx].frameHeight()
- _bounds.height() - _surface.fontHeight();
}
else {
pt.x += people[idx]._imageFrame->sDrawXSize(scaleVal) / 2;
pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx]._imageFrame->sDrawYSize(scaleVal)
- _bounds.height() - _surface.fontHeight();
}
if (pt.y < 0)
pt.y = 0;
break;
}
}
}
if (pt.y == -1) {
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
Object &obj = scene._bgShapes[idx];
if (obj._type == ACTIVE_BG_SHAPE && !scumm_strnicmp(obj._name.c_str(), people._characters[speaker]._portrait, 4)) {
// Place the window centered above the character
pt.x = obj._position.x - _bounds.width() / 2;
pt.y = obj._position.y - _bounds.height() - _surface.fontHeight();
if (pt.y < 0)
pt.y = 0;
if (obj._scaleVal == SCALE_THRESHOLD)
pt.x += obj.frameWidth() / 2;
else
pt.x += obj._imageFrame->sDrawXSize(obj._scaleVal) / 2;
break;
}
}
}
if (pt.y == -1) {
pt.x = SHERLOCK_SCREEN_WIDTH / 2 - _bounds.width() / 2;
pt.y = SHERLOCK_SCREEN_HEIGHT / 2 - _bounds.height() / 2;
}
}
_bounds.moveTo(pt);
}
void WidgetText::render(const Common::String &str) {
Common::StringArray lines;
_remainingText = splitLines(str, lines, _bounds.width() - _surface.widestChar() * 2,
_bounds.height() / (_surface.fontHeight() + 1));
// Allocate a surface for the window
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
// Form the background for the new window
makeInfoArea();
int yp = 5;
for (int lineNum = 0; yp < (_bounds.height() - _surface.fontHeight() / 2); ++lineNum) {
_surface.writeString(lines[lineNum], Common::Point(_surface.widestChar(), yp), INFO_TOP);
yp += _surface.fontHeight() + 1;
}
}
/*----------------------------------------------------------------*/
WidgetMessage::WidgetMessage(SherlockEngine *vm) : WidgetBase(vm) {
_menuCounter = 0;
}
void WidgetMessage::load(const Common::String &str, int time) {
Events &events = *_vm->_events;
Common::Point mousePos = events.mousePos();
_menuCounter = time;
// Set up the bounds for the dialog to be a single line
_bounds = Common::Rect(_surface.stringWidth(str) + _surface.widestChar() * 2 + 6, _surface.fontHeight() + 10);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Allocate a surface for the window
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
// Form the background for the new window and write the line of text
makeInfoArea();
_surface.writeString(str, Common::Point(_surface.widestChar() + 3, 5), INFO_TOP);
}
void WidgetMessage::handleEvents() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
WidgetBase::handleEvents();
--_menuCounter;
// Check if a mouse or keypress has occurred, or the display counter has expired
if (events._pressed || events._released || events._rightPressed || events._rightReleased ||
ui._keyState.keycode || !_menuCounter || ui._action) {
// Close the window
banishWindow();
// Reset cursor and switch back to standard mode
events.setCursor(ARROW);
events.clearEvents();
ui._action = kActionNone;
ui._oldBgFound = -1;
ui._menuMode = STD_MODE;
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,79 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_TEXT_H
#define SHERLOCK_TATTOO_WIDGET_TEXT_H
#include "common/scummsys.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetText: public WidgetBase {
private:
/**
* Center the area the dialog will be drawn on above a given speaker
*/
void centerWindowOnSpeaker(int speaker);
/**
* Build up the text dialog based on the previously set bounds
*/
void render(const Common::String &str);
public:
Common::String _remainingText;
public:
WidgetText(SherlockEngine *vm);
~WidgetText() override {}
/**
* Load the data for the text window
*/
void load(const Common::String &str, int speaker = -1);
};
class WidgetMessage : public WidgetBase {
private:
int _menuCounter;
public:
WidgetMessage(SherlockEngine *vm);
~WidgetMessage() override {}
/**
* Load the data for the text window
*/
void load(const Common::String &str, int time);
/**
* Handle event processing
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,223 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_tooltip.h"
#include "sherlock/tattoo/tattoo_map.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
#define MAX_TOOLTIP_WIDTH 150
void WidgetTooltipBase::draw() {
Screen &screen = *_vm->_screen;
// If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it
if (_oldBounds.width() > 0 && _oldBounds != _bounds)
erase();
if (_bounds.width() > 0 && !_surface.empty()) {
restrictToScreen();
// Blit the affected area to the screen
screen.slamRect(_bounds);
// Draw the widget directly onto the screen. Unlike other widgets, we don't draw to the back buffer,
// since nothing should be drawing on top of tooltips, so there's no need to store in the back buffer
screen.SHtransBlitFrom(_surface, Common::Point(_bounds.left - screen._currentScroll.x,
_bounds.top - screen._currentScroll.y));
// Store a copy of the drawn area for later erasing
_oldBounds = _bounds;
}
}
void WidgetTooltipBase::erase() {
Screen &screen = *_vm->_screen;
if (_oldBounds.width() > 0) {
// Restore the affected area from the back buffer to the screen
screen.slamRect(_oldBounds);
// Reset the old bounds so it won't be erased again
_oldBounds = Common::Rect(0, 0, 0, 0);
}
}
/*----------------------------------------------------------------*/
WidgetTooltip::WidgetTooltip(SherlockEngine *vm) : WidgetTooltipBase (vm), _offsetY(0) {
}
void WidgetTooltip::setText(const Common::String &strIn) {
Events &events = *_vm->_events;
Common::Point mousePos = events.mousePos();
bool reset = false;
Common::String str = Fonts::unescape(strIn);
// Make sure that the description is present
if (!str.empty()) {
int width = _surface.stringWidth(str) + 2;
int height = _surface.stringHeight(str) + 2;
Common::String line1 = str, line2 = "";
// See if we need to split it into two lines
if (width > MAX_TOOLTIP_WIDTH) {
// Go forward word by word to find out where to split the line
const char *s = str.c_str();
const char *space = nullptr;
int dif = 10000;
for (;;) {
// Find end of next word
s = strchr(s + 1, ' ');
if (s == nullptr) {
// Reached end of string
if (space != nullptr) {
line1 = Common::String(str.c_str(), space);
line2 = Common::String(space + 1);
height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4;
}
break;
}
// Found space separating words, so see what width the string up to now is
Common::String tempLine1 = Common::String(str.c_str(), s);
Common::String tempLine2 = Common::String(s + 1);
int width1 = _surface.stringWidth(tempLine1);
int width2 = _surface.stringWidth(tempLine2);
// See if we've found a split point that results in a less overall width
if (ABS(width1 - width2) < dif) {
// Found a better split point
dif = ABS(width1 - width2);
space = s;
line1 = tempLine1;
line2 = tempLine2;
}
}
} else {
// No line split needed
height = _surface.stringHeight(str) + 2;
}
// Reallocate the text surface with the new size
_surface.create(width, height);
_surface.clear(TRANSPARENCY);
if (line2.empty()) {
// Only a single line
_surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP);
} else {
// Two lines to display
int xp, yp;
xp = (width - _surface.stringWidth(line1) - 2) / 2;
_surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP);
xp = (width - _surface.stringWidth(line2) - 2) / 2;
yp = _surface.stringHeight(line1) + 2;
_surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP);
}
// Set the initial display position for the tooltip text
int tagX = mousePos.x - width / 2;
int tagY = mousePos.y - height - _offsetY;
_bounds = Common::Rect(tagX, tagY, tagX + width, tagY + height);
} else {
reset = true;
}
if (reset && !_surface.empty()) {
_surface.free();
}
}
void WidgetTooltip::handleEvents() {
Events &events = *_vm->_events;
Common::Point mousePos = events.mousePos();
// Set the new position for the tooltip
int xp = mousePos.x - _bounds.width() / 2;
int yp = mousePos.y - _bounds.height() - _offsetY;
_bounds.moveTo(xp, yp);
}
/*----------------------------------------------------------------*/
void WidgetSceneTooltip::handleEvents() {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
// See if thay are pointing at a different object and we need to regenerate the tooltip text
if (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()) ||
ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())) {
// See if there is a new object to display text for
if ((ui._bgFound != -1 && (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()))) ||
(ui._arrowZone != -1 && (ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())))) {
Common::String str;
if (ui._bgFound != -1) {
// Clear the Arrow Zone fields so it won't think we're displaying an Arrow Zone cursor
if (scene._currentScene != OVERHEAD_MAP2)
ui._arrowZone = ui._oldArrowZone = -1;
// Get the description string
str = (ui._bgFound < 1000) ? scene._bgShapes[ui._bgFound]._description :
people[ui._bgFound - 1000]._description;
// WORKAORUND: On the train ride to Cambridge, don't show any tooltips
if (scene._currentScene == TRAIN_RIDE)
str = "";
} else {
// Get the exit zone description
str = scene._exits[ui._arrowZone]._dest;
}
setText(str.hasPrefix(" ") ? Common::String() : str);
} else if ((ui._bgFound == -1 && ui._oldBgFound != -1) || (ui._arrowZone == -1 && ui._oldArrowZone != -1)) {
setText("");
}
ui._oldBgFound = ui._bgFound;
} else {
// Set the new position for the tooltip
int tagX = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCREEN_WIDTH - _bounds.width());
int tagY = MAX(mousePos.y - _bounds.height() - _offsetY, 0);
_bounds.moveTo(tagX, tagY);
}
ui._oldArrowZone = ui._arrowZone;
WidgetTooltip::handleEvents();
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,88 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_TOOLTIP_H
#define SHERLOCK_TATTOO_WIDGET_TOOLTIP_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetTooltipBase : public WidgetBase {
public:
WidgetTooltipBase(SherlockEngine *vm) : WidgetBase(vm) {}
~WidgetTooltipBase() override {}
/**
* Erase any previous display of the widget on the screen
*/
void erase() override;
/**
* Update the display of the widget on the screen
*/
void draw() override;
};
class WidgetTooltip: public WidgetTooltipBase {
public:
int _offsetY;
public:
WidgetTooltip(SherlockEngine *vm);
~WidgetTooltip() override {}
/**
* Set the text for the tooltip
*/
void setText(const Common::String &str);
/**
* Handle updating the tooltip state
*/
void handleEvents() override;
};
class WidgetSceneTooltip : public WidgetTooltip {
public:
WidgetSceneTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
/**
* Handle updating the tooltip state
*/
void handleEvents() override;
};
class WidgetMapTooltip : public WidgetTooltip {
public:
WidgetMapTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,328 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/tattoo/widget_verbs.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
WidgetVerbs::WidgetVerbs(SherlockEngine *vm) : WidgetBase(vm) {
_selector = _oldSelector = -1;
_outsideMenu = false;
}
void WidgetVerbs::load(bool objectsOn) {
Events &events = *_vm->_events;
TattooPeople &people = *(TattooPeople *)_vm->_people;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
bool isWatson = false;
if (talk._talkToAbort)
return;
ui._activeObj = ui._bgFound;
_outsideMenu = false;
_verbCommands.clear();
_selector = _oldSelector = -1;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(true);
// Check if we need to show options for the highlighted object
if (objectsOn) {
// Set the verb list accordingly, depending on the target being a
// person or an object
if (ui._personFound) {
TattooPerson &person = people[ui._activeObj - 1000];
if (!scumm_strnicmp(person._npcName.c_str(), "WATS", 4))
isWatson = true;
if (scumm_strnicmp(person._examine.c_str(), "_EXIT", 5))
_verbCommands.push_back(FIXED(Look));
_verbCommands.push_back(FIXED(Talk));
// Add any extra active verbs from the NPC's verb list
for (int idx = 0; idx < 2; ++idx) {
if (!person._use[idx]._verb.empty() && !person._use[idx]._verb.hasPrefix(" ") &&
(person._use[idx]._target.empty() || person._use[idx]._target.hasPrefix(" "))) {
_verbCommands.push_back(person._use[idx]._verb);
}
}
} else {
if (!scumm_strnicmp(ui._bgShape->_name.c_str(), "WATS", 4))
// Looking at Watson
isWatson = true;
if (scumm_strnicmp(ui._bgShape->_examine.c_str(), "_EXIT", 5))
// It's not an exit, so include Look as an option
_verbCommands.push_back(FIXED(Look));
if (ui._bgShape->_aType == PERSON)
_verbCommands.push_back(FIXED(Talk));
// Add any extra active verbs from the object's verb list
for (int idx = 0; idx < 6; ++idx) {
UseType &use = ui._bgShape->_use[idx];
if (!use._verb.empty() && !use._verb.hasPrefix(" ") && !use._verb.hasPrefix("*") &&
(use._target.empty() || use._target.hasPrefix("*") || use._target.hasPrefix(" "))) {
_verbCommands.push_back(use._verb);
}
}
}
}
// If clicked on Watson, have Journal as an option
if (isWatson)
_verbCommands.push_back(FIXED(Journal));
// Add the system commands
_verbCommands.push_back(FIXED(Inventory));
_verbCommands.push_back(FIXED(Options));
// Figure out the needed width to show the commands
int width = 0;
for (uint idx = 0; idx < _verbCommands.size(); ++idx)
width = MAX(width, _surface.stringWidth(_verbCommands[idx]));
width += _surface.widestChar() * 2 + 6;
int height = (_surface.fontHeight() + 7) * _verbCommands.size() + 3;
// Set the bounds
_bounds = Common::Rect(width, height);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Render the window on the internal surface
render();
}
void WidgetVerbs::render() {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
// Create the drawing surface
_surface.create(_bounds.width(), _bounds.height());
_surface.clear(TRANSPARENCY);
// Draw basic background
makeInfoArea();
// Draw the verb commands and the lines separating them
for (uint idx = 0; idx < _verbCommands.size(); ++idx) {
_surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - _surface.stringWidth(_verbCommands[idx])) / 2,
(_surface.fontHeight() + 7) * idx + 5), INFO_TOP);
if (idx < (_verbCommands.size() - 1)) {
_surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.width() - 4, INFO_TOP);
_surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.width() - 4, INFO_MIDDLE);
_surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.width() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1));
_surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width,
(_surface.fontHeight() + 7) * (idx + 1) - 1));
}
}
}
void WidgetVerbs::handleEvents() {
Events &events = *_vm->_events;
FixedText &fixedText = *_vm->_fixedText;
People &people = *_vm->_people;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
Common::String strLook = fixedText.getText(kFixedText_Look);
Common::String strTalk = fixedText.getText(kFixedText_Talk);
Common::String strJournal = fixedText.getText(kFixedText_Journal);
checkTabbingKeys(_verbCommands.size());
// Highlight verb display as necessary
highlightVerbControls();
// Flag if the user has started pressing the button with the cursor outsie the menu
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
// See if they released the mouse button
if (events._released || events._rightReleased) {
// See if they want to close the menu (they clicked outside of the menu)
if (!_bounds.contains(mousePos)) {
if (_outsideMenu) {
if (events._rightReleased) {
// Change to the item (if any) that was right-clicked on, and re-draw the verb menu
ui._bgFound = scene.findBgShape(mousePos);
ui._personFound = ui._bgFound >= 1000;
ui._bgShape = ui._personFound || ui._bgFound == -1 ? nullptr : &scene._bgShapes[ui._bgFound];
bool noDesc = false;
if (ui._personFound) {
if (people[ui._bgFound - 1000]._description.empty() || people[ui._bgFound - 1000]._description.hasPrefix(" "))
noDesc = true;
} else if (ui._bgFound != -1) {
if (ui._bgShape->_description.empty() || ui._bgShape->_description.hasPrefix(" "))
noDesc = true;
} else {
noDesc = true;
}
// Call the Routine to turn on the Commands for this Object
load(!noDesc);
} else {
// Close the window and clear the events
banishWindow();
events.clearEvents();
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
// Reset the active UI mode
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
}
}
} else if (_bounds.contains(mousePos) && _selector != -1) {
// Mouse is within the menu
// Erase the menu
banishWindow();
events.clearEvents();
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
// See if they are activating the Look Command
if (!_verbCommands[_selector].compareToIgnoreCase(strLook)) {
ui._bgFound = ui._activeObj;
if (ui._activeObj >= 1000) {
ui._personFound = true;
} else {
ui._personFound = false;
ui._bgShape = &scene._bgShapes[ui._activeObj];
}
ui.lookAtObject();
} else if (!_verbCommands[_selector].compareToIgnoreCase(strTalk)) {
// Talk command is being activated
talk.initTalk(ui._activeObj);
ui._activeObj = -1;
} else if (!_verbCommands[_selector].compareToIgnoreCase(strJournal)) {
ui.doJournal();
// See if we're in a Lab Table scene
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
} else if (_selector >= ((int)_verbCommands.size() - 2)) {
switch (_selector - (int)_verbCommands.size() + 2) {
case 0:
// Inventory
ui.doInventory(2);
break;
case 1:
// Options
ui.doControls();
break;
default:
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
break;
}
} else {
// If they have selected anything else, process it
people[HOLMES].gotoStand();
if (ui._activeObj < 1000) {
for (int idx = 0; idx < 6; ++idx) {
if (!_verbCommands[_selector].compareToIgnoreCase(scene._bgShapes[ui._activeObj]._use[idx]._verb)) {
// See if they are Picking this object up
if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase("*PICKUP"))
ui.pickUpObject(ui._activeObj);
else
ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj);
}
}
} else {
for (int idx = 0; idx < 2; ++idx) {
if (!_verbCommands[_selector].compareToIgnoreCase(people[ui._activeObj - 1000]._use[idx]._verb))
ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj);
}
}
ui._activeObj = -1;
if (ui._menuMode != MESSAGE_MODE) {
// See if we're in a Lab Table Room
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
}
}
}
} else if (ui._action == kActionTattooExit) {
// User closing the menu with the exit action
banishWindow();
ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
}
}
void WidgetVerbs::highlightVerbControls() {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
Common::Point mousePos = events.mousePos();
// Get highlighted verb
_selector = -1;
Common::Rect bounds = _bounds;
bounds.grow(-3);
if (bounds.contains(mousePos))
_selector = (mousePos.y - bounds.top) / (screen.fontHeight() + 7);
// See if a new verb is being pointed at
if (_selector != _oldSelector) {
// Redraw the verb list
for (int idx = 0; idx < (int)_verbCommands.size(); ++idx) {
byte color = (idx == _selector) ? (byte)COMMAND_HIGHLIGHTED : (byte)INFO_TOP;
_surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - screen.stringWidth(_verbCommands[idx])) / 2,
(screen.fontHeight() + 7) * idx + 5), color);
}
_oldSelector = _selector;
}
}
} // End of namespace Tattoo
} // End of namespace Sherlock

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SHERLOCK_TATTOO_WIDGET_VERBS_H
#define SHERLOCK_TATTOO_WIDGET_VERBS_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "common/str-array.h"
#include "sherlock/tattoo/widget_base.h"
namespace Sherlock {
class SherlockEngine;
namespace Tattoo {
class WidgetVerbs: public WidgetBase {
private:
int _selector, _oldSelector;
bool _outsideMenu;
/**
* Highlights the controls for the verb list
*/
void highlightVerbControls();
/**
* Renders the window on an internal surface for later drawing on-screen
*/
void render();
public:
Common::StringArray _verbCommands;
public:
WidgetVerbs(SherlockEngine *vm);
~WidgetVerbs() override {}
/**
* Turns on the menu with all the verbs that are available for the given object
*/
void load(bool objectsOn);
/**
* Process input for the dialog
*/
void handleEvents() override;
};
} // End of namespace Tattoo
} // End of namespace Sherlock
#endif