aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/access/access.cpp72
-rw-r--r--engines/access/access.h32
-rw-r--r--engines/access/amazon/amazon_game.cpp27
-rw-r--r--engines/access/amazon/amazon_logic.cpp17
-rw-r--r--engines/access/amazon/amazon_player.cpp18
-rw-r--r--engines/access/amazon/amazon_resources.cpp22
-rw-r--r--engines/access/amazon/amazon_resources.h19
-rw-r--r--engines/access/amazon/amazon_scripts.cpp10
-rw-r--r--engines/access/amazon/amazon_scripts.h2
-rw-r--r--engines/access/asurface.cpp21
-rw-r--r--engines/access/asurface.h6
-rw-r--r--engines/access/bubble_box.cpp532
-rw-r--r--engines/access/bubble_box.h34
-rw-r--r--engines/access/char.cpp38
-rw-r--r--engines/access/char.h2
-rw-r--r--engines/access/decompress.cpp44
-rw-r--r--engines/access/decompress.h3
-rw-r--r--engines/access/detection_tables.h17
-rw-r--r--engines/access/inventory.cpp52
-rw-r--r--engines/access/inventory.h1
-rw-r--r--engines/access/martian/martian_game.cpp340
-rw-r--r--engines/access/martian/martian_game.h29
-rw-r--r--engines/access/martian/martian_player.cpp67
-rw-r--r--engines/access/martian/martian_player.h47
-rw-r--r--engines/access/martian/martian_resources.cpp388
-rw-r--r--engines/access/martian/martian_resources.h32
-rw-r--r--engines/access/martian/martian_room.cpp90
-rw-r--r--engines/access/martian/martian_room.h5
-rw-r--r--engines/access/martian/martian_scripts.cpp293
-rw-r--r--engines/access/martian/martian_scripts.h9
-rw-r--r--engines/access/module.mk1
-rw-r--r--engines/access/player.cpp113
-rw-r--r--engines/access/player.h31
-rw-r--r--engines/access/resources.cpp38
-rw-r--r--engines/access/resources.h15
-rw-r--r--engines/access/room.cpp241
-rw-r--r--engines/access/room.h5
-rw-r--r--engines/access/screen.cpp25
-rw-r--r--engines/access/screen.h11
-rw-r--r--engines/access/scripts.cpp412
-rw-r--r--engines/access/scripts.h24
-rw-r--r--engines/access/sound.cpp113
-rw-r--r--engines/access/sound.h8
-rw-r--r--engines/access/video.cpp20
-rw-r--r--engines/access/video.h2
-rw-r--r--engines/agi/detection_tables.h1
-rw-r--r--engines/agi/graphics.cpp5
-rw-r--r--engines/agi/preagi_mickey.cpp2
-rw-r--r--engines/agos/agos.cpp4
-rw-r--r--engines/agos/detection_tables.h8
-rw-r--r--engines/agos/drivers/accolade/adlib.cpp883
-rw-r--r--engines/agos/drivers/accolade/driverfile.cpp166
-rw-r--r--engines/agos/drivers/accolade/mididriver.h44
-rw-r--r--engines/agos/drivers/accolade/mt32.cpp278
-rw-r--r--engines/agos/drivers/simon1/adlib.cpp516
-rw-r--r--engines/agos/drivers/simon1/adlib.h118
-rw-r--r--engines/agos/gfx.cpp7
-rw-r--r--engines/agos/input.cpp1
-rw-r--r--engines/agos/midi.cpp436
-rw-r--r--engines/agos/midi.h23
-rw-r--r--engines/agos/midiparser_s1d.cpp39
-rw-r--r--engines/agos/module.mk4
-rw-r--r--engines/agos/res_snd.cpp5
-rw-r--r--engines/agos/sound.cpp4
-rw-r--r--engines/agos/zones.cpp3
-rw-r--r--engines/bbvs/sound.h2
-rw-r--r--engines/cine/sound.cpp127
-rw-r--r--engines/cruise/sound.cpp114
-rw-r--r--engines/fullpipe/motion.cpp2
-rw-r--r--engines/gob/gob.cpp3
-rw-r--r--engines/gob/map_v2.cpp4
-rw-r--r--engines/gob/sound/adlib.cpp133
-rw-r--r--engines/gob/sound/adlib.h32
-rw-r--r--engines/gob/sound/adlplayer.cpp11
-rw-r--r--engines/gob/sound/adlplayer.h4
-rw-r--r--engines/gob/sound/musplayer.cpp22
-rw-r--r--engines/gob/sound/musplayer.h3
-rw-r--r--engines/gob/sound/sound.cpp18
-rw-r--r--engines/gob/sound/sound.h1
-rw-r--r--engines/groovie/music.cpp57
-rw-r--r--engines/groovie/music.h2
-rw-r--r--engines/kyra/sound_adlib.cpp57
-rw-r--r--engines/lure/res.h1
-rw-r--r--engines/made/made.cpp2
-rw-r--r--engines/made/music.cpp52
-rw-r--r--engines/made/music.h3
-rw-r--r--engines/made/pmvplayer.cpp5
-rw-r--r--engines/made/screenfx.cpp2
-rw-r--r--engines/made/sound.cpp26
-rw-r--r--engines/made/sound.h17
-rw-r--r--engines/mads/hotspots.cpp5
-rw-r--r--engines/mads/nebular/sound_nebular.cpp62
-rw-r--r--engines/mads/nebular/sound_nebular.h60
-rw-r--r--engines/mads/phantom/game_phantom.cpp90
-rw-r--r--engines/mads/phantom/globals_phantom.h96
-rw-r--r--engines/mads/phantom/phantom_scenes.cpp2
-rw-r--r--engines/mads/phantom/phantom_scenes.h40
-rw-r--r--engines/mads/phantom/phantom_scenes1.cpp104
-rw-r--r--engines/mads/phantom/phantom_scenes1.h15
-rw-r--r--engines/mads/sound.cpp3
-rw-r--r--engines/mads/sound.h2
-rw-r--r--engines/mohawk/console.cpp31
-rw-r--r--engines/mohawk/detection_tables.h16
-rw-r--r--engines/mohawk/livingbooks.cpp87
-rw-r--r--engines/mohawk/livingbooks_code.cpp55
-rw-r--r--engines/mohawk/livingbooks_code.h5
-rw-r--r--engines/mohawk/myst_areas.cpp24
-rw-r--r--engines/mohawk/myst_stacks/channelwood.cpp34
-rw-r--r--engines/mohawk/myst_stacks/dni.cpp56
-rw-r--r--engines/mohawk/myst_stacks/intro.cpp23
-rw-r--r--engines/mohawk/myst_stacks/mechanical.cpp64
-rw-r--r--engines/mohawk/myst_stacks/myst.cpp238
-rw-r--r--engines/mohawk/myst_stacks/stoneship.cpp62
-rw-r--r--engines/mohawk/riven.cpp22
-rw-r--r--engines/mohawk/riven_external.cpp85
-rw-r--r--engines/mohawk/riven_scripts.cpp8
-rw-r--r--engines/mohawk/video.cpp553
-rw-r--r--engines/mohawk/video.h313
-rw-r--r--engines/mortevielle/detection_tables.h17
-rw-r--r--engines/mortevielle/menu.cpp9
-rw-r--r--engines/parallaction/adlib.cpp38
-rw-r--r--engines/pegasus/sound.cpp10
-rw-r--r--engines/queen/midiadlib.cpp139
-rw-r--r--engines/queen/midiadlib.h128
-rw-r--r--engines/queen/music.cpp8
-rw-r--r--engines/saga/detection_tables.h33
-rw-r--r--engines/saga/introproc_ihnm.cpp4
-rw-r--r--engines/saga/introproc_ite.cpp70
-rw-r--r--engines/saga/music.cpp48
-rw-r--r--engines/saga/music.h3
-rw-r--r--engines/saga/saga.cpp6
-rw-r--r--engines/saga/saga.h4
-rw-r--r--engines/saga/scene.cpp13
-rw-r--r--engines/saga/scene.h2
-rw-r--r--engines/saga/script.cpp6
-rw-r--r--engines/saga/sndres.cpp15
-rw-r--r--engines/sci/detection.cpp4
-rw-r--r--engines/sci/detection_tables.h10
-rw-r--r--engines/sci/engine/kfile.cpp5
-rw-r--r--engines/sci/engine/script.cpp13
-rw-r--r--engines/sci/graphics/transitions.cpp62
-rw-r--r--engines/sci/graphics/transitions.h1
-rw-r--r--engines/sci/sound/audio.cpp17
-rw-r--r--engines/sci/sound/drivers/adlib.cpp53
-rw-r--r--engines/scumm/detection_tables.h2
-rw-r--r--engines/scumm/players/player_ad.cpp48
-rw-r--r--engines/scumm/players/player_ad.h20
-rw-r--r--engines/scumm/scumm-md5.h12
-rw-r--r--engines/scumm/scumm.cpp3
-rw-r--r--engines/scumm/scumm.h1
-rw-r--r--engines/scumm/string.cpp7
-rw-r--r--engines/sherlock/animation.cpp152
-rw-r--r--engines/sherlock/animation.h7
-rw-r--r--engines/sherlock/debugger.cpp111
-rw-r--r--engines/sherlock/debugger.h37
-rw-r--r--engines/sherlock/detection.cpp15
-rw-r--r--engines/sherlock/detection_tables.h75
-rw-r--r--engines/sherlock/events.cpp177
-rw-r--r--engines/sherlock/events.h57
-rw-r--r--engines/sherlock/fixed_text.cpp38
-rw-r--r--engines/sherlock/fixed_text.h66
-rw-r--r--engines/sherlock/fonts.cpp211
-rw-r--r--engines/sherlock/fonts.h105
-rw-r--r--engines/sherlock/image_file.cpp1098
-rw-r--r--engines/sherlock/image_file.h208
-rw-r--r--engines/sherlock/inventory.cpp291
-rw-r--r--engines/sherlock/inventory.h61
-rw-r--r--engines/sherlock/journal.cpp1244
-rw-r--r--engines/sherlock/journal.h77
-rw-r--r--engines/sherlock/map.cpp543
-rw-r--r--engines/sherlock/map.h135
-rw-r--r--engines/sherlock/module.mk38
-rw-r--r--engines/sherlock/music.cpp476
-rw-r--r--engines/sherlock/music.h62
-rw-r--r--engines/sherlock/objects.cpp1630
-rw-r--r--engines/sherlock/objects.h333
-rw-r--r--engines/sherlock/people.cpp624
-rw-r--r--engines/sherlock/people.h135
-rw-r--r--engines/sherlock/resources.cpp326
-rw-r--r--engines/sherlock/resources.h63
-rw-r--r--engines/sherlock/saveload.cpp266
-rw-r--r--engines/sherlock/saveload.h45
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.cpp510
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.h127
-rw-r--r--engines/sherlock/scalpel/drivers/adlib.cpp161
-rw-r--r--engines/sherlock/scalpel/drivers/mididriver.h16
-rw-r--r--engines/sherlock/scalpel/drivers/mt32.cpp282
-rw-r--r--engines/sherlock/scalpel/scalpel.cpp1002
-rw-r--r--engines/sherlock/scalpel/scalpel.h30
-rw-r--r--engines/sherlock/scalpel/scalpel_darts.cpp (renamed from engines/sherlock/scalpel/darts.cpp)35
-rw-r--r--engines/sherlock/scalpel/scalpel_darts.h (renamed from engines/sherlock/scalpel/darts.h)14
-rw-r--r--engines/sherlock/scalpel/scalpel_debugger.cpp91
-rw-r--r--engines/sherlock/scalpel/scalpel_debugger.h54
-rw-r--r--engines/sherlock/scalpel/scalpel_fixed_text.cpp377
-rw-r--r--engines/sherlock/scalpel/scalpel_fixed_text.h108
-rw-r--r--engines/sherlock/scalpel/scalpel_inventory.cpp296
-rw-r--r--engines/sherlock/scalpel/scalpel_inventory.h74
-rw-r--r--engines/sherlock/scalpel/scalpel_journal.cpp636
-rw-r--r--engines/sherlock/scalpel/scalpel_journal.h99
-rw-r--r--engines/sherlock/scalpel/scalpel_map.cpp596
-rw-r--r--engines/sherlock/scalpel/scalpel_map.h173
-rw-r--r--engines/sherlock/scalpel/scalpel_people.cpp567
-rw-r--r--engines/sherlock/scalpel/scalpel_people.h130
-rw-r--r--engines/sherlock/scalpel/scalpel_saveload.cpp261
-rw-r--r--engines/sherlock/scalpel/scalpel_saveload.h71
-rw-r--r--engines/sherlock/scalpel/scalpel_scene.cpp445
-rw-r--r--engines/sherlock/scalpel/scalpel_scene.h43
-rw-r--r--engines/sherlock/scalpel/scalpel_screen.cpp93
-rw-r--r--engines/sherlock/scalpel/scalpel_screen.h66
-rw-r--r--engines/sherlock/scalpel/scalpel_talk.cpp935
-rw-r--r--engines/sherlock/scalpel/scalpel_talk.h143
-rw-r--r--engines/sherlock/scalpel/scalpel_user_interface.cpp324
-rw-r--r--engines/sherlock/scalpel/scalpel_user_interface.h14
-rw-r--r--engines/sherlock/scalpel/settings.cpp6
-rw-r--r--engines/sherlock/scalpel/tsage/logo.cpp226
-rw-r--r--engines/sherlock/scalpel/tsage/logo.h29
-rw-r--r--engines/sherlock/scene.cpp1375
-rw-r--r--engines/sherlock/scene.h116
-rw-r--r--engines/sherlock/screen.cpp334
-rw-r--r--engines/sherlock/screen.h94
-rw-r--r--engines/sherlock/sherlock.cpp52
-rw-r--r--engines/sherlock/sherlock.h42
-rw-r--r--engines/sherlock/sound.cpp211
-rw-r--r--engines/sherlock/sound.h46
-rw-r--r--engines/sherlock/surface.cpp130
-rw-r--r--engines/sherlock/surface.h48
-rw-r--r--engines/sherlock/talk.cpp1451
-rw-r--r--engines/sherlock/talk.h281
-rw-r--r--engines/sherlock/tattoo/tattoo.cpp152
-rw-r--r--engines/sherlock/tattoo/tattoo.h56
-rw-r--r--engines/sherlock/tattoo/tattoo_darts.cpp1000
-rw-r--r--engines/sherlock/tattoo/tattoo_darts.h173
-rw-r--r--engines/sherlock/tattoo/tattoo_debugger.cpp (renamed from engines/zvision/detection.h)24
-rw-r--r--engines/sherlock/tattoo/tattoo_debugger.h44
-rw-r--r--engines/sherlock/tattoo/tattoo_fixed_text.cpp211
-rw-r--r--engines/sherlock/tattoo/tattoo_fixed_text.h134
-rw-r--r--engines/sherlock/tattoo/tattoo_inventory.cpp63
-rw-r--r--engines/sherlock/tattoo/tattoo_inventory.h48
-rw-r--r--engines/sherlock/tattoo/tattoo_journal.cpp939
-rw-r--r--engines/sherlock/tattoo/tattoo_journal.h114
-rw-r--r--engines/sherlock/tattoo/tattoo_map.cpp434
-rw-r--r--engines/sherlock/tattoo/tattoo_map.h93
-rw-r--r--engines/sherlock/tattoo/tattoo_people.cpp1503
-rw-r--r--engines/sherlock/tattoo/tattoo_people.h281
-rw-r--r--engines/sherlock/tattoo/tattoo_resources.cpp329
-rw-r--r--engines/sherlock/tattoo/tattoo_resources.h42
-rw-r--r--engines/sherlock/tattoo/tattoo_scene.cpp876
-rw-r--r--engines/sherlock/tattoo/tattoo_scene.h96
-rw-r--r--engines/sherlock/tattoo/tattoo_talk.cpp1013
-rw-r--r--engines/sherlock/tattoo/tattoo_talk.h143
-rw-r--r--engines/sherlock/tattoo/tattoo_user_interface.cpp965
-rw-r--r--engines/sherlock/tattoo/tattoo_user_interface.h210
-rw-r--r--engines/sherlock/tattoo/widget_base.cpp375
-rw-r--r--engines/sherlock/tattoo/widget_base.h147
-rw-r--r--engines/sherlock/tattoo/widget_credits.cpp215
-rw-r--r--engines/sherlock/tattoo/widget_credits.h89
-rw-r--r--engines/sherlock/tattoo/widget_files.cpp428
-rw-r--r--engines/sherlock/tattoo/widget_files.h87
-rw-r--r--engines/sherlock/tattoo/widget_foolscap.cpp299
-rw-r--r--engines/sherlock/tattoo/widget_foolscap.h82
-rw-r--r--engines/sherlock/tattoo/widget_inventory.cpp789
-rw-r--r--engines/sherlock/tattoo/widget_inventory.h158
-rw-r--r--engines/sherlock/tattoo/widget_lab.cpp198
-rw-r--r--engines/sherlock/tattoo/widget_lab.h66
-rw-r--r--engines/sherlock/tattoo/widget_options.cpp388
-rw-r--r--engines/sherlock/tattoo/widget_options.h69
-rw-r--r--engines/sherlock/tattoo/widget_password.cpp210
-rw-r--r--engines/sherlock/tattoo/widget_password.h68
-rw-r--r--engines/sherlock/tattoo/widget_quit.cpp155
-rw-r--r--engines/sherlock/tattoo/widget_quit.h57
-rw-r--r--engines/sherlock/tattoo/widget_talk.cpp470
-rw-r--r--engines/sherlock/tattoo/widget_talk.h95
-rw-r--r--engines/sherlock/tattoo/widget_text.cpp228
-rw-r--r--engines/sherlock/tattoo/widget_text.h80
-rw-r--r--engines/sherlock/tattoo/widget_tooltip.cpp223
-rw-r--r--engines/sherlock/tattoo/widget_tooltip.h89
-rw-r--r--engines/sherlock/tattoo/widget_verbs.cpp314
-rw-r--r--engines/sherlock/tattoo/widget_verbs.h72
-rw-r--r--engines/sherlock/user_interface.cpp155
-rw-r--r--engines/sherlock/user_interface.h28
-rw-r--r--engines/sky/music/adlibchannel.cpp12
-rw-r--r--engines/sky/music/adlibchannel.h5
-rw-r--r--engines/sky/music/adlibmusic.cpp56
-rw-r--r--engines/sky/music/adlibmusic.h18
-rw-r--r--engines/sword25/fmv/movieplayer.cpp2
-rw-r--r--engines/tinsel/music.cpp81
-rw-r--r--engines/tinsel/music.h4
-rw-r--r--engines/tinsel/tinsel.cpp5
-rw-r--r--engines/toltecs/music.cpp38
-rw-r--r--engines/toltecs/music.h1
-rw-r--r--engines/tony/window.cpp2
-rw-r--r--engines/tsage/detection_tables.h3
-rw-r--r--engines/tsage/globals.cpp5
-rw-r--r--engines/tsage/sherlock/sherlock_logo.cpp11
-rw-r--r--engines/tsage/sherlock/sherlock_logo.h2
-rw-r--r--engines/tsage/sound.cpp42
-rw-r--r--engines/tsage/sound.h24
-rw-r--r--engines/tsage/tsage.cpp2
-rw-r--r--engines/wintermute/base/base_script_holder.cpp2
-rw-r--r--engines/zvision/configure.engine2
-rw-r--r--engines/zvision/core/clock.h10
-rw-r--r--engines/zvision/core/console.cpp2
-rw-r--r--engines/zvision/detection.cpp271
-rw-r--r--engines/zvision/detection_tables.h277
-rw-r--r--engines/zvision/file/save_manager.cpp2
-rw-r--r--engines/zvision/graphics/render_manager.cpp2
-rw-r--r--engines/zvision/scripting/actions.cpp137
-rw-r--r--engines/zvision/scripting/actions.h1
-rw-r--r--engines/zvision/scripting/controls/input_control.cpp1
-rw-r--r--engines/zvision/scripting/controls/input_control.h3
-rw-r--r--engines/zvision/scripting/menu.cpp74
-rw-r--r--engines/zvision/scripting/menu.h16
-rw-r--r--engines/zvision/sound/zork_raw.cpp7
-rw-r--r--engines/zvision/text/string_manager.h1
-rw-r--r--engines/zvision/text/text.h3
-rw-r--r--engines/zvision/zvision.cpp13
-rw-r--r--engines/zvision/zvision.h30
317 files changed, 36077 insertions, 10128 deletions
diff --git a/engines/access/access.cpp b/engines/access/access.cpp
index 0a4e519c91..56fa6c7533 100644
--- a/engines/access/access.cpp
+++ b/engines/access/access.cpp
@@ -52,11 +52,10 @@ AccessEngine::AccessEngine(OSystem *syst, const AccessGameDescription *gameDesc)
_destIn = nullptr;
_current = nullptr;
_mouseMode = 0;
+ _playerDataCount = 0;
_currentMan = 0;
_currentManOld = -1;
_converseMode = 0;
- _startAboutBox = 0;
- _startTravelBox = 0;
_numAnimTimers = 0;
_startup = 0;
_currentCharFlag = false;
@@ -97,11 +96,33 @@ AccessEngine::AccessEngine(OSystem *syst, const AccessGameDescription *gameDesc)
for (int i = 0; i < 100; i++)
_objectsTable[i] = nullptr;
_clearSummaryFlag = false;
+
+ for (int i = 0; i < 60; i++)
+ _travel[i] = 0;
+ _startTravelItem = _startTravelBox = 0;
+ for (int i = 0; i < 33; i++)
+ _ask[i] = 0;
+ _startAboutItem = _startAboutBox = 0;
+ _byte26CB5 = 0;
+ _bcnt = 0;
+ _boxDataStart = 0;
+ _boxDataEnd = false;
+ _boxSelectY = 0;
+ _boxSelectYOld = -1;
+ _numLines = 0;
+ _tempList = nullptr;
+ _pictureTaken = 0;
+
+ _vidEnd = false;
}
AccessEngine::~AccessEngine() {
delete _animation;
delete _bubbleBox;
+ delete _helpBox;
+ delete _travelBox;
+ delete _invBox;
+ delete _aboutBox;
delete _char;
delete _debugger;
delete _events;
@@ -147,7 +168,18 @@ void AccessEngine::initialize() {
// Create sub-objects of the engine
_animation = new AnimationManager(this);
- _bubbleBox = new BubbleBox(this);
+ _bubbleBox = new BubbleBox(this, TYPE_2, 64, 32, 130, 122, 0, 0, 0, 0, "");
+ if (getGameID() == GType_MartianMemorandum) {
+ _helpBox = new BubbleBox(this, TYPE_1, 64, 24, 146, 122, 1, 32, 2, 76, "HELP");
+ _travelBox = new BubbleBox(this, TYPE_1, 64, 32, 194, 122, 1, 24, 2, 74, "TRAVEL");
+ _invBox = new BubbleBox(this, TYPE_1, 64, 32, 146, 122, 1, 32, 2, 76, "INVENTORY");
+ _aboutBox = new BubbleBox(this, TYPE_1, 64, 32, 194, 122, 1, 32, 2, 76, "ASK ABOUT");
+ } else {
+ _helpBox = nullptr;
+ _travelBox = nullptr;
+ _invBox = nullptr;
+ _aboutBox = nullptr;
+ }
_char = new CharManager(this);
_debugger = Debugger::init(this);
_events = new EventsManager(this);
@@ -416,10 +448,6 @@ void AccessEngine::playVideo(int videoNum, const Common::Point &pt) {
}
}
-void AccessEngine::doLoadSave() {
- error("TODO: doLoadSave");
-}
-
void AccessEngine::freeChar() {
_scripts->freeScriptData();
_animation->clearTimers();
@@ -572,6 +600,36 @@ void AccessEngine::writeSavegameHeader(Common::OutSaveFile *out, AccessSavegameH
out->writeUint32LE(_events->getFrameCounter());
}
+void AccessEngine::SPRINTCHR(char c, int fontNum) {
+ warning("TODO: SPRINTCHR");
+ _fonts._font1.drawChar(_screen, c, _screen->_printOrg);
+}
+
+void AccessEngine::PRINTCHR(Common::String msg, int fontNum) {
+ _events->hideCursor();
+ warning("TODO: PRINTCHR - Handle fontNum");
+
+ for (int i = 0; msg[i]; i++) {
+ if (!(_fonts._charSet._hi & 8)) {
+ _fonts._font1.drawChar(_screen, msg[i], _screen->_printOrg);
+ continue;
+ } else if (_fonts._charSet._hi & 2) {
+ Common::Point oldPos = _screen->_printOrg;
+ int oldFontLo = _fonts._charFor._lo;
+
+ _fonts._charFor._lo = 0;
+ _screen->_printOrg.x++;
+ _screen->_printOrg.y++;
+ SPRINTCHR(msg[i], fontNum);
+
+ _screen->_printOrg = oldPos;
+ _fonts._charFor._lo = oldFontLo;
+ }
+ SPRINTCHR(msg[i], fontNum);
+ }
+ _events->showCursor();
+}
+
bool AccessEngine::shouldQuitOrRestart() {
return shouldQuit() || _restartFl;
}
diff --git a/engines/access/access.h b/engines/access/access.h
index be007e0cb8..37b9fec5a5 100644
--- a/engines/access/access.h
+++ b/engines/access/access.h
@@ -137,6 +137,10 @@ protected:
public:
AnimationManager *_animation;
BubbleBox *_bubbleBox;
+ BubbleBox *_helpBox;
+ BubbleBox *_travelBox;
+ BubbleBox *_invBox;
+ BubbleBox *_aboutBox;
CharManager *_char;
Debugger *_debugger;
EventsManager *_events;
@@ -173,10 +177,9 @@ public:
ImageEntryList _images;
int _mouseMode;
+ int _playerDataCount;
int _currentManOld;
int _converseMode;
- int _startAboutBox;
- int _startTravelBox;
bool _currentCharFlag;
bool _boxSelect;
int _scale;
@@ -204,6 +207,26 @@ public:
uint32 _newDate;
int _flags[256];
+ // Fields used by MM
+ // TODO: Refactor
+ int _travel[60];
+ int _ask[40];
+ int _startTravelItem;
+ int _startTravelBox;
+ int _startAboutItem;
+ int _startAboutBox;
+ int _boxDataStart;
+ bool _boxDataEnd;
+ int _boxSelectY;
+ int _boxSelectYOld;
+ int _numLines;
+ byte _byte26CB5;
+ int _bcnt;
+ byte *_tempList;
+ int _pictureTaken;
+ //
+
+ bool _vidEnd;
bool _clearSummaryFlag;
bool _cheatFl;
bool _restartFl;
@@ -250,8 +273,6 @@ public:
void copyBF2Vid();
- void doLoadSave();
-
void freeChar();
/**
@@ -289,6 +310,9 @@ public:
* Write out a savegame header
*/
void writeSavegameHeader(Common::OutSaveFile *out, AccessSavegameHeader &header);
+
+ void SPRINTCHR(char c, int fontNum);
+ void PRINTCHR(Common::String msg, int fontNum);
};
} // End of namespace Access
diff --git a/engines/access/amazon/amazon_game.cpp b/engines/access/amazon/amazon_game.cpp
index 4c9df7b8ff..7a55873d97 100644
--- a/engines/access/amazon/amazon_game.cpp
+++ b/engines/access/amazon/amazon_game.cpp
@@ -195,8 +195,8 @@ void AmazonEngine::initVariables() {
_timers.push_back(te);
}
- _player->_playerX = _player->_rawPlayer.x = TRAVEL_POS[_player->_roomNumber][0];
- _player->_playerY = _player->_rawPlayer.y = TRAVEL_POS[_player->_roomNumber][1];
+ _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0];
+ _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1];
_room->_selectCommand = -1;
_events->setNormalCursor(CURSOR_CROSSHAIRS);
_mouseMode = 0;
@@ -260,13 +260,24 @@ void AmazonEngine::doEstablish(int screenId, int estabIndex) {
_screen->setIconPalette();
_screen->forceFadeIn();
- _fonts._charSet._lo = 1;
- _fonts._charSet._hi = 10;
- _fonts._charFor._lo = 29;
- _fonts._charFor._hi = 32;
+ if (getGameID() == GType_MartianMemorandum) {
+ _fonts._charSet._lo = 1;
+ _fonts._charSet._hi = 10;
+ _fonts._charFor._lo = 0xF7;
+ _fonts._charFor._hi = 0xFF;
+
+ _screen->_maxChars = 50;
+ _screen->_printOrg = _screen->_printStart = Common::Point(24, 18);
+ } else {
+ _fonts._charSet._lo = 1;
+ _fonts._charSet._hi = 10;
+ _fonts._charFor._lo = 29;
+ _fonts._charFor._hi = 32;
+
+ _screen->_maxChars = 37;
+ _screen->_printOrg = _screen->_printStart = Common::Point(48, 35);
+ }
- _screen->_maxChars = 37;
- _screen->_printOrg = _screen->_printStart = Common::Point(48, 35);
loadEstablish(estabIndex);
uint16 msgOffset;
if (!isCD())
diff --git a/engines/access/amazon/amazon_logic.cpp b/engines/access/amazon/amazon_logic.cpp
index 6dffb85e5e..de53da51cd 100644
--- a/engines/access/amazon/amazon_logic.cpp
+++ b/engines/access/amazon/amazon_logic.cpp
@@ -326,16 +326,8 @@ void Opening::doTitle() {
_vm->_buffer2.copyFrom(*_vm->_screen);
_vm->_buffer1.copyFrom(*_vm->_screen);
screen.forceFadeIn();
- _vm->_sound->playSound(1);
- // WORKAROUND: This delay has been added to replace original game delay that
- // came from loading resources, since nowadays it would be too fast to be visible
- // nowadays to be visible.
- _vm->_events->_vbCount = 70;
- while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
- _vm->_events->pollEventsAndWait();
- if (_vm->shouldQuit())
- return;
+ _vm->_sound->playSound(1, true);
Resource *spriteData = _vm->_files->loadFile(0, 2);
_vm->_objectsTable[0] = new SpriteResource(_vm, spriteData);
@@ -343,7 +335,6 @@ void Opening::doTitle() {
_vm->_files->_setPaletteFlag = false;
_vm->_files->loadScreen(0, 4);
- _vm->_sound->playSound(1);
_vm->_buffer2.copyFrom(*_vm->_screen);
_vm->_buffer1.copyFrom(*_vm->_screen);
@@ -356,7 +347,6 @@ void Opening::doTitle() {
_vm->_buffer2.plotImage(_vm->_objectsTable[0], id, Common::Point(xp, 71));
_vm->_buffer2.copyTo(_vm->_screen);
- _vm->_sound->playSound(1);
_vm->_events->_vbCount = 70;
while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0 && !_skipStart) {
_vm->_events->pollEventsAndWait();
@@ -368,6 +358,7 @@ void Opening::doTitle() {
return;
_vm->_sound->stopSound();
+ _vm->_sound->checkSoundQueue(); // HACK: Clear sound 1 from the queue
_vm->_sound->playSound(0);
screen.forceFadeOut();
_vm->_events->_vbCount = 100;
@@ -386,7 +377,7 @@ void Opening::doTitle() {
_vm->_buffer1.blitFrom(*_vm->_screen);
screen.forceFadeIn();
_vm->_midi->newMusic(1, 0);
- _vm->_events->_vbCount = 700;
+ _vm->_events->_vbCount = 950;
while (!_vm->shouldQuit() && (_vm->_events->_vbCount > 0) && !_vm->_events->isKeyMousePressed()) {
_vm->_events->pollEventsAndWait();
}
@@ -1605,7 +1596,7 @@ void River::moveCanoe() {
moveCanoe2();
} else {
if (events._leftButton && pt.y >= 140) {
- if (pt.x < RMOUSE[8][0]) {
+ if (pt.x < _vm->_room->_rMouse[8][0]) {
// Disk icon wasn't clicked
_vm->_scripts->printString(BAR_MESSAGE);
} else {
diff --git a/engines/access/amazon/amazon_player.cpp b/engines/access/amazon/amazon_player.cpp
index b1ed501fce..903da6c532 100644
--- a/engines/access/amazon/amazon_player.cpp
+++ b/engines/access/amazon/amazon_player.cpp
@@ -48,7 +48,7 @@ void AmazonPlayer::load() {
_downDelta = -2;
_scrollConst = 2;
- for (int i = 0; i < PLAYER_DATA_COUNT; ++i) {
+ for (int i = 0; i < _vm->_playerDataCount; ++i) {
_walkOffRight[i] = OVEROFFR[i];
_walkOffLeft[i] = OVEROFFL[i];
_walkOffUp[i] = OVEROFFU[i];
@@ -78,6 +78,22 @@ void AmazonPlayer::load() {
_diagDownWalkMin = 0;
_diagDownWalkMax = 5;
_game->_guard->setPosition(Common::Point(56, 190));
+ } else {
+ for (int i = 0; i < _vm->_playerDataCount; ++i) {
+ _walkOffRight[i] = SIDEOFFR[i];
+ _walkOffLeft[i] = SIDEOFFL[i];
+ _walkOffUp[i] = SIDEOFFU[i];
+ _walkOffDown[i] = SIDEOFFD[i];
+
+ _walkOffUR[i].x = DIAGOFFURX[i];
+ _walkOffUR[i].y = DIAGOFFURY[i];
+ _walkOffDR[i].x = DIAGOFFDRX[i];
+ _walkOffDR[i].y = DIAGOFFDRY[i];
+ _walkOffUL[i].x = DIAGOFFULX[i];
+ _walkOffUL[i].y = DIAGOFFULY[i];
+ _walkOffDL[i].x = DIAGOFFDLX[i];
+ _walkOffDL[i].y = DIAGOFFDLY[i];
+ }
}
}
diff --git a/engines/access/amazon/amazon_resources.cpp b/engines/access/amazon/amazon_resources.cpp
index 2010c7d842..430aa64f30 100644
--- a/engines/access/amazon/amazon_resources.cpp
+++ b/engines/access/amazon/amazon_resources.cpp
@@ -72,6 +72,19 @@ const char *const FILENAMES_DEMO[] = {
"CHAPTER.AP", "MUSIC.AP", "SOUND.AP", "INV.AP"
};
+const int SIDEOFFR[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 };
+const int SIDEOFFL[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 };
+const int SIDEOFFU[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 };
+const int SIDEOFFD[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 };
+const int DIAGOFFURX[] = { 4, 5, 2, 2, 3, 4, 2, 2, 0 };
+const int DIAGOFFURY[] = { 2, 3, 2, 2, 2, 3, 1, 1, 0 };
+const int DIAGOFFDRX[] = { 4, 5, 4, 3, 5, 4, 5, 1, 0 };
+const int DIAGOFFDRY[] = { 3, 2, 1, 2, 2, 1, 2, 1, 0 };
+const int DIAGOFFULX[] = { 4, 5, 4, 3, 3, 2, 2, 2, 0 };
+const int DIAGOFFULY[] = { 3, 3, 1, 2, 2, 1, 1, 1, 0 };
+const int DIAGOFFDLX[] = { 4, 5, 3, 3, 5, 4, 6, 1, 0 };
+const int DIAGOFFDLY[] = { 2, 2, 1, 2, 3, 1, 2, 1, 0 };
+
const byte MOUSE0[] = {
// hotspot x and y, uint16 LE
0, 0, 0, 0,
@@ -313,7 +326,7 @@ const byte *const CURSORS[10] = {
MOUSE0, MOUSE1, MOUSE2, MOUSE3, CURSEYE, CURSHAND, CURSGET, CURSCLIMB, CURSTALK, CURSHELP
};
-const int TRAVEL_POS[][2] = {
+const int _travelPos[][2] = {
{ -1, 0 },
{ 228, 117 },
{ 28, 98 },
@@ -2406,6 +2419,11 @@ const int CAST_END_OBJ1[4][4] = {
{ 3, 103, 1300, 10 }
};
-} // End of namespace Amazon
+const int RMOUSE[10][2] = {
+ { 0, 35 }, { 0, 0 }, { 36, 70 }, { 71, 106 }, { 107, 141 },
+ { 142, 177 }, { 178, 212 }, { 213, 248 }, { 249, 283 }, { 284, 318 }
+};
+
+} // End of namespace Amazon
} // End of namespace Access
diff --git a/engines/access/amazon/amazon_resources.h b/engines/access/amazon/amazon_resources.h
index a952860bc2..10dea02abc 100644
--- a/engines/access/amazon/amazon_resources.h
+++ b/engines/access/amazon/amazon_resources.h
@@ -45,9 +45,22 @@ struct RiverStruct {
extern const char *const FILENAMES[];
extern const char *const FILENAMES_DEMO[];
+extern const int SIDEOFFR[];
+extern const int SIDEOFFL[];
+extern const int SIDEOFFU[];
+extern const int SIDEOFFD[];
+extern const int DIAGOFFURX[];
+extern const int DIAGOFFURY[];
+extern const int DIAGOFFDRX[];
+extern const int DIAGOFFDRY[];
+extern const int DIAGOFFULX[];
+extern const int DIAGOFFULY[];
+extern const int DIAGOFFDLX[];
+extern const int DIAGOFFDLY[];
+
extern const byte *const CURSORS[10];
-extern const int TRAVEL_POS[][2];
+extern const int _travelPos[][2];
extern const int OVEROFFR[];
extern const int OVEROFFL[];
@@ -138,11 +151,11 @@ extern const int HELP1COORDS[2][4];
extern const int RIVER1OBJ[23][4];
extern const int CAST_END_OBJ[26][4];
-
extern const int CAST_END_OBJ1[4][4];
-} // End of namespace Amazon
+extern const int RMOUSE[10][2];
+} // End of namespace Amazon
} // End of namespace Access
#endif /* ACCESS_AMAZON_RESOURCES_H */
diff --git a/engines/access/amazon/amazon_scripts.cpp b/engines/access/amazon/amazon_scripts.cpp
index 92acb3686d..d8f4663401 100644
--- a/engines/access/amazon/amazon_scripts.cpp
+++ b/engines/access/amazon/amazon_scripts.cpp
@@ -33,6 +33,8 @@ namespace Amazon {
AmazonScripts::AmazonScripts(AccessEngine *vm) : Scripts(vm) {
_game = (AmazonEngine *)_vm;
+
+ setOpcodes_v2();
}
void AmazonScripts::cLoop() {
@@ -378,20 +380,20 @@ void AmazonScripts::executeSpecial(int commandIndex, int param1, int param2) {
typedef void(AmazonScripts::*AmazonScriptMethodPtr)();
void AmazonScripts::executeCommand(int commandIndex) {
- static const AmazonScriptMethodPtr COMMAND_LIST[] = {
- &AmazonScripts::cmdHelp, &AmazonScripts::cmdCycleBack,
+ static const AmazonScriptMethodPtr AMAZON_COMMAND_LIST[] = {
+ &AmazonScripts::cmdHelp_v2, &AmazonScripts::cmdCycleBack,
&AmazonScripts::cmdChapter, &AmazonScripts::cmdSetHelp,
&AmazonScripts::cmdCenterPanel, &AmazonScripts::cmdMainPanel,
&AmazonScripts::CMDRETFLASH
};
if (commandIndex >= 73)
- (this->*COMMAND_LIST[commandIndex - 73])();
+ (this->*AMAZON_COMMAND_LIST[commandIndex - 73])();
else
Scripts::executeCommand(commandIndex);
}
-void AmazonScripts::cmdHelp() {
+void AmazonScripts::cmdHelp_v2() {
Common::String helpMessage = readString();
if (_game->_helpLevel == 0) {
diff --git a/engines/access/amazon/amazon_scripts.h b/engines/access/amazon/amazon_scripts.h
index e10eefb4f5..6d992667f5 100644
--- a/engines/access/amazon/amazon_scripts.h
+++ b/engines/access/amazon/amazon_scripts.h
@@ -49,7 +49,7 @@ protected:
void setInactive();
void boatWalls(int param1, int param2);
- void cmdHelp();
+ void cmdHelp_v2();
void cmdCycleBack();
void cmdChapter();
void cmdSetHelp();
diff --git a/engines/access/asurface.cpp b/engines/access/asurface.cpp
index 5f4372d5af..526690807a 100644
--- a/engines/access/asurface.cpp
+++ b/engines/access/asurface.cpp
@@ -54,6 +54,12 @@ SpriteResource::~SpriteResource() {
SpriteFrame::SpriteFrame(AccessEngine *vm, Common::SeekableReadStream *stream, int frameSize) {
int xSize = stream->readUint16LE();
int ySize = stream->readUint16LE();
+
+ if (vm->getGameID() == GType_MartianMemorandum) {
+ int size = stream->readUint16LE();
+ if (size != frameSize)
+ warning("Unexpected file difference: framesize %d - size %d %d - unknown %d", frameSize, xSize, ySize, size);
+ }
create(xSize, ySize);
// Empty surface
@@ -312,6 +318,21 @@ void ASurface::drawRect() {
Graphics::Surface::fillRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2), _lColor);
}
+void ASurface::drawLine(int x1, int y1, int x2, int y2, int col) {
+ Graphics::Surface::drawLine(x1, y1, x2, y2, col);
+}
+
+void ASurface::drawLine() {
+ Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor);
+}
+
+void ASurface::drawBox() {
+ Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor);
+ Graphics::Surface::drawLine(_orgX1, _orgY2, _orgX2, _orgY2, _lColor);
+ Graphics::Surface::drawLine(_orgX2, _orgY1, _orgX2, _orgY1, _lColor);
+ Graphics::Surface::drawLine(_orgX2, _orgY2, _orgX2, _orgY2, _lColor);
+}
+
void ASurface::flipHorizontal(ASurface &dest) {
dest.create(this->w, this->h);
for (int y = 0; y < h; ++y) {
diff --git a/engines/access/asurface.h b/engines/access/asurface.h
index 4fb47b9c09..022e2534c1 100644
--- a/engines/access/asurface.h
+++ b/engines/access/asurface.h
@@ -95,6 +95,12 @@ public:
virtual void drawRect();
+ virtual void drawLine(int x1, int y1, int x2, int y2, int col);
+
+ virtual void drawLine();
+
+ virtual void drawBox();
+
virtual void transBlitFrom(ASurface *src, const Common::Point &destPos);
virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds);
diff --git a/engines/access/bubble_box.cpp b/engines/access/bubble_box.cpp
index 28c211991c..df8adc1bc6 100644
--- a/engines/access/bubble_box.cpp
+++ b/engines/access/bubble_box.cpp
@@ -26,13 +26,26 @@
namespace Access {
-BubbleBox::BubbleBox(AccessEngine *vm) : Manager(vm) {
- _startItem = 0;
- _startBox = 0;
- _charCol = _rowOff = 0;
- _type = TYPE_2;
- _bounds = Common::Rect(64, 32, 64 + 130, 32 + 122);
- _bubbleDisplStr = "";
+BubbleBox::BubbleBox(AccessEngine *vm, Access::BoxType type, int x, int y, int w, int h, int val1, int val2, int val3, int val4, Common::String title) : Manager(vm) {
+ _type = type;
+ _bounds = Common::Rect(x, y, x + w, y + h);
+ _bubbleDisplStr = title;
+ _btnId1 = val1;
+ _btnX1 = val2;
+ _btnId2 = val3;
+ _btnX2 = val4;
+ _btnId3 = _btnX3 = 0; // Unused in MM and Amazon?
+ _boxStartX = _boxStartY = 0;
+ _bIconStartX = _bIconStartY = 0;
+ _boxEndX = _boxEndY = 0;
+ _boxPStartX = _boxPStartY = 0;
+ // Unused in AGoE
+ for (int i = 0; i < 60; i++) {
+ _tempList[i] = "";
+ _tempListIdx[i] = 0;
+ }
+ _btnUpPos = Common::Rect(0, 0, 0, 0);
+ _btnDownPos = Common::Rect(0, 0, 0, 0);
}
void BubbleBox::load(Common::SeekableReadStream *stream) {
@@ -61,7 +74,7 @@ void BubbleBox::clearBubbles() {
}
void BubbleBox::placeBubble(const Common::String &msg) {
- _vm->_screen->_maxChars = 27;
+ _vm->_screen->_maxChars = (_vm->getGameID() == GType_MartianMemorandum) ? 30 : 27;
placeBubble1(msg);
}
@@ -69,8 +82,8 @@ void BubbleBox::placeBubble1(const Common::String &msg) {
_bubbles.clear();
_vm->_fonts._charSet._lo = 1;
_vm->_fonts._charSet._hi = 8;
- _vm->_fonts._charFor._lo = 29;
- _vm->_fonts._charFor._hi = 32;
+ _vm->_fonts._charFor._lo = (_vm->getGameID() == GType_MartianMemorandum) ? 247 : 29;
+ _vm->_fonts._charFor._hi = (_vm->getGameID() == GType_MartianMemorandum) ? 255 : 32;
calcBubble(msg);
@@ -86,7 +99,7 @@ void BubbleBox::calcBubble(const Common::String &msg) {
Common::Point printStart = _vm->_screen->_printStart;
// Figure out maximum width allowed
- if (_type == TYPE_4) {
+ if (_type == kBoxTypeFileDialog) {
_vm->_fonts._printMaxX = 110;
} else {
_vm->_fonts._printMaxX = _vm->_fonts._font2.stringWidth(_bubbleDisplStr);
@@ -108,7 +121,7 @@ void BubbleBox::calcBubble(const Common::String &msg) {
_vm->_screen->_printOrg.x = _vm->_screen->_printStart.x;
} while (!lastLine);
- if (_type == TYPE_4)
+ if (_type == kBoxTypeFileDialog)
++_vm->_screen->_printOrg.y += 6;
// Determine the width for the area
@@ -119,12 +132,12 @@ void BubbleBox::calcBubble(const Common::String &msg) {
// Determine the height for area
int y = _vm->_screen->_printOrg.y + 6;
- if (_type == TYPE_4)
+ if (_type == kBoxTypeFileDialog)
y += 6;
int height = y - bounds.top;
bounds.setHeight(height);
- height -= (_type == TYPE_4) ? 30 : 24;
+ height -= (_type == kBoxTypeFileDialog) ? 30 : 24;
if (height >= 0)
bounds.setHeight(bounds.height() + 13 - (height % 13));
@@ -137,6 +150,35 @@ void BubbleBox::calcBubble(const Common::String &msg) {
}
void BubbleBox::printBubble(const Common::String &msg) {
+ if (_vm->getGameID() == GType_MartianMemorandum)
+ printBubble_v1(msg);
+ else
+ printBubble_v2(msg);
+}
+
+void BubbleBox::printBubble_v1(const Common::String &msg) {
+ drawBubble(_bubbles.size() - 1);
+
+ // Loop through drawing the lines
+ Common::String s = msg;
+ Common::String line;
+ int width = 0;
+ bool lastLine;
+ do {
+ // Get next line
+ Font &font2 = _vm->_fonts._font2;
+ lastLine = font2.getLine(s, _vm->_screen->_maxChars * 6, line, width);
+ // Draw the text
+ printString(line);
+
+ // Move print position
+ _vm->_screen->_printOrg.y += 6;
+ _vm->_screen->_printOrg.x = _vm->_screen->_printStart.x;
+ } while (!lastLine);
+
+}
+
+void BubbleBox::printBubble_v2(const Common::String &msg) {
drawBubble(_bubbles.size() - 1);
// Loop through drawing the lines
@@ -156,7 +198,7 @@ void BubbleBox::printBubble(const Common::String &msg) {
font2._fontColors[3] = 29;
int xp = _vm->_screen->_printOrg.x;
- if (_type == TYPE_4)
+ if (_type == kBoxTypeFileDialog)
xp = (_bounds.width() - width) / 2 + _bounds.left - 4;
// Draw the text
@@ -170,7 +212,11 @@ void BubbleBox::printBubble(const Common::String &msg) {
void BubbleBox::drawBubble(int index) {
_bounds = _bubbles[index];
- doBox(0, 0);
+ if (_vm->getGameID() == GType_MartianMemorandum) {
+ int btnSelected = 0;
+ doBox_v1(0, 0, btnSelected);
+ } else
+ doBox(0, 0);
}
void BubbleBox::doBox(int item, int box) {
@@ -194,7 +240,7 @@ void BubbleBox::doBox(int item, int box) {
fonts._charSet._lo = 1;
fonts._charSet._hi = 0;
- if (_type == TYPE_4) {
+ if (_type == kBoxTypeFileDialog) {
fonts._charFor._lo = 0xFF;
error("TODO: filename listing");
return;
@@ -212,7 +258,7 @@ void BubbleBox::doBox(int item, int box) {
_vm->_screen->_orgY2 = _bounds.bottom;
_vm->_screen->_lColor = 1;
- int h = _bounds.height() - (_type == TYPE_4 ? 30 : 24);
+ int h = _bounds.height() - (_type == kBoxTypeFileDialog ? 30 : 24);
int ySize = (h < 0) ? 0 : (h + 12) / 13;
int w = _bounds.width() - 24;
int xSize = (w < 0) ? 0 : (w + 19) / 20;
@@ -229,21 +275,21 @@ void BubbleBox::doBox(int item, int box) {
screen.plotImage(icons, 21, Common::Point(xp, screen._orgY1));
// Draw images to form the bottom border
- yp = screen._orgY2 - (_type == TYPE_4 ? 18 : 12);
- screen.plotImage(icons, (_type == TYPE_4) ? 72 : 22,
+ yp = screen._orgY2 - (_type == kBoxTypeFileDialog ? 18 : 12);
+ screen.plotImage(icons, (_type == kBoxTypeFileDialog) ? 72 : 22,
Common::Point(screen._orgX1, yp));
xp = screen._orgX1 + 12;
- yp += (_type == TYPE_4) ? 4 : 8;
+ yp += (_type == kBoxTypeFileDialog) ? 4 : 8;
for (int x = 0; x < xSize; ++x, xp += 20) {
- screen.plotImage(icons, (_type == TYPE_4 ? 62 : 34) + x,
+ screen.plotImage(icons, (_type == kBoxTypeFileDialog ? 62 : 34) + x,
Common::Point(xp, yp));
}
- yp = screen._orgY2 - (_type == TYPE_4 ? 18 : 12);
- screen.plotImage(icons, (_type == TYPE_4) ? 73 : 23, Common::Point(xp, yp));
+ yp = screen._orgY2 - (_type == kBoxTypeFileDialog ? 18 : 12);
+ screen.plotImage(icons, (_type == kBoxTypeFileDialog) ? 73 : 23, Common::Point(xp, yp));
- if (_type == TYPE_4) {
+ if (_type == kBoxTypeFileDialog) {
// Further stuff for filename dialog
error("TODO: Box type 4");
}
@@ -278,4 +324,440 @@ void BubbleBox::doBox(int item, int box) {
delete icons;
}
+void BubbleBox::setCursorPos(int posX, int posY) {
+ _vm->_screen->_printStart = _vm->_screen->_printOrg = Common::Point((posX << 3) + _rowOff, posY << 3);
+ warning("Missing call to setCursorPos");
+}
+
+void BubbleBox::printString(Common::String msg) {
+ warning("TODO: Proper implementation of printString");
+ _vm->_fonts._font1.drawString(_vm->_screen, msg, _vm->_screen->_printOrg);
+}
+
+void BubbleBox::displayBoxData() {
+ _vm->_boxDataEnd = false;
+ _rowOff = 2;
+ _vm->_fonts._charFor._lo = 0xF7;
+ _vm->_fonts._charFor._hi = 0xFF;
+
+ if (_tempList[0].size() == 0)
+ return;
+
+ int idx = 0;
+ if ((_type == TYPE_1) || (_type == TYPE_3)) {
+ _vm->_bcnt = 0;
+
+ if (_tempList[idx].size() == 0) {
+ _vm->_boxDataEnd = true;
+ return;
+ }
+
+ _vm->_events->hideCursor();
+
+ _vm->_screen->_orgX1 = _boxStartX;
+ _vm->_screen->_orgX2 = _boxEndX;
+ _vm->_screen->_orgY1 = _boxStartY;
+ _vm->_screen->_orgY2 = _boxEndY;
+ _vm->_screen->_lColor = 0xFA;
+ _vm->_screen->drawRect();
+ _vm->_events->showCursor();
+ }
+
+ _vm->_events->hideCursor();
+ int oldPStartY = _boxPStartY;
+ ++_boxPStartY;
+
+ idx += _vm->_boxDataStart;
+
+ while (true) {
+ setCursorPos(_boxPStartX, _boxPStartY);
+ printString(_tempList[idx]);
+
+ ++idx;
+ ++_boxPStartY;
+ ++_vm->_bcnt;
+ if (_tempList[idx].size() == 0) {
+ _boxPStartY = oldPStartY;
+ _vm->_events->showCursor();
+ _vm->_boxDataEnd = true;
+ return;
+ }
+
+ if (_vm->_bcnt == _vm->_numLines) {
+ _boxPStartY = oldPStartY;
+ _vm->_events->showCursor();
+ return;
+ }
+ }
+}
+
+void BubbleBox::drawSelectBox() {
+ if (_tempList[0].size() == 0)
+ return;
+
+ if (((_type != TYPE_1) && (_type != TYPE_3)) || !_vm->_bcnt)
+ return;
+
+ if (_vm->_boxSelectYOld != -1) {
+ _vm->_events->hideCursor();
+ _vm->_screen->_lColor = 0xFA;
+
+ int val = _vm->_boxSelectYOld + _boxPStartY + 1;
+ _vm->_screen->_orgY1 = (val << 3) + 2;
+ _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 7;
+ _vm->_screen->_orgX1 = _boxStartX;
+ _vm->_screen->_orgX2 = _boxEndX;
+ _vm->_screen->drawBox();
+ _vm->_events->showCursor();
+ }
+
+ _vm->_events->hideCursor();
+ _vm->_boxSelectYOld = _vm->_boxSelectY;
+ int val = _boxPStartY + _vm->_boxSelectY + 1;
+ _vm->_screen->_orgY1 = (val << 3) + 2;
+ _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 7;
+ _vm->_screen->_orgX1 = _boxStartX;
+ _vm->_screen->_orgX2 = _boxEndX;
+ _vm->_screen->_lColor = 0xFE;
+ _vm->_screen->drawBox();
+ _vm->_events->showCursor();
+
+ if (_type == TYPE_3)
+ warning("TODO: List filenames");
+}
+
+int BubbleBox::doBox_v1(int item, int box, int &btnSelected) {
+ static const int ICONW[] = { 0, 11, 28, 19, 19, 15 };
+
+ FontManager &fonts = _vm->_fonts;
+ int retval_ = -1;
+
+ _startItem = item;
+ _startBox = box;
+
+ // Save state information
+ _vm->_screen->saveScreen();
+ _vm->_screen->setDisplayScan();
+
+ fonts._charFor._hi = 0xff;
+ fonts._charSet._lo = 1;
+ fonts._charSet._hi = 0;
+
+ _vm->_destIn = _vm->_screen; // TODO: Redundant
+
+ if (_type != TYPE_2) {
+ Common::Rect r = _bounds;
+ r.left -= 2;
+ _vm->_screen->saveBlock(r);
+ }
+
+ // Set the up boundaries and color to use for the box background
+ _vm->_screen->_orgX1 = _bounds.left - 2;
+ _vm->_screen->_orgY1 = _bounds.top;
+ _vm->_screen->_orgX2 = _bounds.right - 2;
+ _vm->_screen->_orgY2 = _bounds.bottom;
+ _vm->_screen->_lColor = 0xFB;
+
+ // Draw a background for the entire area
+ _vm->_screen->drawRect();
+
+ // Draw the inner box;
+ ++_vm->_screen->_orgX1;
+ ++_vm->_screen->_orgY1;
+ --_vm->_screen->_orgX2;
+ --_vm->_screen->_orgY2;
+ _vm->_screen->_lColor = 0xF9;
+
+ // Draw the inner border
+ _vm->_screen->drawBox();
+
+ // Get icons data
+ Resource *iconData = _vm->_files->loadFile("ICONS.LZ");
+ SpriteResource *icons = new SpriteResource(_vm, iconData);
+ delete iconData;
+
+ // Draw upper border
+ _vm->_bcnt = (_vm->_screen->_orgX2 - _vm->_screen->_orgX1) >> 4;
+ int oldX = _vm->_screen->_orgX1;
+ for ( ;_vm->_bcnt > 0; --_vm->_bcnt) {
+ _vm->_screen->plotImage(icons, 16, Common::Point(_vm->_screen->_orgX1, _vm->_screen->_orgY1));
+ _vm->_screen->_orgX1 += 16;
+ }
+
+ _vm->_screen->_orgX1 = oldX;
+ int oldY = _vm->_screen->_orgY2;
+ _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 8;
+ _vm->_screen->_lColor = 0xF9;
+
+ _boxStartY = _vm->_screen->_orgY2 + 1;
+ _vm->_screen->_orgY2 = oldY;
+
+ int tmpX = 0;
+ int tmpY = 0;
+ if (_type != TYPE_2) {
+ oldY = _vm->_screen->_orgY1;
+ --_vm->_screen->_orgY2;
+ _vm->_screen->_orgY1 = _vm->_screen->_orgY2 - 8;
+ if (_type == TYPE_3)
+ _vm->_screen->_orgY1 -= 8;
+ _vm->_screen->drawRect();
+ tmpX = _bIconStartX = _vm->_screen->_orgX1;
+
+ _boxStartX = tmpX + 1;
+ tmpY = _boxEndY = _vm->_screen->_orgY1;
+
+ if (_type == TYPE_3)
+ _bIconStartY = tmpY + 9;
+ else
+ _bIconStartY = tmpY + 1;
+
+ if (_type == TYPE_3) {
+ _fileStart = Common::Point((tmpX + 2) >> 3, (tmpY + 2) >> 3);
+ int rowOff = tmpY - (_fileStart.y << 3) + 1;
+ if (rowOff == 8) {
+ rowOff = 0;
+ ++_fileStart.y;
+ }
+ _fileOff.y = _rowOff = rowOff;
+ setCursorPos(_fileStart.x, _fileStart.y);
+ _vm->_fonts._charFor._lo = 0xF7;
+ _vm->_fonts._charFor._hi = 0;
+ printString("FILE: ");
+ _vm->_fonts._charFor._hi = 0xFF;
+ }
+ _vm->_screen->_orgY1 = oldY;
+ }
+
+ if ((_type != TYPE_0) && (_type != TYPE_2)) {
+ _vm->_screen->_orgY1 += 8;
+ if (_type == TYPE_3)
+ _vm->_screen->_orgY2 -= 8;
+
+ _vm->_screen->_orgY2 -= 8;
+ _btnUpPos.right = _btnDownPos.right = _vm->_screen->_orgX2;
+ _btnUpPos.left = _btnDownPos.left = _vm->_screen->_orgX1 = _vm->_screen->_orgX2 - 8;
+ _boxEndX = _vm->_screen->_orgX1 - 1;
+ _vm->_screen->drawBox();
+
+ _vm->_screen->_orgY1 += 6;
+ _vm->_screen->_orgY2 -= 6;
+ _vm->_screen->drawBox();
+
+ _btnUpPos.bottom = _vm->_screen->_orgY1 + 1;
+ _btnUpPos.top = _btnUpPos.bottom - 5;
+ _btnDownPos.top = _vm->_screen->_orgY2 + 1;
+ _btnDownPos.bottom = _btnDownPos.top + 6;
+
+ _vm->_screen->_orgX1 += 4;
+ _vm->_screen->_orgX2 = _vm->_screen->_orgX1;
+ _vm->_screen->_orgY1 -= 4;
+ _vm->_screen->_orgY2 += 2;
+ _vm->_screen->drawLine();
+
+ ++_vm->_screen->_orgY1;
+ --_vm->_screen->_orgX1;
+ ++_vm->_screen->_orgX2;
+ _vm->_screen->drawLine();
+
+ ++_vm->_screen->_orgY1;
+ --_vm->_screen->_orgX1;
+ ++_vm->_screen->_orgX2;
+ _vm->_screen->drawLine();
+
+ _vm->_screen->_orgY1 = _vm->_screen->_orgY2;
+ _vm->_screen->drawLine();
+
+ ++_vm->_screen->_orgX1;
+ --_vm->_screen->_orgX2;
+ ++_vm->_screen->_orgY1;
+ _vm->_screen->drawLine();
+
+ ++_vm->_screen->_orgX1;
+ --_vm->_screen->_orgX2;
+ ++_vm->_screen->_orgY1;
+ _vm->_screen->drawLine();
+ }
+
+ int len = _bubbleDisplStr.size();
+ int newX = _bounds.top >> 3;
+ newX = (len - newX) / 2;
+
+ _boxPStartX = _bounds.left >> 3;
+ newX += _boxPStartX;
+
+ int newY = _bounds.top >> 3;
+ int bp = _bounds.top - (newY << 3) + 1;
+ if (bp == 8) {
+ ++newY;
+ bp = 0;
+ }
+
+ _rowOff = bp;
+ retval_ = _boxPStartY = newY;
+
+ setCursorPos(newX, newY);
+
+ _vm->_fonts._charFor._lo = 0xFF;
+ _vm->_fonts._font1.drawString(_vm->_screen, _bubbleDisplStr, _vm->_screen->_printOrg);
+
+ if (_type == TYPE_2) {
+ _vm->_events->showCursor();
+ warning("TODO: pop values");
+ _vm->_screen->restoreScreen();
+ return retval_;
+ }
+
+ _vm->_destIn = _vm->_screen;
+
+ // Draw buttons
+ int ICON1T = 0;
+ int ICON1X = 0;
+ int ICON1Y = 0;
+ int ICON2T = 0;
+ int ICON2X = 0;
+ int ICON3T = 0;
+ int ICON3X = 0;
+ if (_btnId1) {
+ ICON1T = _btnId1;
+ ICON1X = _bIconStartX + _btnX1;
+ ICON1Y = _bIconStartY;
+ _vm->_screen->plotImage(icons, ICON1T + 10, Common::Point(ICON1X, ICON1Y));
+
+ if (_btnId2) {
+ ICON2T = _btnId2;
+ ICON2X = _bIconStartX + _btnX2;
+ _vm->_screen->plotImage(icons, ICON2T + 10, Common::Point(ICON2X, _bIconStartY));
+
+ if (_btnId3) {
+ ICON3T = _btnId3;
+ ICON3X = _bIconStartX + _btnX3;
+ _vm->_screen->plotImage(icons, ICON3T + 10, Common::Point(ICON3X, _bIconStartY));
+ }
+ }
+ }
+
+ _vm->_screen->restoreScreen();
+ _vm->_boxDataStart = _startItem;
+ _vm->_boxSelectYOld = -1;
+ _vm->_boxSelectY = _startBox;
+
+ _vm->_numLines = (_bounds.bottom >> 3) - 2;
+ if (_type == TYPE_3)
+ --_vm->_numLines;
+
+ _vm->_events->showCursor();
+ displayBoxData();
+ drawSelectBox();
+
+ while (!_vm->shouldQuit()) {
+ _vm->_events->pollEvents();
+ if (!_vm->_events->_leftButton)
+ continue;
+
+ if (((_type == TYPE_1) || (_type != TYPE_3)) && (_vm->_timers[2]._flag == 0)) {
+ ++_vm->_timers[2]._flag;
+ if (_btnUpPos.contains(_vm->_events->_mousePos)) {
+ if (_vm->_bcnt) {
+ if (_vm->_boxSelectY != 0) {
+ --_vm->_boxSelectY;
+ drawSelectBox();
+ } else if (_vm->_boxDataStart != 0) {
+ --_vm->_boxDataStart;
+ displayBoxData();
+ drawSelectBox();
+ }
+ }
+ continue;
+ } else if (_btnDownPos.contains(_vm->_events->_mousePos)) {
+ if (_vm->_bcnt) {
+ if (_vm->_bcnt == _vm->_numLines) {
+ if (_vm->_bcnt != _vm->_boxSelectY + 1) {
+ ++_vm->_boxSelectY;
+ drawSelectBox();
+ } else if (!_vm->_boxDataEnd) {
+ ++_vm->_boxDataStart;
+ displayBoxData();
+ drawSelectBox();
+ }
+ } else if (_vm->_bcnt != _vm->_boxSelectY + 1) {
+ ++_vm->_boxSelectY;
+ drawSelectBox();
+ }
+ }
+ continue;
+ }
+ }
+
+ if ((_vm->_events->_mousePos.x >= _boxStartX) && (_vm->_events->_mousePos.x <= _boxEndX)
+ && (_vm->_events->_mousePos.y >= _boxStartY) && (_vm->_events->_mousePos.y <= _boxEndY)) {
+ int val = (_vm->_events->_mousePos.x >> 3) - _boxPStartY;
+ if (val > _vm->_bcnt)
+ continue;
+ --val;
+ if (_type == TYPE_3)
+ _vm->_boxSelect = val;
+ else {
+ btnSelected = 1;
+ if (_vm->_boxSelectY == val)
+ break;
+ _vm->_boxSelectY = val;
+ _vm->_events->debounceLeft();
+ drawSelectBox();
+ continue;
+ }
+ }
+
+ if ((_vm->_events->_mousePos.y >= ICON1Y) && (_vm->_events->_mousePos.y <= ICON1Y + 8)
+ && (_vm->_events->_mousePos.x >= ICON1X)) {
+ btnSelected = 1;
+ if (_vm->_events->_mousePos.x < ICON1X + ICONW[ICON1T])
+ break;
+
+ if ((_vm->_events->_mousePos.x >= ICON2X) && (_vm->_events->_mousePos.x < ICON2X + ICONW[ICON2T])) {
+ btnSelected = 2;
+ break;
+ }
+
+ if ((_vm->_events->_mousePos.x >= ICON3X) && (_vm->_events->_mousePos.x < ICON3X + ICONW[ICON3T])) {
+ btnSelected = 3;
+ break;
+ }
+
+ if (_type != TYPE_3)
+ continue;
+
+ if ((_vm->_events->_mousePos.x < tmpX) || (_vm->_events->_mousePos.x > tmpX + 144))
+ continue;
+
+ if ((_vm->_events->_mousePos.y < tmpY) || (_vm->_events->_mousePos.y > tmpY + 8))
+ continue;
+
+ warning("TODO: sub175B5 - List of files");
+ }
+ }
+
+ _vm->_events->hideCursor();
+ _vm->_screen->restoreBlock();
+ _vm->_events->showCursor();
+ _vm->_events->debounceLeft();
+ if (_vm->_bcnt == 0)
+ retval_ = -1;
+ else
+ retval_ = _vm->_boxDataStart + _vm->_boxSelectY;
+ return retval_;
+}
+
+void BubbleBox::getList(const char *const data[], int *flags) {
+ int srcIdx = 0;
+ int destIdx = 0;
+ while (data[srcIdx]) {
+ if (flags[srcIdx]) {
+ _tempList[destIdx] = Common::String(data[srcIdx]);
+ _tempListIdx[destIdx] = srcIdx;
+ ++destIdx;
+ }
+ srcIdx++;
+ }
+ _tempList[destIdx] = "";
+}
} // End of namespace Access
diff --git a/engines/access/bubble_box.h b/engines/access/bubble_box.h
index 0b3f139520..9a45721108 100644
--- a/engines/access/bubble_box.h
+++ b/engines/access/bubble_box.h
@@ -36,23 +36,46 @@ namespace Access {
class AccessEngine;
-enum BoxType { TYPE_2 = 2, TYPE_4 = 4 };
+enum BoxType { TYPE_0 = 0, TYPE_1 = 1, TYPE_2 = 2, TYPE_3 = 3, kBoxTypeFileDialog = 4 };
class BubbleBox : public Manager {
private:
int _startItem, _startBox;
int _charCol, _rowOff;
Common::Point _fileStart;
+ Common::Point _fileOff;
+ int _boxStartX, _boxStartY;
+ int _boxEndX, _boxEndY;
+ int _bIconStartX, _bIconStartY;
+ int _boxPStartX, _boxPStartY;
+
+ void displayBoxData();
+ void drawSelectBox();
+ /**
+ * Prints a text bubble and it's contents
+ */
+ void printBubble_v1(const Common::String &msg);
+ void printBubble_v2(const Common::String &msg);
+
public:
BoxType _type;
Common::Rect _bounds;
Common::StringArray _nameIndex;
Common::String _bubbleTitle;
Common::String _bubbleDisplStr;
-
+ Common::String _tempList[60];
+ int _tempListIdx[60];
+ int _btnId1;
+ int _btnX1;
+ int _btnId2;
+ int _btnX2;
+ int _btnId3;
+ int _btnX3;
+ Common::Rect _btnUpPos;
+ Common::Rect _btnDownPos;
Common::Array<Common::Rect> _bubbles;
public:
- BubbleBox(AccessEngine *vm);
+ BubbleBox(AccessEngine *vm, Access::BoxType type, int x, int y, int w, int h, int val1, int val2, int val3, int val4, Common::String title);
void load(Common::SeekableReadStream *stream);
@@ -78,6 +101,11 @@ public:
void drawBubble(int index);
void doBox(int item, int box);
+
+ int doBox_v1(int item, int box, int &btnSelected);
+ void getList(const char *const data[], int *flags);
+ void setCursorPos(int posX, int posY);
+ void printString(Common::String msg);
};
} // End of namespace Access
diff --git a/engines/access/char.cpp b/engines/access/char.cpp
index b359bcf13a..aca7262952 100644
--- a/engines/access/char.cpp
+++ b/engines/access/char.cpp
@@ -27,15 +27,25 @@
namespace Access {
-CharEntry::CharEntry(const byte *data) {
+CharEntry::CharEntry(const byte *data, AccessEngine *vm) {
Common::MemoryReadStream s(data, 999);
_charFlag = s.readByte();
- _estabIndex = s.readSint16LE();
- _screenFile.load(s);
+ if (vm->getGameID() == GType_MartianMemorandum) {
+ _screenFile.load(s);
+ _estabIndex = s.readSint16LE();
+ } else {
+ _estabIndex = s.readSint16LE();
+ _screenFile.load(s);
+ }
+
_paletteFile.load(s);
_startColor = s.readUint16LE();
- _numColors = s.readUint16LE();
+ if (vm->getGameID() == GType_MartianMemorandum) {
+ int lastColor = s.readUint16LE();
+ _numColors = lastColor - _startColor;
+ } else
+ _numColors = s.readUint16LE();
// Load cells
for (byte cell = s.readByte(); cell != 0xff; cell = s.readByte()) {
@@ -73,12 +83,18 @@ CharManager::CharManager(AccessEngine *vm) : Manager(vm) {
// Setup character list
if (_vm->isDemo()) {
for (int i = 0; i < 27; ++i)
- _charTable.push_back(CharEntry(Amazon::CHARTBL_DEMO[i]));
+ _charTable.push_back(CharEntry(Amazon::CHARTBL_DEMO[i], vm));
} else {
for (int i = 0; i < 37; ++i)
- _charTable.push_back(CharEntry(Amazon::CHARTBL[i]));
+ _charTable.push_back(CharEntry(Amazon::CHARTBL[i], vm));
}
break;
+
+ case GType_MartianMemorandum:
+ for (int i = 0; i < 27; ++i)
+ _charTable.push_back(CharEntry(Martian::CHARTBL_MM[i], vm));
+ break;
+
default:
error("Unknown game");
}
@@ -152,8 +168,14 @@ void CharManager::charMenu() {
screen.saveScreen();
screen.setDisplayScan();
- screen.plotImage(spr, 17, Common::Point(0, 176));
- screen.plotImage(spr, 18, Common::Point(155, 176));
+ if (_vm->getGameID() == GType_MartianMemorandum) {
+ screen.plotImage(spr, 17, Common::Point(0, 184));
+ screen.plotImage(spr, 18, Common::Point(193, 184));
+ } else if (_vm->getGameID() == GType_Amazon) {
+ screen.plotImage(spr, 17, Common::Point(0, 176));
+ screen.plotImage(spr, 18, Common::Point(155, 176));
+ } else
+ error("Game not supported");
screen.restoreScreen();
delete spr;
diff --git a/engines/access/char.h b/engines/access/char.h
index 6fb4934978..f2828e9779 100644
--- a/engines/access/char.h
+++ b/engines/access/char.h
@@ -41,7 +41,7 @@ public:
FileIdent _scriptFile;
Common::Array<ExtraCell> _extraCells;
public:
- CharEntry(const byte *data);
+ CharEntry(const byte *data, AccessEngine *vm);
CharEntry();
};
diff --git a/engines/access/decompress.cpp b/engines/access/decompress.cpp
index c5656afa51..3de376c193 100644
--- a/engines/access/decompress.cpp
+++ b/engines/access/decompress.cpp
@@ -46,7 +46,7 @@ void LzwDecompressor::decompress(byte *source, byte *dest) {
maxCodeValue = 512;
copyLength = 0;
- _bitPos = 0;
+ _sourceBitsLeft = 8;
while (1) {
@@ -97,17 +97,39 @@ uint16 LzwDecompressor::getCode() {
const byte bitMasks[9] = {
0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0x0FF
};
- uint16 bits, loCode, hiCode;
- loCode = (READ_LE_UINT16(_source) >> _bitPos) & 0xFF;
- _source++;
- bits = _codeLength - 8;
- hiCode = (READ_LE_UINT16(_source) >> _bitPos) & bitMasks[bits];
- _bitPos += bits;
- if (_bitPos > 8) {
- _source++;
- _bitPos -= 8;
+
+ byte resultBitsLeft = _codeLength;
+ byte resultBitsPos = 0;
+ uint16 result = 0;
+ byte currentByte = *_source;
+ byte currentBits = 0;
+
+ // Get bits of current byte
+ while (resultBitsLeft) {
+ if (resultBitsLeft < _sourceBitsLeft) {
+ // we need less than we have left
+ currentBits = (currentByte >> (8 - _sourceBitsLeft)) & bitMasks[resultBitsLeft];
+ result |= (currentBits << resultBitsPos);
+ _sourceBitsLeft -= resultBitsLeft;
+ resultBitsLeft = 0;
+
+ } else {
+ // we need as much as we have left or more
+ resultBitsLeft -= _sourceBitsLeft;
+ currentBits = currentByte >> (8 - _sourceBitsLeft);
+ result |= (currentBits << resultBitsPos);
+ resultBitsPos += _sourceBitsLeft;
+
+ // Go to next byte
+ _source++;
+
+ _sourceBitsLeft = 8;
+ if (resultBitsLeft) {
+ currentByte = *_source;
+ }
+ }
}
- return (hiCode << 8) | loCode;
+ return result;
}
uint32 decompressDBE(byte *source, byte **dest) {
diff --git a/engines/access/decompress.h b/engines/access/decompress.h
index eea450086b..bea9a1d3f8 100644
--- a/engines/access/decompress.h
+++ b/engines/access/decompress.h
@@ -32,7 +32,8 @@ public:
void decompress(byte *source, byte *dest);
private:
byte *_source;
- byte _codeLength, _bitPos;
+ byte _sourceBitsLeft;
+ byte _codeLength;
uint16 getCode();
};
diff --git a/engines/access/detection_tables.h b/engines/access/detection_tables.h
index 88a64470c5..124f5fcf0d 100644
--- a/engines/access/detection_tables.h
+++ b/engines/access/detection_tables.h
@@ -75,7 +75,22 @@ static const AccessGameDescription gameDescriptions[] = {
{
"martian",
nullptr,
- AD_ENTRY1s("r00.ap", "af98db5ee7f9ef86c6b1f43187a3691b", 31),
+ AD_ENTRY1s("r01.ap", "c081daca9b0cfd710157cf946e343df6", 39352),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ GType_MartianMemorandum,
+ 0
+ },
+
+ {
+ // Martian Memorandum
+ {
+ "martian",
+ "Demo",
+ AD_ENTRY1s("r01.rm", "c2facf9c43047211289044ee39a2322a", 2313),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
diff --git a/engines/access/inventory.cpp b/engines/access/inventory.cpp
index df499ba705..3823b17283 100644
--- a/engines/access/inventory.cpp
+++ b/engines/access/inventory.cpp
@@ -31,10 +31,17 @@ namespace Access {
void InventoryEntry::load(const Common::String &name, const int *data) {
_value = ITEM_NOT_FOUND;
_name = name;
- _otherItem1 = *data++;
- _newItem1 = *data++;
- _otherItem2 = *data++;
- _newItem2 = *data;
+ if (data) {
+ _otherItem1 = *data++;
+ _newItem1 = *data++;
+ _otherItem2 = *data++;
+ _newItem2 = *data;
+ } else {
+ _otherItem1 = -1;
+ _newItem1 = -1;
+ _otherItem2 = -1;
+ _newItem2 = -1;
+ }
}
int InventoryEntry::checkItem(int itemId) {
@@ -67,20 +74,20 @@ InventoryManager::InventoryManager(AccessEngine *vm) : Manager(vm) {
names = Amazon::INVENTORY_NAMES;
combineP = &Amazon::COMBO_TABLE[0][0];
_inv.resize(85);
+ for (uint i = 0; i < _inv.size(); ++i, combineP += 4)
+ _inv[i].load(names[i], combineP);
break;
case GType_MartianMemorandum:
names = Martian::INVENTORY_NAMES;
- combineP = &Martian::COMBO_TABLE[0][0];
- _inv.resize(54);
+ combineP = nullptr;
+ _inv.resize(55);
+ for (uint i = 0; i < _inv.size(); ++i)
+ _inv[i].load(names[i], nullptr);
break;
default:
error("Unknown game");
}
- for (uint i = 0; i < _inv.size(); ++i, combineP += 4) {
- _inv[i].load(names[i], combineP);
- }
-
for (uint i = 0; i < 26; ++i) {
const int *r = INVCOORDS[i];
_invCoords.push_back(Common::Rect(r[0], r[2], r[1], r[3]));
@@ -209,6 +216,31 @@ int InventoryManager::newDisplayInv() {
return result;
}
+int InventoryManager::displayInv() {
+ int *inv = (int *) malloc (Martian::INVENTORY_SIZE * sizeof(int));
+
+ for (int i = 0; i < Martian::INVENTORY_SIZE; i++)
+ inv[i] = _inv[i]._value;
+ _vm->_events->forceSetCursor(CURSOR_CROSSHAIRS);
+ _vm->_invBox->getList(Martian::INVENTORY_NAMES, inv);
+
+ int btnSelected = 0;
+ int boxX = _vm->_invBox->doBox_v1(_startInvItem, _startInvBox, btnSelected);
+ _startInvItem = _vm->_boxDataStart;
+ _startInvBox = _vm->_boxSelectY;
+
+ if (boxX == -1)
+ btnSelected = 2;
+
+ if (btnSelected != 2)
+ _vm->_useItem = _vm->_invBox->_tempListIdx[boxX];
+ else
+ _vm->_useItem = -1;
+
+ free(inv);
+ return 0;
+}
+
void InventoryManager::savedFields() {
Screen &screen = *_vm->_screen;
Room &room = *_vm->_room;
diff --git a/engines/access/inventory.h b/engines/access/inventory.h
index 6a9390eda9..1d88bf4555 100644
--- a/engines/access/inventory.h
+++ b/engines/access/inventory.h
@@ -128,6 +128,7 @@ public:
void refreshInventory();
int newDisplayInv();
+ int displayInv();
/**
* Synchronize savegame data
diff --git a/engines/access/martian/martian_game.cpp b/engines/access/martian/martian_game.cpp
index 4e4a5135a6..3fdba8d260 100644
--- a/engines/access/martian/martian_game.cpp
+++ b/engines/access/martian/martian_game.cpp
@@ -35,108 +35,256 @@ MartianEngine::MartianEngine(OSystem *syst, const AccessGameDescription *gameDes
}
MartianEngine::~MartianEngine() {
+ _introObjects = _spec7Objects = nullptr;
}
-void MartianEngine::playGame() {
- // Do introduction
- doIntroduction();
- if (shouldQuit())
- return;
+void MartianEngine::initObjects() {
+ _room = new MartianRoom(this);
+ _scripts = new MartianScripts(this);
+}
- // Setup the game
- setupGame();
+void MartianEngine::configSelect() {
+ // No implementation required in MM
+}
- _screen->clearScreen();
- _screen->setPanel(0);
- _screen->forceFadeOut();
+void MartianEngine::initVariables() {
+ warning("TODO: initVariables");
+
+ // Set player room and position
+ _player->_roomNumber = 7;
+
+ _inventory->_startInvItem = 0;
+ _inventory->_startInvBox = 0;
+ Common::fill(&_objectsTable[0], &_objectsTable[100], (SpriteResource *)nullptr);
+ _player->_playerOff = false;
+
+ // Setup timers
+ const int TIMER_DEFAULTS[] = { 4, 10, 8, 1, 1, 1, 1, 2 };
+ for (int i = 0; i < 32; ++i) {
+ TimerEntry te;
+ te._initTm = te._timer = (i < 8) ? TIMER_DEFAULTS[i] : 1;
+ te._flag = 1;
+
+ _timers.push_back(te);
+ }
+
+ _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0];
+ _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1];
+ _room->_selectCommand = -1;
+ _events->setNormalCursor(CURSOR_CROSSHAIRS);
+ _mouseMode = 0;
+ _numAnimTimers = 0;
+
+ for (int i = 0; i < 60; i++)
+ _travel[i] = 0;
+ _travel[7] = 1;
+
+ for (int i = 0; i < 40; i++)
+ _ask[i] = 0;
+ _ask[33] = 1;
+}
+
+void MartianEngine::setNoteParams() {
+ _events->hideCursor();
+
+ _screen->_orgX1 = 58;
+ _screen->_orgY1 = 124;
+ _screen->_orgX2 = 297;
+ _screen->_orgY2 = 199;
+ _screen->_lColor = 51;
+ _screen->drawRect();
_events->showCursor();
+}
- // Setup and execute the room
- _room = new MartianRoom(this);
- _scripts = new MartianScripts(this);
- _room->doRoom();
+void MartianEngine::displayNote(const Common::String &msg) {
+ _fonts._charSet._lo = 1;
+ _fonts._charSet._hi = 8;
+ _fonts._charFor._lo = 0;
+ _fonts._charFor._hi = 255;
+
+ _screen->_maxChars = 40;
+ _screen->_printOrg = _screen->_printStart = Common::Point(59, 124);
+
+ setNoteParams();
+
+ Common::String lines = msg;
+ Common::String line;
+ int width = 0;
+ bool lastLine = false;
+ do {
+ lastLine = _fonts._font1.getLine(lines, _screen->_maxChars * 6, line, width);
+ _bubbleBox->printString(line);
+ _screen->_printOrg = Common::Point(_screen->_printStart.x, _screen->_printOrg.y + 6);
+
+ if (_screen->_printOrg.y == 196) {
+ _events->waitKeyMouse();
+ setNoteParams();
+ _screen->_printOrg = _screen->_printStart;
+ }
+ } while (!lastLine);
+ _events->waitKeyMouse();
}
-void MartianEngine::doIntroduction() {
- _screen->setInitialPalettte();
- _events->setCursor(CURSOR_ARROW);
+void MartianEngine::doSpecial5(int param1) {
+ warning("TODO: Push midi song");
+ _midi->stopSong();
+ _midi->_byte1F781 = false;
+ _midi->loadMusic(47, 4);
+ _midi->midiPlay();
+ _screen->setDisplayScan();
+ _events->clearEvents();
+ _screen->forceFadeOut();
+ _events->hideCursor();
+ _files->loadScreen("DATA.SC");
_events->showCursor();
- _screen->setPanel(0);
+ _screen->setIconPalette();
+ _screen->forceFadeIn();
+
+ Resource *cellsRes = _files->loadFile("CELLS00.LZ");
+ _objectsTable[0] = new SpriteResource(this, cellsRes);
+ delete cellsRes;
+
+ _timers[20]._timer = _timers[20]._initTm = 30;
+ Resource *notesRes = _files->loadFile("NOTES.DAT");
+ notesRes->_stream->skip(param1 * 2);
+ int pos = notesRes->_stream->readUint16LE();
+ notesRes->_stream->seek(pos);
+ Common::String msg = "";
+ byte c;
+ while ((c = (char)notesRes->_stream->readByte()) != '\0')
+ msg += c;
+
+ displayNote(msg);
+
+ _midi->stopSong();
+ _midi->freeMusic();
+
+ warning("TODO: Pop Midi");
+ // _midi->_byte1F781 = true;
+}
+
+void MartianEngine::playGame() {
+ // Initialize Martian Memorandum game-specific objects
+ initObjects();
- // TODO: Worry about implementing full intro sequence later
- return;
+ // Setup the game
+ setupGame();
+ configSelect();
- doTitle();
- if (shouldQuit())
- return;
+ if (_loadSaveSlot == -1) {
+ // Do introduction
+ doCredits();
+ if (shouldQuit())
+ return;
- if (!_skipStart) {
- _screen->setPanel(3);
- doOpening();
+ // Display Notes screen
+ doSpecial5(4);
if (shouldQuit())
return;
+ _screen->forceFadeOut();
+ }
- if (!_skipStart) {
- //doTent();
- if (shouldQuit())
- return;
+ do {
+ _restartFl = false;
+ _screen->clearScreen();
+ _screen->setPanel(0);
+ _screen->forceFadeOut();
+ _events->showCursor();
+
+ initVariables();
+
+ // If there's a pending savegame to load, load it
+ if (_loadSaveSlot != -1) {
+ loadGameState(_loadSaveSlot);
+ _loadSaveSlot = -1;
}
- }
- doTitle();
+ // Execute the room
+ _room->doRoom();
+ } while (_restartFl);
}
-void MartianEngine::doTitle() {
- /*
- _screen->setDisplayScan();
- _destIn = &_buffer2;
-
- _screen->forceFadeOut();
+bool MartianEngine::showCredits() {
_events->hideCursor();
+ _screen->clearScreen();
+ _destIn = _screen;
- _sound->queueSound(0, 98, 30);
-
- _files->_setPaletteFlag = false;
- _files->loadScreen(0, 3);
-
- _buffer2.blitFrom(*_screen);
- _buffer1.blitFrom(*_screen);
- _screen->forceFadeIn();
- _sound->playSound(1);
+ int posX = _creditsStream->readSint16LE();
+ int posY = 0;
- Resource *spriteData = _files->loadFile(0, 2);
- _objectsTable[0] = new SpriteResource(this, spriteData);
- delete spriteData;
+ while(posX != -1) {
+ posY = _creditsStream->readSint16LE();
+ int frameNum = _creditsStream->readSint16LE();
+ _screen->plotImage(_introObjects, frameNum, Common::Point(posX, posY));
- _sound->playSound(1);
+ posX = _creditsStream->readSint16LE();
+ }
- _files->_setPaletteFlag = false;
- _files->loadScreen(0, 4);
- _sound->playSound(1);
+ posY = _creditsStream->readSint16LE();
+ if (posY == -1) {
+ _events->showCursor();
+ _screen->forceFadeOut();
+ return true;
+ }
- _buffer2.blitFrom(*_screen);
- _buffer1.blitFrom(*_screen);
- _sound->playSound(1);
+ _screen->forceFadeIn();
+ _timers[3]._timer = _timers[3]._initTm = posY;
- const int COUNTDOWN[6] = { 2, 0x80, 1, 0x7d, 0, 0x87 };
- for (_pCount = 0; _pCount < 3; ++_pCount) {
- _buffer2.blitFrom(_buffer1);
- int id = READ_LE_UINT16(COUNTDOWN + _pCount * 4);
- int xp = READ_LE_UINT16(COUNTDOWN + _pCount * 4 + 2);
- _screen->plotImage(_objectsTable[0], id, Common::Point(xp, 71));
+ while (!shouldQuit() && !_events->isKeyMousePressed() && _timers[3]._timer) {
+ _events->pollEventsAndWait();
}
- // TODO: More to do
- delete _objectsTable[0];
- */
+ _events->showCursor();
+ _screen->forceFadeOut();
+
+ if (_events->_rightButton)
+ return true;
+ else
+ return false;
}
-void MartianEngine::doOpening() {
- warning("TODO doOpening");
+void MartianEngine::doCredits() {
+ _midi->_byte1F781 = false;
+ _midi->loadMusic(47, 3);
+ _midi->midiPlay();
+ _screen->setDisplayScan();
+ _events->hideCursor();
+ _screen->forceFadeOut();
+ Resource *data = _files->loadFile(41, 1);
+ _introObjects = new SpriteResource(this, data);
+ delete data;
+
+ _files->loadScreen(41, 0);
+ _buffer2.copyFrom(*_screen);
+ _buffer1.copyFrom(*_screen);
+ _events->showCursor();
+ _creditsStream = new Common::MemoryReadStream(CREDIT_DATA, 180);
+
+ if (!showCredits()) {
+ _screen->copyFrom(_buffer2);
+ _screen->forceFadeIn();
+
+ _events->_vbCount = 550;
+ while (!shouldQuit() && !_events->isKeyMousePressed() && _events->_vbCount > 0)
+ _events->pollEventsAndWait();
+
+ _screen->forceFadeOut();
+ while (!shouldQuit() && !_events->isKeyMousePressed()&& !showCredits())
+ _events->pollEventsAndWait();
+
+ warning("TODO: Free word_21E2B");
+ _midi->freeMusic();
+ }
}
void MartianEngine::setupGame() {
+ // Load death list
+ _deaths.resize(20);
+ for (int i = 0; i < 20; ++i) {
+ _deaths[i]._screenId = Martian::DEATH_SCREENS[i];
+ _deaths[i]._msg = Martian::DEATHMESSAGE[i];
+ }
// Setup timers
const int TIMER_DEFAULTS[] = { 4, 10, 8, 1, 1, 1, 1, 2 };
@@ -155,12 +303,60 @@ void MartianEngine::setupGame() {
// Set player room and position
_player->_roomNumber = 7;
- _player->_playerX = _player->_rawPlayer.x = TRAVEL_POS[_player->_roomNumber][0];
- _player->_playerY = _player->_rawPlayer.y = TRAVEL_POS[_player->_roomNumber][1];
+ _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0];
+ _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1];
+}
+
+void MartianEngine::showDeathText(Common::String msg) {
+ Common::String line = "";
+ int width = 0;
+ bool lastLine;
+ do {
+ lastLine = _fonts._font2.getLine(msg, _screen->_maxChars * 6, line, width);
+ // Draw the text
+ _bubbleBox->printString(line);
+
+ _screen->_printOrg.y += 6;
+ _screen->_printOrg.x = _screen->_printStart.x;
+
+ if (_screen->_printOrg.y == 180) {
+ _events->waitKeyMouse();
+ _screen->copyBuffer(&_buffer2);
+ _screen->_printOrg.y = _screen->_printStart.y;
+ }
+ } while (!lastLine);
+ _events->waitKeyMouse();
}
-void MartianEngine::drawHelp() {
- error("TODO: drawHelp");
+void MartianEngine::dead(int deathId) {
+ // Load and display death screen
+ _events->hideCursor();
+ _screen->forceFadeOut();
+ _files->loadScreen(48, _deaths[deathId]._screenId);
+ _screen->setIconPalette();
+ _buffer2.copyBuffer(_screen);
+ _screen->forceFadeIn();
+ _events->showCursor();
+
+ // Setup fonts
+ _fonts._charSet._hi = 10;
+ _fonts._charSet._lo = 1;
+ _fonts._charFor._lo = 247;
+ _fonts._charFor._hi = 255;
+ _screen->_maxChars = 50;
+ _screen->_printOrg = Common::Point(24, 18);
+ _screen->_printStart = Common::Point(24, 18);
+
+ // Display death message
+ showDeathText(_deaths[deathId]._msg);
+
+ _screen->forceFadeOut();
+ _room->clearRoom();
+ freeChar();
+
+ // The original was jumping to the restart label in main
+ _restartFl = true;
+ _events->pollEvents();
}
} // End of namespace Martian
diff --git a/engines/access/martian/martian_game.h b/engines/access/martian/martian_game.h
index a83b67a288..9ef6c05c29 100644
--- a/engines/access/martian/martian_game.h
+++ b/engines/access/martian/martian_game.h
@@ -32,40 +32,41 @@ namespace Martian {
class MartianEngine : public AccessEngine {
private:
bool _skipStart;
-
+ SpriteResource *_introObjects;
+ Common::MemoryReadStream *_creditsStream;
/**
* Do the game introduction
*/
- void doIntroduction();
+ void doCredits();
- /**
- * Do title sequence
- */
- void doTitle();
-
- /**
- * Do opening sequence
- */
- void doOpening();
+ bool showCredits();
/**
* Setup variables for the game
*/
void setupGame();
+ void initObjects();
+ void configSelect();
+ void initVariables();
protected:
/**
* Play the game
*/
virtual void playGame();
- virtual void dead(int deathId) {}
+ virtual void dead(int deathId);
+
+ void setNoteParams();
+ void displayNote(const Common::String &msg);
public:
- MartianEngine(OSystem *syst, const AccessGameDescription *gameDesc);
+ SpriteResource *_spec7Objects;
+ MartianEngine(OSystem *syst, const AccessGameDescription *gameDesc);
virtual ~MartianEngine();
- void drawHelp();
+ void doSpecial5(int param1);
+ void showDeathText(Common::String msg);
virtual void establish(int esatabIndex, int sub) {};
};
diff --git a/engines/access/martian/martian_player.cpp b/engines/access/martian/martian_player.cpp
new file mode 100644
index 0000000000..598664a600
--- /dev/null
+++ b/engines/access/martian/martian_player.cpp
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "access/access.h"
+#include "access/room.h"
+#include "access/martian/martian_game.h"
+#include "access/martian/martian_player.h"
+#include "access/martian/martian_resources.h"
+
+namespace Access {
+
+namespace Martian {
+
+MartianPlayer::MartianPlayer(AccessEngine *vm) : Player(vm) {
+ _game = (MartianEngine *)vm;
+}
+
+void MartianPlayer::load() {
+ Player::load();
+
+ // Overwrite game-specific values
+ _playerOffset.x = _vm->_screen->_scaleTable1[20];
+ _playerOffset.y = _vm->_screen->_scaleTable1[52];
+ _leftDelta = -9;
+ _rightDelta = 33;
+ _upDelta = 5;
+ _downDelta = -5;
+ _scrollConst = 5;
+
+ for (int i = 0; i < _vm->_playerDataCount; ++i) {
+ _walkOffRight[i] = SIDEOFFR[i];
+ _walkOffLeft[i] = SIDEOFFL[i];
+ _walkOffUp[i] = SIDEOFFU[i];
+ _walkOffDown[i] = SIDEOFFD[i];
+ }
+
+ _sideWalkMin = 0;
+ _sideWalkMax = 7;
+ _upWalkMin = 8;
+ _upWalkMax = 14;
+ _downWalkMin = 15;
+ _downWalkMax = 23;
+}
+
+} // End of namespace Martian
+
+} // End of namespace Access
diff --git a/engines/access/martian/martian_player.h b/engines/access/martian/martian_player.h
new file mode 100644
index 0000000000..007a32e9b2
--- /dev/null
+++ b/engines/access/martian/martian_player.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ACCESS_MARTIAN_PLAYER_H
+#define ACCESS_MARTIAN_PLAYER_H
+
+#include "common/scummsys.h"
+#include "access/player.h"
+
+namespace Access {
+
+namespace Martian {
+
+class MartianEngine;
+
+class MartianPlayer : public Player {
+private:
+ MartianEngine *_game;
+public:
+ MartianPlayer(AccessEngine *vm);
+ virtual void load();
+};
+
+} // End of namespace Martian
+
+} // End of namespace Access
+
+#endif /* ACCESS_MARTIAN_PLAYER_H */
diff --git a/engines/access/martian/martian_resources.cpp b/engines/access/martian/martian_resources.cpp
index d2b5dfd5d0..474ec2f71c 100644
--- a/engines/access/martian/martian_resources.cpp
+++ b/engines/access/martian/martian_resources.cpp
@@ -41,51 +41,100 @@ const char *const FILENAMES[] = {
};
const byte MOUSE0[] = {
- 0, 0, 0, 0, 0, 2, 0xF7, 5, 0, 3, 0xF7, 0xF7, 5, 0, 3,
- 0xF7, 0xF7, 5, 0, 4, 0xF7, 0xF7, 0xF7, 5, 0, 4, 0xF7,
- 0xF7, 0xF7, 5, 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 5,
- 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 6, 0xF7, 0xF7, 0xF7, 0xF7,
- 0xF7, 5, 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 7,
- 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 6, 0xF7, 0xF7,
- 0xF7, 0xF7, 0xF7, 5, 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5,
- 2, 3, 0xF7, 0xF7, 5, 3, 3, 0xF7, 0xF7, 5, 3, 3, 0xF7,
- 0xF7, 5, 4, 2, 0xF7, 5
+ // hotspot x and y, uint16 LE
+ 0, 0, 0, 0,
+ // byte 1: number of skipped pixels
+ // byte 2: number of plotted pixels
+ // then, pixels
+ 0, 2, 0xF7, 5,
+ 0, 3, 0xF7, 0xF7, 5,
+ 0, 3, 0xF7, 0xF7, 5,
+ 0, 4, 0xF7, 0xF7, 0xF7, 5,
+ 0, 4, 0xF7, 0xF7, 0xF7, 5,
+ 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5,
+ 2, 3, 0xF7, 0xF7, 5,
+ 3, 3, 0xF7, 0xF7, 5,
+ 3, 3, 0xF7, 0xF7, 5,
+ 4, 2, 0xF7, 5
};
const byte MOUSE1[] = {
- 7, 0, 7, 0, 6, 1, 0xF7, 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF,
- 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, 2, 9, 0xFF, 0, 0, 0,
- 0xF7, 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0,
- 0, 0, 0xFF, 0, 13, 0xF7, 0, 0, 0xF7, 0, 0xF7, 0, 0xF7,
- 0, 0xF7, 0, 0, 0xF7, 1, 11, 0xFF, 0, 0, 0, 0, 0xF7,
- 0, 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0xFF, 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF,
- 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, 4, 5, 0xFF, 0xFF, 0,
- 0xFF, 0xFF, 6, 1, 0xF7, 0, 0, 0, 0, 0, 0
+ // hotspot x and y, uint16 LE
+ 7, 0, 7, 0,
+ // byte 1: number of skipped pixels
+ // byte 2: number of plotted pixels
+ // then, pixels
+ 6, 1, 0xF7,
+ 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF,
+ 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF,
+ 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF,
+ 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF,
+ 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0, 0, 0, 0xFF,
+ 0, 13, 0xF7, 0, 0, 0xF7, 0, 0xF7, 0, 0xF7, 0, 0xF7, 0, 0, 0xF7,
+ 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0, 0, 0, 0xFF,
+ 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF,
+ 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF,
+ 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF,
+ 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF,
+ 6, 1, 0xF7,
+ 0, 0,
+ 0, 0,
+ 0, 0
};
const byte MOUSE2[] = {
- 8, 0, 8, 0, 0, 0, 0, 0, 7, 2, 4, 5, 7, 2, 4, 5, 7, 2,
- 4, 5, 7, 2, 4, 5, 7, 2, 4, 5, 2, 12, 4, 4, 4, 4, 4,
- 0, 4, 4, 4, 4, 4, 5, 7, 2, 4, 5, 7, 2, 4, 5, 7, 2, 4,
- 5, 7, 2, 4, 5, 7, 2, 4, 5, 0, 0, 0, 0, 0, 0
+ // hotspot x and y, uint16 LE
+ 8, 0, 8, 0,
+ // byte 1: number of skipped pixels
+ // byte 2: number of plotted pixels
+ // then, pixels
+ 0, 0,
+ 0, 0,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 2, 12, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 7, 2, 4, 5,
+ 0, 0,
+ 0, 0,
+ 0, 0
};
const byte MOUSE3[] = {
- 0, 0, 0, 0, 0, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
- 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5, 0, 12,
- 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 5, 5, 0, 12, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 5,
- 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 5, 0, 0, 6, 6,
- 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 5,
- 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12,
- 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6,
- 6, 6, 5, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 5,
- 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 5, 1, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0,
- 0, 0, 0, 0, 0
+ // hotspot x and y, uint16 LE
+ 0, 0, 0, 0,
+ // byte 1: number of skipped pixels
+ // byte 2: number of plotted pixels
+ // then, pixels
+ 0, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5,
+ 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 5, 5,
+ 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 5, 0, 0, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5,
+ 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5,
+ 1, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 0, 0,
+ 0, 0,
+ 0, 0
};
const byte *const CURSORS[4] = { MOUSE0, MOUSE1, MOUSE2, MOUSE3 };
-const int TRAVEL_POS[][2] = {
+const int _travelPos[][2] = {
{ -1, 0 },
{ 228, 117 },
{ 28, 98 },
@@ -139,17 +188,19 @@ const int TRAVEL_POS[][2] = {
{ -1, 21 }
};
+const int INVENTORY_SIZE = 55;
const char *const INVENTORY_NAMES[] = {
- "CAMERA", "LENS", "PHOTOS", "MAIL", "GUN", "CASH", "COMLINK", "AMMO",
- "LOCKPICK KIT", "EARRING", "RECIEPTS", "PAPER", "LADDER", "BOOTS",
- "DOCUMENTS", "KNIFE", "DAGGER", "KEYS", "ROCK", "LOG", "SHOVEL",
- "STONE", "REMOTE CONTROL", "FOOD AND WATER", "DOOR CARD KEY",
+ "CAMERA", "LENS", "PHOTOS", "MAIL", "GUN",
+ "CASH", "COMLINK", "AMMO", "LOCKPICK KIT", "EARRING",
+ "RECIEPTS", "PAPER", "LADDER", "BOOTS", "DOCUMENTS",
+ "KNIFE", "DAGGER", "KEYS", "ROCK", "LOG",
+ "SHOVEL", "STONE", "REMOTE CONTROL", "FOOD AND WATER", "DOOR CARD KEY",
"FLASHLIGHT", "INTERLOCK KEY", "TOOLS", "REBREATHER", "JET PACK",
- "ROD", "HCL2", "SAFE CARD KEY", "TUNING FORK", "STONE", "ROSE",
- "KEY", "NOTE", "ALLEN WRENCH", "HOVER BOARD", "BLUE PRINTS",
- "LETTER", "MEMORANDUM", "MARKERS", "FILM", "ANDRETTI FILM",
- "GLASSES", "AMULET", "FACIAL KIT", "CAT FOOD", "MONKEY WRENCH",
- "BIG DICK CARD", "BRA", "BOLT"
+ "ROD", "HCL2", "SAFE CARD KEY", "TUNING FORK", "STONE",
+ "ROSE", "KEY", "NOTE", "ALLEN WRENCH", "HOVER BOARD",
+ "BLUE PRINTS", "LETTER", "MEMORANDUM", "MARKERS", "FILM",
+ "ANDRETTI FILM", "GLASSES", "AMULET", "FACIAL KIT", "CAT FOOD",
+ "MONKEY WRENCH", "BIG DICK CARD", "BRA", "BOLT", nullptr
};
const byte ROOM_TABLE1[] = {
@@ -436,12 +487,12 @@ const char *const ROOM_DESCR[] = {
const int ROOM_NUMB = 48;
-const byte CHAR_TABLE0[] = {
+const byte MMCHAR_0[] = {
0x02, 0x31, 0x00, 0x08, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE2[] = {
+const byte MMCHAR_2[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x32, 0x33, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x00, 0x00, 0x00, 0x33,
@@ -454,13 +505,13 @@ const byte CHAR_TABLE2[] = {
0x00, 0x12, 0x00, 0x33, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x13,
0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE3[] = {
+const byte MMCHAR_3[] = {
0x02, 0x31, 0x00, 0x03, 0x00, 0x35, 0x00, 0x37, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x4b, 0x37, 0x00, 0x01, 0x00,
0xff, 0x37, 0x00, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE4[] = {
+const byte MMCHAR_4[] = {
0x01, 0x31, 0x00, 0x0a, 0x00, 0x36, 0x00, 0x35, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x49, 0x35, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x35, 0x00, 0x00, 0x00, 0x35,
@@ -473,7 +524,7 @@ const byte CHAR_TABLE4[] = {
0x00, 0x13, 0x00, 0x35, 0x00, 0x0b, 0x00, 0x35, 0x00, 0x14,
0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE5[] = {
+const byte MMCHAR_5[] = {
0x01, 0x31, 0x00, 0x08, 0x00, 0x37, 0x00, 0x34, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x48, 0x34, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, 0x43,
@@ -490,31 +541,31 @@ const byte CHAR_TABLE5[] = {
0x00, 0x0f, 0x00, 0x43, 0x00, 0x0d, 0x00, 0x34, 0x00, 0x10,
0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE6[] = {
+const byte MMCHAR_6[] = {
0x02, 0x31, 0x00, 0x03, 0x00, 0x38, 0x00, 0x44, 0x00, 0x03,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x4e, 0x44, 0x00, 0x01, 0x00,
0xff, 0x44, 0x00, 0x02, 0x00, 0x44, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE7[] = {
+const byte MMCHAR_7[] = {
0x02, 0x31, 0x00, 0x01, 0x00, 0x39, 0x00, 0x38, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x4c, 0x38, 0x00, 0x01, 0x00,
0xff, 0x38, 0x00, 0x03, 0x00, 0x38, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE8[] = {
+const byte MMCHAR_8[] = {
0x03, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x3b, 0x00, 0x01, 0x00,
0xff, 0x3b, 0x00, 0x02, 0x00, 0x3b, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE9[] = {
+const byte MMCHAR_9[] = {
0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x59, 0x4a, 0x00, 0x01, 0x00,
0xff, 0x4a, 0x00, 0x02, 0x00, 0x4a, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE10[] = {
+const byte MMCHAR_10[] = {
0x01, 0x31, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x36, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x4a, 0x36, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x36, 0x00, 0x00, 0x00, 0x36,
@@ -532,19 +583,19 @@ const byte CHAR_TABLE10[] = {
0x00, 0x36, 0x00, 0x11, 0x00, 0x36, 0x00, 0x21, 0x00, 0x36,
0x00, 0x12, 0x00, 0x36, 0x00, 0x22, 0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE11[] = {
+const byte MMCHAR_11[] = {
0x03, 0xff, 0xff, 0xff, 0xff, 0x3d, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x55, 0x45, 0x00, 0x01, 0x00,
0xff, 0x45, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE12[] = {
+const byte MMCHAR_12[] = {
0x03, 0xff, 0xff, 0xff, 0xff, 0x3e, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x40, 0x00, 0x01, 0x00,
0xff, 0x40, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE13[] = {
+const byte MMCHAR_13[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x46, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x56, 0x46, 0x00, 0x01, 0x00,
0xff, 0x46, 0x00, 0x03, 0x00, 0x46, 0x00, 0x00, 0x00, 0x46,
@@ -557,7 +608,7 @@ const byte CHAR_TABLE13[] = {
0x00, 0x14, 0x00, 0x46, 0x00, 0x0c, 0x00, 0x46, 0x00, 0x15,
0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE15[] = {
+const byte MMCHAR_15[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0x41, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x57, 0x47, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x00, 0x00, 0x00, 0x47,
@@ -565,43 +616,43 @@ const byte CHAR_TABLE15[] = {
0x00, 0x47, 0x00, 0x06, 0x00, 0x47, 0x00, 0x04, 0x00, 0x47,
0x00, 0x07, 0x00, 0xff, 0xff,
};
-const byte CHAR_TABLE16[] = {
+const byte MMCHAR_16[] = {
0x03, 0xff, 0xff, 0xff, 0xff, 0x42, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x54, 0x41, 0x00, 0x01, 0x00,
0xff, 0x41, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE18[] = {
+const byte MMCHAR_18[] = {
0x02, 0x31, 0x00, 0x07, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x03,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x50, 0x3c, 0x00, 0x01, 0x00,
0xff, 0x3c, 0x00, 0x02, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE19[] = {
+const byte MMCHAR_19[] = {
0x02, 0x31, 0x00, 0x07, 0x00, 0x45, 0x00, 0x3d, 0x00, 0x03,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x51, 0x3d, 0x00, 0x01, 0x00,
0xff, 0x3d, 0x00, 0x02, 0x00, 0x3d, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE20[] = {
+const byte MMCHAR_20[] = {
0x02, 0x31, 0x00, 0x02, 0x00, 0x46, 0x00, 0x48, 0x00, 0x02,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x58, 0x48, 0x00, 0x01, 0x00,
0xff, 0x48, 0x00, 0x03, 0x00, 0x48, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE21[] = {
+const byte MMCHAR_21[] = {
0x02, 0x31, 0x00, 0x07, 0x00, 0x47, 0x00, 0x3e, 0x00, 0x03,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x52, 0x3e, 0x00, 0x01, 0x00,
0xff, 0x3e, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE23[] = {
+const byte MMCHAR_23[] = {
0x02, 0x31, 0x00, 0x08, 0x00, 0x49, 0x00, 0x3f, 0x00, 0x03,
0x00, 0x80, 0x00, 0xf7, 0x00, 0x53, 0x3f, 0x00, 0x01, 0x00,
0xff, 0x3f, 0x00, 0x02, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff,
0xff,
};
-const byte CHAR_TABLE24[] = {
+const byte MMCHAR_24[] = {
0x02, 0x32, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x47, 0x32, 0x00, 0x02, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x32, 0x00, 0x01, 0x00, 0x32,
@@ -612,13 +663,13 @@ const byte CHAR_TABLE24[] = {
0x00, 0x08, 0x00, 0x32, 0x00, 0x0f, 0x00, 0x32, 0x00, 0x09,
0x00, 0x32, 0x00, 0x10, 0x00, 0xff, 0xff
};
-const byte CHAR_TABLE25[] = {
+const byte MMCHAR_25[] = {
0x02, 0x39, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF
};
-const byte CHAR_TABLE26[] = {
+const byte MMCHAR_26[] = {
0x01, 0x3a, 0x00, 0x01, 0x00, 0x0a, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x3a, 0x00, 0x02, 0x00,
0xff, 0x3a, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x42,
@@ -638,7 +689,7 @@ const byte CHAR_TABLE26[] = {
0x00, 0x3a, 0x00, 0x14, 0x00, 0x42, 0x00, 0x11, 0x00, 0x3a,
0x00, 0x15, 0x00, 0xff, 0xff
};
-const byte CHAR_TABLE27[] = {
+const byte MMCHAR_27[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x58, 0x49, 0x00, 0x01, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0x49, 0x00, 0x00, 0x00, 0x49,
@@ -650,73 +701,144 @@ const byte CHAR_TABLE27[] = {
0x00, 0x49, 0x00, 0x10, 0x00, 0x49, 0x00, 0x09, 0x00, 0x49,
0x00, 0x11, 0x00, 0xff, 0xff,
};
-const byte *const CHAR_TABLE[] = {
- CHAR_TABLE0, nullptr, CHAR_TABLE2, CHAR_TABLE3, CHAR_TABLE4, CHAR_TABLE5,
- CHAR_TABLE6, CHAR_TABLE7, CHAR_TABLE8, CHAR_TABLE9, CHAR_TABLE10,
- CHAR_TABLE11, CHAR_TABLE12, CHAR_TABLE13, nullptr, CHAR_TABLE15,
- CHAR_TABLE16, nullptr, CHAR_TABLE18, CHAR_TABLE19, CHAR_TABLE20,
- CHAR_TABLE21, nullptr, CHAR_TABLE23, CHAR_TABLE24, CHAR_TABLE25,
- CHAR_TABLE26, CHAR_TABLE27
+
+// HACK: MMCHAR_0 has been used to replace the missing CHAR: 1, 14, 17 and 22
+const byte *const CHARTBL_MM[] = {
+ MMCHAR_0, MMCHAR_0, MMCHAR_2, MMCHAR_3, MMCHAR_4,
+ MMCHAR_5, MMCHAR_6, MMCHAR_7, MMCHAR_8, MMCHAR_9,
+ MMCHAR_10, MMCHAR_11, MMCHAR_12, MMCHAR_13, MMCHAR_0,
+ MMCHAR_15, MMCHAR_16, MMCHAR_0, MMCHAR_18, MMCHAR_19,
+ MMCHAR_20, MMCHAR_21, MMCHAR_0, MMCHAR_23, MMCHAR_24,
+ MMCHAR_25, MMCHAR_26, MMCHAR_27
};
-// TODO: Fix that array
-const int COMBO_TABLE[54][4] = {
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 },
- { -1, -1, -1, -1 }
+const int SIDEOFFR[] = { 4, 0, 7, 10, 3, 1, 2, 13, 0, 0, 0, 0 };
+const int SIDEOFFL[] = { 11, 6, 1, 4, 10, 6, 1, 4, 0, 0, 0, 0 };
+const int SIDEOFFU[] = { 1, 2, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0 };
+const int SIDEOFFD[] = { 2, 0, 1, 1, 0, 1, 1, 1, 2, 0, 0, 0 };
+
+const byte CREDIT_DATA[] = {
+ 0x1F, 0x00, 0x49, 0x00, 0x00, 0x00, 0xB7, 0x00, 0x49, 0x00,
+ 0x01, 0x00, 0x79, 0x00, 0x6F, 0x00, 0x02, 0x00, 0xFF, 0xFF,
+ 0xEA, 0x01, 0x75, 0x00, 0x46, 0x00, 0x03, 0x00, 0x46, 0x00,
+ 0x5E, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x72, 0x00,
+ 0x3E, 0x00, 0x05, 0x00, 0x46, 0x00, 0x57, 0x00, 0x04, 0x00,
+ 0x5C, 0x00, 0x6E, 0x00, 0x06, 0x00, 0xFF, 0xFF, 0xEA, 0x01,
+ 0x63, 0x00, 0x48, 0x00, 0x07, 0x00, 0x2A, 0x00, 0x65, 0x00,
+ 0x08, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x7E, 0x00, 0x39, 0x00,
+ 0x09, 0x00, 0x5C, 0x00, 0x57, 0x00, 0x06, 0x00, 0x45, 0x00,
+ 0x6B, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x5F, 0x00,
+ 0x46, 0x00, 0x0A, 0x00, 0x67, 0x00, 0x62, 0x00, 0x0B, 0x00,
+ 0x47, 0x00, 0x76, 0x00, 0x0C, 0x00, 0xFF, 0xFF, 0xEA, 0x01,
+ 0x62, 0x00, 0x38, 0x00, 0x0D, 0x00, 0x47, 0x00, 0x55, 0x00,
+ 0x0E, 0x00, 0x49, 0x00, 0x6A, 0x00, 0x0F, 0x00, 0xFF, 0xFF,
+ 0xEA, 0x01, 0x18, 0x00, 0x22, 0x00, 0x10, 0x00, 0x17, 0x00,
+ 0x3E, 0x00, 0x11, 0x00, 0x16, 0x00, 0x52, 0x00, 0x12, 0x00,
+ 0xEE, 0x00, 0x7B, 0x00, 0x13, 0x00, 0xB5, 0x00, 0x93, 0x00,
+ 0x0B, 0x00, 0xFF, 0xFF, 0xF4, 0x01, 0xFF, 0xFF, 0xFF, 0xFF
};
-} // End of namespace Martian
+const byte ICON_PALETTE[] = {
+ 0x3F, 0x3F, 0x00, 0x00, 0x07, 0x16,
+ 0x00, 0x0A, 0x1A, 0x00, 0x0D, 0x1F,
+ 0x00, 0x11, 0x28, 0x00, 0x15, 0x30,
+ 0x00, 0x19, 0x39, 0x00, 0x1B, 0x3F,
+ 0x00, 0x2D, 0x3A
+};
+
+const int RMOUSE[10][2] = {
+ { 7, 36 }, { 38, 68 }, { 70, 99 }, { 102, 125 }, { 128, 152 },
+ { 155, 185 }, { 188, 216 }, { 219, 260 }, { 263, 293 }, { 295, 314 }
+};
+
+const char *const TRAVDATA[] = {
+ "GALACTIC PICTURES", "TERRAFORM", "WASHROOM", "MR. BIG", "ALEXIS' HOME",
+ "JOHNNY FEDORA", "JUNKYARD IN", "TEX'S OFFICE", "MURDER SCENE", "PLAZA HOTEL",
+ "RESTAURANT", "GIFT SHOP", "LOVE SCENE", "RICK LOGAN", "HUT",
+ "SMUGGLERS BASE", "PYRAMID", "CASINO", "CAS LOBBY", "BAR",
+ "DUCTWORK", "RESTROOM", "OFFICE", "SAFE", "ALLEY",
+ "POWER PLANT", "PLANT OFFICE", "PLANT ROOM", "TEMPLE", "IN TEMPLE",
+ "JANE MANSFIELD'S HOME", "AEROBICS ACADEMY", "DR. LAWRENCE BARKLEY", "COLONISTS CAMP", "IN SLUM",
+ "REMOTE OUTPOST", "WALK", "CAVE", "PRISON", "ORACLE",
+ "JOCQUES SPARROW", "MAC MALDEN", "CHANTAL VARGAS", "GUY CALLABERO", "ROCKWELL BACHE",
+ "FERRIS COLLETTE", "NORA DESMOND ALEXANDER", "LOWELL PERCIVAL", "MICHELE BLOODWORTH", "BRADLEY ERICSON",
+ "COOPER BRADBURY", nullptr
+};
+
+const char *const _askTBL[] = {
+ "NONE", "MARSHALL ALEXANDER", "TERRAFORM CORP.", "COLLIER STANTON", "ROCKWELL BACHE",
+ "JOCQUES SPARROW", "NORA DESMOND ALEXANDER", "GALACTIC PICTURES", "LAWRENCE BARKLEY", "TMS",
+ "MAC MALDEN", "STANTON EXPEDITION", "LOWELL PERCIVAL", "CHANTAL VARGAS", "RICK LOGAN",
+ "ALEXIS ALEXANDER", "FERRIS COLLETT", "GUY CALLABERO", "ORACLE STONE", "THOMAS DANGERFIELD",
+ "JANE MANSFIELD", "STACY CRAWFORD", "DICK CASTRO", "ROCKY BULLWINKEL", "DEACON HAWKE",
+ "NATHAN BLOODWORTH", "MICHELLE BLOODWORTH", "BRADLEY ERICSON", "COOPER BRADBURY", "MARTIAN MEMORANDUM",
+ "JOHNNY FEDORA", "RHONDA FOXWORTH", "ANGELO ANDRETTI", "TEX MURPHY", "ROBERT BLOODWORTH",
+ "LARRRY HAMMOND", nullptr
+};
+
+byte HELP[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+const byte DEATH_SCREENS[] = {
+ 5, 5, 3, 3, 7, 4, 6, 2, 2, 2, 1, 5, 3, 5, 2, 8, 5, 3, 8, 5
+};
+
+const char *const DEATHMESSAGE[] = {
+ "A VICIOUS THUG PULLS OUT HIS GUN AND AIR CONDITIONS YOUR BRAIN.",
+ "BIG DICK COMES BACK AND ANNOUNCES YOUR TIME IS UP. ONE OF HIS BOYS PROCEEDS TO PART YOUR EYEBROWS.",
+ "ALTHOUGH HIS FIRST SHOT MISSED, THE PUNK FINDS YOU AND TURNS YOU INTO A DOUGHNUT.",
+ "THE CREEP SPOTS YOU. HE TURNS AND FIRES HIS WEAPON. IT BURNS A HOLE A BUZZARD CAN FLY THROUGH.",
+ "OBVIOUSLY RICK LOGAN HAS A FEW TRICK UP HIS SLEEVE. A TREMENDOUS WEIGHT HITS YOUR HEAD. YOU MUMBLE; WATCH OUT FOR THAT TREE...",
+ "SLOWLY SINKING IN THE SLIMY OOZE, YOU THINK OF SEVERAL JELLO WRESTLING MATCHES YOU'VE ATTENDED. BUT NO MORE...",
+ "THE PATH SUDDENLY GIVES WAY AND YOU FEEL MANY STAKES TEAR THROUGH YOUR FLESH. HOW DO YOU LIKE YOUR STAKE",
+ "THE SNAKE SINKS ITS FANGS INTO YOU LEG. THE POISON WORKS QUICKLY. THE SNAKE THEN SWALLOWS YOU WHOLE.",
+ "YOU FADE AWAY, GLOWING LIKE A LIGHTBULB.",
+ "YOU TOUCH THE BUBBLING RADIOACTIVE SELTZER. IT IMMEDIATELY CAUSES VITAL ORGANS TO ELONGATE AND EXPLODE. YOU DIE WITH AN ABSURD AND FOOLISH LOOK ON YOUR FACE.",
+ "THE DOGS PRETTY HUNGRY. IT WON'T TAKE HIM LONG TO FINISH SO SIT BACK AND ENJOY IT.",
+ "ROCKY DOESN'T LIKE BEING FOLLOWED. HE DECIDES TO BEAT YOU. WITHIN AND INCH OF YOUR LIFE. UNFORTUNATELY, HE MISJUDGED THE DISTANCE",
+ "YOU STUMBLE INTO DEADLY LASER FIRE.",
+ "THE OUTPOST AND YOUR BODY PARTS ARE BLOWN TO KINGDOM COME.",
+ "YOU REACH THE TOP, BUT YOUR AIR SOON RUNS OUT LEAVING YOU BREATHLESS.",
+ "YOU DIE IN THE FIERY EXPLOSION.",
+ "YOU FALL HUNDREDS OF FEET TO YOUR DEATH.",
+ "YOU WALK ONTO A PRESSURE SENSITIVE SECURITY PAD. A LASER ZEROS IN AND BLOWS A HOLE THE SIZE OF A SUBARU TIRE THROUGH YOU.",
+ "DANGERFIELD'S EXPERIMENT BACKFIRES. IT RELEASES A DEMON FROM HIS SUBCONSCIOUS WHICH DESTROYS THE ENTIRE PLANET.",
+ "ONCE DANGERFIELD GETS OUT OF HIS CHAMBER, HE PULLS OUT A WEAPON AND LETS YOU HAVE IT."
+};
+
+const char *const SPEC7MESSAGE = {
+ "THOMAS DANGERFIELD'S MAD EXPERIMENT OF ATTEMPTING TO HARNESS THE STONE'S POWER, ENDED HIS LIFE. DANGERFIELD WAS A DECENT HUMAN " \
+ "BEING ONCE, BUT WAS DRIVEN INSANE BY HIS QUEST FOR THE ULTIMATE POWER. ALEXIS AND I DECIDE THAT DEACON HAWKE IS THE ONLY " \
+ "LOGICAL CHOICE FOR THE STONE. WE ARRIVE AT THE TEMPLE WHERE SHE IS WAITING FOR US. SHE TURNS AND WHISPERS; 'YOU HAVE RETURNED " \
+ "THE STONE TO THE MISTRESS OF THE LIGHT. YOU HAVE SURELY SAVED THE WORLD FROM ANNIHILATION BUT YOU MUST CONTINUE TO BE DILIGENT. " \
+ "MANKIND MAY YET PROVE TO BE THE AUTHOR OF HIS OWN DEMISE. REVERENCE LIFE. PROTECT THE LIVING THINGS AND RECYCLE. AND NOW FOR " \
+ "THE SAFETY OF MANKIND, I MUST TAKE THE STONE. PERHAPS SOMEDAY, WHEN THE HUMAN RACE IS READY, IT WILL BE RETURNED. UNTIL THEN "\
+ "FAREWELL...'"
+};
+
+const byte _byte1EEB5[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 1, 0, 1, 1, 1,
+ 1
+};
+
+const int PICTURERANGE[][2] = {
+// { min X, max X}, {min Y, max Y}
+ { 20, 30 }, { 82, 87 },
+ { 20, 30 }, { 105, 110 },
+ { 0, 8 }, { 92, 100 },
+ { 42, 46 }, { 92, 100 },
+ { 9, 41 }, { 88, 104 },
+ { 9, 41 }, { 117, 133 },
+ { -1, -1 }
+};
+
+} // End of namespace Martian
} // End of namespace Access
diff --git a/engines/access/martian/martian_resources.h b/engines/access/martian/martian_resources.h
index a52967d42a..2eb810ac80 100644
--- a/engines/access/martian/martian_resources.h
+++ b/engines/access/martian/martian_resources.h
@@ -31,22 +31,46 @@ namespace Martian {
extern const char *const FILENAMES[];
+extern const int SIDEOFFR[];
+extern const int SIDEOFFL[];
+extern const int SIDEOFFU[];
+extern const int SIDEOFFD[];
+
extern const byte *const CURSORS[4];
-extern const int TRAVEL_POS[][2];
+extern const int _travelPos[][2];
+extern const int INVENTORY_SIZE;
extern const char *const INVENTORY_NAMES[];
extern const byte *const ROOM_TABLE[];
extern const char *const ROOM_DESCR[];
extern const int ROOM_NUMB;
-extern const byte *const CHAR_TABLE[];
+extern const byte *const CHARTBL_MM[];
-extern const int COMBO_TABLE[54][4];
+extern const int SIDEOFFR[];
+extern const int SIDEOFFL[];
+extern const int SIDEOFFU[];
+extern const int SIDEOFFD[];
-} // End of namespace Martian
+extern const byte CREDIT_DATA[];
+extern const byte ICON_PALETTE[];
+
+extern const int RMOUSE[10][2];
+
+extern byte HELP[];
+extern const char *const _askTBL[];
+extern const char *const TRAVDATA[];
+extern const byte DEATH_SCREENS[];
+extern const char *const DEATHMESSAGE[];
+extern const char *const SPEC7MESSAGE;
+
+extern const byte _byte1EEB5[];
+extern const int PICTURERANGE[][2];
+
+} // End of namespace Martian
} // End of namespace Access
#endif /* ACCESS_MARTIAN_RESOURCES_H */
diff --git a/engines/access/martian/martian_room.cpp b/engines/access/martian/martian_room.cpp
index e9d1b9d8cf..d5b03db246 100644
--- a/engines/access/martian/martian_room.cpp
+++ b/engines/access/martian/martian_room.cpp
@@ -43,72 +43,47 @@ void MartianRoom::loadRoom(int roomNumber) {
}
void MartianRoom::reloadRoom() {
- loadRoom(_vm->_player->_roomNumber);
-
- if (_roomFlag != 1) {
- _vm->_currentMan = _roomFlag;
- _vm->_currentManOld = _roomFlag;
- _vm->_manScaleOff = 0;
-
- switch (_vm->_currentMan) {
- case 0:
- _vm->_player->loadSprites("MAN.LZ");
- break;
-
- case 2:
- _vm->_player->loadSprites("JMAN.LZ");
- break;
+// _vm->_currentMan = _roomFlag;
+// _vm->_currentManOld = _roomFlag;
+// _vm->_manScaleOff = 0;
- case 3:
- _vm->_player->loadSprites("OVERHEAD.LZ");
- _vm->_manScaleOff = 1;
- break;
+ _vm->_player->loadTexPalette();
+ _vm->_player->loadSprites("TEX.LZ");
- default:
- break;
- }
- }
+ loadRoom(_vm->_player->_roomNumber);
reloadRoom1();
}
void MartianRoom::reloadRoom1() {
- if (_vm->_player->_roomNumber == 29 || _vm->_player->_roomNumber == 31
- || _vm->_player->_roomNumber == 42 || _vm->_player->_roomNumber == 44) {
- //Resource *spriteData = _vm->_files->loadFile("MAYA.LZ");
- //_vm->_inactive._spritesPtr = new SpriteResource(_vm, spriteData);
- //delete spriteData;
- _vm->_currentCharFlag = false;
- }
-
_selectCommand = -1;
- _vm->_events->setNormalCursor(CURSOR_CROSSHAIRS);
- _vm->_mouseMode = 0;
- _vm->_boxSelect = true;
+ _vm->_boxSelect = false; //-1
_vm->_player->_playerOff = false;
- _vm->_screen->fadeOut();
+ _vm->_screen->forceFadeOut();
+ _vm->_events->hideCursor();
_vm->_screen->clearScreen();
+ _vm->_events->showCursor();
roomSet();
+ _vm->_player->load();
- // TODO: Refactor
+ if (_vm->_player->_roomNumber != 47)
+ _vm->_player->calcManScale();
+ _vm->_events->hideCursor();
+ roomMenu();
_vm->_screen->setBufferScan();
setupRoom();
setWallCodes();
buildScreen();
+ _vm->copyBF2Vid();
- if (!_vm->_screen->_vesaMode) {
- _vm->copyBF2Vid();
- } else if (_vm->_player->_roomNumber != 20 && _vm->_player->_roomNumber != 24
- && _vm->_player->_roomNumber != 33) {
- _vm->_screen->setPalette();
- _vm->copyBF2Vid();
- }
-
+ _vm->_screen->setManPalette();
+ _vm->_events->showCursor();
_vm->_player->_frame = 0;
_vm->_oldRects.clear();
_vm->_newRects.clear();
+ _vm->_events->clearEvents();
}
void MartianRoom::roomSet() {
@@ -116,6 +91,12 @@ void MartianRoom::roomSet() {
_vm->_scripts->_sequence = 1000;
_vm->_scripts->searchForSequence();
_vm->_scripts->executeScript();
+
+ for (int i = 0; i < 30; i++)
+ _byte26CD2[i] = 0;
+
+ for (int i = 0; i < 10; i++)
+ _byte26CBC[i] = 0;
}
void MartianRoom::roomMenu() {
@@ -126,16 +107,31 @@ void MartianRoom::roomMenu() {
_vm->_screen->saveScreen();
_vm->_screen->setDisplayScan();
_vm->_destIn = _vm->_screen; // TODO: Redundant
- _vm->_screen->plotImage(spr, 0, Common::Point(0, 177));
- _vm->_screen->plotImage(spr, 1, Common::Point(143, 177));
+ _vm->_screen->plotImage(spr, 0, Common::Point(5, 184));
+ _vm->_screen->plotImage(spr, 1, Common::Point(155, 184));
_vm->_screen->restoreScreen();
delete spr;
}
void MartianRoom::mainAreaClick() {
+ Common::Point &mousePos = _vm->_events->_mousePos;
+ Common::Point pt = _vm->_events->calcRawMouse();
+ Screen &screen = *_vm->_screen;
+ Player &player = *_vm->_player;
+
+ if (_selectCommand == -1) {
+ player._moveTo = pt;
+ player._playerMove = true;
+ } else if (mousePos.x >= screen._windowXAdd &&
+ mousePos.x <= (screen._windowXAdd + screen._vWindowBytesWide) &&
+ mousePos.y >= screen._windowYAdd &&
+ mousePos.y <= (screen._windowYAdd + screen._vWindowLinesTall)) {
+ if (checkBoxes1(pt) >= 0) {
+ checkBoxes3();
+ }
+ }
}
} // End of namespace Martian
-
} // End of namespace Access
diff --git a/engines/access/martian/martian_room.h b/engines/access/martian/martian_room.h
index 85529ce8f0..11501b6e57 100644
--- a/engines/access/martian/martian_room.h
+++ b/engines/access/martian/martian_room.h
@@ -39,6 +39,9 @@ private:
MartianEngine *_game;
void roomSet();
+
+ int _byte26CD2[30];
+ int _byte26CBC[10];
protected:
virtual void loadRoom(int roomNumber);
@@ -52,8 +55,6 @@ public:
virtual ~MartianRoom();
- virtual void loadRoomData(const byte *roomData) { warning("TODO - loadRoomData"); }
-
virtual void init4Quads() { }
virtual void roomMenu();
diff --git a/engines/access/martian/martian_scripts.cpp b/engines/access/martian/martian_scripts.cpp
index 0578872092..a9b5de5597 100644
--- a/engines/access/martian/martian_scripts.cpp
+++ b/engines/access/martian/martian_scripts.cpp
@@ -34,7 +34,300 @@ MartianScripts::MartianScripts(AccessEngine *vm) : Scripts(vm) {
_game = (MartianEngine *)_vm;
}
+void MartianScripts::cmdSpecial0() {
+ _vm->_sound->stopSound();
+ _vm->_midi->stopSong();
+
+ _vm->_midi->loadMusic(47, 1);
+ _vm->_midi->midiPlay();
+ _vm->_midi->setLoop(true);
+
+ _vm->_events->_vbCount = 300;
+ while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
+ _vm->_events->pollEventsAndWait();
+
+ _vm->_screen->forceFadeOut();
+ _vm->_files->loadScreen("HOUSE.SC");
+
+ _vm->_video->setVideo(_vm->_screen, Common::Point(46, 30), "HVID.VID", 20);
+
+ do {
+ _vm->_video->playVideo();
+ if (_vm->_video->_videoFrame == 4) {
+ _vm->_screen->flashPalette(16);
+ _vm->_sound->playSound(4);
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+ _vm->_timers[31]._timer = _vm->_timers[31]._initTm = 40;
+ }
+ } while (!_vm->_video->_videoEnd && !_vm->shouldQuit());
+
+ if (_vm->_video->_videoEnd) {
+ _vm->_screen->flashPalette(12);
+ _vm->_sound->playSound(4);
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+ _vm->_midi->stopSong();
+ _vm->_midi->freeMusic();
+ warning("TODO: Pop Midi");
+ }
+}
+
+void MartianScripts::cmdSpecial1(int param1) {
+ _vm->_events->hideCursor();
+
+ if (param1 != -1) {
+ _vm->_files->loadScreen(49, param1);
+ _vm->_buffer2.copyBuffer(_vm->_screen);
+ }
+
+ _vm->_screen->setIconPalette();
+ _vm->_screen->forceFadeIn();
+ _vm->_events->showCursor();
+}
+
+void MartianScripts::cmdSpecial3() {
+ _vm->_screen->forceFadeOut();
+ _vm->_events->hideCursor();
+ _vm->_files->loadScreen(57, 3);
+ _vm->_buffer2.copyFrom(*_vm->_screen);
+
+ _vm->_screen->setIconPalette();
+ _vm->_events->showCursor();
+ _vm->_screen->forceFadeIn();
+}
+
+void MartianScripts::doIntro(int param1) {
+ _game->doSpecial5(param1);
+}
+
+void MartianScripts::cmdSpecial6() {
+ _vm->_midi->stopSong();
+ _vm->_screen->setDisplayScan();
+ _vm->_events->clearEvents();
+ _vm->_screen->forceFadeOut();
+ _vm->_events->hideCursor();
+ _vm->_files->loadScreen(49, 9);
+ _vm->_events->showCursor();
+ _vm->_screen->setIconPalette();
+ _vm->_screen->forceFadeIn();
+
+ Resource *cellsRes = _vm->_files->loadFile("CELLS00.LZ");
+ _vm->_objectsTable[0] = new SpriteResource(_vm, cellsRes);
+ delete cellsRes;
+
+ _vm->_timers[20]._timer = _vm->_timers[20]._initTm = 30;
+ _vm->_fonts._charSet._lo = 1;
+ _vm->_fonts._charSet._hi = 10;
+ _vm->_fonts._charFor._lo = 1;
+ _vm->_fonts._charFor._hi = 255;
+
+ _vm->_screen->_maxChars = 50;
+ _vm->_screen->_printOrg = _vm->_screen->_printStart = Common::Point(24, 18);
+
+ Resource *notesRes = _vm->_files->loadFile("ETEXT.DAT");
+ notesRes->_stream->seek(72);
+
+ // Read the message
+ Common::String msg = "";
+ byte c;
+ while ((c = (char)notesRes->_stream->readByte()) != '\0')
+ msg += c;
+
+ //display the message
+ _game->showDeathText(msg);
+
+ delete notesRes;
+ delete _vm->_objectsTable[0];
+ _vm->_objectsTable[0] = nullptr;
+ _vm->_midi->stopSong();
+}
+
+void MartianScripts::cmdSpecial7() {
+ _vm->_room->clearRoom();
+ _vm->_midi->loadMusic(47, 8);
+
+ _vm->_sound->freeSounds();
+ Resource *sound = _vm->_sound->loadSound(46, 14);
+ _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1));
+
+ _vm->_screen->setDisplayScan();
+ _vm->_screen->forceFadeOut();
+ _vm->_events->hideCursor();
+
+ _vm->_files->loadScreen(40, 3);
+ _vm->_buffer1.copyBuffer(_vm->_screen);
+ _vm->_buffer2.copyBuffer(_vm->_screen);
+
+ _vm->_events->showCursor();
+ _vm->_screen->setIconPalette();
+ _vm->_screen->forceFadeIn();
+
+ // Load objects specific to this special scene
+ Resource *data = _vm->_files->loadFile(40, 2);
+ _game->_spec7Objects = new SpriteResource(_vm, data);
+ delete data;
+
+ // Load animation data
+ _vm->_animation->freeAnimationData();
+ Resource *animResource = _vm->_files->loadFile(40, 1);
+ _vm->_animation->loadAnimations(animResource);
+ delete animResource;
+
+ // Load script
+ Resource *newScript = _vm->_files->loadFile(40, 0);
+ _vm->_scripts->setScript(newScript);
+
+ _vm->_images.clear();
+ _vm->_oldRects.clear();
+ _vm->_scripts->_sequence = 0;
+
+ _vm->_sound->playSound(0);
+
+ do {
+ charLoop();
+ } while (_vm->_flags[134] != 1);
+
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+
+ _game->_numAnimTimers = 0;
+ _vm->_animation->freeAnimationData();
+ _vm->_scripts->freeScriptData();
+ _vm->_sound->freeSounds();
+
+ _vm->_screen->forceFadeOut();
+ _vm->_midi->midiPlay();
+ _vm->_midi->setLoop(true);
+ _vm->_events->hideCursor();
+
+ _vm->_files->loadScreen(40, 4);
+ _vm->_buffer1.copyBuffer(_vm->_screen);
+ _vm->_buffer2.copyBuffer(_vm->_screen);
+
+ _vm->_events->showCursor();
+ _vm->_screen->setIconPalette();
+ _vm->_screen->forceFadeIn();
+
+ // Setup fonts
+ _vm->_fonts._charSet._hi = 10;
+ _vm->_fonts._charSet._lo = 1;
+ _vm->_fonts._charFor._lo = 247;
+ _vm->_fonts._charFor._hi = 255;
+ _vm->_screen->_maxChars = 50;
+ _vm->_screen->_printOrg = Common::Point(24, 18);
+ _vm->_screen->_printStart = Common::Point(24, 18);
+
+ // Display death message
+ _game->showDeathText(Common::String(SPEC7MESSAGE));
+
+ _vm->_events->showCursor();
+ _vm->_screen->copyBuffer(&_vm->_buffer1);
+ _vm->_events->hideCursor();
+
+ _vm->_video->setVideo(_vm->_screen, Common::Point(120, 16), FileIdent(40, 5), 10);
+
+ while (!_vm->shouldQuit() && !_vm->_video->_videoEnd) {
+ _vm->_video->playVideo();
+ _vm->_events->pollEventsAndWait();
+ }
+
+ _vm->_sound->freeSounds();
+ sound = _vm->_sound->loadSound(40, 8);
+ _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1));
+ sound = _vm->_sound->loadSound(40, 9);
+ _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1));
+ sound = _vm->_sound->loadSound(40, 10);
+ _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1));
+
+ _vm->_screen->forceFadeOut();
+ _vm->_files->loadScreen(40, 7);
+ _vm->_destIn = _vm->_screen;
+
+ _vm->_screen->plotImage(_game->_spec7Objects, 8, Common::Point(104, 176));
+ _vm->_screen->plotImage(_game->_spec7Objects, 7, Common::Point(102, 160));
+ _vm->_events->showCursor();
+ _vm->_screen->forceFadeIn();
+
+ _vm->_events->_vbCount = 100;
+ while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
+ _vm->_events->pollEventsAndWait();
+
+ _vm->_sound->playSound(0);
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+
+ _vm->_events->_vbCount = 80;
+ while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
+ _vm->_events->pollEventsAndWait();
+
+ _vm->_sound->playSound(1);
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+
+ _vm->_events->_vbCount = 80;
+ while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
+ _vm->_events->pollEventsAndWait();
+
+ _vm->_sound->playSound(2);
+ do {
+ _vm->_events->pollEvents();
+ } while (!_vm->shouldQuit() && _vm->_sound->_playingSound);
+
+ _vm->_sound->freeSounds();
+
+ delete _game->_spec7Objects;
+ _game->_spec7Objects = nullptr;
+
+ _vm->_events->hideCursor();
+ _vm->_screen->forceFadeOut();
+ _vm->_files->loadScreen(40, 6);
+ _vm->_events->showCursor();
+ _vm->_screen->forceFadeIn();
+
+ _vm->_events->waitKeyMouse();
+ _vm->_midi->stopSong();
+ _vm->_midi->freeMusic();
+
+ // The original was jumping to the restart label in main
+ _vm->_restartFl = true;
+ _vm->_events->pollEvents();
+}
+
void MartianScripts::executeSpecial(int commandIndex, int param1, int param2) {
+ switch (commandIndex) {
+ case 0:
+ cmdSpecial0();
+ break;
+ case 1:
+ cmdSpecial1(param1);
+ break;
+ case 2:
+ warning("TODO: cmdSpecial2");
+ break;
+ case 3:
+ cmdSpecial3();
+ break;
+ case 4:
+ warning("TODO: cmdSpecial4");
+ break;
+ case 5:
+ doIntro(param1);
+ break;
+ case 6:
+ cmdSpecial6();
+ break;
+ case 7:
+ cmdSpecial7();
+ break;
+ default:
+ warning("Unexpected Special code %d - Skipped", commandIndex);
+ }
}
typedef void(MartianScripts::*MartianScriptMethodPtr)();
diff --git a/engines/access/martian/martian_scripts.h b/engines/access/martian/martian_scripts.h
index fc7495fc47..9c2141276e 100644
--- a/engines/access/martian/martian_scripts.h
+++ b/engines/access/martian/martian_scripts.h
@@ -35,9 +35,18 @@ class MartianEngine;
class MartianScripts : public Scripts {
private:
MartianEngine *_game;
+
+ void cmdSpecial0();
+ void cmdSpecial1(int param1);
+ void cmdSpecial3();
+ void doIntro(int param1);
+ void cmdSpecial6();
+ void cmdSpecial7();
+
protected:
virtual void executeSpecial(int commandIndex, int param1, int param2);
virtual void executeCommand(int commandIndex);
+
public:
MartianScripts(AccessEngine *vm);
};
diff --git a/engines/access/module.mk b/engines/access/module.mk
index b6961aeca9..f7cf7f2261 100644
--- a/engines/access/module.mk
+++ b/engines/access/module.mk
@@ -28,6 +28,7 @@ MODULE_OBJS := \
amazon/amazon_room.o \
amazon/amazon_scripts.o \
martian/martian_game.o \
+ martian/martian_player.o \
martian/martian_resources.o \
martian/martian_room.o \
martian/martian_scripts.o
diff --git a/engines/access/player.cpp b/engines/access/player.cpp
index 5a2b98293f..0162491aee 100644
--- a/engines/access/player.cpp
+++ b/engines/access/player.cpp
@@ -26,24 +26,25 @@
#include "access/access.h"
#include "access/resources.h"
#include "access/amazon/amazon_player.h"
+#include "access/martian/martian_player.h"
namespace Access {
Player *Player::init(AccessEngine *vm) {
switch (vm->getGameID()) {
case GType_Amazon:
+ vm->_playerDataCount = 8;
return new Amazon::AmazonPlayer(vm);
+ case GType_MartianMemorandum:
+ vm->_playerDataCount = 10;
+ return new Martian::MartianPlayer(vm);
default:
+ vm->_playerDataCount = 8;
return new Player(vm);
}
}
Player::Player(AccessEngine *vm) : Manager(vm), ImageEntry() {
- Common::fill(&_walkOffRight[0], &_walkOffRight[PLAYER_DATA_COUNT], 0);
- Common::fill(&_walkOffLeft[0], &_walkOffLeft[PLAYER_DATA_COUNT], 0);
- Common::fill(&_walkOffUp[0], &_walkOffUp[PLAYER_DATA_COUNT], 0);
- Common::fill(&_walkOffDown[0], &_walkOffDown[PLAYER_DATA_COUNT], 0);
-
_playerSprites = nullptr;
_playerSprites1 = nullptr;
_manPal1 = nullptr;
@@ -78,14 +79,36 @@ Player::Player(AccessEngine *vm) : Manager(vm), ImageEntry() {
_downWalkMin = _downWalkMax = 0;
_diagUpWalkMin = _diagUpWalkMax = 0;
_diagDownWalkMin = _diagDownWalkMax = 0;
+ _walkOffRight = _walkOffLeft = nullptr;
+ _walkOffUp = _walkOffDown = nullptr;
+ _walkOffUR = _walkOffDR = nullptr;
+ _walkOffUL = _walkOffDL = nullptr;
}
Player::~Player() {
delete _playerSprites;
delete[] _manPal1;
+ delete[] _walkOffRight;
+ delete[] _walkOffLeft;
+ delete[] _walkOffUp;
+ delete[] _walkOffDown;
+ delete[] _walkOffUR;
+ delete[] _walkOffDR;
+ delete[] _walkOffUL;
+ delete[] _walkOffDL;
}
void Player::load() {
+ int dataCount = _vm->_playerDataCount;
+ _walkOffRight = new int[dataCount];
+ _walkOffLeft = new int[dataCount];
+ _walkOffUp = new int[dataCount];
+ _walkOffDown = new int[dataCount];
+ _walkOffUR = new Common::Point[dataCount];
+ _walkOffDR = new Common::Point[dataCount];
+ _walkOffUL = new Common::Point[dataCount];
+ _walkOffDL = new Common::Point[dataCount];
+
_playerOffset.x = _vm->_screen->_scaleTable1[25];
_playerOffset.y = _vm->_screen->_scaleTable1[67];
_leftDelta = -3;
@@ -94,21 +117,6 @@ void Player::load() {
_downDelta = -10;
_scrollConst = 5;
- for (int i = 0; i < PLAYER_DATA_COUNT; ++i) {
- _walkOffRight[i] = SIDEOFFR[i];
- _walkOffLeft[i] = SIDEOFFL[i];
- _walkOffUp[i] = SIDEOFFU[i];
- _walkOffDown[i] = SIDEOFFD[i];
- _walkOffUR[i].x = DIAGOFFURX[i];
- _walkOffUR[i].y = DIAGOFFURY[i];
- _walkOffDR[i].x = DIAGOFFDRX[i];
- _walkOffDR[i].y = DIAGOFFDRY[i];
- _walkOffUL[i].x = DIAGOFFULX[i];
- _walkOffUL[i].y = DIAGOFFULY[i];
- _walkOffDL[i].x = DIAGOFFDLX[i];
- _walkOffDL[i].y = DIAGOFFDLY[i];
- }
-
_sideWalkMin = 0;
_sideWalkMax = 7;
_upWalkMin = 16;
@@ -122,16 +130,35 @@ void Player::load() {
_playerSprites = _playerSprites1;
if (_manPal1) {
- Common::copy(_manPal1 + 0x270, _manPal1 + 0x270 + 0x60, _vm->_screen->_manPal);
+ // Those values are from MM as Amazon doesn't use it
+ Common::copy(_manPal1 + 0x2A0, _manPal1 + 0x2A0 + 0x42, _vm->_screen->_manPal);
} else {
Common::fill(_vm->_screen->_manPal, _vm->_screen->_manPal + 0x60, 0);
}
}
+void Player::loadTexPalette() {
+ Resource *texPal = _vm->_files->loadFile("TEXPAL.COL");
+ int size = texPal->_size;
+ assert(size == 768);
+ _manPal1 = new byte[size];
+ memcpy(_manPal1, texPal->data(), size);
+}
+
void Player::loadSprites(const Common::String &name) {
freeSprites();
Resource *data = _vm->_files->loadFile(name);
+
+#if 0
+ Common::DumpFile *outFile = new Common::DumpFile();
+ Common::String outName = name + ".dump";
+ outFile->open(outName);
+ outFile->write(data->data(), data->_size);
+ outFile->finalize();
+ outFile->close();
+#endif
+
_playerSprites1 = new SpriteResource(_vm, data);
delete data;
}
@@ -357,7 +384,7 @@ void Player::walkRight() {
if (_frame > _sideWalkMax)
_frame = _sideWalkMin;
- plotCom(0);
+ plotCom0();
}
}
@@ -634,15 +661,19 @@ void Player::checkMove() {
}
void Player::plotCom(int flags) {
- _flags &= ~2;
- _flags &= ~8;
+ _flags &= ~IMGFLAG_BACKWARDS;
+ _flags &= ~IMGFLAG_UNSCALED;
_flags |= flags;
plotCom3();
}
+void Player::plotCom0() {
+ plotCom(_vm->getGameID() == GType_Amazon ? 0 : IMGFLAG_BACKWARDS);
+}
+
void Player::plotCom1() {
- plotCom(2);
+ plotCom(_vm->getGameID() == GType_Amazon ? IMGFLAG_BACKWARDS : 0);
}
void Player::plotCom2() {
@@ -709,8 +740,12 @@ void Player::checkScroll() {
}
}
-bool Player::scrollUp() {
- _scrollAmount = -(_vm->_screen->_clipHeight - _playerY - _scrollThreshold);
+bool Player::scrollUp(int forcedAmount) {
+ if (forcedAmount == -1)
+ _scrollAmount = -(_vm->_screen->_clipHeight - _playerY - _scrollThreshold);
+ else
+ _scrollAmount = forcedAmount;
+
if ((_vm->_scrollRow + _vm->_screen->_vWindowHeight) >=
_vm->_room->_playFieldHeight)
return true;
@@ -737,8 +772,12 @@ bool Player::scrollUp() {
return false;
}
-bool Player::scrollDown() {
- _scrollAmount = -(_playerY - _scrollThreshold);
+bool Player::scrollDown(int forcedAmount) {
+ if (forcedAmount == -1)
+ _scrollAmount = -(_playerY - _scrollThreshold);
+ else
+ _scrollAmount = forcedAmount;
+
_scrollFlag = true;
_vm->_scrollY -= _scrollAmount;
if (_vm->_scrollY >= 0)
@@ -762,9 +801,13 @@ bool Player::scrollDown() {
return true;
}
-bool Player::scrollLeft() {
+bool Player::scrollLeft(int forcedAmount) {
Screen &screen = *_vm->_screen;
- _scrollAmount = -(_vm->_screen->_clipWidth - _playerX - _scrollThreshold);
+ if (forcedAmount == -1)
+ _scrollAmount = -(_vm->_screen->_clipWidth - _playerX - _scrollThreshold);
+ else
+ _scrollAmount = forcedAmount;
+
if ((_vm->_scrollCol + screen._vWindowWidth) == _vm->_room->_playFieldWidth) {
_scrollEnd = 2;
_vm->_scrollX = 0;
@@ -789,8 +832,12 @@ bool Player::scrollLeft() {
}
}
-bool Player::scrollRight() {
- _scrollAmount = -(_playerX - _scrollThreshold);
+bool Player::scrollRight(int forcedAmount) {
+ if (forcedAmount == -1)
+ _scrollAmount = -(_playerX - _scrollThreshold);
+ else
+ _scrollAmount = forcedAmount;
+
_scrollFlag = true;
_vm->_scrollX -= _scrollAmount;
diff --git a/engines/access/player.h b/engines/access/player.h
index 329cc15ed2..3c554556dd 100644
--- a/engines/access/player.h
+++ b/engines/access/player.h
@@ -31,8 +31,6 @@
namespace Access {
-#define PLAYER_DATA_COUNT 8
-
enum Direction {
NONE = 0,
UP = 1,
@@ -58,11 +56,11 @@ protected:
int _diagUpWalkMin, _diagUpWalkMax;
int _diagDownWalkMin, _diagDownWalkMax;
SpriteResource *_playerSprites1;
- byte *_manPal1;
int _scrollEnd;
int _inactiveYOff;
void plotCom(int v1);
+ void plotCom0();
void plotCom1();
void plotCom2();
void plotCom3();
@@ -76,22 +74,19 @@ protected:
void walkUpRight();
void walkDownRight();
void checkScrollUp();
- bool scrollUp();
- bool scrollDown();
- bool scrollLeft();
- bool scrollRight();
public:
Direction _playerDirection;
SpriteResource *_playerSprites;
// Fields in original Player structure
- int _walkOffRight[PLAYER_DATA_COUNT];
- int _walkOffLeft[PLAYER_DATA_COUNT];
- int _walkOffUp[PLAYER_DATA_COUNT];
- int _walkOffDown[PLAYER_DATA_COUNT];
- Common::Point _walkOffUR[PLAYER_DATA_COUNT];
- Common::Point _walkOffDR[PLAYER_DATA_COUNT];
- Common::Point _walkOffUL[PLAYER_DATA_COUNT];
- Common::Point _walkOffDL[PLAYER_DATA_COUNT];
+ byte *_manPal1;
+ int *_walkOffRight;
+ int *_walkOffLeft;
+ int *_walkOffUp;
+ int *_walkOffDown;
+ Common::Point *_walkOffUR;
+ Common::Point *_walkOffDR;
+ Common::Point *_walkOffUL;
+ Common::Point *_walkOffDL;
byte _rawTempL;
int _rawXTemp;
byte _rawYTempL;
@@ -125,6 +120,8 @@ public:
virtual void load();
+ void loadTexPalette();
+
void loadSprites(const Common::String &name);
void freeSprites();
@@ -137,6 +134,10 @@ public:
void calcPlayer();
+ bool scrollUp(int forcedAmount = -1);
+ bool scrollDown(int forcedAmount = -1);
+ bool scrollLeft(int forcedAmount = -1);
+ bool scrollRight(int forcedAmount = -1);
void checkScroll();
void checkMove();
diff --git a/engines/access/resources.cpp b/engines/access/resources.cpp
index 4157cdfc0d..8699a4a82f 100644
--- a/engines/access/resources.cpp
+++ b/engines/access/resources.cpp
@@ -46,35 +46,17 @@ const byte INITIAL_PALETTE[18 * 3] = {
0x00, 0x00, 0x00
};
-const int SIDEOFFR[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 };
-const int SIDEOFFL[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 };
-const int SIDEOFFU[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 };
-const int SIDEOFFD[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 };
-const int DIAGOFFURX[] = { 4, 5, 2, 2, 3, 4, 2, 2, 0 };
-const int DIAGOFFURY[] = { 2, 3, 2, 2, 2, 3, 1, 1, 0 };
-const int DIAGOFFDRX[] = { 4, 5, 4, 3, 5, 4, 5, 1, 0 };
-const int DIAGOFFDRY[] = { 3, 2, 1, 2, 2, 1, 2, 1, 0 };
-const int DIAGOFFULX[] = { 4, 5, 4, 3, 3, 2, 2, 2, 0 };
-const int DIAGOFFULY[] = { 3, 3, 1, 2, 2, 1, 1, 1, 0 };
-const int DIAGOFFDLX[] = { 4, 5, 3, 3, 5, 4, 6, 1, 0 };
-const int DIAGOFFDLY[] = { 2, 2, 1, 2, 3, 1, 2, 1, 0 };
-
-const int RMOUSE[10][2] = {
- { 0, 35 }, { 0, 0 }, { 36, 70 }, { 71, 106 }, { 107, 141 },
- { 142, 177 }, { 178, 212 }, { 213, 248 }, { 249, 283 }, { 284, 318 }
-};
-
-const char *const LOOK_MESSAGE = "LOOKING THERE REVEALS NOTHING OF INTEREST.";
-const char *const GET_MESSAGE = "YOU CAN'T TAKE THAT.";
-const char *const OPEN_MESSAGE = "THAT DOESN'T OPEN.";
-const char *const MOVE_MESSAGE = "THAT WON'T MOVE.";
-const char *const USE_MESSAGE = "THAT DOESN'T SEEM TO WORK.";
-const char *const GO_MESSAGE = "YOU CAN'T CLIMB THAT.";
-const char *const HELP_MESSAGE = "THIS OBJECT REQUIRES NO HINTS";
-const char *const TALK_MESSAGE = "THERE SEEMS TO BE NO RESPONSE.";
const char *const GENERAL_MESSAGES[] = {
- LOOK_MESSAGE, OPEN_MESSAGE, MOVE_MESSAGE, GET_MESSAGE, USE_MESSAGE,
- GO_MESSAGE, TALK_MESSAGE, HELP_MESSAGE, HELP_MESSAGE, USE_MESSAGE
+ "LOOKING THERE REVEALS NOTHING OF INTEREST.", // LOOK_MESSAGE
+ "THAT DOESN'T OPEN.", // OPEN_MESSAGE
+ "THAT WON'T MOVE." // MOVE_MESSAGE
+ "YOU CAN'T TAKE THAT.", // GET_MESSAGE
+ "THAT DOESN'T SEEM TO WORK.", // USE_MESSAGE
+ "YOU CAN'T CLIMB THAT.", // GO_MESSAGE
+ "THERE SEEMS TO BE NO RESPONSE.", // TALK_MESSAGE
+ "THIS OBJECT REQUIRES NO HINTS", // HELP_MESSAGE
+ "THIS OBJECT REQUIRES NO HINTS", // HELP_MESSAGE
+ "THAT DOESN'T SEEM TO WORK.", // USE_MESSAGE
};
const int INVCOORDS[][4] = {
diff --git a/engines/access/resources.h b/engines/access/resources.h
index 8d59b1b1f1..07b8e5ada9 100644
--- a/engines/access/resources.h
+++ b/engines/access/resources.h
@@ -29,21 +29,6 @@ namespace Access {
extern const byte INITIAL_PALETTE[18 * 3];
-extern const int SIDEOFFR[];
-extern const int SIDEOFFL[];
-extern const int SIDEOFFU[];
-extern const int SIDEOFFD[];
-extern const int DIAGOFFURX[];
-extern const int DIAGOFFURY[];
-extern const int DIAGOFFDRX[];
-extern const int DIAGOFFDRY[];
-extern const int DIAGOFFULX[];
-extern const int DIAGOFFULY[];
-extern const int DIAGOFFDLX[];
-extern const int DIAGOFFDLY[];
-
-extern const int RMOUSE[10][2];
-
extern const char *const GENERAL_MESSAGES[];
extern const int INVCOORDS[][4];
diff --git a/engines/access/room.cpp b/engines/access/room.cpp
index 607259ec6f..c91b37c65d 100644
--- a/engines/access/room.cpp
+++ b/engines/access/room.cpp
@@ -38,6 +38,23 @@ Room::Room(AccessEngine *vm) : Manager(vm) {
_selectCommand = 0;
_conFlag = false;
_selectCommand = -1;
+
+ switch (vm->getGameID()) {
+ case GType_Amazon:
+ for (int i = 0; i < 10; i++) {
+ _rMouse[i][0] = Amazon::RMOUSE[i][0];
+ _rMouse[i][1] = Amazon::RMOUSE[i][1];
+ }
+ break;
+ case GType_MartianMemorandum:
+ for (int i = 0; i < 10; i++) {
+ _rMouse[i][0] = Martian::RMOUSE[i][0];
+ _rMouse[i][1] = Martian::RMOUSE[i][1];
+ }
+ break;
+ default:
+ error("Game not supported");
+ }
}
Room::~Room() {
@@ -55,6 +72,91 @@ void Room::freeTileData() {
_tile = nullptr;
}
+void Room::clearCamera() {
+ _vm->_player->_scrollFlag = true;
+ _vm->_events->hideCursor();
+
+ _vm->_screen->_orgX1 = 48;
+ _vm->_screen->_orgY1 = 24;
+ _vm->_screen->_orgX2 = 274;
+ _vm->_screen->_orgY2 = 152;
+ _vm->_screen->_lColor = 0;
+ _vm->_screen->drawRect();
+
+ _vm->_events->showCursor();
+
+ _vm->_events->_vbCount = 4;
+ while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0)
+ _vm->_events->pollEventsAndWait();
+}
+
+void Room::takePicture() {
+ _vm->_events->pollEvents();
+ if (!_vm->_events->_leftButton)
+ return;
+
+ Common::Array<Common::Rect> pictureCoords;
+ for (int i = 0; Martian::PICTURERANGE[i][0] != -1; i += 2) {
+ pictureCoords.push_back(Common::Rect(Martian::PICTURERANGE[i][0], Martian::PICTURERANGE[i + 1][0],
+ Martian::PICTURERANGE[i][1], Martian::PICTURERANGE[i + 1][1]));
+ }
+
+ int result = _vm->_events->checkMouseBox1(pictureCoords);
+
+ if (result == 4) {
+ _vm->_events->debounceLeft();
+ if (_vm->_inventory->_inv[44]._value != ITEM_IN_INVENTORY) {
+ Common::String msg = "YOU HAVE NO MORE FILM.";
+ _vm->_scripts->doCmdPrint_v1(msg);
+ return;
+ }
+
+ // TODO: simplify the second part of the test when tested
+ if ((_vm->_scrollCol < 35) || ((_vm->_scrollRow >= 10) && (_vm->_scrollRow >= 20))){
+ Common::String msg = "THAT ISN'T INTERESTING ENOUGH TO WASTE FILM ON.";
+ _vm->_scripts->doCmdPrint_v1(msg);
+ return;
+ }
+
+ if (_vm->_inventory->_inv[26]._value != ITEM_USED) {
+ Common::String msg = "ALTHOUGH IT WOULD MAKE A NICE PICTURE, YOU MAY FIND SOMETHING MORE INTERESTING TO USE YOUR FILM ON.";
+ _vm->_scripts->doCmdPrint_v1(msg);
+ return;
+ }
+
+ Common::String msg = "THAT PHOTO MAY COME IN HANDY SOME DAY.";
+ _vm->_scripts->doCmdPrint_v1(msg);
+ _vm->_inventory->_inv[8]._value = ITEM_IN_INVENTORY;
+ _vm->_pictureTaken++;
+ if (_vm->_pictureTaken == 16)
+ _vm->_inventory->_inv[44]._value = ITEM_USED;
+
+ _vm->_events->debounceLeft();
+ _vm->_sound->playSound(0);
+ clearCamera();
+ return;
+ } else if (result == 5) {
+ if (_vm->_flags[26] != 2) {
+ _vm->_video->closeVideo();
+ _vm->_video->_videoEnd = true;
+ }
+ _vm->_player->_roomNumber = 7;
+ _vm->_room->_function = FN_CLEAR1;
+ return;
+ } else if (result >= 0)
+ _vm->_player->_move = (Direction)(result + 1);
+
+ _vm->_player->_scrollFlag = false;
+ if (_vm->_player->_move == UP)
+ _vm->_player->scrollDown(2);
+ else if (_vm->_player->_move == DOWN)
+ _vm->_player->scrollUp(2);
+ else if (_vm->_player->_move == LEFT)
+ _vm->_player->scrollRight(2);
+ else if (_vm->_player->_move == RIGHT)
+ _vm->_player->scrollLeft(2);
+}
+
void Room::doRoom() {
bool reloadFlag = false;
@@ -84,9 +186,13 @@ void Room::doRoom() {
_vm->_events->pollEventsAndWait();
_vm->_canSaveLoad = false;
- _vm->_player->walk();
- _vm->_midi->midiRepeat();
- _vm->_player->checkScroll();
+ if ((_vm->getGameID() == GType_MartianMemorandum) && (_vm->_player->_roomNumber == 47)) {
+ takePicture();
+ } else {
+ _vm->_player->walk();
+ _vm->_midi->midiRepeat();
+ _vm->_player->checkScroll();
+ }
doCommands();
if (_vm->shouldQuitOrRestart())
@@ -136,7 +242,8 @@ void Room::doRoom() {
} else {
_vm->plotList();
- if (_vm->_events->_mousePos.y < 177)
+ if (((_vm->getGameID() == GType_MartianMemorandum) && (_vm->_events->_mousePos.y < 184)) ||
+ ((_vm->getGameID() == GType_Amazon) && (_vm->_events->_mousePos.y < 177)))
_vm->_events->setCursor(_vm->_events->_normalMouse);
else
_vm->_events->setCursor(CURSOR_ARROW);
@@ -173,8 +280,8 @@ void Room::loadRoomData(const byte *roomData) {
_vm->_establishFlag = false;
if (roomInfo._estIndex != -1) {
_vm->_establishFlag = true;
- if (_vm->_establishTable[roomInfo._estIndex] != 1) {
- _vm->_establishTable[roomInfo._estIndex] = 1;
+ if (!_vm->_establishTable[roomInfo._estIndex]) {
+ _vm->_establishTable[roomInfo._estIndex] = true;
_vm->establish(0, roomInfo._estIndex);
}
}
@@ -455,8 +562,8 @@ void Room::doCommands() {
if (_vm->_events->_mouseRow >= 22) {
// Mouse in user interface area
for (commandId = 0; commandId < 10; ++commandId) {
- if (_vm->_events->_mousePos.x >= RMOUSE[commandId][0] &&
- _vm->_events->_mousePos.x < RMOUSE[commandId][1])
+ if (_vm->_events->_mousePos.x >= _rMouse[commandId][0] &&
+ _vm->_events->_mousePos.x < _rMouse[commandId][1])
break;
}
if (commandId < 10)
@@ -489,9 +596,6 @@ void Room::cycleCommand(int incr) {
}
void Room::handleCommand(int commandId) {
- if (commandId == 1)
- --commandId;
-
if (commandId == 9) {
_vm->_events->debounceLeft();
_vm->_canSaveLoad = true;
@@ -510,41 +614,90 @@ void Room::executeCommand(int commandId) {
EventsManager &events = *_vm->_events;
_selectCommand = commandId;
- switch (commandId) {
- case 0:
- events.forceSetCursor(CURSOR_LOOK);
- break;
- case 2:
- events.forceSetCursor(CURSOR_USE);
- break;
- case 3:
- events.forceSetCursor(CURSOR_TAKE);
- break;
- case 4:
- events.setCursor(CURSOR_ARROW);
- if (_vm->_inventory->newDisplayInv() == 2) {
- commandOff();
+ if (_vm->getGameID() == GType_MartianMemorandum) {
+ switch (commandId) {
+ case 4:
+ events.setCursor(CURSOR_ARROW);
+ if (_vm->_inventory->displayInv() == 2) {
+ commandOff();
+ return;
+ }
+ if (_vm->_useItem == 39) {
+ if (_vm->_player->_roomNumber == 23)
+ _vm->_currentMan = 1;
+ commandOff();
+ return;
+ } else if (_vm->_useItem == 6) {
+ _vm->_flags[3] = 2;
+ _vm->_scripts->converse1(24);
+
+ _conFlag = true;
+ while (_conFlag && !_vm->shouldQuitOrRestart()) {
+ _conFlag = false;
+ _vm->_scripts->executeScript();
+ }
+
+ _vm->_boxSelect = true;
+ return;
+ }
+ break;
+ case 7:
+ walkCursor();
return;
+ case 8: {
+ events.forceSetCursor(CURSOR_CROSSHAIRS);
+ _vm->_scripts->_sequence = 10000;
+ _vm->_scripts->searchForSequence();
+
+ _conFlag = true;
+ while (_conFlag && !_vm->shouldQuitOrRestart()) {
+ _conFlag = false;
+ _vm->_scripts->executeScript();
+ }
+
+ _vm->_boxSelect = true;
+ return;
+ }
+ default:
+ // No set cursor in MM. Forcing to CROSSHAIRS
+ events.setCursor(CURSOR_CROSSHAIRS);
+ break;
+ }
+ } else {
+ switch (commandId) {
+ case 0:
+ case 1:
+ events.forceSetCursor(CURSOR_LOOK);
+ break;
+ case 2:
+ events.forceSetCursor(CURSOR_USE);
+ break;
+ case 3:
+ events.forceSetCursor(CURSOR_TAKE);
+ break;
+ case 4:
+ events.setCursor(CURSOR_ARROW);
+ if (_vm->_inventory->newDisplayInv() == 2) {
+ commandOff();
+ return;
+ }
+ break;
+ case 5:
+ events.forceSetCursor(CURSOR_CLIMB);
+ break;
+ case 6:
+ events.forceSetCursor(CURSOR_TALK);
+ break;
+ case 7:
+ walkCursor();
+ return;
+ case 8:
+ events.forceSetCursor(CURSOR_HELP);
+ break;
+ default:
+ break;
}
- break;
- case 5:
- events.forceSetCursor(CURSOR_CLIMB);
- break;
- case 6:
- events.forceSetCursor(CURSOR_TALK);
- break;
- case 7:
- walkCursor();
- return;
- case 8:
- events.forceSetCursor(CURSOR_HELP);
- break;
- default:
- break;
}
-
- // Draw the default toolbar menu at the bottom of the screen
- roomMenu();
_vm->_screen->saveScreen();
_vm->_screen->setDisplayScan();
@@ -555,7 +708,7 @@ void Room::executeCommand(int commandId) {
// Draw the button as selected
_vm->_screen->plotImage(spr, _selectCommand + 2,
- Common::Point(RMOUSE[_selectCommand][0], 176));
+ Common::Point(_rMouse[_selectCommand][0], (_vm->getGameID() == GType_MartianMemorandum) ? 184 : 176));
_vm->_screen->restoreScreen();
_vm->_boxSelect = true;
diff --git a/engines/access/room.h b/engines/access/room.h
index eec273e3f4..12e7375428 100644
--- a/engines/access/room.h
+++ b/engines/access/room.h
@@ -72,6 +72,8 @@ private:
int calcLR(int yp);
int calcUD(int xp);
+ void takePicture();
+
/**
* Cycles forwards or backwards through the list of commands
*/
@@ -103,6 +105,8 @@ protected:
*/
void executeCommand(int commandId);
+ void clearCamera();
+
virtual void reloadRoom() = 0;
virtual void reloadRoom1() = 0;
@@ -126,6 +130,7 @@ public:
byte *_tile;
int _selectCommand;
bool _conFlag;
+ int _rMouse[10][2];
public:
Room(AccessEngine *vm);
diff --git a/engines/access/screen.cpp b/engines/access/screen.cpp
index 970a8f3079..41f6194238 100644
--- a/engines/access/screen.cpp
+++ b/engines/access/screen.cpp
@@ -113,6 +113,20 @@ void Screen::setInitialPalettte() {
g_system->getPaletteManager()->setPalette(INITIAL_PALETTE, 0, 18);
}
+void Screen::setManPalette() {
+ for (int i = 0; i < 0x42; i++) {
+ _rawPalette[672 + i] = VGA_COLOR_TRANS(_manPal[i]);
+ }
+}
+
+void Screen::setIconPalette() {
+ if (_vm->getGameID() == GType_MartianMemorandum) {
+ for (int i = 0; i < 0x1B; i++) {
+ _rawPalette[741 + i] = VGA_COLOR_TRANS(Martian::ICON_PALETTE[i]);
+ }
+ }
+}
+
void Screen::loadPalette(int fileNum, int subfile) {
Resource *res = _vm->_files->loadFile(fileNum, subfile);
byte *palette = res->data();
@@ -189,7 +203,7 @@ void Screen::forceFadeIn() {
for (int idx = 0; idx < PALETTE_SIZE; ++idx, ++srcP, ++destP) {
if (*destP != *srcP) {
repeatFlag = true;
- *destP = MAX((int)*destP + FADE_AMOUNT, (int)*srcP);
+ *destP = MIN((int)*destP + FADE_AMOUNT, (int)*srcP);
}
}
@@ -267,6 +281,11 @@ void Screen::drawRect() {
ASurface::drawRect();
}
+void Screen::drawBox() {
+ addDirtyRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2));
+ ASurface::drawBox();
+}
+
void Screen::transBlitFrom(ASurface *src, const Common::Point &destPos) {
addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + src->w, destPos.y + src->h));
ASurface::transBlitFrom(src, destPos);
@@ -322,6 +341,10 @@ void Screen::cyclePaletteBackwards() {
}
}
+void Screen::flashPalette(int count) {
+ warning("TODO: Implement flashPalette");
+}
+
void Screen::addDirtyRect(const Common::Rect &r) {
_dirtyRects.push_back(r);
assert(r.isValidRect() && r.width() > 0 && r.height() > 0);
diff --git a/engines/access/screen.h b/engines/access/screen.h
index d45a533f9a..52485e5c7c 100644
--- a/engines/access/screen.h
+++ b/engines/access/screen.h
@@ -92,6 +92,8 @@ public:
virtual void drawRect();
+ virtual void drawBox();
+
virtual void transBlitFrom(ASurface *src, const Common::Point &destPos);
virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds);
@@ -137,7 +139,12 @@ public:
/**
* Set icon palette
*/
- void setIconPalette() {}
+ void setIconPalette();
+
+ /**
+ * Set Tex palette (Martian Memorandum)
+ */
+ void setManPalette();
void loadPalette(int fileNum, int subfile);
@@ -151,6 +158,8 @@ public:
void getPalette(byte *pal);
+ void flashPalette(int count);
+
/**
* Copy a buffer to the screen
*/
diff --git a/engines/access/scripts.cpp b/engines/access/scripts.cpp
index 1bd24894d7..41dd5cc0d0 100644
--- a/engines/access/scripts.cpp
+++ b/engines/access/scripts.cpp
@@ -39,12 +39,101 @@ Scripts::Scripts(AccessEngine *vm) : Manager(vm) {
_choiceStart = 0;
_charsOrg = Common::Point(0, 0);
_texsOrg = Common::Point(0, 0);
+ setOpcodes();
}
Scripts::~Scripts() {
freeScriptData();
}
+void Scripts::setOpcodes() {
+ COMMAND_LIST[0] = &Scripts::cmdObject;
+ COMMAND_LIST[1] = &Scripts::cmdEndObject;
+ COMMAND_LIST[2] = &Scripts::cmdJumpLook;
+ COMMAND_LIST[3] = &Scripts::cmdJumpHelp;
+ COMMAND_LIST[4] = &Scripts::cmdJumpGet;
+ COMMAND_LIST[5] = &Scripts::cmdJumpMove;
+ COMMAND_LIST[6] = &Scripts::cmdJumpUse;
+ COMMAND_LIST[7] = &Scripts::cmdJumpTalk;
+ COMMAND_LIST[8] = &Scripts::cmdNull;
+ COMMAND_LIST[9] = &Scripts::cmdPrint_v1;
+ COMMAND_LIST[10] = &Scripts::cmdRetPos;
+ COMMAND_LIST[11] = &Scripts::cmdAnim;
+ COMMAND_LIST[12] = &Scripts::cmdSetFlag;
+ COMMAND_LIST[13] = &Scripts::cmdCheckFlag;
+ COMMAND_LIST[14] = &Scripts::cmdGoto;
+ COMMAND_LIST[15] = &Scripts::cmdAddScore;
+ COMMAND_LIST[16] = &Scripts::cmdSetInventory;
+ COMMAND_LIST[17] = &Scripts::cmdCheckInventory;
+ COMMAND_LIST[18] = &Scripts::cmdSetTex;
+ COMMAND_LIST[19] = &Scripts::cmdNewRoom;
+ COMMAND_LIST[20] = &Scripts::cmdConverse;
+ COMMAND_LIST[21] = &Scripts::cmdCheckFrame;
+ COMMAND_LIST[22] = &Scripts::cmdCheckAnim;
+ COMMAND_LIST[23] = &Scripts::cmdSnd;
+ COMMAND_LIST[24] = &Scripts::cmdRetNeg;
+ COMMAND_LIST[25] = &Scripts::cmdRetPos;
+ COMMAND_LIST[26] = &Scripts::cmdCheckLoc;
+ COMMAND_LIST[27] = &Scripts::cmdSetAnim;
+ COMMAND_LIST[28] = &Scripts::cmdDispInv_v1;
+ COMMAND_LIST[29] = &Scripts::cmdSetAbout;
+ COMMAND_LIST[30] = &Scripts::cmdSetTimer;
+ COMMAND_LIST[31] = &Scripts::cmdCheckTimer;
+ COMMAND_LIST[32] = &Scripts::cmdSetTravel;
+ COMMAND_LIST[33] = &Scripts::cmdJumpGoto;
+ COMMAND_LIST[34] = &Scripts::cmdSetVideo;
+ COMMAND_LIST[35] = &Scripts::cmdPlayVideo;
+ COMMAND_LIST[36] = &Scripts::cmdPlotImage;
+ COMMAND_LIST[37] = &Scripts::cmdSetDisplay;
+ COMMAND_LIST[38] = &Scripts::cmdSetBuffer;
+ COMMAND_LIST[39] = &Scripts::cmdSetScroll;
+ COMMAND_LIST[40] = &Scripts::cmdSaveRect;
+ COMMAND_LIST[41] = &Scripts::cmdVideoEnded;
+ COMMAND_LIST[42] = &Scripts::cmdSetBufVid;
+ COMMAND_LIST[43] = &Scripts::cmdPlayBufVid;
+ COMMAND_LIST[44] = &Scripts::cmdRemoveLast;
+ COMMAND_LIST[45] = &Scripts::cmdDoTravel;
+ COMMAND_LIST[46] = &Scripts::cmdCheckAbout;
+ COMMAND_LIST[47] = &Scripts::cmdSpecial;
+ COMMAND_LIST[48] = &Scripts::cmdSetCycle;
+ COMMAND_LIST[49] = &Scripts::cmdCycle;
+ COMMAND_LIST[50] = &Scripts::cmdCharSpeak;
+ COMMAND_LIST[51] = &Scripts::cmdTexSpeak;
+ COMMAND_LIST[52] = &Scripts::cmdTexChoice;
+ COMMAND_LIST[53] = &Scripts::cmdWait;
+ COMMAND_LIST[54] = &Scripts::cmdSetConPos;
+ COMMAND_LIST[55] = &Scripts::cmdCheckVFrame;
+ COMMAND_LIST[56] = &Scripts::cmdJumpChoice;
+ COMMAND_LIST[57] = &Scripts::cmdReturnChoice;
+ COMMAND_LIST[58] = &Scripts::cmdClearBlock;
+ COMMAND_LIST[59] = &Scripts::cmdLoadSound;
+ COMMAND_LIST[60] = &Scripts::cmdFreeSound;
+ COMMAND_LIST[61] = &Scripts::cmdSetVideoSound;
+ COMMAND_LIST[62] = &Scripts::cmdPlayVideoSound;
+ COMMAND_LIST[63] = &Scripts::cmdPrintWatch;
+ COMMAND_LIST[64] = &Scripts::cmdDispAbout;
+ COMMAND_LIST[65] = &Scripts::cmdPushLocation;
+ COMMAND_LIST[66] = &Scripts::cmdCheckTravel;
+ COMMAND_LIST[67] = &Scripts::cmdBlock;
+ COMMAND_LIST[68] = &Scripts::cmdPlayerOff;
+ COMMAND_LIST[69] = &Scripts::cmdPlayerOn;
+ COMMAND_LIST[70] = &Scripts::cmdDead;
+ COMMAND_LIST[71] = &Scripts::cmdFadeOut;
+ COMMAND_LIST[72] = &Scripts::cmdEndVideo;
+ COMMAND_LIST[73] = &Scripts::cmdHelp_v1;
+}
+
+void Scripts::setOpcodes_v2() {
+ COMMAND_LIST[9] = &Scripts::cmdPrint_v2;
+ COMMAND_LIST[15] = &Scripts::cmdSetInventory;
+ COMMAND_LIST[28] = &Scripts::cmdDispInv_v2;
+ COMMAND_LIST[29] = &Scripts::cmdSetTimer;
+ COMMAND_LIST[32] = &Scripts::cmdJumpGoto;
+ COMMAND_LIST[40] = &Scripts::cmdVideoEnded;
+ COMMAND_LIST[45] = COMMAND_LIST[46] = &Scripts::cmdSpecial;
+ COMMAND_LIST[63] = COMMAND_LIST[64] = COMMAND_LIST[66] = COMMAND_LIST[67] = &Scripts::cmdPushLocation;
+}
+
void Scripts::setScript(Resource *res, bool restartFlag) {
_resource = res;
_data = res->_stream;
@@ -86,6 +175,50 @@ void Scripts::charLoop() {
_endFlag = endFlag;
}
+void Scripts::clearWatch() {
+ _vm->_events->hideCursor();
+ _vm->_screen->_orgX1 = 128;
+ _vm->_screen->_orgY1 = 57;
+ _vm->_screen->_orgX2 = 228;
+ _vm->_screen->_orgY2 = 106;
+ _vm->_screen->_lColor = 0;
+ _vm->_screen->drawRect();
+
+ _vm->_events->showCursor();
+}
+
+void Scripts::printWatch() {
+ _vm->_fonts._charSet._lo = 8;
+ _vm->_fonts._charSet._hi = 2;
+ _vm->_fonts._charFor._lo = 2;
+ _vm->_fonts._charFor._hi = 255;
+
+ _vm->_screen->_maxChars = 19;
+ _vm->_screen->_printOrg = Common::Point(128, 58);
+ _vm->_screen->_printStart = Common::Point(128, 58);
+ clearWatch();
+
+ Common::String msg = readString();
+ Common::String line = "";
+ int width = 0;
+ bool lastLine;
+ do {
+ lastLine = _vm->_fonts._font2.getLine(msg, _vm->_screen->_maxChars * 6, line, width);
+ // Draw the text
+ _vm->_bubbleBox->printString(line);
+
+ _vm->_screen->_printOrg.y += 6;
+ _vm->_screen->_printOrg.x = _vm->_screen->_printStart.x;
+
+ if (_vm->_screen->_printOrg.y == 106) {
+ _vm->_events->waitKeyMouse();
+ clearWatch();
+ _vm->_screen->_printOrg.y = _vm->_screen->_printStart.y;
+ }
+ } while (!lastLine);
+ _vm->_events->waitKeyMouse();
+}
+
void Scripts::findNull() {
// No implementation required in ScummVM, the strings in the script files are already skipped by the use of readByte()
}
@@ -109,37 +242,7 @@ int Scripts::executeScript() {
return _returnCode;
}
-typedef void(Scripts::*ScriptMethodPtr)();
-
void Scripts::executeCommand(int commandIndex) {
- static const ScriptMethodPtr COMMAND_LIST[] = {
- &Scripts::cmdObject, &Scripts::cmdEndObject, &Scripts::cmdJumpLook,
- &Scripts::cmdJumpHelp, &Scripts::cmdJumpGet, &Scripts::cmdJumpMove,
- &Scripts::cmdJumpUse, &Scripts::cmdJumpTalk, &Scripts::cmdNull,
- &Scripts::cmdPrint, &Scripts::cmdRetPos, &Scripts::cmdAnim,
- &Scripts::cmdSetFlag, &Scripts::cmdCheckFlag, &Scripts::cmdGoto,
- &Scripts::cmdAddScore, &Scripts::cmdSetInventory, &Scripts::cmdCheckInventory,
- &Scripts::cmdSetTex, &Scripts::cmdNewRoom, &Scripts::cmdConverse,
- &Scripts::cmdCheckFrame, &Scripts::cmdCheckAnim, &Scripts::cmdSnd,
- &Scripts::cmdRetNeg, &Scripts::cmdRetPos, &Scripts::cmdCheckLoc,
- &Scripts::cmdSetAnim, &Scripts::cmdDispInv, &Scripts::cmdSetAbout,
- &Scripts::cmdSetTimer, &Scripts::cmdCheckTimer, &Scripts::cmdSetTravel,
- &Scripts::cmdJumpGoto, &Scripts::cmdSetVideo, &Scripts::cmdPlayVideo,
- &Scripts::cmdPlotImage, &Scripts::cmdSetDisplay, &Scripts::cmdSetBuffer,
- &Scripts::cmdSetScroll, &Scripts::cmdSaveRect, &Scripts::cmdVideoEnded,
- &Scripts::cmdSetBufVid, &Scripts::cmdPlayBufVid, &Scripts::cmdRemoveLast,
- &Scripts::cmdDoTravel, &Scripts::cmdCheckAbout, &Scripts::cmdSpecial,
- &Scripts::cmdSetCycle, &Scripts::cmdCycle, &Scripts::cmdCharSpeak,
- &Scripts::cmdTexSpeak, &Scripts::cmdTexChoice, &Scripts::cmdWait,
- &Scripts::cmdSetConPos, &Scripts::cmdCheckVFrame, &Scripts::cmdJumpChoice,
- &Scripts::cmdReturnChoice, &Scripts::cmdClearBlock, &Scripts::cmdLoadSound,
- &Scripts::cmdFreeSound, &Scripts::cmdSetVideoSound, &Scripts::cmdPlayVideoSound,
- &Scripts::cmdPrintWatch, &Scripts::cmdDispAbout, &Scripts::cmdPushLocation,
- &Scripts::cmdCheckTravel, &Scripts::cmdBlock, &Scripts::cmdPlayerOff,
- &Scripts::cmdPlayerOn, &Scripts::cmdDead, &Scripts::cmdFadeOut,
- &Scripts::cmdEndVideo
- };
-
(this->*COMMAND_LIST[commandIndex])();
}
@@ -198,18 +301,36 @@ void Scripts::cmdNull() {
#define PRINT_TIMER 25
-void Scripts::cmdPrint() {
+void Scripts::cmdPrint_v2() {
// Get a text line for display
Common::String msg = readString();
printString(msg);
}
-void Scripts::printString(const Common::String &msg) {
+void Scripts::doCmdPrint_v1(Common::String msg) {
_vm->_screen->_printOrg = Common::Point(20, 42);
- _vm->_screen->_printStart = Common::Point(20, 42);
- _vm->_timers[PRINT_TIMER]._timer = 50;
- _vm->_timers[PRINT_TIMER]._initTm = 50;
- ++_vm->_timers[PRINT_TIMER]._flag;
+ _vm->_screen->_printStart = Common::Point(20, 32);
+ _vm->_bubbleBox->placeBubble(msg);
+ _vm->_events->waitKeyMouse();
+ _vm->_events->hideCursor();
+ _vm->_screen->restoreBlock();
+ _vm->_events->showCursor();
+ findNull();
+}
+
+void Scripts::cmdPrint_v1() {
+ Common::String msg = readString();
+ doCmdPrint_v1(msg);
+}
+
+void Scripts::printString(const Common::String &msg) {
+ if (_vm->getGameID() != GType_MartianMemorandum) {
+ _vm->_screen->_printOrg = Common::Point(20, 42);
+ _vm->_screen->_printStart = Common::Point(20, 42);
+ _vm->_timers[PRINT_TIMER]._timer = 50;
+ _vm->_timers[PRINT_TIMER]._initTm = 50;
+ ++_vm->_timers[PRINT_TIMER]._flag;
+ }
// Display the text in a bubble, and wait for a keypress or mouse click
_vm->_bubbleBox->placeBubble(msg);
@@ -268,11 +389,6 @@ void Scripts::cmdGoto() {
}
void Scripts::cmdAddScore() {
- if (!_vm->isDemo()) {
- cmdSetInventory();
- return;
- }
-
_data->skip(1);
}
@@ -338,8 +454,8 @@ void Scripts::cmdNewRoom() {
cmdRetPos();
}
-void Scripts::cmdConverse() {
- _vm->_conversation = _data->readUint16LE();
+void Scripts::converse1(int val) {
+ _vm->_conversation = val;
_vm->_room->clearRoom();
_vm->freeChar();
_vm->_char->loadChar(_vm->_conversation);
@@ -355,6 +471,11 @@ void Scripts::cmdConverse() {
}
}
+void Scripts::cmdConverse() {
+ int val = _data->readUint16LE();
+ converse1(val);
+}
+
void Scripts::cmdCheckFrame() {
int id = _data->readUint16LE();
Animation *anim = _vm->_animation->findAnimation(id);
@@ -409,17 +530,19 @@ void Scripts::cmdSetAnim() {
_vm->_animation->setAnimTimer(anim);
}
-void Scripts::cmdDispInv() {
+void Scripts::cmdDispInv_v1() {
+ _vm->_inventory->displayInv();
+}
+
+void Scripts::cmdDispInv_v2() {
_vm->_inventory->newDisplayInv();
}
void Scripts::cmdSetAbout() {
- if (!_vm->isDemo()) {
- cmdSetTimer();
- return;
- }
-
- error("TODO: DEMO - cmdSetAbout");
+ int idx = _data->readByte();
+ int val = _data->readByte();
+ _vm->_ask[idx] = val;
+ _vm->_startAboutBox = _vm->_startAboutItem = 0;
}
void Scripts::cmdSetTimer() {
@@ -461,11 +584,10 @@ void Scripts::cmdCheckTimer() {
}
void Scripts::cmdSetTravel() {
- if (!_vm->isDemo()) {
- cmdJumpGoto();
- return;
- }
- error("TODO: DEMO - cmdSetTravel");
+ int idx = _data->readByte();
+ int dest = _data->readByte();
+ _vm->_travel[idx] = dest;
+ _vm->_startTravelItem = _vm->_startTravelBox = 0;
}
void Scripts::cmdJumpGoto() {
@@ -517,11 +639,11 @@ void Scripts::cmdSetScroll() {
}
void Scripts::cmdSaveRect() {
- if (!_vm->isDemo()) {
- cmdVideoEnded();
- return;
- }
- error("TODO: DEMO - cmdSaveRect");
+ int x = _vm->_screen->_lastBoundsX;
+ int y = _vm->_screen->_lastBoundsY;
+ int w = _vm->_screen->_lastBoundsW;
+ int h = _vm->_screen->_lastBoundsH;
+ _vm->_newRects.push_back(Common::Rect(x, y, x + w, x + h));
}
void Scripts::cmdVideoEnded() {
@@ -553,19 +675,81 @@ void Scripts::cmdRemoveLast() {
}
void Scripts::cmdDoTravel() {
- if (!_vm->isDemo()) {
- cmdSpecial();
+ while (true) {
+ _vm->_travelBox->getList(Martian::TRAVDATA, _vm->_travel);
+ int btnSelected = 0;
+ int boxX = _vm->_travelBox->doBox_v1(_vm->_startTravelItem, _vm->_startTravelBox, btnSelected);
+ _vm->_startTravelItem = _vm->_boxDataStart;
+ _vm->_startTravelBox = _vm->_boxSelectY;
+
+ if (boxX == -1)
+ btnSelected = 2;
+
+ if (btnSelected != 2) {
+ int idx = _vm->_travelBox->_tempListIdx[boxX];
+ if (Martian::_byte1EEB5[idx] != _vm->_byte26CB5) {
+ _vm->_bubbleBox->_bubbleTitle = "_travel";
+ _vm->_bubbleBox->printString("YOU CAN'T GET THERE FROM HERE.");
+ continue;
+ }
+ if (_vm->_player->_roomNumber != idx) {
+ _vm->_player->_roomNumber = idx;
+ _vm->_room->_function = FN_CLEAR1;
+ if (Martian::_travelPos[idx][0] == -1) {
+ _vm->_player->_roomNumber = idx;
+ _vm->_room->_conFlag = true;
+ _vm->_scripts->converse1(Martian::_travelPos[idx][1]);
+ return;
+ }
+ _vm->_player->_rawPlayer = Common::Point(Martian::_travelPos[idx][0], Martian::_travelPos[idx][1]);
+ cmdRetPos();
+ return;
+ }
+ }
+
+ if (_vm->_player->_roomNumber == -1)
+ continue;
+
return;
}
- error("TODO: DEMO - cmdDoTravel");
}
-void Scripts::cmdCheckAbout() {
- if (!_vm->isDemo()) {
- cmdSpecial();
- return;
+void Scripts::cmdHelp_v1() {
+ int idx = 0;
+ for (int i = 0; i < 40; i++) {
+ byte c = _data->readByte();
+ if (c != 0xFF) {
+ Common::String tmpStr = c + readString();
+ if (Martian::HELP[i]) {
+ _vm->_helpBox->_tempList[idx] = tmpStr;
+ _vm->_helpBox->_tempListIdx[idx] = i;
+ ++idx;
+ }
+ } else
+ break;
}
- error("TODO: DEMO - cmdCheckAbout");
+ _vm->_helpBox->_tempList[idx] = "";
+
+ int btnSelected = 0;
+ int boxX = _vm->_helpBox->doBox_v1(0, 0, btnSelected);
+
+ if (boxX == -1)
+ btnSelected = 2;
+
+ if (btnSelected != 2)
+ _vm->_useItem = _vm->_helpBox->_tempListIdx[boxX];
+ else
+ _vm->_useItem = -1;
+}
+
+void Scripts::cmdCheckAbout() {
+ int idx = _data->readSint16LE();
+ int val = _data->readSint16LE();
+
+ if (_vm->_ask[idx] == val)
+ cmdGoto();
+ else
+ _data->skip(2);
}
void Scripts::cmdSpecial() {
@@ -574,7 +758,7 @@ void Scripts::cmdSpecial() {
int p2 = _data->readUint16LE();
if (_specialFunction == 1) {
- if (_vm->_establishTable[p2] == 1)
+ if (_vm->_establishTable[p2])
return;
_vm->_screen->savePalette();
@@ -616,33 +800,48 @@ void Scripts::cmdCharSpeak() {
void Scripts::cmdTexSpeak() {
_vm->_screen->_printOrg = _texsOrg;
_vm->_screen->_printStart = _texsOrg;
- _vm->_screen->_maxChars = 20;
+ _vm->_screen->_maxChars = (_vm->getGameID() == GType_MartianMemorandum) ? 23 : 20;
byte v;
Common::String tmpStr = "";
while ((v = _data->readByte()) != 0)
tmpStr += (char)v;
- _vm->_bubbleBox->_bubbleDisplStr = Common::String("JASON");
+ if (_vm->getGameID() == GType_MartianMemorandum)
+ _vm->_bubbleBox->_bubbleDisplStr = Common::String("TEX");
+ else
+ _vm->_bubbleBox->_bubbleDisplStr = Common::String("JASON");
+
_vm->_bubbleBox->placeBubble1(tmpStr);
findNull();
}
#define BTN_COUNT 6
void Scripts::cmdTexChoice() {
- static const int BTN_RANGES[BTN_COUNT][2] = {
- { 0, 76 }, { 77, 154 }, { 155, 232 }, { 233, 276 }, { 0, 0 },
- { 277, 319 }
+ // MM is defining 2 times the last range in the original.
+ static const int BTN_RANGES_v1[BTN_COUNT][2] = {
+ { 0, 60 }, { 64, 124 }, { 129, 192 }, { 194, 227 }, { 233, 292 }, { 297, 319 }
+ };
+
+ static const int BTN_RANGES_v2[BTN_COUNT][2] = {
+ { 0, 76 }, { 77, 154 }, { 155, 232 }, { 233, 276 }, { 0, 0 }, { 277, 319 }
};
+
_vm->_oldRects.clear();
_choiceStart = _data->pos() - 1;
_vm->_fonts._charSet._lo = 1;
_vm->_fonts._charSet._hi = 8;
- _vm->_fonts._charFor._lo = 55;
_vm->_fonts._charFor._hi = 255;
+
+ if (_vm->getGameID() == GType_MartianMemorandum) {
+ _vm->_fonts._charFor._lo = 247;
+ _vm->_screen->_maxChars = 23;
+ } else {
+ _vm->_fonts._charFor._lo = 55;
+ _vm->_screen->_maxChars = 20;
+ }
- _vm->_screen->_maxChars = 20;
_vm->_screen->_printOrg = _texsOrg;
_vm->_screen->_printStart = _texsOrg;
@@ -659,7 +858,7 @@ void Scripts::cmdTexChoice() {
Common::Array<Common::Rect> responseCoords;
responseCoords.push_back(_vm->_bubbleBox->_bounds);
- _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11;
+ _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11);
findNull();
@@ -672,7 +871,7 @@ void Scripts::cmdTexChoice() {
_vm->_bubbleBox->calcBubble(tmpStr);
_vm->_bubbleBox->printBubble(tmpStr);
responseCoords.push_back(_vm->_bubbleBox->_bounds);
- _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11;
+ _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11);
}
findNull();
@@ -687,7 +886,7 @@ void Scripts::cmdTexChoice() {
_vm->_bubbleBox->calcBubble(tmpStr);
_vm->_bubbleBox->printBubble(tmpStr);
responseCoords.push_back(_vm->_bubbleBox->_bounds);
- _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11;
+ _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11);
}
findNull();
@@ -702,11 +901,13 @@ void Scripts::cmdTexChoice() {
_vm->_bubbleBox->_bubbleDisplStr = _vm->_bubbleBox->_bubbleTitle;
if (_vm->_events->_leftButton) {
- if (_vm->_events->_mouseRow >= 22) {
+ if (_vm->_events->_mouseRow >= ((_vm->getGameID() == GType_MartianMemorandum) ? 23 : 22)) {
_vm->_events->debounceLeft();
int x = _vm->_events->_mousePos.x;
for (int i = 0; i < BTN_COUNT; i++) {
- if ((x >= BTN_RANGES[i][0]) && (x < BTN_RANGES[i][1])) {
+ if (((_vm->getGameID() == GType_MartianMemorandum) && (x >= BTN_RANGES_v1[i][0]) && (x < BTN_RANGES_v1[i][1]))
+ || ((_vm->getGameID() == GType_Amazon) && (x >= BTN_RANGES_v2[i][0]) && (x < BTN_RANGES_v2[i][1]))) {
+
choice = i;
break;
}
@@ -827,39 +1028,46 @@ void Scripts::cmdPlayVideoSound() {
}
void Scripts::cmdPrintWatch() {
- if (!_vm->isDemo()) {
- cmdPushLocation();
- return;
- }
- error("TODO: DEMO - cmdPrintWatch");
+ printWatch();
+ findNull();
}
void Scripts::cmdDispAbout() {
- if (!_vm->isDemo()) {
- cmdPushLocation();
- return;
- }
- error("TODO: DEMO - cmdDispAbout");
+ _vm->_travelBox->getList(Martian::_askTBL, _vm->_ask);
+ int btnSelected = 0;
+ int boxX = _vm->_aboutBox->doBox_v1(_vm->_startAboutItem, _vm->_startAboutBox, btnSelected);
+ _vm->_startAboutItem = _vm->_boxDataStart;
+ _vm->_startAboutBox = _vm->_boxSelectY;
+
+ if (boxX == -1)
+ btnSelected = 2;
+
+ if (btnSelected == 2)
+ _vm->_useItem = -1;
+ else
+ _vm->_useItem = _vm->_travelBox->_tempListIdx[boxX];
}
void Scripts::cmdPushLocation() {
- error("TODO cmdPushLocation");
+ _choiceStart = _data->pos() - 1;
}
void Scripts::cmdCheckTravel() {
- if (!_vm->isDemo()) {
- cmdPushLocation();
- return;
- }
- error("TODO: DEMO - cmdCheckTravel");
+ int idx = _data->readSint16LE();
+ int val = _data->readUint16LE();
+
+ if (_vm->_travel[idx] == val)
+ cmdGoto();
+ else
+ _data->skip(2);
}
void Scripts::cmdBlock() {
- if (!_vm->isDemo()) {
- cmdPushLocation();
- return;
- }
- error("TODO: DEMO - cmdBlock");
+ error("TODO: cmdBlock");
+ /*int val1 = */_data->readSint16LE();
+ /*int val2 = */_data->readUint16LE();
+ /*int val3 = */_data->readSint16LE();
+ /*int val4 = */_data->readUint16LE();
}
void Scripts::cmdPlayerOff() {
diff --git a/engines/access/scripts.h b/engines/access/scripts.h
index cfadf6d901..07fd6acfb1 100644
--- a/engines/access/scripts.h
+++ b/engines/access/scripts.h
@@ -35,18 +35,25 @@ class Scripts;
#define SCRIPT_START_BYTE 0xE0
#define ROOM_SCRIPT 2000
+typedef void(Scripts::*ScriptMethodPtr)();
+
class Scripts : public Manager {
private:
Resource *_resource;
int _specialFunction;
- void charLoop();
+ void clearWatch();
+ void printWatch();
+
protected:
Common::SeekableReadStream *_data;
+ ScriptMethodPtr COMMAND_LIST[100];
virtual void executeSpecial(int commandIndex, int param1, int param2) = 0;
virtual void executeCommand(int commandIndex);
+ void charLoop();
+
/**
* Read a null terminated string from the script
*/
@@ -61,7 +68,8 @@ protected:
void cmdJumpUse();
void cmdJumpTalk();
void cmdNull();
- void cmdPrint();
+ void cmdPrint_v1();
+ void cmdPrint_v2();
void cmdAnim();
void cmdSetFlag();
void cmdCheckFlag();
@@ -83,7 +91,8 @@ protected:
void cmdRetNeg();
void cmdCheckLoc();
void cmdSetAnim();
- void cmdDispInv();
+ void cmdDispInv_v1();
+ void cmdDispInv_v2();
void cmdSetAbout();
void cmdSetTimer();
void cmdCheckTimer();
@@ -127,7 +136,8 @@ protected:
void cmdDead();
void cmdFadeOut();
void cmdEndVideo();
- void cmdHelp();
+ void cmdHelp_v1();
+ void cmdHelp_v2();
void cmdCycleBack();
void cmdSetHelp();
public:
@@ -138,11 +148,15 @@ public:
int _choice;
int32 _choiceStart;
Common::Point _charsOrg, _texsOrg;
+
public:
Scripts(AccessEngine *vm);
virtual ~Scripts();
+ void setOpcodes();
+ void setOpcodes_v2();
+
void setScript(Resource *data, bool restartFlag = false);
void freeScriptData();
@@ -152,6 +166,7 @@ public:
int executeScript();
void findNull();
+ void doCmdPrint_v1(Common::String msg);
/**
* Print a given message to the screen in a bubble box
@@ -161,6 +176,7 @@ public:
// Script commands that need to be public
void cmdFreeSound();
void cmdRetPos();
+ void converse1(int val);
};
} // End of namespace Access
diff --git a/engines/access/sound.cpp b/engines/access/sound.cpp
index da267bdc4c..51ffb88f37 100644
--- a/engines/access/sound.cpp
+++ b/engines/access/sound.cpp
@@ -21,9 +21,12 @@
*/
#include "common/algorithm.h"
+#include "common/config-manager.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/wave.h"
+// Miles Audio
+#include "audio/miles.h"
#include "access/access.h"
#include "access/sound.h"
@@ -47,9 +50,6 @@ void SoundManager::clearSounds() {
if (_mixer->isSoundHandleActive(_effectsHandle))
_mixer->stopHandle(_effectsHandle);
- if (_queue.size())
- _queue.remove_at(0);
-
while (_queue.size()) {
delete _queue[0];
_queue.remove_at(0);
@@ -75,26 +75,26 @@ Resource *SoundManager::loadSound(int fileNum, int subfile) {
return _vm->_files->loadFile(fileNum, subfile);
}
-void SoundManager::playSound(int soundIndex) {
- debugC(1, kDebugSound, "playSound(%d)", soundIndex);
+void SoundManager::playSound(int soundIndex, bool loop) {
+ debugC(1, kDebugSound, "playSound(%d, %d)", soundIndex, loop);
int priority = _soundTable[soundIndex]._priority;
- playSound(_soundTable[soundIndex]._res, priority);
+ playSound(_soundTable[soundIndex]._res, priority, loop);
}
-void SoundManager::playSound(Resource *res, int priority) {
+void SoundManager::playSound(Resource *res, int priority, bool loop) {
debugC(1, kDebugSound, "playSound");
byte *resourceData = res->data();
assert(res->_size >= 32);
+ Audio::RewindableAudioStream *audioStream;
+
if (READ_BE_UINT32(resourceData) == MKTAG('R','I','F','F')) {
// CD version uses WAVE-files
Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(resourceData, res->_size, DisposeAfterUse::NO);
- Audio::RewindableAudioStream *audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES);
- _queue.push_back(audioStream);
-
+ audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES);
} else if (READ_BE_UINT32(resourceData) == MKTAG('S', 'T', 'E', 'V')) {
// sound files have a fixed header of 32 bytes in total
// header content:
@@ -134,16 +134,20 @@ void SoundManager::playSound(Resource *res, int priority) {
return;
}
- Audio::RewindableAudioStream *audioStream = Audio::makeRawStream(resourceData + 32, sampleSize, sampleRate, 0, DisposeAfterUse::NO);
- _queue.push_back(audioStream);
-
+ audioStream = Audio::makeRawStream(resourceData + 32, sampleSize, sampleRate, 0, DisposeAfterUse::NO);
} else
error("Unknown format");
+ if (loop) {
+ _queue.push_back(new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::NO));
+ } else {
+ _queue.push_back(audioStream);
+ }
+
if (!_mixer->isSoundHandleActive(_effectsHandle))
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle,
_queue[0], -1, _mixer->kMaxChannelVolume, 0,
- DisposeAfterUse::YES);
+ DisposeAfterUse::NO);
}
void SoundManager::checkSoundQueue() {
@@ -152,12 +156,13 @@ void SoundManager::checkSoundQueue() {
if (_queue.empty() || _mixer->isSoundHandleActive(_effectsHandle))
return;
+ delete _queue[0];
_queue.remove_at(0);
- if (_queue.size())
+ if (_queue.size() && _queue[0])
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle,
_queue[0], -1, _mixer->kMaxChannelVolume, 0,
- DisposeAfterUse::YES);
+ DisposeAfterUse::NO);
}
bool SoundManager::isSFXPlaying() {
@@ -178,7 +183,7 @@ void SoundManager::loadSounds(Common::Array<RoomInfo::SoundIdent> &sounds) {
void SoundManager::stopSound() {
debugC(3, kDebugSound, "stopSound");
- _mixer->stopHandle(Audio::SoundHandle());
+ _mixer->stopHandle(_effectsHandle);
}
void SoundManager::freeSounds() {
@@ -194,18 +199,63 @@ MusicManager::MusicManager(AccessEngine *vm) : _vm(vm) {
_music = nullptr;
_tempMusic = nullptr;
_isLooping = false;
+ _driver = nullptr;
+ _byte1F781 = false;
+
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ // Amazon Guardians of Eden uses MIDPAK inside MIDIDRV.AP
+ // AdLib patches are inside MIDIDRV.AP too, 2nd resource file
+ //
+ // Amazon Guardians of Eden (demo) seems to use another type of driver, possibly written by Access themselves
+ // Martian Memorandum uses this other type of driver as well, which means it makes sense to reverse engineer it.
+ //
+ switch (musicType) {
+ case MT_ADLIB: {
+ if (_vm->getGameID() == GType_Amazon) {
+ Resource *midiDrvResource = _vm->_files->loadFile(92, 1);
+ Common::MemoryReadStream *adLibInstrumentStream = new Common::MemoryReadStream(midiDrvResource->data(), midiDrvResource->_size);
+
+ _driver = Audio::MidiDriver_Miles_AdLib_create("", "", adLibInstrumentStream);
+
+ delete midiDrvResource;
+ delete adLibInstrumentStream;
+ } else {
+ MidiPlayer::createDriver();
+ }
+ break;
+ }
+ case MT_MT32:
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ _nativeMT32 = true;
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ _nativeMT32 = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+#if 0
MidiPlayer::createDriver();
MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+#endif
- int retValue = _driver->open();
- if (retValue == 0) {
- if (_nativeMT32)
- _driver->sendMT32Reset();
- else
- _driver->sendGMReset();
+ if (_driver) {
+ int retValue = _driver->open();
+ if (retValue == 0) {
+ if (_nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
- _driver->setTimerCallback(this, &timerCallback);
+ _driver->setTimerCallback(this, &timerCallback);
+ }
}
}
@@ -215,16 +265,23 @@ MusicManager::~MusicManager() {
}
void MusicManager::send(uint32 b) {
+ // Pass data directly to driver
+ _driver->send(b);
+#if 0
if ((b & 0xF0) == 0xC0 && !_nativeMT32) {
b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
}
Audio::MidiPlayer::send(b);
+#endif
}
void MusicManager::midiPlay() {
debugC(1, kDebugSound, "midiPlay");
+ if (!_driver)
+ return;
+
if (_music->_size < 4) {
error("midiPlay() wrong music resource size");
}
@@ -262,6 +319,8 @@ bool MusicManager::checkMidiDone() {
void MusicManager::midiRepeat() {
debugC(1, kDebugSound, "midiRepeat");
+ if (!_driver)
+ return;
if (!_parser)
return;
@@ -274,6 +333,9 @@ void MusicManager::midiRepeat() {
void MusicManager::stopSong() {
debugC(1, kDebugSound, "stopSong");
+ if (!_driver)
+ return;
+
stop();
}
@@ -292,6 +354,9 @@ void MusicManager::loadMusic(FileIdent file) {
void MusicManager::newMusic(int musicId, int mode) {
debugC(1, kDebugSound, "newMusic(%d, %d)", musicId, mode);
+ if (!_driver)
+ return;
+
if (mode == 1) {
stopSong();
freeMusic();
diff --git a/engines/access/sound.h b/engines/access/sound.h
index 90f6656e26..e11a6b9730 100644
--- a/engines/access/sound.h
+++ b/engines/access/sound.h
@@ -49,20 +49,21 @@ private:
AccessEngine *_vm;
Audio::Mixer *_mixer;
Audio::SoundHandle _effectsHandle;
- Common::Array<Audio::RewindableAudioStream *> _queue;
+ Common::Array<Audio::AudioStream *> _queue;
void clearSounds();
- void playSound(Resource *res, int priority);
+ void playSound(Resource *res, int priority, bool loop);
public:
Common::Array<SoundEntry> _soundTable;
+ bool _playingSound;
public:
SoundManager(AccessEngine *vm, Audio::Mixer *mixer);
~SoundManager();
void loadSoundTable(int idx, int fileNum, int subfile, int priority = 1);
- void playSound(int soundIndex);
+ void playSound(int soundIndex, bool loop = false);
void checkSoundQueue();
bool isSFXPlaying();
@@ -84,6 +85,7 @@ private:
public:
Resource *_music;
+ bool _byte1F781;
public:
MusicManager(AccessEngine *vm);
diff --git a/engines/access/video.cpp b/engines/access/video.cpp
index 63d0aa5c89..5fc5f6762c 100644
--- a/engines/access/video.cpp
+++ b/engines/access/video.cpp
@@ -48,17 +48,13 @@ VideoPlayer::~VideoPlayer() {
closeVideo();
}
-
-void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate) {
+void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, int rate) {
_vidSurface = vidSurface;
vidSurface->_orgX1 = pt.x;
vidSurface->_orgY1 = pt.y;
_vm->_timers[31]._timer = rate;
_vm->_timers[31]._initTm = rate;
- // Open up video stream
- _videoData = _vm->_files->loadFile(videoFile);
-
// Load in header
_header._frameCount = _videoData->_stream->readUint16LE();
_header._width = _videoData->_stream->readUint16LE();
@@ -91,6 +87,20 @@ void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const
_videoEnd = false;
}
+void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const Common::String filename, int rate) {
+ // Open up video stream
+ _videoData = _vm->_files->loadFile(filename);
+
+ setVideo(vidSurface, pt, rate);
+}
+
+void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate) {
+ // Open up video stream
+ _videoData = _vm->_files->loadFile(videoFile);
+
+ setVideo(vidSurface, pt, rate);
+}
+
void VideoPlayer::closeVideo() {
delete _videoData;
_videoData = nullptr;
diff --git a/engines/access/video.h b/engines/access/video.h
index 17825db367..83c8995d3e 100644
--- a/engines/access/video.h
+++ b/engines/access/video.h
@@ -51,6 +51,7 @@ private:
Common::Rect _videoBounds;
void getFrame();
+ void setVideo(ASurface *vidSurface, const Common::Point &pt, int rate);
public:
int _videoFrame;
bool _soundFlag;
@@ -64,6 +65,7 @@ public:
* Start up a video
*/
void setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate);
+ void setVideo(ASurface *vidSurface, const Common::Point &pt, const Common::String filename, int rate);
/**
* Decodes a frame of the video
diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h
index 0ae822a538..af3d93288e 100644
--- a/engines/agi/detection_tables.h
+++ b/engines/agi/detection_tables.h
@@ -850,6 +850,7 @@ static const AGIGameDescription gameDescriptions[] = {
FANMADE("Tex McPhilip 3 - A Destiny of Sin (Demo v0.25)", "992d12031a486ad84e592ff5d7c9d782"),
FANMADE("The 13th Disciple (v1.00)", "887719ad59afce9a41ec057dbb73ad73"),
FANMADE("The Adventures of a Crazed Hermit", "6e3086cbb794d3299a9c5a9792295511"),
+ FANMADE("The Gourd of the Beans", "246f4d94946afb547482d44a53616d06"),
FANMADE("The Grateful Dead", "c2146631afacf8cb455ce24f3d2d46e7"),
FANMADE("The Legend of Shay-Larah 1 - The Lost Prince", "04e720c8e30c9cf12db22ea14a24a3dd"),
FANMADE("The Legend of Zelda: The Fungus of Time (Demo v1.00)", "dcaf8166ceb62a3d9b9aea7f3b197c09"),
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp
index 32d0fdc06a..c5cede71ef 100644
--- a/engines/agi/graphics.cpp
+++ b/engines/agi/graphics.cpp
@@ -72,6 +72,7 @@ static const uint8 egaPalette[16 * 3] = {
* from Donald Duck's Playground (1986) to Manhunter II (1989).
* 16 RGB colors. 3 bits per color component.
*/
+#if 0
static const uint8 atariStAgiPalette[16 * 3] = {
0x0, 0x0, 0x0,
0x0, 0x0, 0x7,
@@ -90,6 +91,7 @@ static const uint8 atariStAgiPalette[16 * 3] = {
0x7, 0x7, 0x4,
0x7, 0x7, 0x7
};
+#endif
/**
* Second generation Apple IIGS AGI palette.
@@ -109,6 +111,8 @@ static const uint8 atariStAgiPalette[16 * 3] = {
* 3.001 (Black Cauldron v1.0O 1989-02-24 (CE))
* 3.003 (Gold Rush! v1.0M 1989-02-28 (CE))
*/
+#if 0
+// FIXME: Identical to amigaAgiPaletteV2
static const uint8 appleIIgsAgiPaletteV2[16 * 3] = {
0x0, 0x0, 0x0,
0x0, 0x0, 0xF,
@@ -127,6 +131,7 @@ static const uint8 appleIIgsAgiPaletteV2[16 * 3] = {
0xE, 0xE, 0x0,
0xF, 0xF, 0xF
};
+#endif
/**
* First generation Amiga & Apple IIGS AGI palette.
diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp
index a1572d7f1f..883107a957 100644
--- a/engines/agi/preagi_mickey.cpp
+++ b/engines/agi/preagi_mickey.cpp
@@ -2299,6 +2299,8 @@ void MickeyEngine::init() {
_gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER] = true;
#endif
+
+ setflag(fSoundOn, true); // enable sound
}
Common::Error MickeyEngine::go() {
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 6eda2eb9aa..8952e649fd 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -585,7 +585,9 @@ Common::Error AGOSEngine::init() {
((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
(getPlatform() == Common::kPlatformDOS)) {
- int ret = _midi->open(getGameType());
+ bool isDemo = (getFeatures() & GF_DEMO) ? true : false;
+
+ int ret = _midi->open(getGameType(), isDemo);
if (ret)
warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h
index 2f4709c49e..793d4081cf 100644
--- a/engines/agos/detection_tables.h
+++ b/engines/agos/detection_tables.h
@@ -1385,7 +1385,7 @@ static const AGOSGameDescription gameDescriptions[] = {
{
{
"simon1",
- "Floppy",
+ "Infocom Floppy",
{
{ "gamepc", GAME_BASEFILE, "9f93d27432ce44a787eef10adb640870", 37070},
@@ -1409,7 +1409,7 @@ static const AGOSGameDescription gameDescriptions[] = {
{
{
"simon1",
- "Floppy",
+ "Infocom Floppy",
{
{ "gamepc", GAME_BASEFILE, "62de24fc579b94fac7d3d23201b65b14", -1},
@@ -1599,11 +1599,11 @@ static const AGOSGameDescription gameDescriptions[] = {
GF_TALKIE
},
- // Simon the Sorcerer 1 - English DOS CD alternate?
+ // Simon the Sorcerer 1 - English DOS CD (Infocom)
{
{
"simon1",
- "CD",
+ "Infocom CD",
{
{ "gamepc", GAME_BASEFILE, "c0b948b6821d2140f8b977144f21027a", -1},
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
new file mode 100644
index 0000000000..294be2b8a7
--- /dev/null
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -0,0 +1,883 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "agos/agos.h"
+#include "agos/drivers/accolade/mididriver.h"
+
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "audio/fmopl.h"
+#include "audio/softsynth/emumidi.h"
+
+namespace AGOS {
+
+#define AGOS_ADLIB_VOICES_COUNT 11
+#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6
+#define AGOS_ADLIB_VOICES_PERCUSSION_START 6
+#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5
+#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9
+
+// 5 instruments on top of the regular MIDI ones
+// used by the MUSIC.DRV variant for percussion instruments
+#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5
+
+const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11
+};
+
+const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = {
+ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+// percussion:
+// voice 6 - base drum - also uses operator 13h
+// voice 7 - snare drum
+// voice 8 - tom tom
+// voice 9 - cymbal
+// voice 10 - hi hat
+const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = {
+ 0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+// hardcoded, dumped from Accolade music system
+// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently
+const byte percussionKeyNoteChannelTable[] = {
+ 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08,
+ 0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F,
+ 0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08
+};
+
+struct InstrumentEntry {
+ byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
+ byte reg40op1; // Level Key Scaling / Total Level
+ byte reg60op1; // Attack Rate / Decay Rate
+ byte reg80op1; // Sustain Level / Release Rate
+ byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
+ byte reg40op2; // Level Key Scaling / Total Level
+ byte reg60op2; // Attack Rate / Decay Rate
+ byte reg80op2; // Sustain Level / Release Rate
+ byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use
+};
+
+// hardcoded, dumped from Accolade music system (INSTR.DAT variant)
+const uint16 frequencyLookUpTable[12] = {
+ 0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF,
+ 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B
+};
+
+// hardcoded, dumped from Accolade music system (MUSIC.DRV variant)
+const uint16 frequencyLookUpTableMusicDrv[12] = {
+ 0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB,
+ 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF
+};
+
+//
+// Accolade adlib music driver
+//
+// Remarks:
+//
+// There are at least 2 variants of this sound system.
+// One for the games Elvira 1 + Elvira 2
+// It seems it was also used for the game "Altered Destiny"
+// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo
+//
+// First one uses the file INSTR.DAT for instrument data, channel mapping etc.
+// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc.
+//
+// The second variant supported dynamic channel allocation for the FM voice channels, but this
+// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too.
+//
+// I have currently not implemented dynamic channel allocation.
+
+class MidiDriver_Accolade_AdLib : public MidiDriver {
+public:
+ MidiDriver_Accolade_AdLib();
+ virtual ~MidiDriver_Accolade_AdLib();
+
+ // MidiDriver
+ int open();
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
+
+ void setVolume(byte volume);
+ virtual uint32 property(int prop, uint32 param);
+
+ bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
+
+ void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
+
+private:
+ bool _musicDrvMode;
+
+ // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel
+ byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
+ // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments
+ byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
+ // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
+ signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT];
+ // simple mapping between MIDI key notes and MT32 key notes
+ byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT];
+
+ // from INSTR.DAT/MUSIC.DRV - adlib instrument data
+ InstrumentEntry *_instrumentTable;
+ byte _instrumentCount;
+
+ struct ChannelEntry {
+ const InstrumentEntry *currentInstrumentPtr;
+ byte currentNote;
+ byte currentA0hReg;
+ byte currentB0hReg;
+ int16 volumeAdjust;
+
+ ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0),
+ currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { }
+ };
+
+ byte _percussionReg;
+
+ OPL::OPL *_opl;
+ int _masterVolume;
+
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+
+ bool _isOpen;
+
+ // stores information about all FM voice channels
+ ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT];
+
+ void onTimer();
+
+ void resetAdLib();
+ void resetAdLibOperatorRegisters(byte baseRegister, byte value);
+ void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
+
+ void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
+ void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
+ void setRegister(int reg, int value);
+ void noteOn(byte FMvoiceChannel, byte note, byte velocity);
+ void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity);
+ void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote);
+};
+
+MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib()
+ : _masterVolume(15), _opl(0),
+ _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) {
+ memset(_channelMapping, 0, sizeof(_channelMapping));
+ memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
+ memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust));
+ memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping));
+
+ _instrumentTable = NULL;
+ _instrumentCount = 0;
+ _musicDrvMode = false;
+ _percussionReg = 0x20;
+}
+
+MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() {
+ if (_instrumentTable) {
+ delete[] _instrumentTable;
+ _instrumentCount = 0;
+ }
+}
+
+int MidiDriver_Accolade_AdLib::open() {
+// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
+
+ _opl = OPL::Config::create(OPL::Config::kOpl2);
+
+ if (!_opl)
+ return -1;
+
+ _opl->init();
+
+ _isOpen = true;
+
+ _opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer));
+
+ resetAdLib();
+
+ // Finally set up default instruments
+ for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) {
+ if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // Regular FM voices with instrument 0
+ programChangeSetInstrument(FMvoiceNr, 0, 0);
+ } else {
+ byte percussionInstrument;
+ if (!_musicDrvMode) {
+ // INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5
+ percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
+ } else {
+ // MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84
+ percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
+ }
+ programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument);
+ }
+ }
+
+ // driver initialization does this here:
+ // INSTR.DAT
+ // noteOn(9, 0x29, 0);
+ // noteOff(9, 0x26, false);
+ // MUSIC.DRV
+ // noteOn(9, 0x26, 0);
+ // noteOff(9, 0x26, false);
+
+ return 0;
+}
+
+void MidiDriver_Accolade_AdLib::close() {
+ delete _opl;
+ _isOpen = false;
+}
+
+void MidiDriver_Accolade_AdLib::setVolume(byte volume) {
+ _masterVolume = volume;
+ //renewNotes(-1, true);
+}
+
+void MidiDriver_Accolade_AdLib::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLib() {
+ // The original driver sent 0x00 to register 0x00 up to 0xF5
+ setRegister(0xBD, 0x00); // Disable rhythm
+
+ // reset FM voice instrument data
+ resetAdLibOperatorRegisters(0x20, 0);
+ resetAdLibOperatorRegisters(0x60, 0);
+ resetAdLibOperatorRegisters(0x80, 0);
+ resetAdLibFMVoiceChannelRegisters(0xA0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xB0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xC0, 0);
+ resetAdLibOperatorRegisters(0xE0, 0);
+ resetAdLibOperatorRegisters(0x40, 0x3F); // original driver sent 0x00
+
+ setRegister(0x01, 0x20); // enable waveform control on both operators
+ setRegister(0x04, 0x60); // Timer control
+
+ setRegister(0x08, 0); // select FM music mode
+ setRegister(0xBD, 0x20); // Enable rhythm
+
+ // reset our percussion register
+ _percussionReg = 0x20;
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
+ byte operatorIndex;
+
+ for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
+ switch (operatorIndex) {
+ case 0x06:
+ case 0x07:
+ case 0x0E:
+ case 0x0F:
+ break;
+ default:
+ setRegister(baseRegister + operatorIndex, value);
+ }
+ }
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
+ byte FMvoiceChannel;
+
+ for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ setRegister(baseRegister + FMvoiceChannel, value);
+ }
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Accolade_AdLib::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ byte mappedChannel = _channelMapping[channel];
+ byte mappedInstrument = 0;
+
+ // Ignore everything that is outside of our channel range
+ if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT)
+ return;
+
+ switch (command) {
+ case 0x80:
+ noteOff(mappedChannel, op1, false);
+ break;
+ case 0x90:
+ // Convert noteOn with velocity 0 to a noteOff
+ if (op2 == 0)
+ return noteOff(mappedChannel, op1, false);
+
+ noteOn(mappedChannel, op1, op2);
+ break;
+ case 0xb0: // Control change
+ // Doesn't seem to be implemented
+ break;
+ case 0xc0: // Program Change
+ mappedInstrument = _instrumentMapping[op1];
+ programChange(mappedChannel, mappedInstrument, op1);
+ break;
+ case 0xa0: // Polyphonic key pressure (aftertouch)
+ case 0xd0: // Channel pressure (aftertouch)
+ // Aftertouch doesn't seem to be implemented
+ break;
+ case 0xe0:
+ // No pitch bend change
+ break;
+ case 0xf0: // SysEx
+ warning("ADLIB: SysEx: %x", b);
+ break;
+ default:
+ warning("ADLIB: Unknown event %02x", command);
+ }
+}
+
+void MidiDriver_Accolade_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+}
+
+void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) {
+ byte adjustedNote = note;
+ byte adjustedVelocity = velocity;
+ byte regValueA0h = 0;
+ byte regValueB0h = 0;
+
+ // adjust velocity
+ int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust;
+ channelVolumeAdjust += adjustedVelocity;
+ channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F);
+
+ // TODO: adjust to global volume
+ // original drivers had a global volume variable, which was 0 for full volume, -64 for half volume
+ // and -128 for mute
+
+ adjustedVelocity = channelVolumeAdjust;
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT
+ // force note-off
+ noteOff(FMvoiceChannel, note, true);
+
+ } else {
+ // MUSIC.DRV
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // force note-off, but only for actual FM voice channels
+ noteOff(FMvoiceChannel, note, true);
+ }
+ }
+
+ if (FMvoiceChannel != 9) {
+ // regular FM voice
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT: adjust key note
+ while (adjustedNote < 24)
+ adjustedNote += 12;
+ adjustedNote -= 12;
+ }
+
+ } else {
+ // percussion channel
+ // MUSIC.DRV variant didn't do this adjustment, it directly used a pointer
+ adjustedNote -= 36;
+ if (adjustedNote > 40) { // Security check
+ warning("ADLIB: bad percussion channel note");
+ return;
+ }
+
+ byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote];
+ if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT)
+ return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug
+
+ // Map the keynote accordingly
+ adjustedNote = _percussionKeyNoteMapping[adjustedNote];
+ // Now overwrite the FM voice channel
+ FMvoiceChannel = percussionChannel;
+ }
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT
+
+ // Save this key note
+ _channels[FMvoiceChannel].currentNote = adjustedNote;
+
+ adjustedVelocity += 24;
+ if (adjustedVelocity > 120)
+ adjustedVelocity = 120;
+ adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+
+ } else {
+ // MUSIC.DRV
+ adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+ }
+
+ // Set volume of voice channel
+ noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity);
+ if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // Set second operator for FM voices + first percussion
+ noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity);
+ }
+
+ if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // Percussion
+ byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
+
+ // Enable bit of the requested percussion type
+ assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
+ _percussionReg |= percussionBits[percussionIdx];
+ setRegister(0xBD, _percussionReg);
+ }
+
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) {
+ // FM voice, Base Drum, Snare Drum + Tom Tom
+ byte adlibNote = adjustedNote;
+ byte adlibOctave = 0;
+ byte adlibFrequencyIdx = 0;
+ uint16 adlibFrequency = 0;
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT
+ if (adlibNote >= 0x60)
+ adlibNote = 0x5F;
+
+ adlibOctave = (adlibNote / 12) - 1;
+ adlibFrequencyIdx = adlibNote % 12;
+ adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx];
+
+ if (adlibFrequency & 0x8000)
+ adlibOctave++;
+ if (adlibOctave & 0x80) {
+ adlibOctave++;
+ adlibFrequency = adlibFrequency >> 1;
+ }
+
+ } else {
+ // MUSIC.DRV variant
+ if (adlibNote >= 19)
+ adlibNote -= 19;
+
+ adlibOctave = (adlibNote / 12);
+ adlibFrequencyIdx = adlibNote % 12;
+ // additional code, that will lookup octave and do a multiplication with it
+ // noteOn however calls the frequency calculation in a way that it multiplies with 0
+ adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx];
+ }
+
+ regValueA0h = adlibFrequency & 0xFF;
+ regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2);
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // set Key-On flag for regular FM voices, but not for percussion
+ regValueB0h |= 0x20;
+ }
+
+ setRegister(0xA0 + FMvoiceChannel, regValueA0h);
+ setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+ _channels[FMvoiceChannel].currentA0hReg = regValueA0h;
+ _channels[FMvoiceChannel].currentB0hReg = regValueB0h;
+
+ if (_musicDrvMode) {
+ // MUSIC.DRV
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) {
+ _channels[FMvoiceChannel].currentNote = adjustedNote;
+ }
+ }
+ }
+}
+
+// 100% the same for INSTR.DAT and MUSIC.DRV variants
+// except for a bug, that was introduced for MUSIC.DRV
+void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) {
+ byte operatorReg = 0;
+ byte regValue40h = 0;
+ const InstrumentEntry *curInstrument = NULL;
+
+ regValue40h = (63 - adjustedVelocity) & 0x3F;
+
+ if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) {
+ // first operator of FM voice channels or first percussion channel
+ curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr;
+ if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound
+ // only one does, instrument wants fixed volume
+ if (operatorNr == 1) {
+ regValue40h = curInstrument->reg40op1;
+ } else {
+ regValue40h = curInstrument->reg40op2;
+ }
+
+ // not sure, if we are supposed to implement these bugs, or not
+#if 0
+ if (!_musicDrvMode) {
+ // Table is 16 bytes instead of 18 bytes
+ if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) {
+ regValue40h = 0;
+ warning("volume set bug (original)");
+ }
+ }
+ if (_musicDrvMode) {
+ // MUSIC.DRV variant has a bug, which will overwrite these registers
+ // for all operators above 11 / 0Bh, which means percussion will always
+ // get a value of 0 (the table holding those bytes was 12 bytes instead of 18
+ if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ regValue40h = 0;
+ warning("volume set bug (original)");
+ }
+ }
+#endif
+ }
+ }
+
+ if (operatorNr == 1) {
+ operatorReg = operator1Register[FMvoiceChannel];
+ } else {
+ operatorReg = operator2Register[FMvoiceChannel];
+ }
+ assert(operatorReg != 0xFF); // Security check
+ setRegister(0x40 + operatorReg, regValue40h);
+}
+
+void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) {
+ byte adjustedNote = note;
+ byte regValueB0h = 0;
+
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // regular FM voice
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT: adjust key note
+ while (adjustedNote < 24)
+ adjustedNote += 12;
+ adjustedNote -= 12;
+ }
+
+ if (!dontCheckNote) {
+ // check, if current note is also the current actually playing channel note
+ if (_channels[FMvoiceChannel].currentNote != adjustedNote)
+ return; // not the same -> ignore this note off command
+ }
+
+ regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit
+ setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+
+ } else {
+ // percussion
+ adjustedNote -= 36;
+ if (adjustedNote > 40) { // Security check
+ warning("ADLIB: bad percussion channel note");
+ return;
+ }
+
+ byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote];
+ if (percussionChannel > AGOS_ADLIB_VOICES_COUNT)
+ return;
+
+ byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
+
+ // Disable bit of the requested percussion type
+ assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
+ _percussionReg &= ~percussionBits[percussionIdx];
+ setRegister(0xBD, _percussionReg);
+ }
+}
+
+void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
+ if (mappedInstrumentNr >= _instrumentCount) {
+ warning("ADLIB: tried to set non-existent instrument");
+ return; // out of range
+ }
+
+ // setup instrument
+ //warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
+
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // Regular FM voice
+ programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr);
+
+ } else {
+ // Percussion
+ // set default instrument (again)
+ byte percussionInstrumentNr = 0;
+ const InstrumentEntry *instrumentPtr;
+
+ if (!_musicDrvMode) {
+ // INSTR.DAT: percussion default instruments start at instrument 1
+ percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
+ } else {
+ // MUSIC.DRV: percussion default instruments start at instrument 0x80
+ percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
+ }
+ if (percussionInstrumentNr >= _instrumentCount) {
+ warning("ADLIB: tried to set non-existent instrument");
+ return;
+ }
+ instrumentPtr = &_instrumentTable[percussionInstrumentNr];
+ _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
+ _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[percussionInstrumentNr];
+ }
+}
+
+void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
+ const InstrumentEntry *instrumentPtr;
+ byte op1Reg = 0;
+ byte op2Reg = 0;
+
+ if (mappedInstrumentNr >= _instrumentCount) {
+ warning("ADLIB: tried to set non-existent instrument");
+ return; // out of range
+ }
+
+ // setup instrument
+ instrumentPtr = &_instrumentTable[mappedInstrumentNr];
+ //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
+
+ op1Reg = operator1Register[FMvoiceChannel];
+ op2Reg = operator2Register[FMvoiceChannel];
+
+ setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
+ setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
+ setRegister(0x60 + op1Reg, instrumentPtr->reg60op1);
+ setRegister(0x80 + op1Reg, instrumentPtr->reg80op1);
+
+ if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // set 2nd operator as well for FM voices and first percussion voice
+ setRegister(0x20 + op2Reg, instrumentPtr->reg20op2);
+ setRegister(0x40 + op2Reg, instrumentPtr->reg40op2);
+ setRegister(0x60 + op2Reg, instrumentPtr->reg60op2);
+ setRegister(0x80 + op2Reg, instrumentPtr->reg80op2);
+
+ if (!_musicDrvMode) {
+ // set Feedback / Algorithm as well
+ setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
+ } else {
+ if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+ // set Feedback / Algorithm as well for regular FM voices only
+ setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
+ }
+ }
+ }
+
+ // Remember instrument
+ _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
+ _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[MIDIinstrumentNr];
+}
+
+void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) {
+ _opl->writeReg(reg, value);
+ //warning("OPL %x %x (%d)", reg, value, value);
+}
+
+uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) {
+ return 0;
+}
+
+// Called right at the start, we get an INSTR.DAT entry
+bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
+ uint16 channelMappingOffset = 0;
+ uint16 channelMappingSize = 0;
+ uint16 instrumentMappingOffset = 0;
+ uint16 instrumentMappingSize = 0;
+ uint16 instrumentVolumeAdjustOffset = 0;
+ uint16 instrumentVolumeAdjustSize = 0;
+ uint16 keyNoteMappingOffset = 0;
+ uint16 keyNoteMappingSize = 0;
+ uint16 instrumentCount = 0;
+ uint16 instrumentDataOffset = 0;
+ uint16 instrumentDataSize = 0;
+ uint16 instrumentEntrySize = 0;
+
+ if (!useMusicDrvFile) {
+ // INSTR.DAT: we expect at least 354 bytes
+ if (driverDataSize < 354)
+ return false;
+
+ // Data is like this:
+ // 128 bytes instrument mapping
+ // 128 bytes instrument volume adjust (signed!)
+ // 16 bytes unknown
+ // 16 bytes channel mapping
+ // 64 bytes key note mapping (not used for MT32)
+ // 1 byte instrument count
+ // 1 byte bytes per instrument
+ // x bytes no instruments used for MT32
+
+ channelMappingOffset = 256 + 16;
+ channelMappingSize = 16;
+ instrumentMappingOffset = 0;
+ instrumentMappingSize = 128;
+ instrumentVolumeAdjustOffset = 128;
+ instrumentVolumeAdjustSize = 128;
+ keyNoteMappingOffset = 256 + 16 + 16;
+ keyNoteMappingSize = 64;
+
+ byte instrDatInstrumentCount = driverData[256 + 16 + 16 + 64];
+ byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1];
+
+ // We expect 9 bytes per instrument
+ if (instrDatBytesPerInstrument != 9)
+ return false;
+ // And we also expect at least one adlib instrument
+ if (!instrDatInstrumentCount)
+ return false;
+
+ instrumentCount = instrDatInstrumentCount;
+ instrumentDataOffset = 256 + 16 + 16 + 64 + 2;
+ instrumentDataSize = instrDatBytesPerInstrument * instrDatInstrumentCount;
+ instrumentEntrySize = instrDatBytesPerInstrument;
+
+ } else {
+ // MUSIC.DRV: we expect at least 468 bytes
+ if (driverDataSize < 468)
+ return false;
+
+ // music.drv is basically a driver, but with a few fixed locations for certain data
+
+ channelMappingOffset = 396;
+ channelMappingSize = 16;
+ instrumentMappingOffset = 140;
+ instrumentMappingSize = 128;
+ instrumentVolumeAdjustOffset = 140 + 128;
+ instrumentVolumeAdjustSize = 128;
+ keyNoteMappingOffset = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn)
+ keyNoteMappingSize = 64;
+
+ // seems to have used 128 + 5 instruments
+ // 128 regular ones and an additional 5 for percussion
+ instrumentCount = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT;
+ instrumentDataOffset = 722;
+ instrumentEntrySize = 9;
+ instrumentDataSize = instrumentCount * instrumentEntrySize;
+ }
+
+ // Channel mapping
+ if (channelMappingSize) {
+ // Get these 16 bytes for MIDI channel mapping
+ if (channelMappingSize != sizeof(_channelMapping))
+ return false;
+
+ memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
+ } else {
+ // Set up straight mapping
+ for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
+ _channelMapping[channelNr] = channelNr;
+ }
+ }
+
+ if (instrumentMappingSize) {
+ // And these for instrument mapping
+ if (instrumentMappingSize > sizeof(_instrumentMapping))
+ return false;
+
+ memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
+ }
+ // Set up straight mapping for the remaining data
+ for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
+ _instrumentMapping[instrumentNr] = instrumentNr;
+ }
+
+ if (instrumentVolumeAdjustSize) {
+ if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust))
+ return false;
+
+ memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize);
+ }
+
+ // Get key note mapping, if available
+ if (keyNoteMappingSize) {
+ if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping))
+ return false;
+
+ memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize);
+ }
+
+ // Check, if there are enough bytes left to hold all instrument data
+ if (driverDataSize < (instrumentDataOffset + instrumentDataSize))
+ return false;
+
+ // We release previous instrument data, just in case
+ if (_instrumentTable)
+ delete[] _instrumentTable;
+
+ _instrumentTable = new InstrumentEntry[instrumentCount];
+ _instrumentCount = instrumentCount;
+
+ byte *instrDATReadPtr = driverData + instrumentDataOffset;
+ InstrumentEntry *instrumentWritePtr = _instrumentTable;
+
+ for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
+ memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry));
+ instrDATReadPtr += instrumentEntrySize;
+ instrumentWritePtr++;
+ }
+
+ // Enable MUSIC.DRV-Mode (slightly different behaviour)
+ if (useMusicDrvFile)
+ _musicDrvMode = true;
+
+ if (_musicDrvMode) {
+ // Extra code for MUSIC.DRV
+
+ // This was done during "programChange" in the original driver
+ instrumentWritePtr = _instrumentTable;
+ for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
+ instrumentWritePtr->reg80op1 |= 0x03; // set release rate
+ instrumentWritePtr->reg80op2 |= 0x03;
+ instrumentWritePtr++;
+ }
+ }
+ return true;
+}
+
+MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename) {
+ byte *driverData = NULL;
+ uint16 driverDataSize = 0;
+ bool isMusicDrvFile = false;
+
+ MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile);
+ if (!driverData)
+ error("ACCOLADE-ADLIB: error during readDriver()");
+
+ MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib();
+ if (driver) {
+ if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
+ delete driver;
+ driver = nullptr;
+ }
+ }
+
+ delete[] driverData;
+ return driver;
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/accolade/driverfile.cpp b/engines/agos/drivers/accolade/driverfile.cpp
new file mode 100644
index 0000000000..4ff2fd550f
--- /dev/null
+++ b/engines/agos/drivers/accolade/driverfile.cpp
@@ -0,0 +1,166 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "agos/agos.h"
+#include "audio/mididrv.h"
+#include "common/error.h"
+#include "common/file.h"
+
+namespace AGOS {
+
+// this reads and gets Accolade driver data
+// we need it for channel mapping, instrument mapping and other things
+// this driver data chunk gets passed to the actual music driver (MT32 / AdLib)
+void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile) {
+ Common::File *driverStream = new Common::File();
+
+ isMusicDrvFile = false;
+
+ if (!driverStream->open(filename)) {
+ error("%s: unable to open file", filename.c_str());
+ }
+
+ if (filename == "INSTR.DAT") {
+ // INSTR.DAT: used by Elvira 1
+ uint32 streamSize = driverStream->size();
+ uint32 streamLeft = streamSize;
+ uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib
+ uint16 chunkSize = 0;
+
+ switch (requestedDriverType) {
+ case MT_ADLIB:
+ skipChunks = 0;
+ break;
+ case MT_MT32:
+ skipChunks = 1; // Skip one entry for MT32
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ do {
+ if (streamLeft < 2)
+ error("%s: unexpected EOF", filename.c_str());
+
+ chunkSize = driverStream->readUint16LE();
+ streamLeft -= 2;
+
+ if (streamLeft < chunkSize)
+ error("%s: unexpected EOF", filename.c_str());
+
+ if (skipChunks) {
+ // Skip the chunk
+ driverStream->skip(chunkSize);
+ streamLeft -= chunkSize;
+
+ skipChunks--;
+ }
+ } while (skipChunks);
+
+ // Seek over the ASCII string until there is a NUL terminator
+ byte curByte = 0;
+
+ do {
+ if (chunkSize == 0)
+ error("%s: no actual instrument data found", filename.c_str());
+
+ curByte = driverStream->readByte();
+ chunkSize--;
+ } while (curByte);
+
+ driverDataSize = chunkSize;
+
+ // Read the requested instrument data entry
+ driverData = new byte[driverDataSize];
+ driverStream->read(driverData, driverDataSize);
+
+ } else if (filename == "MUSIC.DRV") {
+ // MUSIC.DRV / used by Elvira 2 / Waxworks / Simon 1 demo
+ uint32 streamSize = driverStream->size();
+ uint32 streamLeft = streamSize;
+ uint16 getChunk = 0; // 4 for MT32, 2 for AdLib
+
+ switch (requestedDriverType) {
+ case MT_ADLIB:
+ getChunk = 2;
+ break;
+ case MT_MT32:
+ getChunk = 4;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ if (streamLeft < 2)
+ error("%s: unexpected EOF", filename.c_str());
+
+ uint16 chunkCount = driverStream->readUint16LE();
+ streamLeft -= 2;
+
+ if (getChunk >= chunkCount)
+ error("%s: required chunk not available", filename.c_str());
+
+ uint16 headerOffset = 2 + (28 * getChunk);
+ streamLeft -= (28 * getChunk);
+
+ if (streamLeft < 28)
+ error("%s: unexpected EOF", filename.c_str());
+
+ // Seek to required chunk
+ driverStream->seek(headerOffset);
+ driverStream->skip(20); // skip over name
+ streamLeft -= 20;
+
+ uint16 musicDrvSignature = driverStream->readUint16LE();
+ uint16 musicDrvType = driverStream->readUint16LE();
+ uint16 chunkOffset = driverStream->readUint16LE();
+ uint16 chunkSize = driverStream->readUint16LE();
+
+ // Security checks
+ if (musicDrvSignature != 0xFEDC)
+ error("%s: chunk signature mismatch", filename.c_str());
+ if (musicDrvType != 1)
+ error("%s: not a music driver", filename.c_str());
+ if (chunkOffset >= streamSize)
+ error("%s: driver chunk points outside of file", filename.c_str());
+
+ streamLeft = streamSize - chunkOffset;
+ if (streamLeft < chunkSize)
+ error("%s: driver chunk is larger than file", filename.c_str());
+
+ driverDataSize = chunkSize;
+
+ // Read the requested instrument data entry
+ driverData = new byte[driverDataSize];
+
+ driverStream->seek(chunkOffset);
+ driverStream->read(driverData, driverDataSize);
+ isMusicDrvFile = true;
+ }
+
+ driverStream->close();
+ delete driverStream;
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
new file mode 100644
index 0000000000..253fb6b736
--- /dev/null
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
+#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
+
+#include "agos/agos.h"
+#include "audio/mididrv.h"
+#include "common/error.h"
+
+namespace AGOS {
+
+#define AGOS_MIDI_CHANNEL_COUNT 16
+#define AGOS_MIDI_INSTRUMENT_COUNT 128
+
+#define AGOS_MIDI_KEYNOTE_COUNT 64
+
+extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile);
+
+extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename);
+extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
+
+} // End of namespace AGOS
+
+#endif // AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp
new file mode 100644
index 0000000000..319e0ebf56
--- /dev/null
+++ b/engines/agos/drivers/accolade/mt32.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "agos/agos.h"
+#include "agos/drivers/accolade/mididriver.h"
+
+#include "audio/mididrv.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace AGOS {
+
+class MidiDriver_Accolade_MT32 : public MidiDriver {
+public:
+ MidiDriver_Accolade_MT32();
+ virtual ~MidiDriver_Accolade_MT32();
+
+ // MidiDriver
+ int open();
+ void close();
+ bool isOpen() const { return _isOpen; }
+
+ void send(uint32 b);
+
+ MidiChannel *allocateChannel() {
+ if (_driver)
+ return _driver->allocateChannel();
+ return NULL;
+ }
+ MidiChannel *getPercussionChannel() {
+ if (_driver)
+ return _driver->getPercussionChannel();
+ return NULL;
+ }
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ if (_driver)
+ _driver->setTimerCallback(timer_param, timer_proc);
+ }
+
+ uint32 getBaseTempo() {
+ if (_driver) {
+ return _driver->getBaseTempo();
+ }
+ return 1000000 / _baseFreq;
+ }
+
+protected:
+ Common::Mutex _mutex;
+ MidiDriver *_driver;
+ bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI
+
+ bool _isOpen;
+ int _baseFreq;
+
+private:
+ // simple mapping between MIDI channel and MT32 channel
+ byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
+ // simple mapping between MIDI instruments and MT32 instruments
+ byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
+
+public:
+ bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
+};
+
+MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() {
+ _driver = NULL;
+ _isOpen = false;
+ _nativeMT32 = false;
+ _baseFreq = 250;
+
+ memset(_channelMapping, 0, sizeof(_channelMapping));
+ memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
+}
+
+MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() {
+ Common::StackLock lock(_mutex);
+ if (_driver) {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ }
+ _driver = NULL;
+}
+
+int MidiDriver_Accolade_MT32::open() {
+ assert(!_driver);
+
+// debugC(kDebugLevelMT32Driver, "MT32: starting driver");
+
+ // Setup midi driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ // check, if we got a real MT32 (or MUNT, or MUNT over MIDI)
+ switch (musicType) {
+ case MT_MT32:
+ _nativeMT32 = true;
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _nativeMT32 = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ _driver = MidiDriver::createMidi(dev);
+ if (!_driver)
+ return 255;
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ if (_nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+
+ return 0;
+}
+
+void MidiDriver_Accolade_MT32::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Accolade_MT32::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+
+ if (command == 0xF0) {
+ if (_driver) {
+ _driver->send(b);
+ }
+ return;
+ }
+
+ byte mappedChannel = _channelMapping[channel];
+
+ if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) {
+ // channel mapped to an actual MIDI channel, so use that one
+ b = (b & 0xFFFFFFF0) | mappedChannel;
+ if (command == 0xC0) {
+ // Program change
+ // Figure out the requested instrument
+ byte midiInstrument = (b >> 8) & 0xFF;
+ byte mappedInstrument = _instrumentMapping[midiInstrument];
+
+ // If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments
+ if (!_nativeMT32) {
+ mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]);
+ }
+ // And replace it
+ b = (b & 0xFFFF00FF) | (mappedInstrument << 8);
+ }
+
+ if (_driver) {
+ _driver->send(b);
+ }
+ }
+}
+
+// Called right at the start, we get an INSTR.DAT entry
+bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
+ uint16 channelMappingOffset = 0;
+ uint16 channelMappingSize = 0;
+ uint16 instrumentMappingOffset = 0;
+ uint16 instrumentMappingSize = 0;
+
+ if (!useMusicDrvFile) {
+ // INSTR.DAT: we expect at least 354 bytes
+ if (driverDataSize < 354)
+ return false;
+
+ // Data is like this:
+ // 128 bytes instrument mapping
+ // 128 bytes instrument volume adjust (signed!) (not used for MT32)
+ // 16 bytes unknown
+ // 16 bytes channel mapping
+ // 64 bytes key note mapping (not really used for MT32)
+ // 1 byte instrument count
+ // 1 byte bytes per instrument
+ // x bytes no instruments used for MT32
+
+ channelMappingOffset = 256 + 16;
+ channelMappingSize = 16;
+ instrumentMappingOffset = 0;
+ instrumentMappingSize = 128;
+
+ } else {
+ // MUSIC.DRV: we expect at least 468 bytes
+ if (driverDataSize < 468)
+ return false;
+
+ channelMappingOffset = 396;
+ channelMappingSize = 16;
+ instrumentMappingOffset = 140;
+ instrumentMappingSize = 128;
+ }
+
+ // Channel mapping
+ if (channelMappingSize) {
+ // Get these 16 bytes for MIDI channel mapping
+ if (channelMappingSize != sizeof(_channelMapping))
+ return false;
+
+ memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
+ } else {
+ // Set up straight mapping
+ for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
+ _channelMapping[channelNr] = channelNr;
+ }
+ }
+
+ if (instrumentMappingSize) {
+ // And these for instrument mapping
+ if (instrumentMappingSize > sizeof(_instrumentMapping))
+ return false;
+
+ memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
+ }
+ // Set up straight mapping for the remaining data
+ for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
+ _instrumentMapping[instrumentNr] = instrumentNr;
+ }
+ return true;
+}
+
+MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
+ byte *driverData = NULL;
+ uint16 driverDataSize = 0;
+ bool isMusicDrvFile = false;
+
+ MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, isMusicDrvFile);
+ if (!driverData)
+ error("ACCOLADE-ADLIB: error during readDriver()");
+
+ MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32();
+ if (driver) {
+ if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
+ delete driver;
+ driver = nullptr;
+ }
+ }
+
+ delete[] driverData;
+ return driver;
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp
new file mode 100644
index 0000000000..7f1370e8bd
--- /dev/null
+++ b/engines/agos/drivers/simon1/adlib.cpp
@@ -0,0 +1,516 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "agos/drivers/simon1/adlib.h"
+
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "common/file.h"
+
+namespace AGOS {
+
+enum {
+ kChannelUnused = 0xFF,
+ kChannelOrphanedFlag = 0x80,
+
+ kOPLVoicesCount = 9
+};
+
+MidiDriver_Simon1_AdLib::Voice::Voice()
+ : channel(kChannelUnused), note(0), instrTotalLevel(0), instrScalingLevel(0), frequency(0) {
+}
+
+MidiDriver_Simon1_AdLib::MidiDriver_Simon1_AdLib(const byte *instrumentData)
+ : _isOpen(false), _opl(nullptr), _timerProc(nullptr), _timerParam(nullptr),
+ _melodyVoices(0), _amvdrBits(0), _rhythmEnabled(false), _voices(), _midiPrograms(),
+ _instruments(instrumentData) {
+}
+
+MidiDriver_Simon1_AdLib::~MidiDriver_Simon1_AdLib() {
+ close();
+ delete[] _instruments;
+}
+
+int MidiDriver_Simon1_AdLib::open() {
+ if (_isOpen) {
+ return MERR_ALREADY_OPEN;
+ }
+
+ _opl = OPL::Config::create();
+ if (!_opl) {
+ return MERR_DEVICE_NOT_AVAILABLE;
+ }
+
+ if (!_opl->init()) {
+ delete _opl;
+ _opl = nullptr;
+
+ return MERR_CANNOT_CONNECT;
+ }
+
+ _opl->start(new Common::Functor0Mem<void, MidiDriver_Simon1_AdLib>(this, &MidiDriver_Simon1_AdLib::onTimer));
+
+ _opl->writeReg(0x01, 0x20);
+ _opl->writeReg(0x08, 0x40);
+ _opl->writeReg(0xBD, 0xC0);
+ reset();
+
+ _isOpen = true;
+ return 0;
+}
+
+bool MidiDriver_Simon1_AdLib::isOpen() const {
+ return _isOpen;
+}
+
+void MidiDriver_Simon1_AdLib::close() {
+ setTimerCallback(nullptr, nullptr);
+
+ if (_isOpen) {
+ _opl->stop();
+ delete _opl;
+ _opl = nullptr;
+
+ _isOpen = false;
+ }
+}
+
+void MidiDriver_Simon1_AdLib::send(uint32 b) {
+ int channel = b & 0x0F;
+ int command = b & 0xF0;
+ int param1 = (b >> 8) & 0xFF;
+ int param2 = (b >> 16) & 0xFF;
+
+ // The percussion channel is handled specially. The AdLib output uses
+ // channels 11 to 15 for percussions. For this, the original converted
+ // note on on the percussion channel to note on channels 11 to 15 before
+ // giving it to the AdLib output. We do this in here for simplicity.
+ if (command == 0x90 && channel == 9) {
+ param1 -= 36;
+ if (param1 < 0 || param1 >= ARRAYSIZE(_rhythmMap)) {
+ return;
+ }
+
+ channel = _rhythmMap[param1].channel;
+ MidiDriver::send(0xC0 | channel, _rhythmMap[param1].program, 0);
+
+ param1 = _rhythmMap[param1].note;
+ MidiDriver::send(0x80 | channel, param1, param2);
+
+ param2 >>= 1;
+ }
+
+ switch (command) {
+ case 0x80: // note OFF
+ noteOff(channel, param1);
+ break;
+
+ case 0x90: // note ON
+ if (param2 == 0) {
+ noteOff(channel, param1);
+ } else {
+ noteOn(channel, param1, param2);
+ }
+ break;
+
+ case 0xB0: // control change
+ controlChange(channel, param1, param2);
+ break;
+
+ case 0xC0: // program change
+ programChange(channel, param1);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void MidiDriver_Simon1_AdLib::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ _timerParam = timer_param;
+ _timerProc = timer_proc;
+}
+
+uint32 MidiDriver_Simon1_AdLib::getBaseTempo() {
+ return 1000000 / OPL::OPL::kDefaultCallbackFrequency;
+}
+
+void MidiDriver_Simon1_AdLib::onTimer() {
+ if (_timerProc) {
+ (*_timerProc)(_timerParam);
+ }
+}
+
+void MidiDriver_Simon1_AdLib::reset() {
+ resetOPLVoices();
+ resetRhythm();
+ for (int i = 0; i < kNumberOfVoices; ++i) {
+ _voices[i].channel = kChannelUnused;
+ }
+ resetVoices();
+}
+
+void MidiDriver_Simon1_AdLib::resetOPLVoices() {
+ _amvdrBits &= 0xE0;
+ _opl->writeReg(0xBD, _amvdrBits);
+ for (int i = 8; i >= 0; --i) {
+ _opl->writeReg(0xB0 + i, 0);
+ }
+}
+
+void MidiDriver_Simon1_AdLib::resetRhythm() {
+ _melodyVoices = 9;
+ _amvdrBits = 0xC0;
+ _opl->writeReg(0xBD, _amvdrBits);
+}
+
+void MidiDriver_Simon1_AdLib::resetVoices() {
+ memset(_midiPrograms, 0, sizeof(_midiPrograms));
+ for (int i = 0; i < kNumberOfVoices; ++i) {
+ _voices[i].channel = kChannelUnused;
+ }
+
+ for (int i = 0; i < kOPLVoicesCount; ++i) {
+ resetRhythm();
+ _opl->writeReg(0x08, 0x00);
+
+ int oplRegister = _operatorMap[i];
+ for (int j = 0; j < 4; ++j) {
+ oplRegister += 0x20;
+
+ _opl->writeReg(oplRegister + 0, _operatorDefaults[2 * j + 0]);
+ _opl->writeReg(oplRegister + 3, _operatorDefaults[2 * j + 1]);
+ }
+
+ _opl->writeReg(oplRegister + 0x60, 0x00);
+ _opl->writeReg(oplRegister + 0x63, 0x00);
+
+ // This seems to be serious bug but the original does it the same way.
+ _opl->writeReg(_operatorMap[i] + i, 0x08);
+ }
+}
+
+int MidiDriver_Simon1_AdLib::allocateVoice(uint channel) {
+ for (int i = 0; i < _melodyVoices; ++i) {
+ if (_voices[i].channel == (channel | kChannelOrphanedFlag)) {
+ return i;
+ }
+ }
+
+ for (int i = 0; i < _melodyVoices; ++i) {
+ if (_voices[i].channel == kChannelUnused) {
+ return i;
+ }
+ }
+
+ for (int i = 0; i < _melodyVoices; ++i) {
+ if (_voices[i].channel > 0x7F) {
+ return i;
+ }
+ }
+
+ // The original had some logic for a priority based reuse of channels.
+ // However, the priority value is always 0, which causes the first channel
+ // to be picked all the time.
+ const int voice = 0;
+ _opl->writeReg(0xA0 + voice, (_voices[voice].frequency ) & 0xFF);
+ _opl->writeReg(0xB0 + voice, (_voices[voice].frequency >> 8) & 0xFF);
+ return voice;
+}
+
+void MidiDriver_Simon1_AdLib::noteOff(uint channel, uint note) {
+ if (_melodyVoices <= 6 && channel >= 11) {
+ _amvdrBits &= ~(_rhythmInstrumentMask[channel - 11]);
+ _opl->writeReg(0xBD, _amvdrBits);
+ } else {
+ for (int i = 0; i < _melodyVoices; ++i) {
+ if (_voices[i].note == note && _voices[i].channel == channel) {
+ _voices[i].channel |= kChannelOrphanedFlag;
+ _opl->writeReg(0xA0 + i, (_voices[i].frequency ) & 0xFF);
+ _opl->writeReg(0xB0 + i, (_voices[i].frequency >> 8) & 0xFF);
+ return;
+ }
+ }
+ }
+}
+
+void MidiDriver_Simon1_AdLib::noteOn(uint channel, uint note, uint velocity) {
+ if (_rhythmEnabled && channel >= 11) {
+ noteOnRhythm(channel, note, velocity);
+ return;
+ }
+
+ const int voiceNum = allocateVoice(channel);
+ Voice &voice = _voices[voiceNum];
+
+ if ((voice.channel & 0x7F) != channel) {
+ setupInstrument(voiceNum, _midiPrograms[channel]);
+ }
+ voice.channel = channel;
+
+ _opl->writeReg(0x43 + _operatorMap[voiceNum], (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel);
+
+ voice.note = note;
+ if (note >= 0x80) {
+ note = 0;
+ }
+
+ const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note];
+ const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F];
+
+ uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2);
+ uint lowByte = frequency & 0x00FF;
+ voice.frequency = (highByte << 8) | lowByte;
+
+ _opl->writeReg(0xA0 + voiceNum, lowByte);
+ _opl->writeReg(0xB0 + voiceNum, highByte | 0x20);
+}
+
+void MidiDriver_Simon1_AdLib::noteOnRhythm(uint channel, uint note, uint velocity) {
+ const uint voiceNum = channel - 5;
+ Voice &voice = _voices[voiceNum];
+
+ _amvdrBits |= _rhythmInstrumentMask[voiceNum - 6];
+
+ const uint level = (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel;
+ if (voiceNum == 6) {
+ _opl->writeReg(0x43 + _rhythmOperatorMap[voiceNum - 6], level);
+ } else {
+ _opl->writeReg(0x40 + _rhythmOperatorMap[voiceNum - 6], level);
+ }
+
+ voice.note = note;
+ if (note >= 0x80) {
+ note = 0;
+ }
+
+ const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note];
+ const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F];
+
+ uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2);
+ uint lowByte = frequency & 0x00FF;
+ voice.frequency = (highByte << 8) | lowByte;
+
+ const uint oplOperator = _rhythmVoiceMap[voiceNum - 6];
+ _opl->writeReg(0xA0 + oplOperator, lowByte);
+ _opl->writeReg(0xB0 + oplOperator, highByte);
+
+ _opl->writeReg(0xBD, _amvdrBits);
+}
+
+void MidiDriver_Simon1_AdLib::controlChange(uint channel, uint controller, uint value) {
+ // Enable/Disable Rhythm Section
+ if (controller == 0x67) {
+ resetVoices();
+ _rhythmEnabled = (value != 0);
+
+ if (_rhythmEnabled) {
+ _melodyVoices = 6;
+ _amvdrBits = 0xE0;
+ } else {
+ _melodyVoices = 9;
+ _amvdrBits = 0xC0;
+ }
+
+ _voices[6].channel = kChannelUnused;
+ _voices[7].channel = kChannelUnused;
+ _voices[8].channel = kChannelUnused;
+
+ _opl->writeReg(0xBD, _amvdrBits);
+ }
+}
+
+void MidiDriver_Simon1_AdLib::programChange(uint channel, uint program) {
+ _midiPrograms[channel] = program;
+
+ if (_rhythmEnabled && channel >= 11) {
+ setupInstrument(channel - 5, program);
+ } else {
+ // Fully unallocate all previously allocated but now unused voices for
+ // this MIDI channel.
+ for (uint i = 0; i < kOPLVoicesCount; ++i) {
+ if (_voices[i].channel == (channel | kChannelOrphanedFlag)) {
+ _voices[i].channel = kChannelUnused;
+ }
+ }
+
+ // Set the program for all voices allocted for this MIDI channel.
+ for (uint i = 0; i < kOPLVoicesCount; ++i) {
+ if (_voices[i].channel == channel) {
+ setupInstrument(i, program);
+ }
+ }
+ }
+}
+
+void MidiDriver_Simon1_AdLib::setupInstrument(uint voice, uint instrument) {
+ const byte *instrumentData = _instruments + instrument * 16;
+
+ int scaling = instrumentData[3];
+ if (_rhythmEnabled && voice >= 7) {
+ scaling = instrumentData[2];
+ }
+
+ const int scalingLevel = scaling & 0xC0;
+ const int totalLevel = scaling & 0x3F;
+
+ _voices[voice].instrScalingLevel = scalingLevel;
+ _voices[voice].instrTotalLevel = (-(totalLevel - 0x3F)) & 0xFF;
+
+ if (!_rhythmEnabled || voice <= 6) {
+ int oplRegister = _operatorMap[voice];
+ for (int j = 0; j < 4; ++j) {
+ oplRegister += 0x20;
+ _opl->writeReg(oplRegister + 0, *instrumentData++);
+ _opl->writeReg(oplRegister + 3, *instrumentData++);
+ }
+ oplRegister += 0x60;
+ _opl->writeReg(oplRegister + 0, *instrumentData++);
+ _opl->writeReg(oplRegister + 3, *instrumentData++);
+
+ _opl->writeReg(0xC0 + voice, *instrumentData++);
+ } else {
+ voice -= 7;
+
+ int oplRegister = _rhythmOperatorMap[voice + 1];
+ for (int j = 0; j < 4; ++j) {
+ oplRegister += 0x20;
+ _opl->writeReg(oplRegister + 0, *instrumentData++);
+ ++instrumentData;
+ }
+ oplRegister += 0x60;
+ _opl->writeReg(oplRegister + 0, *instrumentData++);
+ ++instrumentData;
+
+ _opl->writeReg(0xC0 + _rhythmVoiceMap[voice + 1], *instrumentData++);
+ }
+}
+
+const int MidiDriver_Simon1_AdLib::_operatorMap[9] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11,
+ 0x12
+};
+
+const int MidiDriver_Simon1_AdLib::_operatorDefaults[8] = {
+ 0x01, 0x11, 0x4F, 0x00, 0xF1, 0xF2, 0x53, 0x74
+};
+
+const int MidiDriver_Simon1_AdLib::_rhythmOperatorMap[5] = {
+ 0x10, 0x14, 0x12, 0x15, 0x11
+};
+
+const uint MidiDriver_Simon1_AdLib::_rhythmInstrumentMask[5] = {
+ 0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+const int MidiDriver_Simon1_AdLib::_rhythmVoiceMap[5] = {
+ 6, 7, 8, 8, 7
+};
+
+const int MidiDriver_Simon1_AdLib::_frequencyIndexAndOctaveTable[128] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x40, 0x41, 0x42, 0x43,
+ 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x60, 0x61, 0x62, 0x63,
+ 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x70, 0x71, 0x72, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B,
+ 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B
+};
+
+const int MidiDriver_Simon1_AdLib::_frequencyTable[16] = {
+ 0x0157, 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202,
+ 0x0220, 0x0241, 0x0263, 0x0287, 0x2100, 0xD121, 0xA307, 0x46A4
+};
+
+const MidiDriver_Simon1_AdLib::RhythmMap MidiDriver_Simon1_AdLib::_rhythmMap[39] = {
+ { 11, 123, 40 },
+ { 12, 127, 50 },
+ { 12, 124, 1 },
+ { 12, 124, 90 },
+ { 13, 125, 50 },
+ { 13, 125, 25 },
+ { 15, 127, 80 },
+ { 13, 125, 25 },
+ { 15, 127, 40 },
+ { 13, 125, 35 },
+ { 15, 127, 90 },
+ { 13, 125, 35 },
+ { 13, 125, 45 },
+ { 14, 126, 90 },
+ { 13, 125, 45 },
+ { 15, 127, 90 },
+ { 0, 0, 0 },
+ { 15, 127, 60 },
+ { 0, 0, 0 },
+ { 13, 125, 60 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 13, 125, 45 },
+ { 13, 125, 40 },
+ { 13, 125, 35 },
+ { 13, 125, 30 },
+ { 13, 125, 25 },
+ { 13, 125, 80 },
+ { 13, 125, 40 },
+ { 13, 125, 80 },
+ { 13, 125, 40 },
+ { 14, 126, 40 },
+ { 15, 127, 60 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 14, 126, 80 },
+ { 0, 0, 0 },
+ { 13, 125, 100 }
+};
+
+MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
+ // Load instrument data.
+ Common::File ibk;
+
+ if (!ibk.open(instrumentFilename)) {
+ return nullptr;
+ }
+
+ if (ibk.readUint32BE() != 0x49424b1a) {
+ return nullptr;
+ }
+
+ byte *instrumentData = new byte[128 * 16];
+ if (ibk.read(instrumentData, 128 * 16) != 128 * 16) {
+ delete[] instrumentData;
+ return nullptr;
+ }
+
+ return new MidiDriver_Simon1_AdLib(instrumentData);
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h
new file mode 100644
index 0000000000..6057bf1b16
--- /dev/null
+++ b/engines/agos/drivers/simon1/adlib.h
@@ -0,0 +1,118 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AGOS_SIMON1_ADLIB_H
+#define AGOS_SIMON1_ADLIB_H
+
+#include "audio/mididrv.h"
+#include "audio/fmopl.h"
+
+namespace AGOS {
+
+class MidiDriver_Simon1_AdLib : public MidiDriver {
+public:
+ MidiDriver_Simon1_AdLib(const byte *instrumentData);
+ virtual ~MidiDriver_Simon1_AdLib();
+
+ // MidiDriver API
+ virtual int open();
+ virtual bool isOpen() const;
+ virtual void close();
+
+ virtual void send(uint32 b);
+
+ virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+ virtual uint32 getBaseTempo();
+
+ virtual MidiChannel *allocateChannel() { return 0; }
+ virtual MidiChannel *getPercussionChannel() { return 0; }
+private:
+ bool _isOpen;
+
+ OPL::OPL *_opl;
+
+ Common::TimerManager::TimerProc _timerProc;
+ void *_timerParam;
+ void onTimer();
+
+ void reset();
+ void resetOPLVoices();
+
+ void resetRhythm();
+ int _melodyVoices;
+ uint8 _amvdrBits;
+ bool _rhythmEnabled;
+
+ enum {
+ kNumberOfVoices = 11,
+ kNumberOfMidiChannels = 16
+ };
+
+ struct Voice {
+ Voice();
+
+ uint channel;
+ uint note;
+ uint instrTotalLevel;
+ uint instrScalingLevel;
+ uint frequency;
+ };
+
+ void resetVoices();
+ int allocateVoice(uint channel);
+
+ Voice _voices[kNumberOfVoices];
+ uint _midiPrograms[kNumberOfMidiChannels];
+
+ void noteOff(uint channel, uint note);
+ void noteOn(uint channel, uint note, uint velocity);
+ void noteOnRhythm(uint channel, uint note, uint velocity);
+ void controlChange(uint channel, uint controller, uint value);
+ void programChange(uint channel, uint program);
+
+ void setupInstrument(uint voice, uint instrument);
+ const byte *_instruments;
+
+ static const int _operatorMap[9];
+ static const int _operatorDefaults[8];
+
+ static const int _rhythmOperatorMap[5];
+ static const uint _rhythmInstrumentMask[5];
+ static const int _rhythmVoiceMap[5];
+
+ static const int _frequencyIndexAndOctaveTable[128];
+ static const int _frequencyTable[16];
+
+ struct RhythmMap {
+ int channel;
+ int program;
+ int note;
+ };
+
+ static const RhythmMap _rhythmMap[39];
+};
+
+MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename);
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 5a1f9f1917..867842276a 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -1303,6 +1303,13 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vgaSpriteId) {
} else {
setWindowImage(mode, vgaSpriteId);
}
+
+ // Amiga versions wait for verb area to be displayed.
+ if (getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformAmiga && vgaSpriteId == 1) {
+ _copyScnFlag = 5;
+ while (_copyScnFlag && !shouldQuit())
+ delay(1);
+ }
}
void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCase) {
diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp
index 687a8ef1cf..686b1c35b2 100644
--- a/engines/agos/input.cpp
+++ b/engines/agos/input.cpp
@@ -707,6 +707,7 @@ bool AGOSEngine::processSpecialKeys() {
if (_midiEnabled) {
_midi->pause(_musicPaused);
}
+ _mixer->pauseHandle(_modHandle, _musicPaused);
syncSoundSettings();
break;
case 's':
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index e5875a8353..d11ff201ea 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -23,10 +23,21 @@
#include "common/config-manager.h"
#include "common/file.h"
#include "common/textconsole.h"
+#include "common/memstream.h"
#include "agos/agos.h"
#include "agos/midi.h"
+#include "agos/drivers/accolade/mididriver.h"
+#include "agos/drivers/simon1/adlib.h"
+// Miles Audio for Simon 2
+#include "audio/miles.h"
+
+// PKWARE data compression library decompressor required for Simon 2
+#include "common/dcl.h"
+
+#include "gui/message.h"
+
namespace AGOS {
@@ -42,8 +53,7 @@ MidiPlayer::MidiPlayer() {
_driver = 0;
_map_mt32_to_gm = false;
- _adlibPatches = NULL;
-
+ _adLibMusic = false;
_enable_sfx = true;
_current = 0;
@@ -54,9 +64,11 @@ MidiPlayer::MidiPlayer() {
_paused = false;
_currentTrack = 255;
- _loopTrackDefault = false;
+ _loopTrack = 0;
_queuedTrack = 255;
_loopQueuedTrack = 0;
+
+ _musicMode = kMusicModeDisabled;
}
MidiPlayer::~MidiPlayer() {
@@ -70,15 +82,183 @@ MidiPlayer::~MidiPlayer() {
}
_driver = NULL;
clearConstructs();
- unloadAdlibPatches();
}
-int MidiPlayer::open(int gameType) {
+int MidiPlayer::open(int gameType, bool isDemo) {
// Don't call open() twice!
assert(!_driver);
- // Setup midi driver
- MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
+ Common::String accoladeDriverFilename;
+ MusicType musicType = MT_INVALID;
+
+ switch (gameType) {
+ case GType_ELVIRA1:
+ _musicMode = kMusicModeAccolade;
+ accoladeDriverFilename = "INSTR.DAT";
+ break;
+ case GType_ELVIRA2:
+ case GType_WW:
+ // Attention: Elvira 2 shipped with INSTR.DAT and MUSIC.DRV
+ // MUSIC.DRV is the correct one. INSTR.DAT seems to be a left-over
+ _musicMode = kMusicModeAccolade;
+ accoladeDriverFilename = "MUSIC.DRV";
+ break;
+ case GType_SIMON1:
+ if (isDemo) {
+ _musicMode = kMusicModeAccolade;
+ accoladeDriverFilename = "MUSIC.DRV";
+ } else if (Common::File::exists("MT_FM.IBK")) {
+ _musicMode = kMusicModeSimon1;
+ }
+ break;
+ case GType_SIMON2:
+ //_musicMode = kMusicModeMilesAudio;
+ // currently disabled, because there are a few issues
+ // MT32 seems to work fine now, AdLib seems to use bad instruments and is also outputting music on
+ // the right speaker only. The original driver did initialize the panning to 0 and the Simon2 XMIDI
+ // tracks don't set panning at all. We can reset panning to be centered, which would solve this
+ // issue, but we still don't know who's setting it in the original interpreter.
+ break;
+ default:
+ break;
+ }
+
+ MidiDriver::DeviceHandle dev;
+ int ret = 0;
+
+ if (_musicMode != kMusicModeDisabled) {
+ dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+ musicType = MidiDriver::getMusicType(dev);
+
+ switch (musicType) {
+ case MT_ADLIB:
+ case MT_MT32:
+ break;
+ case MT_GM:
+ if (!ConfMan.getBool("native_mt32")) {
+ // Not a real MT32 / no MUNT
+ ::GUI::MessageDialog dialog(("You appear to be using a General MIDI device,\n"
+ "but your game only supports Roland MT32 MIDI.\n"
+ "We try to map the Roland MT32 instruments to\n"
+ "General MIDI ones. It is still possible that\n"
+ "some tracks sound incorrect."));
+ dialog.runModal();
+ }
+ // Switch to MT32 driver in any case
+ musicType = MT_MT32;
+ break;
+ default:
+ _musicMode = kMusicModeDisabled;
+ break;
+ }
+ }
+
+ switch (_musicMode) {
+ case kMusicModeAccolade: {
+ // Setup midi driver
+ switch (musicType) {
+ case MT_ADLIB:
+ _driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
+ break;
+ case MT_MT32:
+ _driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ if (!_driver)
+ return 255;
+
+ ret = _driver->open();
+ if (ret == 0) {
+ // Reset is done inside our MIDI driver
+ _driver->setTimerCallback(this, &onTimer);
+ }
+
+ //setTimerRate(_driver->getBaseTempo());
+ return 0;
+ }
+
+ case kMusicModeMilesAudio: {
+ switch (musicType) {
+ case MT_ADLIB: {
+ Common::File instrumentDataFile;
+ if (instrumentDataFile.exists("MIDPAK.AD")) {
+ // if there is a file called MIDPAK.AD, use it directly
+ warning("SIMON 2: using MIDPAK.AD");
+ _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "MIDPAK.AD");
+ } else {
+ // if there is no file called MIDPAK.AD, try to extract it from the file SETUP.SHR
+ // if we didn't do this, the user would be forced to "install" the game instead of simply
+ // copying all files from CD-ROM.
+ Common::SeekableReadStream *midpakAdLibStream;
+ midpakAdLibStream = simon2SetupExtractFile("MIDPAK.AD");
+ if (!midpakAdLibStream)
+ error("MidiPlayer: could not extract MIDPAK.AD from SETUP.SHR");
+
+ // Pass this extracted data to the driver
+ warning("SIMON 2: using MIDPAK.AD extracted from SETUP.SHR");
+ _driver = Audio::MidiDriver_Miles_AdLib_create("", "", midpakAdLibStream);
+ delete midpakAdLibStream;
+ }
+ // TODO: not sure what's going wrong with AdLib
+ // it doesn't seem to matter if we use the regular XMIDI tracks or the 2nd set meant for MT32
+ break;
+ }
+ case MT_MT32:
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ _nativeMT32 = true; // use 2nd set of XMIDI tracks
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ _nativeMT32 = true; // use 2nd set of XMIDI tracks
+ }
+ break;
+
+ default:
+ break;
+ }
+ if (!_driver)
+ return 255;
+
+ ret = _driver->open();
+ if (ret == 0) {
+ // Reset is done inside our MIDI driver
+ _driver->setTimerCallback(this, &onTimer);
+ }
+ return 0;
+ }
+
+ case kMusicModeSimon1: {
+ // This only handles the original AdLib driver of Simon1.
+ if (musicType == MT_ADLIB) {
+ _adLibMusic = true;
+ _map_mt32_to_gm = false;
+ _nativeMT32 = false;
+
+ _driver = createMidiDriverSimon1AdLib("MT_FM.IBK");
+ if (_driver && _driver->open() == 0) {
+ _driver->setTimerCallback(this, &onTimer);
+ // Like the original, we enable the rhythm support by default.
+ _driver->send(0xB0, 0x67, 0x01);
+ return 0;
+ }
+
+ delete _driver;
+ _driver = nullptr;
+ }
+
+ _musicMode = kMusicModeDisabled;
+ }
+
+ default:
+ break;
+ }
+
+ dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
+ _adLibMusic = (MidiDriver::getMusicType(dev) == MT_ADLIB);
_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
_driver = MidiDriver::createMidi(dev);
@@ -88,15 +268,9 @@ int MidiPlayer::open(int gameType) {
if (_nativeMT32)
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
- /* Disabled due to not sounding right, and low volume level
- if (gameType == GType_SIMON1 && MidiDriver::getMusicType(dev) == MT_ADLIB) {
- loadAdlibPatches();
- }
- */
-
_map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32);
- int ret = _driver->open();
+ ret = _driver->open();
if (ret)
return ret;
_driver->setTimerCallback(this, &onTimer);
@@ -113,6 +287,33 @@ void MidiPlayer::send(uint32 b) {
if (!_current)
return;
+ if (_musicMode != kMusicModeDisabled) {
+ // Handle volume control for Simon1 output.
+ if (_musicMode == kMusicModeSimon1) {
+ // The driver does not support any volume control, thus we simply
+ // scale the velocities on note on for now.
+ // TODO: We should probably handle this at output level at some
+ // point. Then we can allow volume changes to affect already
+ // playing notes too. For now this simple change allows us to
+ // have some simple volume control though.
+ if ((b & 0xF0) == 0x90) {
+ byte volume = (b >> 16) & 0x7F;
+
+ if (_current == &_sfx) {
+ volume = volume * _sfxVolume / 255;
+ } else if (_current == &_music) {
+ volume = volume * _musicVolume / 255;
+ }
+
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ }
+ }
+
+ // Send directly to Accolade/Miles/Simon1 Audio driver
+ _driver->send(b);
+ return;
+ }
+
byte channel = (byte)(b & 0x0F);
if ((b & 0xFFF0) == 0x07B0) {
// Adjust volume changes by master music and master sfx volume.
@@ -123,10 +324,8 @@ void MidiPlayer::send(uint32 b) {
else if (_current == &_music)
volume = volume * _musicVolume / 255;
b = (b & 0xFF00FFFF) | (volume << 16);
- } else if ((b & 0xF0) == 0xC0) {
- if (_map_mt32_to_gm && !_adlibPatches) {
- b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8);
- }
+ } else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) {
+ b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8);
} else if ((b & 0xFFF0) == 0x007BB0) {
// Only respond to an All Notes Off if this channel
// has already been allocated.
@@ -146,8 +345,10 @@ void MidiPlayer::send(uint32 b) {
_current->volume[channel] = 127;
}
+ // Allocate channels if needed
if (!_current->channel[channel])
_current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+
if (_current->channel[channel]) {
if (channel == 9) {
if (_current == &_sfx)
@@ -155,16 +356,7 @@ void MidiPlayer::send(uint32 b) {
else if (_current == &_music)
_current->channel[9]->volume(_current->volume[9] * _musicVolume / 255);
}
-
- if ((b & 0xF0) == 0xC0 && _adlibPatches) {
- // NOTE: In the percussion channel, this function is a
- // no-op. Any percussion instruments you hear may
- // be the stock ones from adlib.cpp.
- _driver->sysEx_customInstrument(_current->channel[channel]->getNumber(), 'ADL ', _adlibPatches + 30 * ((b >> 8) & 0xFF));
- } else {
- _current->channel[channel]->send(b);
- }
-
+ _current->channel[channel]->send(b);
if ((b & 0xFFF0) == 0x79B0) {
// We have received a "Reset All Controllers" message
// and passed it on to the MIDI driver. This may or may
@@ -186,13 +378,13 @@ void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) {
return;
} else if (_current == &_sfx) {
clearConstructs(_sfx);
- } else if (_current->loopTrack) {
+ } else if (_loopTrack) {
_current->parser->jumpToTick(0);
} else if (_queuedTrack != 255) {
_currentTrack = 255;
byte destination = _queuedTrack;
_queuedTrack = 255;
- _current->loopTrack = _loopQueuedTrack;
+ _loopTrack = _loopQueuedTrack;
_loopQueuedTrack = false;
// Remember, we're still inside the locked mutex.
@@ -320,7 +512,7 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) {
void MidiPlayer::setLoop(bool loop) {
Common::StackLock lock(_mutex);
- _loopTrackDefault = loop;
+ _loopTrack = loop;
}
void MidiPlayer::queueTrack(int track, bool loop) {
@@ -375,47 +567,6 @@ void MidiPlayer::resetVolumeTable() {
}
}
-void MidiPlayer::loadAdlibPatches() {
- Common::File ibk;
-
- if (!ibk.open("mt_fm.ibk"))
- return;
-
- if (ibk.readUint32BE() == 0x49424b1a) {
- _adlibPatches = new byte[128 * 30];
- byte *ptr = _adlibPatches;
-
- memset(_adlibPatches, 0, 128 * 30);
-
- for (int i = 0; i < 128; i++) {
- byte instr[16];
-
- ibk.read(instr, 16);
-
- ptr[0] = instr[0]; // Modulator Sound Characteristics
- ptr[1] = instr[2]; // Modulator Scaling/Output Level
- ptr[2] = ~instr[4]; // Modulator Attack/Decay
- ptr[3] = ~instr[6]; // Modulator Sustain/Release
- ptr[4] = instr[8]; // Modulator Wave Select
- ptr[5] = instr[1]; // Carrier Sound Characteristics
- ptr[6] = instr[3]; // Carrier Scaling/Output Level
- ptr[7] = ~instr[5]; // Carrier Attack/Delay
- ptr[8] = ~instr[7]; // Carrier Sustain/Release
- ptr[9] = instr[9]; // Carrier Wave Select
- ptr[10] = instr[10]; // Feedback/Connection
-
- // The remaining six bytes are reserved for future use
-
- ptr += 30;
- }
- }
-}
-
-void MidiPlayer::unloadAdlibPatches() {
- delete[] _adlibPatches;
- _adlibPatches = NULL;
-}
-
static const int simon1_gmf_size[] = {
8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138,
6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717,
@@ -472,24 +623,27 @@ void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) {
// 1 BYTE : Major version
// 1 BYTE : Minor version
// 1 BYTE : Ticks (Ranges from 2 - 8, always 2 for SFX)
- // 1 BYTE : Loop control. 0 = no loop, 1 = loop
-
- // In the original, the ticks value indicated how many
- // times the music timer was called before it actually
- // did something. The larger the value the slower the
- // music.
- //
- // We, on the other hand, have a timer rate which is
- // used to control by how much the music advances on
- // each onTimer() call. The larger the value, the
- // faster the music.
- //
- // It seems that 4 corresponds to our base tempo, so
- // this should be the right way to calculate it.
- timerRate = (4 * _driver->getBaseTempo()) / p->data[5];
- p->loopTrack = (p->data[6] != 0);
- } else {
- p->loopTrack = _loopTrackDefault;
+ // 1 BYTE : Loop control. 0 = no loop, 1 = loop (Music only)
+ if (!sfx) {
+ // In the original, the ticks value indicated how many
+ // times the music timer was called before it actually
+ // did something. The larger the value the slower the
+ // music.
+ //
+ // We, on the other hand, have a timer rate which is
+ // used to control by how much the music advances on
+ // each onTimer() call. The larger the value, the
+ // faster the music.
+ //
+ // It seems that 4 corresponds to our base tempo, so
+ // this should be the right way to calculate it.
+ timerRate = (4 * _driver->getBaseTempo()) / p->data[5];
+
+ // According to bug #1004919 calling setLoop() from
+ // within a lock causes a lockup, though I have no
+ // idea when this actually happens.
+ _loopTrack = (p->data[6] != 0);
+ }
}
MidiParser *parser = MidiParser::createParser_SMF();
@@ -559,8 +713,6 @@ void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) {
p->song_sizes[i] = size;
}
- p->loopTrack = _loopTrackDefault;
-
if (!sfx) {
_currentTrack = 255;
resetVolumeTable();
@@ -592,7 +744,6 @@ void MidiPlayer::loadXMIDI(Common::File *in, bool sfx) {
in->seek(pos, 0);
p->data = (byte *)calloc(size, 1);
in->read(p->data, size);
- p->loopTrack = _loopTrackDefault;
} else {
error("Expected 'FORM' tag but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]);
}
@@ -637,8 +788,105 @@ void MidiPlayer::loadS1D(Common::File *in, bool sfx) {
_currentTrack = 255;
resetVolumeTable();
}
- p->loopTrack = _loopTrackDefault;
p->parser = parser; // That plugs the power cord into the wall
}
+#define MIDI_SETUP_BUNDLE_HEADER_SIZE 56
+#define MIDI_SETUP_BUNDLE_FILEHEADER_SIZE 48
+#define MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE 12
+
+// PKWARE data compression library (called "DCL" in ScummVM) was used for storing files within SETUP.SHR
+// we need it to be able to get the file MIDPAK.AD, otherwise we would have to require the user
+// to "install" the game before being able to actually play it, when using AdLib.
+//
+// SETUP.SHR file format:
+// [bundle file header]
+// [compressed file header] [compressed file data]
+// * compressed file count
+Common::SeekableReadStream *MidiPlayer::simon2SetupExtractFile(const Common::String &requestedFileName) {
+ Common::File *setupBundleStream = new Common::File();
+ uint32 bundleSize = 0;
+ uint32 bundleBytesLeft = 0;
+ byte bundleHeader[MIDI_SETUP_BUNDLE_HEADER_SIZE];
+ byte bundleFileHeader[MIDI_SETUP_BUNDLE_FILEHEADER_SIZE];
+ uint16 bundleFileCount = 0;
+ uint16 bundleFileNr = 0;
+
+ Common::String fileName;
+ uint32 fileCompressedSize = 0;
+ byte *fileCompressedDataPtr = nullptr;
+
+ Common::SeekableReadStream *extractedStream = nullptr;
+
+ if (!setupBundleStream->open("setup.shr"))
+ error("MidiPlayer: could not open setup.shr");
+
+ bundleSize = setupBundleStream->size();
+ bundleBytesLeft = bundleSize;
+
+ if (bundleSize < MIDI_SETUP_BUNDLE_HEADER_SIZE)
+ error("MidiPlayer: unexpected EOF in setup.shr");
+
+ if (setupBundleStream->read(bundleHeader, MIDI_SETUP_BUNDLE_HEADER_SIZE) != MIDI_SETUP_BUNDLE_HEADER_SIZE)
+ error("MidiPlayer: setup.shr read error");
+ bundleBytesLeft -= MIDI_SETUP_BUNDLE_HEADER_SIZE;
+
+ // Verify header byte
+ if (bundleHeader[13] != 't')
+ error("MidiPlayer: setup.shr bundle header data mismatch");
+
+ bundleFileCount = READ_LE_UINT16(&bundleHeader[14]);
+
+ // Search for requested file
+ while (bundleFileNr < bundleFileCount) {
+ if (bundleBytesLeft < sizeof(bundleFileHeader))
+ error("MidiPlayer: unexpected EOF in setup.shr");
+
+ if (setupBundleStream->read(bundleFileHeader, sizeof(bundleFileHeader)) != sizeof(bundleFileHeader))
+ error("MidiPlayer: setup.shr read error");
+ bundleBytesLeft -= MIDI_SETUP_BUNDLE_FILEHEADER_SIZE;
+
+ // Extract filename from file-header
+ fileName.clear();
+ for (byte curPos = 0; curPos < MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE; curPos++) {
+ if (!bundleFileHeader[curPos]) // terminating NUL
+ break;
+ fileName.insertChar(bundleFileHeader[curPos], curPos);
+ }
+
+ // Get compressed
+ fileCompressedSize = READ_LE_UINT32(&bundleFileHeader[20]);
+ if (!fileCompressedSize)
+ error("MidiPlayer: compressed file is 0 bytes, data corruption?");
+ if (bundleBytesLeft < fileCompressedSize)
+ error("MidiPlayer: unexpected EOF in setup.shr");
+
+ if (fileName == requestedFileName) {
+ // requested file found
+ fileCompressedDataPtr = new byte[fileCompressedSize];
+
+ if (setupBundleStream->read(fileCompressedDataPtr, fileCompressedSize) != fileCompressedSize)
+ error("MidiPlayer: setup.shr read error");
+
+ Common::MemoryReadStream *compressedStream = nullptr;
+
+ compressedStream = new Common::MemoryReadStream(fileCompressedDataPtr, fileCompressedSize);
+ // we don't know the unpacked size, let decompressor figure it out
+ extractedStream = Common::decompressDCL(compressedStream);
+ delete compressedStream;
+ break;
+ }
+
+ // skip compressed size
+ setupBundleStream->skip(fileCompressedSize);
+ bundleBytesLeft -= fileCompressedSize;
+
+ bundleFileNr++;
+ }
+ setupBundleStream->close();
+ delete setupBundleStream;
+
+ return extractedStream;
+}
+
} // End of namespace AGOS
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 7e78bfef28..fb987fddcf 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -33,10 +33,16 @@ class File;
namespace AGOS {
+enum kMusicMode {
+ kMusicModeDisabled = 0,
+ kMusicModeAccolade = 1,
+ kMusicModeMilesAudio = 2,
+ kMusicModeSimon1 = 3
+};
+
struct MusicInfo {
MidiParser *parser;
byte *data;
- bool loopTrack;
byte num_songs; // For Type 1 SMF resources
byte *songs[16]; // For Type 1 SMF resources
uint32 song_sizes[16]; // For Type 1 SMF resources
@@ -47,7 +53,6 @@ struct MusicInfo {
MusicInfo() { clear(); }
void clear() {
parser = 0; data = 0; num_songs = 0;
- loopTrack = false;
memset(songs, 0, sizeof(songs));
memset(song_sizes, 0, sizeof(song_sizes));
memset(channel, 0, sizeof(channel));
@@ -73,21 +78,18 @@ protected:
// These are only used for music.
byte _currentTrack;
- bool _loopTrackDefault;
+ bool _loopTrack;
byte _queuedTrack;
bool _loopQueuedTrack;
- byte *_adlibPatches;
-
protected:
static void onTimer(void *data);
void clearConstructs();
void clearConstructs(MusicInfo &info);
void resetVolumeTable();
- void loadAdlibPatches();
- void unloadAdlibPatches();
public:
+ bool _adLibMusic;
bool _enable_sfx;
public:
@@ -113,12 +115,17 @@ public:
void setVolume(int musicVol, int sfxVol);
public:
- int open(int gameType);
+ int open(int gameType, bool isDemo);
// MidiDriver_BASE interface implementation
virtual void send(uint32 b);
virtual void metaEvent(byte type, byte *data, uint16 length);
+private:
+ kMusicMode _musicMode;
+
+private:
+ Common::SeekableReadStream *simon2SetupExtractFile(const Common::String &requestedFileName);
};
} // End of namespace AGOS
diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp
index c2c08bf451..7b9a058efc 100644
--- a/engines/agos/midiparser_s1d.cpp
+++ b/engines/agos/midiparser_s1d.cpp
@@ -179,12 +179,43 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
unloadMusic();
+ if (!size)
+ return false;
+
// The original actually just ignores the first two bytes.
byte *pos = data;
- if (*(pos++) != 0xFC)
- debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos);
-
- pos += 1;
+ if (*pos == 0xFC) {
+ // SysEx found right at the start
+ // this seems to happen since Elvira 2, we ignore it
+ // 3rd byte after the SysEx seems to be saved into a global
+
+ // We expect at least 4 bytes in total
+ if (size < 4)
+ return false;
+
+ byte skipOffset = pos[2]; // get second byte after the SysEx
+ // pos[1] seems to have been ignored
+ // pos[3] is saved into a global inside the original interpreters
+
+ // Waxworks + Simon 1 demo typical header is:
+ // 0xFC 0x29 0x07 0x01 [0x00/0x01]
+ // Elvira 2 typical header is:
+ // 0xFC 0x04 0x06 0x06
+
+ if (skipOffset >= 6) {
+ // should be at least 6, so that we skip over the 2 size bytes and the
+ // smallest SysEx possible
+ skipOffset -= 2; // 2 size bytes were already read by previous code outside of this method
+
+ if (size <= skipOffset) // Skip to the end of file? -> something is not correct
+ return false;
+
+ // Do skip over the bytes
+ pos += skipOffset;
+ } else {
+ warning("MidiParser_S1D: unexpected skip offset in music file");
+ }
+ }
// And now we're at the actual data. Only one track.
_numTracks = 1;
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 7069d8005b..e7b773d76f 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -1,6 +1,10 @@
MODULE := engines/agos
MODULE_OBJS := \
+ drivers/accolade/adlib.o \
+ drivers/accolade/driverfile.o \
+ drivers/accolade/mt32.o \
+ drivers/simon1/adlib.o \
agos.o \
charset.o \
charset-fontdata.o \
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 5d6ab60c8b..d04f1735d6 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -220,6 +220,7 @@ void AGOSEngine::playModule(uint16 music) {
}
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream);
+ _mixer->pauseHandle(_modHandle, _musicPaused);
}
void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
@@ -309,7 +310,9 @@ void AGOSEngine::stopMusic() {
}
void AGOSEngine::playSting(uint16 soundId) {
- if (!_midi->_enable_sfx)
+ // The sound effects in floppy disk version of
+ // Simon the Sorcerer 1 are only meant for AdLib
+ if (!_midi->_adLibMusic || !_midi->_enable_sfx)
return;
char filename[15];
diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp
index 812f46504f..762f60bd91 100644
--- a/engines/agos/sound.cpp
+++ b/engines/agos/sound.cpp
@@ -515,7 +515,7 @@ void Sound::readSfxFile(const Common::String &filename) {
// This method is only used by Simon2
void Sound::loadSfxTable(const char *gameFilename, uint32 base) {
- stopAll();
+ stopAllSfx();
delete _effects;
const bool dataIsUnsigned = true;
@@ -684,7 +684,7 @@ void Sound::playRawData(byte *soundData, uint sound, uint size, uint freq) {
memcpy(buffer, soundData, size);
byte flags = 0;
- if (_vm->getPlatform() == Common::kPlatformDOS)
+ if (_vm->getPlatform() == Common::kPlatformDOS && _vm->getGameId() != GID_ELVIRA2)
flags = Audio::FLAG_UNSIGNED;
Audio::AudioStream *stream = Audio::makeRawStream(buffer, size, freq, flags);
diff --git a/engines/agos/zones.cpp b/engines/agos/zones.cpp
index 1644213579..5a753d9b4b 100644
--- a/engines/agos/zones.cpp
+++ b/engines/agos/zones.cpp
@@ -94,8 +94,7 @@ void AGOSEngine::loadZone(uint16 zoneNum, bool useError) {
vpe->sfxFile = NULL;
- if ((getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) &&
- getGameType() == GType_ELVIRA2) {
+ if (getGameType() == GType_ELVIRA2) {
// A singe sound file is used for Amiga and AtariST versions
if (loadVGASoundFile(1, 3)) {
vpe->sfxFile = _block;
diff --git a/engines/bbvs/sound.h b/engines/bbvs/sound.h
index 4e44c2b962..4d3253c48e 100644
--- a/engines/bbvs/sound.h
+++ b/engines/bbvs/sound.h
@@ -38,7 +38,7 @@ public:
void stop();
bool isPlaying();
protected:
- Audio::SeekableAudioStream *_stream;
+ Audio::RewindableAudioStream *_stream;
Audio::SoundHandle _handle;
// Keep the filename for debugging purposes
Common::String _filename;
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index 069a4787ac..0c788b816c 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -101,7 +101,7 @@ struct AdLibSoundInstrument {
byte amDepth;
};
-class AdLibSoundDriver : public PCSoundDriver, Audio::AudioStream {
+class AdLibSoundDriver : public PCSoundDriver {
public:
AdLibSoundDriver(Audio::Mixer *mixer);
virtual ~AdLibSoundDriver();
@@ -112,14 +112,8 @@ public:
virtual void stopChannel(int channel);
virtual void stopAll();
- // AudioStream interface
- virtual int readBuffer(int16 *buffer, const int numSamples);
- virtual bool isStereo() const { return false; }
- virtual bool endOfData() const { return false; }
- virtual int getRate() const { return _sampleRate; }
-
void initCard();
- void update(int16 *buf, int len);
+ void onTimer();
void setupInstrument(const byte *data, int channel);
void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg);
virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi) = 0;
@@ -128,10 +122,8 @@ protected:
UpdateCallback _upCb;
void *_upRef;
- FM_OPL *_opl;
- int _sampleRate;
+ OPL::OPL *_opl;
Audio::Mixer *_mixer;
- Audio::SoundHandle _soundHandle;
byte _vibrato;
int _channelsVolumeTable[4];
@@ -282,17 +274,19 @@ void PCSoundDriver::resetChannel(int channel) {
AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
: _upCb(0), _upRef(0), _mixer(mixer) {
- _sampleRate = _mixer->getOutputRate();
- _opl = makeAdLibOPL(_sampleRate);
+
+ _opl = OPL::Config::create();
+ if (!_opl || !_opl->init())
+ error("Failed to create OPL");
+
memset(_channelsVolumeTable, 0, sizeof(_channelsVolumeTable));
memset(_instrumentsTable, 0, sizeof(_instrumentsTable));
initCard();
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50);
}
AdLibSoundDriver::~AdLibSoundDriver() {
- _mixer->stopHandle(_soundHandle);
- OPLDestroy(_opl);
+ delete _opl;
}
void AdLibSoundDriver::setUpdateCallback(UpdateCallback upCb, void *ref) {
@@ -322,71 +316,52 @@ void AdLibSoundDriver::stopChannel(int channel) {
channel = 6;
}
if (ins->mode == 0 || channel == 6) {
- OPLWriteReg(_opl, 0xB0 | channel, 0);
+ _opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriver::stopAll() {
int i;
for (i = 0; i < 18; ++i) {
- OPLWriteReg(_opl, 0x40 | _operatorsTable[i], 63);
+ _opl->writeReg(0x40 | _operatorsTable[i], 63);
}
for (i = 0; i < 9; ++i) {
- OPLWriteReg(_opl, 0xB0 | i, 0);
+ _opl->writeReg(0xB0 | i, 0);
}
- OPLWriteReg(_opl, 0xBD, 0);
-}
-
-int AdLibSoundDriver::readBuffer(int16 *buffer, const int numSamples) {
- update(buffer, numSamples);
- return numSamples;
+ _opl->writeReg(0xBD, 0);
}
void AdLibSoundDriver::initCard() {
_vibrato = 0x20;
- OPLWriteReg(_opl, 0xBD, _vibrato);
- OPLWriteReg(_opl, 0x08, 0x40);
+ _opl->writeReg(0xBD, _vibrato);
+ _opl->writeReg(0x08, 0x40);
static const int oplRegs[] = { 0x40, 0x60, 0x80, 0x20, 0xE0 };
for (int i = 0; i < 9; ++i) {
- OPLWriteReg(_opl, 0xB0 | i, 0);
+ _opl->writeReg(0xB0 | i, 0);
}
for (int i = 0; i < 9; ++i) {
- OPLWriteReg(_opl, 0xC0 | i, 0);
+ _opl->writeReg(0xC0 | i, 0);
}
for (int j = 0; j < 5; j++) {
for (int i = 0; i < 18; ++i) {
- OPLWriteReg(_opl, oplRegs[j] | _operatorsTable[i], 0);
+ _opl->writeReg(oplRegs[j] | _operatorsTable[i], 0);
}
}
- OPLWriteReg(_opl, 1, 0x20);
- OPLWriteReg(_opl, 1, 0);
+ _opl->writeReg(1, 0x20);
+ _opl->writeReg(1, 0);
}
-void AdLibSoundDriver::update(int16 *buf, int len) {
- static int samplesLeft = 0;
- while (len != 0) {
- int count = samplesLeft;
- if (count > len) {
- count = len;
- }
- samplesLeft -= count;
- len -= count;
- YM3812UpdateOne(_opl, buf, count);
- if (samplesLeft == 0) {
- if (_upCb) {
- (*_upCb)(_upRef);
- }
- samplesLeft = _sampleRate / 50;
- }
- buf += count;
+void AdLibSoundDriver::onTimer() {
+ if (_upCb) {
+ (*_upCb)(_upRef);
}
}
@@ -408,32 +383,32 @@ void AdLibSoundDriver::setupInstrument(const byte *data, int channel) {
if (ins->mode == 0 || ins->channel == 6) {
reg = &ins->regMod;
- OPLWriteReg(_opl, 0x20 | mod, reg->vibrato);
+ _opl->writeReg(0x20 | mod, reg->vibrato);
if (reg->freqMod) {
tmp = reg->outputLevel & 0x3F;
} else {
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel];
tmp = 63 - (2 * tmp + 127) / (2 * 127);
}
- OPLWriteReg(_opl, 0x40 | mod, tmp | (reg->keyScaling << 6));
- OPLWriteReg(_opl, 0x60 | mod, reg->attackDecay);
- OPLWriteReg(_opl, 0x80 | mod, reg->sustainRelease);
+ _opl->writeReg(0x40 | mod, tmp | (reg->keyScaling << 6));
+ _opl->writeReg(0x60 | mod, reg->attackDecay);
+ _opl->writeReg(0x80 | mod, reg->sustainRelease);
if (ins->mode != 0) {
- OPLWriteReg(_opl, 0xC0 | ins->channel, reg->feedbackStrength);
+ _opl->writeReg(0xC0 | ins->channel, reg->feedbackStrength);
} else {
- OPLWriteReg(_opl, 0xC0 | channel, reg->feedbackStrength);
+ _opl->writeReg(0xC0 | channel, reg->feedbackStrength);
}
- OPLWriteReg(_opl, 0xE0 | mod, ins->waveSelectMod);
+ _opl->writeReg(0xE0 | mod, ins->waveSelectMod);
}
reg = &ins->regCar;
- OPLWriteReg(_opl, 0x20 | car, reg->vibrato);
+ _opl->writeReg(0x20 | car, reg->vibrato);
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel];
tmp = 63 - (2 * tmp + 127) / (2 * 127);
- OPLWriteReg(_opl, 0x40 | car, tmp | (reg->keyScaling << 6));
- OPLWriteReg(_opl, 0x60 | car, reg->attackDecay);
- OPLWriteReg(_opl, 0x80 | car, reg->sustainRelease);
- OPLWriteReg(_opl, 0xE0 | car, ins->waveSelectCar);
+ _opl->writeReg(0x40 | car, tmp | (reg->keyScaling << 6));
+ _opl->writeReg(0x60 | car, reg->attackDecay);
+ _opl->writeReg(0x80 | car, reg->sustainRelease);
+ _opl->writeReg(0xE0 | car, ins->waveSelectCar);
}
void AdLibSoundDriver::loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg) {
@@ -490,16 +465,16 @@ void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
if (channel == 6)
oct = 0;
freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = (oct << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
}
if (ins->mode != 0) {
_vibrato |= 1 << (10 - ins->channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
@@ -515,16 +490,16 @@ void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, in
if (ins->mode == 0 || channel == 6) {
uint16 note = 12;
int freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
}
if (ins->mode != 0) {
_vibrato |= 1 << (10 - ins->channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
@@ -562,15 +537,15 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
}
freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = (oct << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
@@ -580,11 +555,11 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in
setupInstrument(data, channel);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
- OPLWriteReg(_opl, 0xB0 | channel, 0);
+ _opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
if (ins->mode != 0) {
channel = ins->channel;
@@ -599,15 +574,15 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in
note = ins->amDepth;
}
int freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
diff --git a/engines/cruise/sound.cpp b/engines/cruise/sound.cpp
index 0b0fab8c4a..f57435f4f7 100644
--- a/engines/cruise/sound.cpp
+++ b/engines/cruise/sound.cpp
@@ -108,7 +108,7 @@ struct VolumeEntry {
int adjusted;
};
-class AdLibSoundDriver : public PCSoundDriver, Audio::AudioStream {
+class AdLibSoundDriver : public PCSoundDriver {
public:
AdLibSoundDriver(Audio::Mixer *mixer);
virtual ~AdLibSoundDriver();
@@ -118,14 +118,8 @@ public:
virtual void stopChannel(int channel);
virtual void stopAll();
- // AudioStream interface
- virtual int readBuffer(int16 *buffer, const int numSamples);
- virtual bool isStereo() const { return false; }
- virtual bool endOfData() const { return false; }
- virtual int getRate() const { return _sampleRate; }
-
void initCard();
- void update(int16 *buf, int len);
+ void onTimer();
void setupInstrument(const byte *data, int channel);
void setupInstrument(const AdLibSoundInstrument *ins, int channel);
void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg);
@@ -135,10 +129,8 @@ public:
void adjustVolume(int channel, int volume);
protected:
- FM_OPL *_opl;
- int _sampleRate;
+ OPL::OPL *_opl;
Audio::Mixer *_mixer;
- Audio::SoundHandle _soundHandle;
byte _vibrato;
VolumeEntry _channelsVolumeTable[5];
@@ -302,8 +294,9 @@ void PCSoundDriver::syncSounds() {
AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
: _mixer(mixer) {
- _sampleRate = _mixer->getOutputRate();
- _opl = makeAdLibOPL(_sampleRate);
+ _opl = OPL::Config::create();
+ if (!_opl || !_opl->init())
+ error("Failed to create OPL");
for (int i = 0; i < 5; ++i) {
_channelsVolumeTable[i].original = 0;
@@ -311,15 +304,15 @@ AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
}
memset(_instrumentsTable, 0, sizeof(_instrumentsTable));
initCard();
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_musicVolume = ConfMan.getBool("music_mute") ? 0 : MIN(255, ConfMan.getInt("music_volume"));
_sfxVolume = ConfMan.getBool("sfx_mute") ? 0 : MIN(255, ConfMan.getInt("sfx_volume"));
+
+ _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50);
}
AdLibSoundDriver::~AdLibSoundDriver() {
- _mixer->stopHandle(_soundHandle);
- OPLDestroy(_opl);
+ delete _opl;
}
void AdLibSoundDriver::syncSounds() {
@@ -368,70 +361,51 @@ void AdLibSoundDriver::stopChannel(int channel) {
channel = 6;
}
if (ins->mode == 0 || channel == 6) {
- OPLWriteReg(_opl, 0xB0 | channel, 0);
+ _opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
void AdLibSoundDriver::stopAll() {
for (int i = 0; i < 18; ++i)
- OPLWriteReg(_opl, 0x40 | _operatorsTable[i], 63);
+ _opl->writeReg(0x40 | _operatorsTable[i], 63);
for (int i = 0; i < 9; ++i)
- OPLWriteReg(_opl, 0xB0 | i, 0);
-
- OPLWriteReg(_opl, 0xBD, 0);
-}
+ _opl->writeReg(0xB0 | i, 0);
-int AdLibSoundDriver::readBuffer(int16 *buffer, const int numSamples) {
- update(buffer, numSamples);
- return numSamples;
+ _opl->writeReg(0xBD, 0);
}
void AdLibSoundDriver::initCard() {
_vibrato = 0x20;
- OPLWriteReg(_opl, 0xBD, _vibrato);
- OPLWriteReg(_opl, 0x08, 0x40);
+ _opl->writeReg(0xBD, _vibrato);
+ _opl->writeReg(0x08, 0x40);
static const int oplRegs[] = { 0x40, 0x60, 0x80, 0x20, 0xE0 };
for (int i = 0; i < 9; ++i) {
- OPLWriteReg(_opl, 0xB0 | i, 0);
+ _opl->writeReg(0xB0 | i, 0);
}
for (int i = 0; i < 9; ++i) {
- OPLWriteReg(_opl, 0xC0 | i, 0);
+ _opl->writeReg(0xC0 | i, 0);
}
for (int j = 0; j < 5; j++) {
for (int i = 0; i < 18; ++i) {
- OPLWriteReg(_opl, oplRegs[j] | _operatorsTable[i], 0);
+ _opl->writeReg(oplRegs[j] | _operatorsTable[i], 0);
}
}
- OPLWriteReg(_opl, 1, 0x20);
- OPLWriteReg(_opl, 1, 0);
+ _opl->writeReg(1, 0x20);
+ _opl->writeReg(1, 0);
}
-void AdLibSoundDriver::update(int16 *buf, int len) {
- static int samplesLeft = 0;
- while (len != 0) {
- int count = samplesLeft;
- if (count > len) {
- count = len;
- }
- samplesLeft -= count;
- len -= count;
- YM3812UpdateOne(_opl, buf, count);
- if (samplesLeft == 0) {
- if (_upCb) {
- (*_upCb)(_upRef);
- }
- samplesLeft = _sampleRate / 50;
- }
- buf += count;
+void AdLibSoundDriver::onTimer() {
+ if (_upCb) {
+ (*_upCb)(_upRef);
}
}
@@ -457,32 +431,32 @@ void AdLibSoundDriver::setupInstrument(const AdLibSoundInstrument *ins, int chan
if (ins->mode == 0 || ins->channel == 6) {
reg = &ins->regMod;
- OPLWriteReg(_opl, 0x20 | mod, reg->vibrato);
+ _opl->writeReg(0x20 | mod, reg->vibrato);
if (reg->freqMod) {
tmp = reg->outputLevel & 0x3F;
} else {
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel].adjusted;
tmp = 63 - (2 * tmp + 127) / (2 * 127);
}
- OPLWriteReg(_opl, 0x40 | mod, tmp | (reg->keyScaling << 6));
- OPLWriteReg(_opl, 0x60 | mod, reg->attackDecay);
- OPLWriteReg(_opl, 0x80 | mod, reg->sustainRelease);
+ _opl->writeReg(0x40 | mod, tmp | (reg->keyScaling << 6));
+ _opl->writeReg(0x60 | mod, reg->attackDecay);
+ _opl->writeReg(0x80 | mod, reg->sustainRelease);
if (ins->mode != 0) {
- OPLWriteReg(_opl, 0xC0 | ins->channel, reg->feedbackStrength);
+ _opl->writeReg(0xC0 | ins->channel, reg->feedbackStrength);
} else {
- OPLWriteReg(_opl, 0xC0 | channel, reg->feedbackStrength);
+ _opl->writeReg(0xC0 | channel, reg->feedbackStrength);
}
- OPLWriteReg(_opl, 0xE0 | mod, ins->waveSelectMod);
+ _opl->writeReg(0xE0 | mod, ins->waveSelectMod);
}
reg = &ins->regCar;
- OPLWriteReg(_opl, 0x20 | car, reg->vibrato);
+ _opl->writeReg(0x20 | car, reg->vibrato);
tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel].adjusted;
tmp = 63 - (2 * tmp + 127) / (2 * 127);
- OPLWriteReg(_opl, 0x40 | car, tmp | (reg->keyScaling << 6));
- OPLWriteReg(_opl, 0x60 | car, reg->attackDecay);
- OPLWriteReg(_opl, 0x80 | car, reg->sustainRelease);
- OPLWriteReg(_opl, 0xE0 | car, ins->waveSelectCar);
+ _opl->writeReg(0x40 | car, tmp | (reg->keyScaling << 6));
+ _opl->writeReg(0x60 | car, reg->attackDecay);
+ _opl->writeReg(0x80 | car, reg->sustainRelease);
+ _opl->writeReg(0xE0 | car, ins->waveSelectCar);
}
void AdLibSoundDriver::loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg) {
@@ -551,15 +525,15 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
}
freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
@@ -570,11 +544,11 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in
setupInstrument(data, channel);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
- OPLWriteReg(_opl, 0xB0 | channel, 0);
+ _opl->writeReg(0xB0 | channel, 0);
}
if (ins->mode != 0) {
_vibrato &= ~(1 << (10 - ins->channel));
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
if (ins->mode != 0) {
channel = ins->channel;
@@ -589,15 +563,15 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in
note = ins->amDepth;
}
int freq = _freqTable[note % 12];
- OPLWriteReg(_opl, 0xA0 | channel, freq);
+ _opl->writeReg(0xA0 | channel, freq);
freq = ((note / 12) << 2) | ((freq & 0x300) >> 8);
if (ins->mode == 0) {
freq |= 0x20;
}
- OPLWriteReg(_opl, 0xB0 | channel, freq);
+ _opl->writeReg(0xB0 | channel, freq);
if (ins->mode != 0) {
_vibrato |= 1 << (10 - channel);
- OPLWriteReg(_opl, 0xBD, _vibrato);
+ _opl->writeReg(0xBD, _vibrato);
}
}
diff --git a/engines/fullpipe/motion.cpp b/engines/fullpipe/motion.cpp
index 9573e0517b..5845ad1501 100644
--- a/engines/fullpipe/motion.cpp
+++ b/engines/fullpipe/motion.cpp
@@ -1232,7 +1232,7 @@ MessageQueue *MovGraph::method50(StaticANIObject *ani, MovArr *movarr, int stati
return 0;
uint idx;
- int movidx;
+ int movidx = 0;
bool done = false;
for (idx = 0; idx <= _items.size() && !done; idx++) {
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index 5ab3271a8f..24bdb858d8 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -389,6 +389,9 @@ void GobEngine::syncSoundSettings() {
Engine::syncSoundSettings();
_init->updateConfig();
+
+ if (_sound)
+ _sound->adlibSyncVolume();
}
void GobEngine::pauseGame() {
diff --git a/engines/gob/map_v2.cpp b/engines/gob/map_v2.cpp
index cb5abe9644..370e62c88f 100644
--- a/engines/gob/map_v2.cpp
+++ b/engines/gob/map_v2.cpp
@@ -163,11 +163,9 @@ void Map_v2::loadMapObjects(const char *avjFile) {
mapHeight = _screenHeight / _tilesHeight;
mapWidth = _screenWidth / _tilesWidth;
- for (int i = 0; i < mapHeight; i++) {
+ for (int i = 0; i < mapHeight; i++)
for (int j = 0; j < mapWidth; j++)
setPass(j, i, mapData.readSByte());
- _vm->_inter->_variables->getAddressOff8(var + i * _passWidth);
- }
}
mapData.seek(tmpPos);
diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp
index 65b43cae7a..1e024d5a50 100644
--- a/engines/gob/sound/adlib.cpp
+++ b/engines/gob/sound/adlib.cpp
@@ -37,6 +37,28 @@ static const int kPitchTomToSnare = 7;
static const int kPitchSnareDrum = kPitchTom + kPitchTomToSnare;
+// Attenuation map for GUI volume slider
+// Note: no volume control in the original engine
+const uint8 AdLib::kVolumeTable[Audio::Mixer::kMaxMixerVolume + 1] = {
+ 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 61, 59, 57, 56, 55,
+ 53, 52, 51, 50, 49, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41,
+ 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33,
+ 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27,
+ 27, 26, 26, 26, 26, 25, 25, 25, 24, 24, 24, 24, 23, 23, 23, 23,
+ 22, 22, 22, 22, 21, 21, 21, 21, 21, 20, 20, 20, 20, 19, 19, 19,
+ 19, 19, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 16, 16, 16,
+ 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 13,
+ 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11,
+ 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9,
+ 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0
+};
+
// Is the operator a modulator (0) or a carrier (1)?
const uint8 AdLib::kOperatorType[kOperatorCount] = {
0, 0, 0, 1, 1, 1,
@@ -93,23 +115,20 @@ const uint16 AdLib::kHihatParams [kParamCount] = {
0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 };
-AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer), _opl(0),
- _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true) {
-
- _rate = _mixer->getOutputRate();
+AdLib::AdLib(int callbackFreq) : _opl(0),
+ _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true), _volume(0) {
initFreqs();
createOPL();
initOPL();
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
- this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ syncVolume();
+
+ _opl->start(new Common::Functor0Mem<void, AdLib>(this, &AdLib::onTimer), callbackFreq);
}
AdLib::~AdLib() {
- _mixer->stopHandle(_handle);
-
delete _opl;
}
@@ -136,46 +155,38 @@ void AdLib::createOPL() {
}
_opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2);
- if (!_opl || !_opl->init(_rate)) {
+ if (!_opl || !_opl->init()) {
delete _opl;
error("Could not create an AdLib emulator");
}
}
-int AdLib::readBuffer(int16 *buffer, const int numSamples) {
+void AdLib::onTimer() {
Common::StackLock slock(_mutex);
- // Nothing to do, fill with silence
- if (!_playing) {
- memset(buffer, 0, numSamples * sizeof(int16));
- return numSamples;
+ // Nothing to do
+ if (!_playing)
+ return;
+
+ // Check if there's anything to do on this step
+ // If not, decrease the poll number and move on
+ if (_toPoll > 0) {
+ _toPoll--;
+ return;
}
- // Read samples from the OPL, polling in more music when necessary
- uint32 samples = numSamples;
- while (samples && _playing) {
- if (_toPoll) {
- const uint32 render = MIN(samples, _toPoll);
-
- _opl->readBuffer(buffer, render);
-
- buffer += render;
- samples -= render;
- _toPoll -= render;
-
- } else {
- // Song ended, fill the rest with silence
- if (_ended) {
- memset(buffer, 0, samples * sizeof(int16));
- samples = 0;
- break;
- }
-
- // Poll more music
- _toPoll = pollMusic(_first);
- _first = false;
+ // Poll until we have to delay until the next poll
+ while (_toPoll == 0 && _playing) {
+ // Song ended, break out
+ if (_ended) {
+ _toPoll = 0;
+ break;
}
+
+ // Poll more music
+ _toPoll = pollMusic(_first);
+ _first = false;
}
// Song ended, loop if requested
@@ -195,24 +206,6 @@ int AdLib::readBuffer(int16 *buffer, const int numSamples) {
} else
_playing = false;
}
-
- return numSamples;
-}
-
-bool AdLib::isStereo() const {
- return _opl->isStereo();
-}
-
-bool AdLib::endOfData() const {
- return !_playing;
-}
-
-bool AdLib::endOfStream() const {
- return false;
-}
-
-int AdLib::getRate() const {
- return _rate;
}
bool AdLib::isPlaying() const {
@@ -231,10 +224,6 @@ void AdLib::setRepeating(int32 repCount) {
_repCount = repCount;
}
-uint32 AdLib::getSamplesPerSecond() const {
- return _rate * (isStereo() ? 2 : 1);
-}
-
void AdLib::startPlay() {
Common::StackLock slock(_mutex);
@@ -442,6 +431,13 @@ void AdLib::writeKeyScaleLevelVolume(uint8 oper) {
volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper];
volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume));
+ // Adjust carriers for GUI volume slider
+ if (kOperatorType[oper] == 1) {
+ volume += kVolumeTable[_volume];
+ if (volume > 63)
+ volume = 63;
+ }
+
uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6;
writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale);
@@ -639,4 +635,23 @@ void AdLib::setFreq(uint8 voice, uint16 note, bool on) {
writeOPL(0xB0 + voice, value);
}
+void AdLib::setTimerFrequency(int timerFrequency) {
+ _opl->setCallbackFrequency(timerFrequency);
+}
+
+void AdLib::syncVolume() {
+ Common::StackLock slock(_mutex);
+
+ bool mute = false;
+ if (ConfMan.hasKey("mute"))
+ mute = ConfMan.getBool("mute");
+
+ _volume = (mute ? 0 : ConfMan.getInt("music_volume"));
+
+ if (_playing) {
+ for(int i = 0; i < kOperatorCount; i++)
+ writeKeyScaleLevelVolume(i);
+ }
+}
+
} // End of namespace Gob
diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h
index 8071249374..d60458295c 100644
--- a/engines/gob/sound/adlib.h
+++ b/engines/gob/sound/adlib.h
@@ -35,9 +35,9 @@ namespace OPL {
namespace Gob {
/** Base class for a player of an AdLib music format. */
-class AdLib : public Audio::AudioStream {
+class AdLib {
public:
- AdLib(Audio::Mixer &mixer);
+ AdLib(int callbackFrequency);
virtual ~AdLib();
bool isPlaying() const; ///< Are we currently playing?
@@ -53,13 +53,7 @@ public:
void startPlay();
void stopPlay();
-
-// AudioStream API
- int readBuffer(int16 *buffer, const int numSamples);
- bool isStereo() const;
- bool endOfData() const;
- bool endOfStream() const;
- int getRate() const;
+ void syncVolume();
protected:
enum kVoice {
@@ -120,8 +114,6 @@ protected:
static const int kOPLMidC = 48; ///< A mid C for the OPL.
- /** Return the number of samples per second. */
- uint32 getSamplesPerSecond() const;
/** Write a value into an OPL register. */
void writeOPL(byte reg, byte val);
@@ -135,7 +127,7 @@ protected:
/** The callback function that's called for polling more AdLib commands.
*
* @param first Is this the first poll since the start of the song?
- * @return The number of samples until the next poll.
+ * @return The number of ticks until the next poll.
*/
virtual uint32 pollMusic(bool first) = 0;
@@ -207,7 +199,14 @@ protected:
/** Switch a voice off. */
void noteOff(uint8 voice);
+ /**
+ * Set the OPL timer frequency
+ */
+ void setTimerFrequency(int timerFrequency);
+
private:
+ static const uint8 kVolumeTable[Audio::Mixer::kMaxMixerVolume + 1];
+
static const uint8 kOperatorType [kOperatorCount];
static const uint8 kOperatorOffset[kOperatorCount];
static const uint8 kOperatorVoice [kOperatorCount];
@@ -226,13 +225,11 @@ private:
static const uint16 kHihatParams [kParamCount];
- Audio::Mixer *_mixer;
- Audio::SoundHandle _handle;
OPL::OPL *_opl;
Common::Mutex _mutex;
- uint32 _rate;
+ int _volume;
uint32 _toPoll;
@@ -300,6 +297,11 @@ private:
void changePitch(uint8 voice, uint16 pitchBend);
void setFreq(uint8 voice, uint16 note, bool on);
+
+ /**
+ * Callback function for OPL
+ */
+ void onTimer();
};
} // End of namespace Gob
diff --git a/engines/gob/sound/adlplayer.cpp b/engines/gob/sound/adlplayer.cpp
index 384a928360..6354d8c37f 100644
--- a/engines/gob/sound/adlplayer.cpp
+++ b/engines/gob/sound/adlplayer.cpp
@@ -28,7 +28,7 @@
namespace Gob {
-ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+ADLPlayer::ADLPlayer() : AdLib(1000),
_songData(0), _songDataSize(0), _playPos(0) {
}
@@ -135,14 +135,7 @@ uint32 ADLPlayer::pollMusic(bool first) {
if (delay & 0x80)
delay = ((delay & 3) << 8) | *_playPos++;
- return getSampleDelay(delay);
-}
-
-uint32 ADLPlayer::getSampleDelay(uint16 delay) const {
- if (delay == 0)
- return 0;
-
- return ((uint32)delay * getSamplesPerSecond()) / 1000;
+ return delay;
}
void ADLPlayer::rewind() {
diff --git a/engines/gob/sound/adlplayer.h b/engines/gob/sound/adlplayer.h
index 3edd238343..50e1db5b70 100644
--- a/engines/gob/sound/adlplayer.h
+++ b/engines/gob/sound/adlplayer.h
@@ -36,7 +36,7 @@ namespace Gob {
/** A player for Coktel Vision's ADL music format. */
class ADLPlayer : public AdLib {
public:
- ADLPlayer(Audio::Mixer &mixer);
+ ADLPlayer();
~ADLPlayer();
bool load(Common::SeekableReadStream &adl);
@@ -76,8 +76,6 @@ private:
bool readHeader (Common::SeekableReadStream &adl, int &timbreCount);
bool readTimbres (Common::SeekableReadStream &adl, int timbreCount);
bool readSongData(Common::SeekableReadStream &adl);
-
- uint32 getSampleDelay(uint16 delay) const;
};
} // End of namespace Gob
diff --git a/engines/gob/sound/musplayer.cpp b/engines/gob/sound/musplayer.cpp
index 7001a5724b..dcbb712b56 100644
--- a/engines/gob/sound/musplayer.cpp
+++ b/engines/gob/sound/musplayer.cpp
@@ -27,7 +27,7 @@
namespace Gob {
-MUSPlayer::MUSPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+MUSPlayer::MUSPlayer() : AdLib(60),
_songData(0), _songDataSize(0), _playPos(0), _songID(0) {
}
@@ -43,15 +43,6 @@ void MUSPlayer::unload() {
unloadMUS();
}
-uint32 MUSPlayer::getSampleDelay(uint16 delay) const {
- if (delay == 0)
- return 0;
-
- uint32 freq = (_ticksPerBeat * _tempo) / 60;
-
- return ((uint32)delay * getSamplesPerSecond()) / freq;
-}
-
void MUSPlayer::skipToTiming() {
while (*_playPos < 0x80)
_playPos++;
@@ -66,8 +57,12 @@ uint32 MUSPlayer::pollMusic(bool first) {
return 0;
}
- if (first)
- return getSampleDelay(*_playPos++);
+ if (first) {
+ // Set the timer frequency on first run.
+ // Do not set it in rewind() for thread safety reasons.
+ setTimerFrequency((_ticksPerBeat * _tempo) / 60);
+ return *_playPos++;
+ }
uint16 delay = 0;
while (delay == 0) {
@@ -100,6 +95,7 @@ uint32 MUSPlayer::pollMusic(bool first) {
uint32 denom = *_playPos++;
_tempo = _baseTempo * num + ((_baseTempo * denom) >> 7);
+ setTimerFrequency((_ticksPerBeat * _tempo) / 60);
_playPos++;
} else {
@@ -182,7 +178,7 @@ uint32 MUSPlayer::pollMusic(bool first) {
delay += *_playPos++;
}
- return getSampleDelay(delay);
+ return delay;
}
void MUSPlayer::rewind() {
diff --git a/engines/gob/sound/musplayer.h b/engines/gob/sound/musplayer.h
index c76c5aab38..973192ffd9 100644
--- a/engines/gob/sound/musplayer.h
+++ b/engines/gob/sound/musplayer.h
@@ -40,7 +40,7 @@ namespace Gob {
*/
class MUSPlayer : public AdLib {
public:
- MUSPlayer(Audio::Mixer &mixer);
+ MUSPlayer();
~MUSPlayer();
/** Load the instruments (.SND or .TBR) */
@@ -97,7 +97,6 @@ private:
bool readMUSHeader(Common::SeekableReadStream &mus);
bool readMUSSong (Common::SeekableReadStream &mus);
- uint32 getSampleDelay(uint16 delay) const;
void setInstrument(uint8 voice, uint8 instrument);
void skipToTiming();
diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp
index d2b2d3d6e8..22dfe9d3c3 100644
--- a/engines/gob/sound/sound.cpp
+++ b/engines/gob/sound/sound.cpp
@@ -234,7 +234,7 @@ bool Sound::adlibLoadADL(const char *fileName) {
return false;
if (!_adlPlayer)
- _adlPlayer = new ADLPlayer(*_vm->_mixer);
+ _adlPlayer = new ADLPlayer();
debugC(1, kDebugSound, "AdLib: Loading ADL data (\"%s\")", fileName);
@@ -256,7 +256,7 @@ bool Sound::adlibLoadADL(byte *data, uint32 size, int index) {
return false;
if (!_adlPlayer)
- _adlPlayer = new ADLPlayer(*_vm->_mixer);
+ _adlPlayer = new ADLPlayer();
debugC(1, kDebugSound, "AdLib: Loading ADL data (%d)", index);
@@ -425,6 +425,16 @@ int32 Sound::adlibGetRepeating() const {
return false;
}
+void Sound::adlibSyncVolume() {
+ if (!_hasAdLib)
+ return;
+
+ if (_adlPlayer)
+ _adlPlayer->syncVolume();
+ if (_mdyPlayer)
+ _mdyPlayer->syncVolume();
+}
+
void Sound::adlibSetRepeating(int32 repCount) {
if (!_hasAdLib)
return;
@@ -739,7 +749,7 @@ void Sound::createMDYPlayer() {
delete _adlPlayer;
_adlPlayer = 0;
- _mdyPlayer = new MUSPlayer(*_vm->_mixer);
+ _mdyPlayer = new MUSPlayer();
}
void Sound::createADLPlayer() {
@@ -749,7 +759,7 @@ void Sound::createADLPlayer() {
delete _mdyPlayer;
_mdyPlayer= 0;
- _adlPlayer = new ADLPlayer(*_vm->_mixer);
+ _adlPlayer = new ADLPlayer();
}
} // End of namespace Gob
diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h
index c959959755..6ebc323b18 100644
--- a/engines/gob/sound/sound.h
+++ b/engines/gob/sound/sound.h
@@ -96,6 +96,7 @@ public:
int32 adlibGetRepeating() const;
void adlibSetRepeating(int32 repCount);
+ void adlibSyncVolume();
// Infogrames
diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp
index 2c164e020c..c00290b155 100644
--- a/engines/groovie/music.cpp
+++ b/engines/groovie/music.cpp
@@ -36,6 +36,7 @@
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/midiparser.h"
+#include "audio/miles.h"
namespace Groovie {
@@ -379,14 +380,56 @@ bool MusicPlayerMidi::loadParser(Common::SeekableReadStream *stream, bool loop)
MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String &gtlName) :
MusicPlayerMidi(vm) {
- // Create the parser
- _midiParser = MidiParser::createParser_XMIDI();
// Create the driver
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
- _driver = MidiDriver::createMidi(dev);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+ _driver = NULL;
+
+ // new Miles Audio support, to disable set milesAudioEnabled to false
+ _milesAudioMode = false;
+ bool milesAudioEnabled = true;
+ MidiParser::XMidiNewTimbreListProc newTimbreListProc = NULL;
+
+ if (milesAudioEnabled) {
+ // 7th Guest uses FAT.AD/FAT.OPL/FAT.MT
+ // 11th Hour uses SAMPLE.AD/SAMPLE.OPL/SAMPLE.MT
+ switch (musicType) {
+ case MT_ADLIB:
+ _driver = Audio::MidiDriver_Miles_AdLib_create(gtlName + ".AD", gtlName + ".OPL");
+ break;
+ case MT_MT32:
+ _driver = Audio::MidiDriver_Miles_MT32_create(gtlName + ".MT");
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _driver = Audio::MidiDriver_Miles_MT32_create(gtlName + ".MT");
+ musicType = MT_MT32;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (musicType == MT_MT32) {
+ newTimbreListProc = Audio::MidiDriver_Miles_MT32_processXMIDITimbreChunk;
+ }
+ }
+
+ if (_driver) {
+ _milesAudioMode = true;
+ }
+
+ if (!_driver) {
+ // No driver yet? create a generic one
+ _driver = MidiDriver::createMidi(dev);
+ }
+
assert(_driver);
+ // Create the parser
+ _midiParser = MidiParser::createParser_XMIDI(NULL, NULL, newTimbreListProc, _driver);
+
_driver->open(); // TODO: Handle return value != 0 (indicating an error)
// Set the parser's driver
@@ -400,6 +443,9 @@ MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String &gtlName)
_chanBanks[i] = 0;
}
+ if (_milesAudioMode)
+ return;
+
// Load the Global Timbre Library
if (MidiDriver::getMusicType(dev) == MT_ADLIB) {
// MIDI through AdLib
@@ -433,6 +479,11 @@ MusicPlayerXMI::~MusicPlayerXMI() {
}
void MusicPlayerXMI::send(uint32 b) {
+ if (_milesAudioMode) {
+ MusicPlayerMidi::send(b);
+ return;
+ }
+
if ((b & 0xFFF0) == 0x72B0) { // XMIDI Patch Bank Select 114
// From AIL2's documentation: XMIDI Patch Bank Select controller (114)
// selects a bank to be used when searching the next patches
diff --git a/engines/groovie/music.h b/engines/groovie/music.h
index 4853840673..dcb91d42a8 100644
--- a/engines/groovie/music.h
+++ b/engines/groovie/music.h
@@ -134,6 +134,8 @@ private:
// Output music type
uint8 _musicType;
+ bool _milesAudioMode;
+
// Timbres
class Timbre {
public:
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp
index c0e0f67b8e..1d741d8bd0 100644
--- a/engines/kyra/sound_adlib.cpp
+++ b/engines/kyra/sound_adlib.cpp
@@ -55,7 +55,7 @@
namespace Kyra {
-class AdLibDriver : public Audio::AudioStream {
+class AdLibDriver {
public:
AdLibDriver(Audio::Mixer *mixer, int version);
~AdLibDriver();
@@ -70,34 +70,6 @@ public:
void callback();
- // AudioStream API
- int readBuffer(int16 *buffer, const int numSamples) {
- int32 samplesLeft = numSamples;
- memset(buffer, 0, sizeof(int16) * numSamples);
- while (samplesLeft) {
- if (!_samplesTillCallback) {
- callback();
- _samplesTillCallback = _samplesPerCallback;
- _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
- if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) {
- _samplesTillCallback++;
- _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND;
- }
- }
-
- int32 render = MIN(samplesLeft, _samplesTillCallback);
- samplesLeft -= render;
- _samplesTillCallback -= render;
- YM3812UpdateOne(_adlib, buffer, render);
- buffer += render;
- }
- return numSamples;
- }
-
- bool isStereo() const { return false; }
- bool endOfData() const { return false; }
- int getRate() const { return _mixer->getOutputRate(); }
-
void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; }
void setMusicVolume(uint8 volume);
@@ -334,11 +306,6 @@ private:
// _unkTable2_2[] - One of the tables in _unkTable2[]
// _unkTable2_3[] - One of the tables in _unkTable2[]
- int32 _samplesPerCallback;
- int32 _samplesPerCallbackRemainder;
- int32 _samplesTillCallback;
- int32 _samplesTillCallbackRemainder;
-
int _curChannel;
uint8 _soundTrigger;
@@ -365,7 +332,7 @@ private:
uint8 _unkValue19;
uint8 _unkValue20;
- FM_OPL *_adlib;
+ OPL::OPL *_adlib;
uint8 *_soundData;
uint32 _soundDataSize;
@@ -411,7 +378,6 @@ private:
Common::Mutex _mutex;
Audio::Mixer *_mixer;
- Audio::SoundHandle _soundHandle;
uint8 _musicVolume, _sfxVolume;
@@ -427,8 +393,9 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
_mixer = mixer;
- _adlib = makeAdLibOPL(getRate());
- assert(_adlib);
+ _adlib = OPL::Config::create();
+ if (!_adlib || !_adlib->init())
+ error("Failed to create OPL");
memset(_channels, 0, sizeof(_channels));
_soundData = 0;
@@ -451,13 +418,6 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
_tablePtr1 = _tablePtr2 = 0;
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
- _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND;
- _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND;
- _samplesTillCallback = 0;
- _samplesTillCallbackRemainder = 0;
-
_syncJumpMask = 0;
_musicVolume = 0;
@@ -467,11 +427,12 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) {
_programQueueStart = _programQueueEnd = 0;
_retrySounds = false;
+
+ _adlib->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::callback), CALLBACKS_PER_SECOND);
}
AdLibDriver::~AdLibDriver() {
- _mixer->stopHandle(_soundHandle);
- OPLDestroy(_adlib);
+ delete _adlib;
_adlib = 0;
}
@@ -877,7 +838,7 @@ void AdLibDriver::resetAdLibState() {
// New calling style: writeOPL(0xAB, 0xCD)
void AdLibDriver::writeOPL(byte reg, byte val) {
- OPLWriteReg(_adlib, reg, val);
+ _adlib->writeReg(reg, val);
}
void AdLibDriver::initChannel(Channel &channel) {
diff --git a/engines/lure/res.h b/engines/lure/res.h
index 9002ca3056..19fbde1aad 100644
--- a/engines/lure/res.h
+++ b/engines/lure/res.h
@@ -100,7 +100,6 @@ public:
static Resources &getReference();
void reset();
- byte *getResource(uint16 resId);
RoomDataList &roomData() { return _roomData; }
RoomData *getRoom(uint16 roomNumber);
bool checkHotspotExtent(HotspotData *hotspot);
diff --git a/engines/made/made.cpp b/engines/made/made.cpp
index af8156fc3e..ab07ef757b 100644
--- a/engines/made/made.cpp
+++ b/engines/made/made.cpp
@@ -275,7 +275,7 @@ void MadeEngine::handleEvents() {
}
Common::Error MadeEngine::run() {
- _music = new MusicPlayer();
+ _music = new MusicPlayer(getGameID() == GID_RTZ);
syncSoundSettings();
// Initialize backend
diff --git a/engines/made/music.cpp b/engines/made/music.cpp
index b2917b58ed..f57da833c2 100644
--- a/engines/made/music.cpp
+++ b/engines/made/music.cpp
@@ -25,27 +25,67 @@
// MIDI and digital music class
#include "made/music.h"
+#include "made/redreader.h"
#include "made/resource.h"
#include "audio/midiparser.h"
+#include "audio/miles.h"
+
+#include "common/file.h"
+#include "common/stream.h"
namespace Made {
-MusicPlayer::MusicPlayer() : _isGM(false) {
- MidiPlayer::createDriver();
+MusicPlayer::MusicPlayer(bool milesAudio) : _isGM(false),_milesAudioMode(false) {
+ MusicType musicType = MT_INVALID;
+ if (milesAudio) {
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+ musicType = MidiDriver::getMusicType(dev);
+ Common::SeekableReadStream *adLibInstrumentStream = nullptr;
+ switch (musicType) {
+ case MT_ADLIB:
+ _milesAudioMode = true;
+ if (Common::File::exists("rtzcd.red")) {
+ // Installing Return to Zork produces both a SAMPLE.AD and
+ // a SAMPLE.OPL file, but they are identical. The resource
+ // file appears to only contain SAMPLE.AD.
+ adLibInstrumentStream = RedReader::loadFromRed("rtzcd.red", "SAMPLE.AD");
+ }
+ _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL", adLibInstrumentStream);
+ delete adLibInstrumentStream;
+ break;
+ case MT_MT32:
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ default:
+ _milesAudioMode = false;
+ MidiPlayer::createDriver();
+ break;
+ }
+ } else {
+ MidiPlayer::createDriver();
+ }
int ret = _driver->open();
if (ret == 0) {
- if (_nativeMT32)
- _driver->sendMT32Reset();
- else
- _driver->sendGMReset();
+ if (musicType != MT_ADLIB) {
+ if (_nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+ }
_driver->setTimerCallback(this, &timerCallback);
}
}
void MusicPlayer::send(uint32 b) {
+ if (_milesAudioMode) {
+ _driver->send(b);
+ return;
+ }
+
if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
}
diff --git a/engines/made/music.h b/engines/made/music.h
index 95d1bd35c1..558b41c2e2 100644
--- a/engines/made/music.h
+++ b/engines/made/music.h
@@ -38,7 +38,7 @@ enum MusicFlags {
class MusicPlayer : public Audio::MidiPlayer {
public:
- MusicPlayer();
+ MusicPlayer(bool milesAudio);
void playXMIDI(GenericResource *midiResource, MusicFlags flags = MUSIC_NORMAL);
void playSMF(GenericResource *midiResource, MusicFlags flags = MUSIC_NORMAL);
@@ -51,6 +51,7 @@ public:
protected:
bool _isGM;
+ bool _milesAudioMode;
};
} // End of namespace Made
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 6ea0dc24d0..453e2a4872 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -118,6 +118,8 @@ bool PmvPlayer::play(const char *filename) {
// get it to work well?
_audioStream = Audio::makeQueuingAudioStream(soundFreq, false);
+ SoundDecoderData *soundDecoderData = new SoundDecoderData();
+
while (!_vm->shouldQuit() && !_aborted && !_fd->eos() && frameNumber < frameCount) {
int32 frameTime = _vm->_system->getMillis();
@@ -153,7 +155,7 @@ bool PmvPlayer::play(const char *filename) {
soundSize = chunkCount * chunkSize;
soundData = (byte *)malloc(soundSize);
- decompressSound(audioData + 8, soundData, chunkSize, chunkCount);
+ decompressSound(audioData + 8, soundData, chunkSize, chunkCount, NULL, soundDecoderData);
_audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
}
@@ -213,6 +215,7 @@ bool PmvPlayer::play(const char *filename) {
}
+ delete soundDecoderData;
delete[] frameData;
_audioStream->finish();
diff --git a/engines/made/screenfx.cpp b/engines/made/screenfx.cpp
index 3f98cbb9ab..bae59f05cc 100644
--- a/engines/made/screenfx.cpp
+++ b/engines/made/screenfx.cpp
@@ -201,7 +201,7 @@ void ScreenEffects::startBlendedPalette(byte *palette, byte *newPalette, int col
}
void ScreenEffects::stepBlendedPalette() {
- if (_blendedPaletteStatus._active && _blendedPaletteStatus._value < _blendedPaletteStatus._maxValue) {
+ if (_blendedPaletteStatus._active && _blendedPaletteStatus._value <= _blendedPaletteStatus._maxValue) {
setBlendedPalette(_blendedPaletteStatus._palette, _blendedPaletteStatus._newPalette,
_blendedPaletteStatus._colorCount, _blendedPaletteStatus._value, _blendedPaletteStatus._maxValue);
if (_blendedPaletteStatus._value == _blendedPaletteStatus._maxValue)
diff --git a/engines/made/sound.cpp b/engines/made/sound.cpp
index 91e855cbf5..ad49031e7b 100644
--- a/engines/made/sound.cpp
+++ b/engines/made/sound.cpp
@@ -133,10 +133,10 @@ void ManholeEgaSoundDecompressor::update3() {
_sample2 += _sample1;
}
-void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray) {
+void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray, SoundDecoderData *soundDecoderData) {
- int16 prevSample = 0, workSample = 0;
- byte soundBuffer[1025];
+ int16 prevSample, workSample;
+ byte* soundBuffer;
byte deltaSoundBuffer[1024];
int16 soundBuffer2[16];
byte deltaType, type;
@@ -159,6 +159,15 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou
if (soundEnergyArray)
soundEnergyArray->clear();
+ if (soundDecoderData) {
+ soundBuffer = soundDecoderData->_soundBuffer;
+ prevSample = soundDecoderData->_prevSample;
+ } else {
+ soundBuffer = new byte[1025];
+ memset(soundBuffer, 0x80, 1025);
+ prevSample = 0;
+ }
+
while (chunkCount--) {
deltaType = (*source) >> 6;
workChunkSize = chunkSize;
@@ -233,6 +242,11 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou
}
if (deltaType > 0) {
+ // NB: The original did not add this extra value at the end (as far
+ // as I can tell), and so technically read past the filled part of
+ // soundBuffer.
+ soundBuffer[workChunkSize] = soundBuffer[workChunkSize - 1];
+
if (deltaType == 1) {
for (i = 0; i < chunkSize - 1; i += 2) {
l = i / 2;
@@ -255,9 +269,13 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou
prevSample = workSample;
memcpy(dest, soundBuffer, chunkSize);
dest += chunkSize;
-
}
+ if (soundDecoderData) {
+ soundDecoderData->_prevSample = prevSample;
+ } else {
+ delete[] soundBuffer;
+ }
}
} // End of namespace Made
diff --git a/engines/made/sound.h b/engines/made/sound.h
index 6ffca13aaa..72537322f9 100644
--- a/engines/made/sound.h
+++ b/engines/made/sound.h
@@ -53,7 +53,22 @@ struct SoundEnergyItem {
typedef Common::Array<SoundEnergyItem> SoundEnergyArray;
-void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray = NULL);
+
+// Persistent data for decompressSound(). When calling decompressSound()
+// repeatedly (for the same stream), pass the same SoundDecoderData object to
+// ensure decoding properly resumes.
+class SoundDecoderData {
+public:
+ SoundDecoderData() {
+ memset(_soundBuffer, 0x80, sizeof(_soundBuffer));
+ _prevSample = 0;
+ }
+
+ byte _soundBuffer[1025];
+ int16 _prevSample;
+};
+
+void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray = NULL, SoundDecoderData *decoderData = NULL);
} // End of namespace Made
diff --git a/engines/mads/hotspots.cpp b/engines/mads/hotspots.cpp
index bd28645504..8afef2e524 100644
--- a/engines/mads/hotspots.cpp
+++ b/engines/mads/hotspots.cpp
@@ -193,9 +193,8 @@ Hotspot::Hotspot(Common::SeekableReadStream &f, bool isV2) {
_active = f.readByte() != 0;
_cursor = (CursorType)f.readByte();
if (isV2) {
- // This looks to be some sort of bitmask. Perhaps it signifies
- // the valid verbs for this hotspot
- f.skip(2); // unknown
+ f.skip(1); // cursor
+ f.skip(1); // syntax
}
_vocabId = f.readUint16LE();
_verbId = f.readUint16LE();
diff --git a/engines/mads/nebular/sound_nebular.cpp b/engines/mads/nebular/sound_nebular.cpp
index 240c18f6dc..711f82a05b 100644
--- a/engines/mads/nebular/sound_nebular.cpp
+++ b/engines/mads/nebular/sound_nebular.cpp
@@ -21,6 +21,7 @@
*/
#include "audio/audiostream.h"
+#include "audio/fmopl.h"
#include "audio/decoders/raw.h"
#include "common/algorithm.h"
#include "common/debug.h"
@@ -156,7 +157,7 @@ AdlibSample::AdlibSample(Common::SeekableReadStream &s) {
/*-----------------------------------------------------------------------*/
-ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, int dataOffset) {
+ASound::ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::String &filename, int dataOffset) {
// Open up the appropriate sound file
if (!_soundFile.open(filename))
error("Could not open file - %s", filename.c_str());
@@ -188,11 +189,6 @@ ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename,
_randomSeed = 1234;
_amDep = _vibDep = _splitPoint = true;
- _samplesTillCallback = 0;
- _samplesTillCallbackRemainder = 0;
- _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND;
- _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND;
-
for (int i = 0; i < 11; ++i) {
_channelData[i]._field0 = 0;
_channelData[i]._freqMask = 0;
@@ -210,23 +206,19 @@ ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename,
_mixer = mixer;
_opl = opl;
- _opl->init(getRate());
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1,
- Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
// Initialize the Adlib
adlibInit();
// Reset the adlib
command0();
+
+ _opl->start(new Common::Functor0Mem<void, ASound>(this, &ASound::onTimer), CALLBACKS_PER_SECOND);
}
ASound::~ASound() {
Common::List<CachedDataEntry>::iterator i;
for (i = _dataCache.begin(); i != _dataCache.end(); ++i)
delete[] (*i)._data;
-
- _mixer->stopHandle(_soundHandle);
}
void ASound::validate() {
@@ -832,32 +824,10 @@ void ASound::updateFNumber() {
write2(8, hiReg, val2);
}
-int ASound::readBuffer(int16 *buffer, const int numSamples) {
+void ASound::onTimer() {
Common::StackLock slock(_driverMutex);
-
- int32 samplesLeft = numSamples;
- memset(buffer, 0, sizeof(int16) * numSamples);
- while (samplesLeft) {
- if (!_samplesTillCallback) {
- poll();
- flush();
-
- _samplesTillCallback = _samplesPerCallback;
- _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
- if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) {
- _samplesTillCallback++;
- _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND;
- }
- }
-
- int32 render = MIN<int>(samplesLeft, _samplesTillCallback);
- samplesLeft -= render;
- _samplesTillCallback -= render;
-
- _opl->readBuffer(buffer, render);
- buffer += render;
- }
- return numSamples;
+ poll();
+ flush();
}
void ASound::setVolume(int volume) {
@@ -984,7 +954,7 @@ const ASound1::CommandPtr ASound1::_commandList[42] = {
&ASound1::command40, &ASound1::command41
};
-ASound1::ASound1(Audio::Mixer *mixer, FM_OPL *opl)
+ASound1::ASound1(Audio::Mixer *mixer, OPL::OPL *opl)
: ASound(mixer, opl, "asound.001", 0x1520) {
_cmd23Toggle = false;
@@ -1285,7 +1255,7 @@ const ASound2::CommandPtr ASound2::_commandList[44] = {
&ASound2::command40, &ASound2::command41, &ASound2::command42, &ASound2::command43
};
-ASound2::ASound2(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) {
+ASound2::ASound2(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) {
_command12Param = 0xFD;
// Load sound samples
@@ -1656,7 +1626,7 @@ const ASound3::CommandPtr ASound3::_commandList[61] = {
&ASound3::command60
};
-ASound3::ASound3(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.003", 0x15B0) {
+ASound3::ASound3(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.003", 0x15B0) {
_command39Flag = false;
// Load sound samples
@@ -2060,7 +2030,7 @@ const ASound4::CommandPtr ASound4::_commandList[61] = {
&ASound4::command60
};
-ASound4::ASound4(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.004", 0x14F0) {
+ASound4::ASound4(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.004", 0x14F0) {
// Load sound samples
_soundFile.seek(_dataOffset + 0x122);
for (int i = 0; i < 210; ++i)
@@ -2316,7 +2286,7 @@ const ASound5::CommandPtr ASound5::_commandList[42] = {
&ASound5::command40, &ASound5::command41
};
-ASound5::ASound5(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) {
+ASound5::ASound5(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) {
// Load sound samples
_soundFile.seek(_dataOffset + 0x144);
for (int i = 0; i < 164; ++i)
@@ -2557,7 +2527,7 @@ const ASound6::CommandPtr ASound6::_commandList[30] = {
&ASound6::nullCommand, &ASound6::command29
};
-ASound6::ASound6(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.006", 0x1390) {
+ASound6::ASound6(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.006", 0x1390) {
// Load sound samples
_soundFile.seek(_dataOffset + 0x122);
for (int i = 0; i < 200; ++i)
@@ -2713,7 +2683,7 @@ const ASound7::CommandPtr ASound7::_commandList[38] = {
&ASound7::command36, &ASound7::command37
};
-ASound7::ASound7(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.007", 0x1460) {
+ASound7::ASound7(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.007", 0x1460) {
// Load sound samples
_soundFile.seek(_dataOffset + 0x122);
for (int i = 0; i < 214; ++i)
@@ -2919,7 +2889,7 @@ const ASound8::CommandPtr ASound8::_commandList[38] = {
&ASound8::command36, &ASound8::command37
};
-ASound8::ASound8(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.008", 0x1490) {
+ASound8::ASound8(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.008", 0x1490) {
// Load sound samples
_soundFile.seek(_dataOffset + 0x122);
for (int i = 0; i < 174; ++i)
@@ -3175,7 +3145,7 @@ const ASound9::CommandPtr ASound9::_commandList[52] = {
&ASound9::command48, &ASound9::command49, &ASound9::command50, &ASound9::command51
};
-ASound9::ASound9(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.009", 0x16F0) {
+ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.009", 0x16F0) {
_v1 = _v2 = 0;
_soundPtr = nullptr;
diff --git a/engines/mads/nebular/sound_nebular.h b/engines/mads/nebular/sound_nebular.h
index 9bc1a49458..2b80b08d89 100644
--- a/engines/mads/nebular/sound_nebular.h
+++ b/engines/mads/nebular/sound_nebular.h
@@ -28,9 +28,12 @@
#include "common/mutex.h"
#include "common/queue.h"
#include "audio/audiostream.h"
-#include "audio/fmopl.h"
#include "audio/mixer.h"
+namespace OPL {
+class OPL;
+}
+
namespace MADS {
class SoundManager;
@@ -142,7 +145,7 @@ struct CachedDataEntry {
/**
* Base class for the sound player resource files
*/
-class ASound : public Audio::AudioStream {
+class ASound {
private:
Common::List<CachedDataEntry> _dataCache;
uint16 _randomSeed;
@@ -192,6 +195,11 @@ private:
void processSample();
void updateFNumber();
+
+ /**
+ * Timer function for OPL
+ */
+ void onTimer();
protected:
int _commandParam;
@@ -273,8 +281,7 @@ protected:
int nullCommand() { return 0; }
public:
Audio::Mixer *_mixer;
- FM_OPL *_opl;
- Audio::SoundHandle _soundHandle;
+ OPL::OPL *_opl;
AdlibChannel _channels[ADLIB_CHANNEL_COUNT];
AdlibChannel *_activeChannelPtr;
AdlibChannelData _channelData[11];
@@ -306,10 +313,6 @@ public:
int _activeChannelReg;
int _v11;
bool _amDep, _vibDep, _splitPoint;
- int _samplesPerCallback;
- int _samplesPerCallbackRemainder;
- int _samplesTillCallback;
- int _samplesTillCallbackRemainder;
public:
/**
* Constructor
@@ -318,7 +321,7 @@ public:
* @param filename Specifies the adlib sound player file to use
* @param dataOffset Offset in the file of the data segment
*/
- ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, int dataOffset);
+ ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::String &filename, int dataOffset);
/**
* Destructor
@@ -363,27 +366,6 @@ public:
*/
CachedDataEntry &getCachedData(byte *pData);
- // AudioStream interface
- /**
- * Main buffer read
- */
- virtual int readBuffer(int16 *buffer, const int numSamples);
-
- /**
- * Mono sound only
- */
- virtual bool isStereo() const { return false; }
-
- /**
- * Data is continuously pushed, so definitive end
- */
- virtual bool endOfData() const { return false; }
-
- /**
- * Return sample rate
- */
- virtual int getRate() const { return 11025; }
-
/**
* Set the volume
*/
@@ -433,7 +415,7 @@ private:
void command111213();
int command2627293032();
public:
- ASound1(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound1(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -485,7 +467,7 @@ private:
void command9Randomize();
void command9Apply(byte *data, int val, int incr);
public:
- ASound2(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound2(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -545,7 +527,7 @@ private:
void command9Randomize();
void command9Apply(byte *data, int val, int incr);
public:
- ASound3(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound3(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -583,7 +565,7 @@ private:
void method1();
public:
- ASound4(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound4(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -629,7 +611,7 @@ private:
int command42();
int command43();
public:
- ASound5(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound5(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -658,7 +640,7 @@ private:
int command25();
int command29();
public:
- ASound6(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound6(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -690,7 +672,7 @@ private:
int command36();
int command37();
public:
- ASound7(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound7(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -733,7 +715,7 @@ private:
void method1(byte *pData);
void adjustRange(byte *pData, byte v, int incr);
public:
- ASound8(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound8(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
@@ -792,7 +774,7 @@ private:
int command59();
int command60();
public:
- ASound9(Audio::Mixer *mixer, FM_OPL *opl);
+ ASound9(Audio::Mixer *mixer, OPL::OPL *opl);
virtual int command(int commandId, int param);
};
diff --git a/engines/mads/phantom/game_phantom.cpp b/engines/mads/phantom/game_phantom.cpp
index 959a726edf..592a108aea 100644
--- a/engines/mads/phantom/game_phantom.cpp
+++ b/engines/mads/phantom/game_phantom.cpp
@@ -52,31 +52,74 @@ void GamePhantom::startGame() {
void GamePhantom::initializeGlobals() {
_globals.reset();
- warning("TODO: sub_316DA()");
+ // TODO: Catacombs setup
_player._facing = FACING_NORTH;
_player._turnToFacing = FACING_NORTH;
- _globals[kCurrentYear] = 1993;
-
- /* Section #1 variables */
- // TODO
-
- /* Section #2 variables */
- // TODO
-
- /* Section #3 variables */
- // TODO
-
- /* Section #4 variables */
- // TODO
-
- /* Section #5 variables */
- // TODO
-
- /* Section #9 variables */
- // TODO
-
+ _globals[kTempVar] = false;
+ _globals[kRoom103104Transition] = 1; // new room
+ _globals[kCurrentYear] = 1993;
+ _globals[kTrapDoorStatus] = 0; // open
+ _globals[kChristineDoorStatus] = 0; // Christine is in her room
+ _globals[kSandbagStatus] = 0; // sandbag is secure
+ _globals[kJacquesStatus] = 0; // alive
+ _globals[kChrisFStatus] = 1; // Christine F. is alive in 1993
+ _globals[kBrieTalkStatus] = 0; // before Brie motions
+ _globals[kPanelIn206] = 0; // not discovered
+ _globals[kFightStatus] = 0;
+ _globals[kJuliesDoor] = 1; // cracked open
+ _globals[kPrompterStandStatus] = 0;
+ _globals[kChrisDStatus] = 0; // before love
+ _globals[kJulieNameIsKnown] = 0;
+ _globals[kDoorsIn205] = 0; // both locked
+ _globals[kMadameGiryLocation] = 1; // middle
+ _globals[kTicketPeoplePresent] = 0;
+ _globals[kCoffinStatus] = 0; // closed and locked
+ _globals[kDoneBrieConv203] = 0;
+ _globals[kFlorentNameIsKnown] = 0;
+ _globals[kDegasNameIsKnown] = 0;
+ _globals[kMadameGiryShowsUp] = false;
+ _globals[kJacquesNameIsKnown] = 0;
+ _globals[kCharlesNameIsKnown] = false;
+ _globals[kTopFloorLocked] = true;
+ _globals[kMadameNameIsKnown] = 0;
+ _globals[kChrisKickedRaoulOut] = false;
+ _globals[kLookedAtCase] = false;
+ _globals[kRingIsOnFinger] = false;
+ _globals[kHeListened] = false;
+ _globals[kKnockedOverHead] = false;
+ _globals[kObservedPhan104] = false;
+ _globals[kReadBook] = false;
+ _globals[kCanFindBookInLibrary] = false;
+ _globals[kLookedAtSkullFace] = false;
+ _globals[kScannedBookcase] = false;
+ _globals[kRanConvIn205] = false;
+ _globals[kDoneRichConv203] = false;
+ _globals[kHintThatDaaeIsHome1] = false;
+ _globals[kHintThatDaaeIsHome2] = false;
+ _globals[kMakeBrieLeave203] = false;
+ _globals[kMakeRichLeave203] = false;
+ _globals[kCameFromFade] = false;
+ _globals[kChristineToldEnvelope] = false;
+ _globals[kLeaveAngelMusicOn] = false;
+ _globals[kDoorIn409IsOpen] = false;
+ _globals[kUnknown] = false;
+ _globals[kCobwebIsCut] = false;
+ _globals[kChristineIsInBoat] = false;
+ _globals[kRightDoorIsOpen504] = false;
+ _globals[kChrisLeft505] = false;
+ _globals[kChrisWillTakeSeat] = true;
+ _globals[kFlickedLever1] = 0;
+ _globals[kFlickedLever2] = 0;
+ _globals[kFlickedLever3] = 0;
+ _globals[kFlickedLever4] = 0;
+ _globals[kPlayerScore] = 0;
+ _globals[kPlayerScoreFlags] = 0;
+
+ _globals[kMusicSelected] = _vm->getRandomNumber(1, 4);
+
+ _player._spritesPrefix = "RAL"; // Fixed prefix
Player::preloadSequences("RAL", 1);
}
@@ -108,6 +151,11 @@ void GamePhantom::checkShowDialog() {
// TODO: Copied from Nebular
if (_vm->_dialogs->_pendingDialog && _player._stepEnabled && !_globals[5]) {
_player.releasePlayerSprites();
+
+ // HACK: Skip the main menu (since it'll then try to show Rex's main menu)
+ if (_vm->_dialogs->_pendingDialog == DIALOG_MAIN_MENU)
+ _vm->_dialogs->_pendingDialog = DIALOG_NONE;
+
_vm->_dialogs->showDialog();
_vm->_dialogs->_pendingDialog = DIALOG_NONE;
}
diff --git a/engines/mads/phantom/globals_phantom.h b/engines/mads/phantom/globals_phantom.h
index 44d8c9e4bc..c23b53cdf5 100644
--- a/engines/mads/phantom/globals_phantom.h
+++ b/engines/mads/phantom/globals_phantom.h
@@ -33,23 +33,91 @@ namespace MADS {
namespace Phantom {
enum GlobalId {
+ // Global variables
+
kWalkerTiming = 0,
-// kWalkerTiming0 = 1,
+ kWalkerTiming2 = 1,
+ kStopWalkerDisabled = 2, // disable walker idle animations
+ kTempInterface = 3,
+ kWalkerConverse = 4, // conversation started with an NPC
+ kWalkerConverseState = 5,
+ kWalkerConverseNow = 6,
kCurrentYear = 10, // current year (1881 or 1993)
-
- //kTalkInanimateCount = 4,
-
- /* Section #1 variables */
-
- /* Section #2 variables */
-
- /* Section #3 Variables */
-
- /* Section #4 Variables */
-
- /* Section #5 Variables */
-
+ kMusicSelected = 11,
+ kPlayerScore = 12,
+ kPlayerScoreFlags = 13,
+ kDoneBrieConv203 = 14,
+ kLanternStatus = 15,
+
+ // Section #1 variables
+ kLeaveAngelMusicOn = 19,
+ kTrapDoorStatus = 20,
+ kChristineDoorStatus = 21,
+ kSandbagStatus = 22,
+ kChrisFStatus = 23,
+ kBrieTalkStatus = 24,
+ kJuliesDoor = 25,
+ kPrompterStandStatus = 26,
+ kChrisDStatus = 27,
+ kJulieNameIsKnown = 28,
+ kChrisKickedRaoulOut = 29,
+ kJacquesNameIsKnown = 30,
+ kJacquesStatus = 31,
+ kFlorentNameIsKnown = 32,
+ kCharlesNameIsKnown = 33,
+ kRoom103104Transition = 34,
+ kObservedPhan104 = 35,
+ kDeathLocation = 36,
+ kMakeBrieLeave203 = 37,
+ kHintThatDaaeIsHome1 = 38,
+ kHintThatDaaeIsHome2 = 39,
+
+ // Section #2 variables
+ kChristineToldEnvelope = 40,
+ kReadBook = 41,
+ kScannedBookcase = 42,
+ kRanConvIn205 = 43,
+ kDoorsIn205 = 44,
+ kPanelIn206 = 45,
+ kMadameNameIsKnown = 46,
+ kMadameGiryLocation = 47,
+ kLookedAtCase = 48,
+ kMadameGiryShowsUp = 49,
+ kDoneRichConv203 = 50,
+ kCameFromFade = 51,
+ kTicketPeoplePresent = 52,
+ kDegasNameIsKnown = 53,
+ kTempVar = 54,
+ kFlickedLever1 = 55,
+ kFlickedLever2 = 56,
+ kFlickedLever3 = 57,
+ kFlickedLever4 = 58,
+
+ // Section #3 Variables
+ kTopFloorLocked = 60,
+
+ // Section #4 Variables
+ kCatacombsRoom = 80,
+ // TODO
+ kDoorIn409IsOpen = 93,
+ kUnknown = 94, // TODO
+ kCobwebIsCut = 95,
+
+ // Section #5 Variables
+ kChristineIsInBoat = 100,
+ kChrisWillTakeSeat = 101,
+ kRightDoorIsOpen504 = 102,
+ kCoffinStatus = 103,
+ kChrisLeft505 = 104,
+ kKnockedOverHead = 105,
+ kFightStatus = 106,
+ kHeListened = 107,
+ kCanFindBookInLibrary = 108,
+ kRingIsOnFinger = 109,
+ kLookedAtSkullFace = 110,
+ kCableHookWasSeparate = 111,
+ kMakeRichLeave203 = 112
};
class PhantomGlobals : public Globals {
diff --git a/engines/mads/phantom/phantom_scenes.cpp b/engines/mads/phantom/phantom_scenes.cpp
index 57c2bb2c9b..f7f4d154df 100644
--- a/engines/mads/phantom/phantom_scenes.cpp
+++ b/engines/mads/phantom/phantom_scenes.cpp
@@ -43,7 +43,7 @@ SceneLogic *SceneFactory::createScene(MADSEngine *vm) {
switch (scene._nextSceneId) {
// Scene group #1 (theater, stage and dressing rooms)
case 101: // seats
- return new DummyScene(vm); // TODO
+ return new Scene101(vm);
case 102: // music stands
return new Scene102(vm);
case 103: // below stage
diff --git a/engines/mads/phantom/phantom_scenes.h b/engines/mads/phantom/phantom_scenes.h
index 55218f219a..c0a823ae06 100644
--- a/engines/mads/phantom/phantom_scenes.h
+++ b/engines/mads/phantom/phantom_scenes.h
@@ -35,7 +35,24 @@ namespace MADS {
namespace Phantom {
enum Verb {
+ VERB_LOOK = 0x3,
+ VERB_TAKE = 0x4,
+ VERB_PUSH = 0x5,
+ VERB_OPEN = 0x6,
+ VERB_PUT = 0x7,
+ VERB_TALK_TO = 0x8,
+ VERB_GIVE = 0x9,
+ VERB_PULL = 0xA,
+ VERB_CLOSE = 0xB,
+ VERB_THROW = 0xC,
+ VERB_WALK_TO = 0xD,
+ VERB_CLIMB_DOWN = 0x21,
+ VERB_CLIMB_INTO = 0x22,
+ VERB_CLIMB_THROUGH = 0x23,
+ VERB_EXIT_TO = 0x37,
+ VERB_JUMP_INTO = 0x53,
VERB_LOOK_AT = 0x60,
+ VERB_LOOK_THROUGH = 0x61,
VERB_TURN_OFF = 0x95,
VERB_TURN_ON = 0x96,
VERB_UNLOCK = 0x97,
@@ -43,22 +60,14 @@ enum Verb {
VERB_WALK_DOWN = 0x9A,
VERB_WALK_THROUGH = 0x9B,
VERB_WALK_UP = 0x9C,
+ VERB_CLIMB_UP = 0xA5,
+ VERB_WALK_ONTO = 0xA6,
+ VERB_WALK = 0xA7
};
enum Noun {
NOUN_GAME = 0x1,
NOUN_QSAVE = 0x2,
- NOUN_LOOK = 0x3,
- NOUN_TAKE = 0x4,
- NOUN_PUSH = 0x5,
- NOUN_OPEN = 0x6,
- NOUN_PUT = 0x7,
- NOUN_TALK_TO = 0x8,
- NOUN_GIVE = 0x9,
- NOUN_PULL = 0xA,
- NOUN_CLOSE = 0xB,
- NOUN_THROW = 0xC,
- NOUN_WALK_TO = 0xD,
NOUN_ = 0xE,
NOUN_IN_ONE = 0xF,
NOUN_IN_TWO = 0x10,
@@ -78,9 +87,6 @@ enum Noun {
NOUN_CEILING = 0x1E,
NOUN_CHAIR = 0x1F,
NOUN_CIRCULAR_STAIRCASE = 0x20,
- NOUN_CLIMB_DOWN = 0x21,
- NOUN_CLIMB_INTO = 0x22,
- NOUN_CLIMB_THROUGH = 0x23,
NOUN_COLUMN_PROP = 0x24,
NOUN_CONDUCTORS_STAND = 0x25,
NOUN_CORRIDOR = 0x26,
@@ -100,7 +106,6 @@ enum Noun {
NOUN_EXIT = 0x34,
NOUN_EXIT_DOWN = 0x35,
NOUN_EXIT_SIGN = 0x36,
- NOUN_EXIT_TO = 0x37,
NOUN_EXIT_TO_BACKSTAGE = 0x38,
NOUN_EXIT_TO_CELLAR = 0x39,
NOUN_EXIT_TO_CORRIDOR = 0x3A,
@@ -128,7 +133,6 @@ enum Noun {
NOUN_HOUSE = 0x50,
NOUN_IN_ONE2 = 0x51,
NOUN_IN_TWO2 = 0x52,
- NOUN_JUMP_INTO = 0x53,
NOUN_JUNK = 0x54,
NOUN_KEY = 0x55,
NOUN_LAMP = 0x56,
@@ -141,7 +145,6 @@ enum Noun {
NOUN_LOCK = 0x5D,
NOUN_LOCKING_RAIL = 0x5E,
NOUN_LOCKRAIL = 0x5F,
- NOUN_LOOK_THROUGH = 0x61,
NOUN_MANNEQUINS = 0x62,
NOUN_MIRROR = 0x63,
NOUN_MUMMY_PROP = 0x64,
@@ -202,9 +205,6 @@ enum Noun {
NOUN_WEDDING_RING = 0xA2,
NOUN_YELLOW_FRAME = 0xA3,
NOUN_PROP = 0xA4,
- NOUN_CLIMB_UP = 0xA5,
- NOUN_WALK_ONTO = 0xA6,
- NOUN_WALK = 0xA7,
NOUN_LEFT_DOOR = 0xA8,
NOUN_RIGHT_DOOR = 0xA9,
NOUN_DOOR_TO_PIT = 0xAA,
diff --git a/engines/mads/phantom/phantom_scenes1.cpp b/engines/mads/phantom/phantom_scenes1.cpp
index b4035dc68b..2d991fd3bc 100644
--- a/engines/mads/phantom/phantom_scenes1.cpp
+++ b/engines/mads/phantom/phantom_scenes1.cpp
@@ -42,6 +42,92 @@ void Scene1xx::sceneEntrySound() {
/*------------------------------------------------------------------------*/
+Scene101::Scene101(MADSEngine *vm) : Scene1xx(vm) {
+
+}
+
+void Scene101::synchronize(Common::Serializer &s) {
+ Scene1xx::synchronize(s);
+
+}
+
+void Scene101::setup() {
+ //setPlayerSpritesPrefix();
+ setAAName();
+}
+
+void Scene101::enter() {
+ // TODO
+
+ if (_globals[kCurrentYear] == 1993) {
+ _globals._spriteIndexes[0] = _scene->_sprites.addSprites(formAnimName('z', -1));
+ // TODO
+ //_scene->_sequences.setDepth(_globals._sequenceIndexes[0], 14);
+ } else {
+ // TODO
+ }
+
+ // TODO
+}
+
+void Scene101::step() {
+ // TODO
+}
+
+void Scene101::preActions() {
+ if (_action.isAction(VERB_EXIT_TO, NOUN_ORCHESTRA_PIT)) {
+ // TODO: Handle Brie
+ _game._player._walkOffScreenSceneId = 102;
+ } else if (_action.isAction(VERB_EXIT_TO, NOUN_GRAND_FOYER)) {
+ // TODO: Handle Brie
+ _game._player._walkOffScreenSceneId = 202;
+ } else if (_action.isAction(VERB_TAKE, NOUN_MONSIEUR_BRIE)) {
+ _vm->_dialogs->show(10121);
+ } else if (_action.isAction(VERB_TALK_TO, NOUN_MONSIEUR_BRIE)) {
+ if (_globals[kBrieTalkStatus] == 2)
+ _game._player._needToWalk = false;
+ }
+
+ // TODO
+}
+
+void Scene101::actions() {
+ // TODO: Brie conversation
+
+ // TODO: Look around
+
+ if (_action.isAction(VERB_LOOK) || _action.isAction(VERB_LOOK_AT)) {
+ if (_action.isObject(NOUN_AISLE)) {
+ _vm->_dialogs->show(10112);
+ } else if (_action.isObject(NOUN_CHANDELIER)) {
+ _vm->_dialogs->show(10113);
+ } else if (_action.isObject(NOUN_BACK_WALL)) {
+ _vm->_dialogs->show(10114);
+ } else if (_action.isObject(NOUN_SIDE_WALL)) {
+ _vm->_dialogs->show(10115);
+ } else if (_action.isObject(NOUN_SEATS)) {
+ // TODO: Finish this
+ _vm->_dialogs->show(10116);
+ } else if (_action.isObject(NOUN_GRAND_FOYER)) {
+ _vm->_dialogs->show(10117);
+ } else if (_action.isObject(NOUN_ORCHESTRA_PIT)) {
+ _vm->_dialogs->show(10118);
+ } else if (_action.isObject(NOUN_MONSIEUR_BRIE)) {
+ _vm->_dialogs->show(10120);
+ }
+
+ _game._player._stepEnabled = true;
+ } else if (_action.isAction(VERB_TALK_TO, NOUN_MONSIEUR_BRIE)) {
+ if (_globals[kBrieTalkStatus] == 2)
+ _vm->_dialogs->show(10122);
+ _game._player._stepEnabled = true;
+ } else if (_action.isAction(VERB_TAKE, NOUN_MONSIEUR_BRIE)) {
+ _game._player._stepEnabled = true;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
Scene102::Scene102(MADSEngine *vm) : Scene1xx(vm) {
_animRunningFl = false;
}
@@ -60,22 +146,34 @@ void Scene102::setup() {
void Scene102::enter() {
_animRunningFl = false;
- // TODO: Load sprite series
+ _globals._spriteIndexes[2] = _scene->_sprites.addSprites(formAnimName('x', 0));
+ _globals._spriteIndexes[3] = _scene->_sprites.addSprites("*RAL86");
+
+ if (_globals[kCurrentYear] == 1993) {
+ _globals._spriteIndexes[0] = _scene->_sprites.addSprites(formAnimName('z', -1));
+ // TODO
+ //_scene->_sequences.setDepth(_globals._sequenceIndexes[0], 14);
+ } else {
+ // TODO
+ }
if (_scene->_priorSceneId == 101) {
_game._player._playerPos = Common::Point(97, 79);
_game._player._facing = FACING_SOUTHEAST;
// TODO
+ _game._player.walk(Common::Point(83, 87), FACING_SOUTHEAST);
+ _scene->_sequences.setDepth(_globals._sequenceIndexes[2], 14);
} else if (_scene->_priorSceneId == 104) {
// Player fell from pit -> death
// TODO
} else if (_scene->_priorSceneId == 103 || _scene->_priorSceneId != -1) {
_game._player._playerPos = Common::Point(282, 145);
_game._player._facing = FACING_WEST;
- // TODO: Door closing animation
_animRunningFl = true;
+ // TODO: Door closing animation
} else if (_scene->_priorSceneId == -1) {
// TODO
+ _scene->_sequences.setDepth(_globals._sequenceIndexes[2], 14);
}
sceneEntrySound();
@@ -109,6 +207,8 @@ void Scene102::actions() {
if (_animRunningFl) {
// TODO
} else {
+ _scene->_nextSceneId = 103; // FIXME: temporary HACK - remove!
+
switch (_game._trigger) {
case 70: // try again
case 0:
diff --git a/engines/mads/phantom/phantom_scenes1.h b/engines/mads/phantom/phantom_scenes1.h
index ef2cdb7aa0..0f5f56a4cf 100644
--- a/engines/mads/phantom/phantom_scenes1.h
+++ b/engines/mads/phantom/phantom_scenes1.h
@@ -53,6 +53,21 @@ public:
Scene1xx(MADSEngine *vm) : PhantomScene(vm) {}
};
+class Scene101 : public Scene1xx {
+private:
+ // TODO
+
+public:
+ Scene101(MADSEngine *vm);
+ virtual void synchronize(Common::Serializer &s);
+
+ virtual void setup();
+ virtual void enter();
+ virtual void step();
+ virtual void preActions();
+ virtual void actions();
+};
+
class Scene102 : public Scene1xx {
private:
bool _animRunningFl;
diff --git a/engines/mads/sound.cpp b/engines/mads/sound.cpp
index 7b9388eee3..4a35edb80f 100644
--- a/engines/mads/sound.cpp
+++ b/engines/mads/sound.cpp
@@ -21,6 +21,7 @@
*/
#include "audio/audiostream.h"
+#include "audio/fmopl.h"
#include "audio/decoders/raw.h"
#include "common/memstream.h"
#include "mads/sound.h"
@@ -39,7 +40,7 @@ SoundManager::SoundManager(MADSEngine *vm, Audio::Mixer *mixer) {
_masterVolume = 255;
_opl = OPL::Config::create();
- _opl->init(11025);
+ _opl->init();
// Validate sound files
switch (_vm->getGameID()) {
diff --git a/engines/mads/sound.h b/engines/mads/sound.h
index 16128f8284..9882f65e5a 100644
--- a/engines/mads/sound.h
+++ b/engines/mads/sound.h
@@ -37,7 +37,7 @@ class SoundManager {
private:
MADSEngine *_vm;
Audio::Mixer *_mixer;
- FM_OPL *_opl;
+ OPL::OPL *_opl;
Nebular::ASound *_driver;
bool _pollSoundEnabled;
bool _soundPollFlag;
diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp
index d49f3e8637..9b5bae78be 100644
--- a/engines/mohawk/console.cpp
+++ b/engines/mohawk/console.cpp
@@ -248,9 +248,9 @@ bool MystConsole::Cmd_PlayMovie(int argc, const char **argv) {
return true;
}
- int8 stackNum = 0;
-
+ Common::String fileName;
if (argc == 3 || argc > 4) {
+ int8 stackNum = 0;
for (byte i = 1; i <= ARRAYSIZE(mystStackNames); i++)
if (!scumm_stricmp(argv[2], mystStackNames[i - 1])) {
stackNum = i;
@@ -261,16 +261,27 @@ bool MystConsole::Cmd_PlayMovie(int argc, const char **argv) {
debugPrintf("\'%s\' is not a stack name!\n", argv[2]);
return true;
}
+
+ fileName = _vm->wrapMovieFilename(argv[1], stackNum - 1);
+ } else {
+ fileName = argv[1];
}
- if (argc == 2)
- _vm->_video->playMovie(argv[1], 0, 0);
- else if (argc == 3)
- _vm->_video->playMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), 0, 0);
- else if (argc == 4)
- _vm->_video->playMovie(argv[1], atoi(argv[2]), atoi(argv[3]));
- else
- _vm->_video->playMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), atoi(argv[3]), atoi(argv[4]));
+ VideoHandle handle = _vm->_video->playMovie(fileName);
+ if (!handle) {
+ debugPrintf("Failed to open movie '%s'\n", fileName.c_str());
+ return true;
+ }
+
+ if (argc == 4) {
+ handle->setX(atoi(argv[2]));
+ handle->setY(atoi(argv[3]));
+ } else if (argc > 4) {
+ handle->setX(atoi(argv[3]));
+ handle->setY(atoi(argv[4]));
+ } else {
+ handle->center();
+ }
return false;
}
diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h
index 7632cde294..6bb836b5b8 100644
--- a/engines/mohawk/detection_tables.h
+++ b/engines/mohawk/detection_tables.h
@@ -1379,6 +1379,22 @@ static const MohawkGameDescription gameDescriptions[] = {
"GRANDMA.EXE"
},
+ // Just Grandma and Me 1.0, Macintosh
+ {
+ {
+ "grandma",
+ "v1.0",
+ AD_ENTRY1("BookOutline", "9162483da06179e76f4a082412245efa"),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GType_LIVINGBOOKSV1,
+ GF_LB_10,
+ 0
+ },
+
// Just Grandma and Me 1.1 Mac
// From eisnerguy1 in bug#3610725
{
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index 998ef048f6..5af8fac901 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -363,6 +363,44 @@ void MohawkEngine_LivingBooks::destroyPage() {
_focus = NULL;
}
+// Replace any colons (originally a slash) with another character
+static Common::String replaceColons(const Common::String &in, char replace) {
+ Common::String out;
+
+ for (uint32 i = 0; i < in.size(); i++) {
+ if (in[i] == ':')
+ out += replace;
+ else
+ out += in[i];
+ }
+
+ return out;
+}
+
+// Helper function to assist in opening pages
+static bool tryOpenPage(Archive *archive, const Common::String &fileName) {
+ // Try the plain file name first
+ if (archive->openFile(fileName))
+ return true;
+
+ // No colons, then bail out
+ if (!fileName.contains(':'))
+ return false;
+
+ // Try replacing colons with underscores (in case the original was
+ // a Mac version and had slashes not as a separator).
+ if (archive->openFile(replaceColons(fileName, '_')))
+ return true;
+
+ // Try replacing colons with slashes (in case the original was a Mac
+ // version and had slashes as a separator).
+ if (archive->openFile(replaceColons(fileName, '/')))
+ return true;
+
+ // Failed to open the archive
+ return false;
+}
+
bool MohawkEngine_LivingBooks::loadPage(LBMode mode, uint page, uint subpage) {
destroyPage();
@@ -410,7 +448,7 @@ bool MohawkEngine_LivingBooks::loadPage(LBMode mode, uint page, uint subpage) {
}
Archive *pageArchive = createArchive();
- if (!filename.empty() && pageArchive->openFile(filename)) {
+ if (!filename.empty() && tryOpenPage(pageArchive, filename)) {
_page = new LBPage(this);
_page->open(pageArchive, 1000);
} else {
@@ -824,18 +862,18 @@ int MohawkEngine_LivingBooks::getIntFromConfig(const Common::String &section, co
Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(const Common::String &section, const Common::String &key, Common::String &leftover) {
Common::String string = getStringFromConfig(section, key, leftover);
- Common::String x;
- uint32 i = 0;
if (string.hasPrefix("//")) {
// skip "//CD-ROM Title/" prefixes which we don't care about
- i = 3;
+ uint i = 3;
while (i < string.size() && string[i - 1] != '/')
i++;
+
+ // Already uses slashes, no need to convert further
+ return string.c_str() + i;
}
- x = string.c_str() + i;
- return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(x) : convertWinFileName(x);
+ return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(string) : convertWinFileName(string);
}
Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::String &string, Common::String &leftover) {
@@ -866,8 +904,10 @@ Common::String MohawkEngine_LivingBooks::convertMacFileName(const Common::String
for (uint32 i = 0; i < string.size(); i++) {
if (i == 0 && string[i] == ':') // First character should be ignored (another colon)
continue;
- if (string[i] == ':')
+ if (string[i] == ':') // Directory separator
filename += '/';
+ else if (string[i] == '/') // Literal slash
+ filename += ':'; // Replace by colon, as used by Mac OS X for slash
else
filename += string[i];
}
@@ -3772,7 +3812,7 @@ LBMovieItem::~LBMovieItem() {
void LBMovieItem::update() {
if (_playing) {
VideoHandle videoHandle = _vm->_video->findVideoHandle(_resourceId);
- if (videoHandle == NULL_VID_HANDLE || _vm->_video->endOfVideo(videoHandle))
+ if (!videoHandle || videoHandle->endOfVideo())
done(true);
}
@@ -3783,8 +3823,11 @@ bool LBMovieItem::togglePlaying(bool playing, bool restart) {
if (playing) {
if ((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) {
debug("toggled video for phase %d", _phase);
- _vm->_video->playMovie(_resourceId, _rect.left, _rect.top);
+ VideoHandle handle = _vm->_video->playMovie(_resourceId);
+ if (!handle)
+ error("Failed to open tMOV %d", _resourceId);
+ handle->moveTo(_rect.left, _rect.top);
return true;
}
}
@@ -3808,9 +3851,9 @@ bool LBMiniGameItem::togglePlaying(bool playing, bool restart) {
// just skip to the most logical page. For optional minigames, this
// will return the player to the previous page. For mandatory minigames,
// this will send the player to the next page.
- // TODO: Document mini games from Arthur's Reading Race
- uint16 destPage;
+ uint16 destPage = 0;
+ bool returnToMenu = false;
// Figure out what minigame we have and bring us back to a page where
// the player can continue
@@ -3820,13 +3863,31 @@ bool LBMiniGameItem::togglePlaying(bool playing, bool restart) {
destPage = 5;
else if (_desc == "Fall") // Green Eggs and Ham: Fall minigame
destPage = 13;
+ else if (_desc == "MagicWrite3") // Arthur's Reading Race: "Let Me Write" minigame (Page 3)
+ destPage = 3;
+ else if (_desc == "MagicWrite4") // Arthur's Reading Race: "Let Me Write" minigame (Page 4)
+ destPage = 4;
+ else if (_desc == "MagicSpy5") // Arthur's Reading Race: "I Spy" minigame (Page 5)
+ destPage = 5;
+ else if (_desc == "MagicSpy6") // Arthur's Reading Race: "I Spy" minigame (Page 6)
+ destPage = 6;
+ else if (_desc == "MagicWrite7") // Arthur's Reading Race: "Let Me Write" minigame (Page 7)
+ destPage = 7;
+ else if (_desc == "MagicSpy8") // Arthur's Reading Race: "I Spy" minigame (Page 8)
+ destPage = 8;
+ else if (_desc == "MagicRace") // Arthur's Reading Race: Race minigame
+ returnToMenu = true;
else
error("Unknown minigame '%s'", _desc.c_str());
GUI::MessageDialog dialog(Common::String::format("The '%s' minigame is not supported yet.", _desc.c_str()));
dialog.runModal();
- _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage));
+ // Go back to the menu if requested, otherwise go to the requested page
+ if (returnToMenu)
+ _vm->addNotifyEvent(NotifyEvent(kLBNotifyGoToControls, 1));
+ else
+ _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage));
return false;
}
@@ -3863,7 +3924,7 @@ void LBProxyItem::load() {
debug(1, "LBProxyItem loading archive '%s' with id %d", filename.c_str(), baseId);
Archive *pageArchive = _vm->createArchive();
- if (!pageArchive->openFile(filename))
+ if (!tryOpenPage(pageArchive, filename))
error("failed to open archive '%s' (for proxy '%s')", filename.c_str(), _desc.c_str());
_page = new LBPage(_vm);
_page->open(pageArchive, baseId);
diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp
index 6dcd8c3ce7..b5ea547414 100644
--- a/engines/mohawk/livingbooks_code.cpp
+++ b/engines/mohawk/livingbooks_code.cpp
@@ -842,8 +842,8 @@ CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = {
{ "bottom", &LBCode::cmdBottom },
// 0x10
{ "right", &LBCode::cmdRight },
- { "xpos", 0 },
- { "ypos", 0 },
+ { "xpos", &LBCode::cmdXPos },
+ { "ypos", &LBCode::cmdYPos },
{ "playFrom", 0 },
{ "move", &LBCode::cmdMove },
{ 0, 0 },
@@ -852,13 +852,13 @@ CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = {
{ "resetDragParams", 0 },
{ "enableRollover", &LBCode::cmdUnimplemented /* FIXME */ },
{ "setCursor", 0 },
- { "width", 0 },
- { "height", 0 },
+ { "width", &LBCode::cmdWidth },
+ { "height", &LBCode::cmdHeight },
{ "getFrameBounds", 0 }, // also "getFrameRect"
{ "traceRect", 0 },
{ "sqrt", 0 },
// 0x20
- { "deleteVar", 0 },
+ { "deleteVar", &LBCode::cmdDeleteVar },
{ "saveVars", 0 },
{ "scriptLink", 0 },
{ "setViewOrigin", &LBCode::cmdUnimplemented },
@@ -1131,6 +1131,38 @@ void LBCode::cmdRight(const Common::Array<LBValue> &params) {
_stack.push(rect.right);
}
+void LBCode::cmdXPos(const Common::Array<LBValue> &params) {
+ if (params.size() != 1)
+ error("too many parameters (%d) to xpos", params.size());
+
+ Common::Point point = params[0].toPoint();
+ _stack.push(point.x);
+}
+
+void LBCode::cmdYPos(const Common::Array<LBValue> &params) {
+ if (params.size() != 1)
+ error("too many parameters (%d) to ypos", params.size());
+
+ Common::Point point = params[0].toPoint();
+ _stack.push(point.y);
+}
+
+void LBCode::cmdWidth(const Common::Array<LBValue> &params) {
+ if (params.size() > 1)
+ error("too many parameters (%d) to width", params.size());
+
+ Common::Rect rect = getRectFromParams(params);
+ _stack.push(rect.width());
+}
+
+void LBCode::cmdHeight(const Common::Array<LBValue> &params) {
+ if (params.size() > 1)
+ error("too many parameters (%d) to height", params.size());
+
+ Common::Rect rect = getRectFromParams(params);
+ _stack.push(rect.height());
+}
+
void LBCode::cmdMove(const Common::Array<LBValue> &params) {
if (params.size() != 1 && params.size() != 2)
error("incorrect number of parameters (%d) to move", params.size());
@@ -1263,6 +1295,14 @@ void LBCode::cmdGetProperty(const Common::Array<LBValue> &params) {
_stack.push(target->_variables[name]);
}
+void LBCode::cmdDeleteVar(const Common::Array<LBValue> &params) {
+ if (params.size() != 1)
+ error("incorrect number of parameters (%d) to deleteVar", params.size());
+
+ const Common::String &string = params[0].toString();
+ _vm->_variables.erase(string);
+}
+
void LBCode::cmdExec(const Common::Array<LBValue> &params) {
if (params.size() != 1)
error("incorrect number of parameters (%d) to exec", params.size());
@@ -1706,6 +1746,10 @@ uint LBCode::parseCode(const Common::String &source) {
if (token != ' ' && token != '(' && wasFunction)
error("while parsing script '%s', encountered incomplete function call", source.c_str());
+ // Skip C++-style comments
+ if (token == '/' && lookahead == '/')
+ break;
+
// First, we check for simple operators.
for (uint i = 0; i < NUM_LB_OPERATORS; i++) {
if (token != operators[i].token)
@@ -1736,6 +1780,7 @@ uint LBCode::parseCode(const Common::String &source) {
switch (token) {
// whitespace
case ' ':
+ case '\t':
// ignore
break;
// literal string
diff --git a/engines/mohawk/livingbooks_code.h b/engines/mohawk/livingbooks_code.h
index 080377ce99..6f6297d592 100644
--- a/engines/mohawk/livingbooks_code.h
+++ b/engines/mohawk/livingbooks_code.h
@@ -263,6 +263,10 @@ public:
void cmdLeft(const Common::Array<LBValue> &params);
void cmdBottom(const Common::Array<LBValue> &params);
void cmdRight(const Common::Array<LBValue> &params);
+ void cmdXPos(const Common::Array<LBValue> &params);
+ void cmdYPos(const Common::Array<LBValue> &params);
+ void cmdWidth(const Common::Array<LBValue> &params);
+ void cmdHeight(const Common::Array<LBValue> &params);
void cmdMove(const Common::Array<LBValue> &params);
void cmdSetDragParams(const Common::Array<LBValue> &params);
void cmdNewList(const Common::Array<LBValue> &params);
@@ -273,6 +277,7 @@ public:
void cmdDeleteAt(const Common::Array<LBValue> &params);
void cmdSetProperty(const Common::Array<LBValue> &params);
void cmdGetProperty(const Common::Array<LBValue> &params);
+ void cmdDeleteVar(const Common::Array<LBValue> &params);
void cmdExec(const Common::Array<LBValue> &params);
void cmdReturn(const Common::Array<LBValue> &params);
void cmdSetPlayParams(const Common::Array<LBValue> &params);
diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp
index 4a3001774a..7a9596d8e0 100644
--- a/engines/mohawk/myst_areas.cpp
+++ b/engines/mohawk/myst_areas.cpp
@@ -223,20 +223,26 @@ VideoHandle MystResourceType6::playMovie() {
VideoHandle handle = _vm->_video->findVideoHandle(_videoFile);
// If the video is not running, play it
- if (handle == NULL_VID_HANDLE || _vm->_video->endOfVideo(handle)) {
- handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop);
+ if (!handle || handle->endOfVideo()) {
+ handle = _vm->_video->playMovie(_videoFile);
+ if (!handle)
+ error("Failed to open '%s'", _videoFile.c_str());
+
+ handle->moveTo(_left, _top);
+ handle->setLooping(_loop != 0);
+
if (_direction == -1) {
- _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle));
- _vm->_video->setVideoRate(handle, -1);
+ handle->seek(handle->getDuration());
+ handle->setRate(-1);
}
} else {
// Resume the video
- _vm->_video->pauseMovie(handle, false);
+ handle->pause(false);
}
if (_playBlocking) {
_vm->_video->waitUntilMovieEnds(handle);
- handle = NULL_VID_HANDLE;
+ return VideoHandle();
}
return handle;
@@ -249,13 +255,13 @@ void MystResourceType6::handleCardChange() {
bool MystResourceType6::isPlaying() {
VideoHandle handle = _vm->_video->findVideoHandle(_videoFile);
- return handle != NULL_VID_HANDLE && !_vm->_video->endOfVideo(handle);
+ return handle && !handle->endOfVideo();
}
void MystResourceType6::pauseMovie(bool pause) {
VideoHandle handle = _vm->_video->findVideoHandle(_videoFile);
- if (handle != NULL_VID_HANDLE && !_vm->_video->endOfVideo(handle))
- _vm->_video->pauseMovie(handle, pause);
+ if (handle && !handle->endOfVideo())
+ handle->pause(pause);
}
MystResourceType7::MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) {
diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp
index 2dd5745550..dfa15a9b6c 100644
--- a/engines/mohawk/myst_stacks/channelwood.cpp
+++ b/engines/mohawk/myst_stacks/channelwood.cpp
@@ -299,13 +299,17 @@ bool Channelwood::pipeChangeValve(bool open, uint16 mask) {
void Channelwood::o_bridgeToggle(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Bridge rise / skink video", op);
- VideoHandle bridge = _vm->_video->playMovie(_vm->wrapMovieFilename("bridge", kChannelwoodStack), 292, 203);
+ VideoHandle bridge = _vm->_video->playMovie(_vm->wrapMovieFilename("bridge", kChannelwoodStack));
+ if (!bridge)
+ error("Failed to open 'bridge' movie");
+
+ bridge->moveTo(292, 203);
// Toggle bridge state
if (_state.waterPumpBridgeState)
- _vm->_video->setVideoBounds(bridge, Audio::Timestamp(0, 3050, 600), Audio::Timestamp(0, 6100, 600));
+ bridge->setBounds(Audio::Timestamp(0, 3050, 600), Audio::Timestamp(0, 6100, 600));
else
- _vm->_video->setVideoBounds(bridge, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3050, 600));
+ bridge->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3050, 600));
_vm->_video->waitUntilMovieEnds(bridge);
}
@@ -317,13 +321,17 @@ void Channelwood::o_pipeExtend(uint16 op, uint16 var, uint16 argc, uint16 *argv)
debugC(kDebugScript, "\tsoundId: %d", soundId);
_vm->_sound->replaceSoundMyst(soundId);
- VideoHandle pipe = _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack), 267, 170);
+ VideoHandle pipe = _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack));
+ if (!pipe)
+ error("Failed to open 'pipebrid' movie");
+
+ pipe->moveTo(267, 170);
// Toggle pipe state
if (_state.pipeState)
- _vm->_video->setVideoBounds(pipe, Audio::Timestamp(0, 3040, 600), Audio::Timestamp(0, 6080, 600));
+ pipe->setBounds(Audio::Timestamp(0, 3040, 600), Audio::Timestamp(0, 6080, 600));
else
- _vm->_video->setVideoBounds(pipe, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3040, 600));
+ pipe->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3040, 600));
_vm->_video->waitUntilMovieEnds(pipe);
_vm->_sound->resumeBackgroundMyst();
@@ -605,23 +613,29 @@ void Channelwood::o_hologramMonitor(uint16 op, uint16 var, uint16 argc, uint16 *
_vm->_video->stopVideos();
+ VideoHandle handle;
+
switch (button) {
case 0:
- _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack), 227, 70);
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack));
break;
case 1:
- _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack), 227, 70);
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack));
break;
case 2:
- _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack), 227, 70);
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack));
break;
case 3:
- _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack), 227, 70);
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack));
break;
default:
warning("Opcode %d Control Variable Out of Range", op);
break;
}
+
+ // Move the video to the right location
+ if (handle)
+ handle->moveTo(227, 70);
}
}
diff --git a/engines/mohawk/myst_stacks/dni.cpp b/engines/mohawk/myst_stacks/dni.cpp
index 3eb3c40cbb..6ba0b63423 100644
--- a/engines/mohawk/myst_stacks/dni.cpp
+++ b/engines/mohawk/myst_stacks/dni.cpp
@@ -103,14 +103,14 @@ void Dni::o_handPage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
VideoHandle atrus = _vm->_video->findVideoHandle(_video);
// Good ending and Atrus asked to give page
- if (_globals.ending == 1 && _vm->_video->getTime(atrus) > (uint)Audio::Timestamp(0, 6801, 600).msecs()) {
+ if (_globals.ending == 1 && atrus && atrus->getTime() > (uint)Audio::Timestamp(0, 6801, 600).msecs()) {
_globals.ending = 2;
_globals.heldPage = 0;
_vm->setMainCursor(kDefaultMystCursor);
// Play movie end (atrus leaving)
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 14813, 600), _vm->_video->getDuration(atrus));
- _vm->_video->setVideoLooping(atrus, false);
+ atrus->setBounds(Audio::Timestamp(0, 14813, 600), atrus->getDuration());
+ atrus->setLooping(false);
_atrusLeft = true;
_waitForLoop = false;
@@ -121,8 +121,12 @@ void Dni::o_handPage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
void Dni::atrusLeft_run() {
if (_vm->_system->getMillis() > _atrusLeftTime + 63333) {
_video = _vm->wrapMovieFilename("atrus2", kDniStack);
- VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77);
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 98000, 600));
+ VideoHandle atrus = _vm->_video->playMovie(_video);
+ if (!atrus)
+ error("Failed to open '%s'", _video.c_str());
+
+ atrus->moveTo(215, 77);
+ atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 98000, 600));
_waitForLoop = true;
_loopStart = 73095;
@@ -139,9 +143,13 @@ void Dni::atrusLeft_run() {
void Dni::loopVideo_run() {
if (!_vm->_video->isVideoPlaying()) {
- VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77);
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, _loopStart, 600), Audio::Timestamp(0, _loopEnd, 600));
- _vm->_video->setVideoLooping(atrus, true);
+ VideoHandle atrus = _vm->_video->playMovie(_video);
+ if (!atrus)
+ error("Failed to open '%s'", _video.c_str());
+
+ atrus->moveTo(215, 77);
+ atrus->setBounds(Audio::Timestamp(0, _loopStart, 600), Audio::Timestamp(0, _loopEnd, 600));
+ atrus->setLooping(true);
_waitForLoop = false;
}
@@ -155,14 +163,23 @@ void Dni::atrus_run() {
// Atrus asking for page
if (!_vm->_video->isVideoPlaying()) {
_video = _vm->wrapMovieFilename("atr1page", kDniStack);
- VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77, true);
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 7388, 600), Audio::Timestamp(0, 14700, 600));
+ VideoHandle atrus = _vm->_video->playMovie(_video);
+ if (!atrus)
+ error("Failed to open '%s'", _video.c_str());
+
+ atrus->moveTo(215, 77);
+ atrus->setLooping(true);
+ atrus->setBounds(Audio::Timestamp(0, 7388, 600), Audio::Timestamp(0, 14700, 600));
}
} else if (_globals.ending != 3 && _globals.ending != 4) {
if (_globals.heldPage == 13) {
_video = _vm->wrapMovieFilename("atr1page", kDniStack);
- VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77);
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 14700, 600));
+ VideoHandle atrus = _vm->_video->playMovie(_video);
+ if (!atrus)
+ error("Failed to open '%s'", _video.c_str());
+
+ atrus->moveTo(215, 77);
+ atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 14700, 600));
_waitForLoop = true;
_loopStart = 7388;
@@ -173,8 +190,12 @@ void Dni::atrus_run() {
} else {
_video = _vm->wrapMovieFilename("atr1nopg", kDniStack);
- VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77);
- _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 46175, 600));
+ VideoHandle atrus = _vm->_video->playMovie(_video);
+ if (!atrus)
+ error("Failed to open '%s'", _video.c_str());
+
+ atrus->moveTo(215, 77);
+ atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 46175, 600));
_waitForLoop = true;
_loopStart = 30656;
@@ -184,7 +205,12 @@ void Dni::atrus_run() {
_globals.ending = 3;
}
} else if (!_vm->_video->isVideoPlaying()) {
- _vm->_video->playMovie(_vm->wrapMovieFilename("atrwrite", kDniStack), 215, 77, true);
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("atrwrite", kDniStack));
+ if (!handle)
+ error("Failed to open atrwrite movie");
+
+ handle->moveTo(215, 77);
+ handle->setLooping(true);
}
}
diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp
index 2a33379198..dc66984398 100644
--- a/engines/mohawk/myst_stacks/intro.cpp
+++ b/engines/mohawk/myst_stacks/intro.cpp
@@ -98,10 +98,16 @@ void Intro::introMovies_run() {
// Play Intro Movies
// This is all quite messy...
+ VideoHandle handle;
+
switch (_introStep) {
case 0:
_introStep = 1;
- _vm->_video->playMovie(_vm->wrapMovieFilename("broder", kIntroStack));
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("broder", kIntroStack));
+ if (!handle)
+ error("Failed to open broder movie");
+
+ handle->center();
break;
case 1:
if (!_vm->_video->isVideoPlaying())
@@ -109,7 +115,11 @@ void Intro::introMovies_run() {
break;
case 2:
_introStep = 3;
- _vm->_video->playMovie(_vm->wrapMovieFilename("cyanlogo", kIntroStack));
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cyanlogo", kIntroStack));
+ if (!handle)
+ error("Failed to open cyanlogo movie");
+
+ handle->center();
break;
case 3:
if (!_vm->_video->isVideoPlaying())
@@ -118,8 +128,13 @@ void Intro::introMovies_run() {
case 4:
_introStep = 5;
- if (!(_vm->getFeatures() & GF_DEMO)) // The demo doesn't have the intro video
- _vm->_video->playMovie(_vm->wrapMovieFilename("intro", kIntroStack));
+ if (!(_vm->getFeatures() & GF_DEMO)) { // The demo doesn't have the intro video
+ handle = _vm->_video->playMovie(_vm->wrapMovieFilename("intro", kIntroStack));
+ if (!handle)
+ error("Failed to open intro movie");
+
+ handle->center();
+ }
break;
case 5:
if (!_vm->_video->isVideoPlaying())
diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp
index b5d1285435..ffcaa226c6 100644
--- a/engines/mohawk/myst_stacks/mechanical.cpp
+++ b/engines/mohawk/myst_stacks/mechanical.cpp
@@ -316,12 +316,16 @@ void Mechanical::o_snakeBoxTrigger(uint16 op, uint16 var, uint16 argc, uint16 *a
void Mechanical::o_fortressStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Play Stairs Movement Movie", op);
- VideoHandle staircase = _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack), 174, 222);
+ VideoHandle staircase = _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack));
+ if (!staircase)
+ error("Failed to open hhstairs movie");
+
+ staircase->moveTo(174, 222);
if (_state.staircaseState) {
- _vm->_video->setVideoBounds(staircase, Audio::Timestamp(0, 840, 600), Audio::Timestamp(0, 1680, 600));
+ staircase->setBounds(Audio::Timestamp(0, 840, 600), Audio::Timestamp(0, 1680, 600));
} else {
- _vm->_video->setVideoBounds(staircase, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 840, 600));
+ staircase->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 840, 600));
}
_vm->_video->waitUntilMovieEnds(staircase);
@@ -571,8 +575,12 @@ void Mechanical::o_elevatorWindowMovie(uint16 op, uint16 var, uint16 argc, uint1
debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime);
- VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack), 253, 0);
- _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
+ VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack));
+ if (!window)
+ error("Failed to open ewindow movie");
+
+ window->moveTo(253, 0);
+ window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
_vm->_video->waitUntilMovieEnds(window);
}
@@ -644,8 +652,12 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 *
debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime);
- VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack), 206, 38);
- _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
+ VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack));
+ if (!window)
+ error("Failed to open hcelev movie");
+
+ window->moveTo(206, 38);
+ window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
_vm->_video->waitUntilMovieEnds(window);
}
@@ -653,7 +665,7 @@ void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 arg
debugC(kDebugScript, "Opcode %d: Set fortress position", op);
VideoHandle gears = _fortressRotationGears->playMovie();
- uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames();
+ uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames();
// Myst ME short movie workaround, explained in o_fortressRotation_init
if (_fortressRotationShortMovieWorkaround) {
@@ -788,9 +800,8 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin
void Mechanical::fortressRotation_run() {
VideoHandle gears = _fortressRotationGears->playMovie();
- double oldRate = _vm->_video->getVideoRate(gears).toDouble();
-
- uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames();
+ double oldRate = gears->getRate().toDouble();
+ uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames();
// Myst ME short movie workaround, explained in o_fortressRotation_init
if (_fortressRotationShortMovieWorkaround) {
@@ -837,19 +848,19 @@ void Mechanical::fortressRotation_run() {
newRate = CLIP<double>(newRate, -2.5, 2.5);
- _vm->_video->setVideoRate(gears, Common::Rational((int)(newRate * 1000.0), 1000));
+ gears->setRate(Common::Rational((int)(newRate * 1000.0), 1000));
_gearsWereRunning = true;
} else if (_gearsWereRunning) {
// The fortress has stopped. Set its new position
_fortressPosition = (moviePosition + 900) / 1800 % 4;
- _vm->_video->setVideoRate(gears, 0);
+ gears->setRate(0);
if (!_fortressRotationShortMovieWorkaround) {
- _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600));
+ gears->seek(Audio::Timestamp(0, 1800 * _fortressPosition, 600));
} else {
- _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600));
+ gears->seek(Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600));
}
_vm->_sound->playSoundBlocking(_fortressRotationSounds[_fortressPosition]);
@@ -864,9 +875,9 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin
_fortressRotationGears = static_cast<MystResourceType6 *>(_invokingResource);
VideoHandle gears = _fortressRotationGears->playMovie();
- _vm->_video->setVideoLooping(gears, true);
- _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600));
- _vm->_video->setVideoRate(gears, 0);
+ gears->setLooping(true);
+ gears->seek(Audio::Timestamp(0, 1800 * _fortressPosition, 600));
+ gears->setRate(0);
_fortressRotationSounds[0] = argv[0];
_fortressRotationSounds[1] = argv[1];
@@ -884,7 +895,7 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin
// ScummVM simulates a longer movie by counting the number of times the movie
// looped and adding that time to the current movie position.
// Hence allowing the fortress position to be properly computed.
- uint32 movieDuration = _vm->_video->getDuration(gears).convertToFramerate(600).totalNumberOfFrames();
+ uint32 movieDuration = gears->getDuration().convertToFramerate(600).totalNumberOfFrames();
if (movieDuration == 3680) {
_fortressRotationShortMovieWorkaround = true;
_fortressRotationShortMovieCount = 0;
@@ -924,8 +935,8 @@ void Mechanical::fortressSimulation_run() {
_fortressSimulationStartup->pauseMovie(true);
VideoHandle holo = _fortressSimulationHolo->playMovie();
- _vm->_video->setVideoLooping(holo, true);
- _vm->_video->setVideoRate(holo, 0);
+ holo->setLooping(true);
+ holo->setRate(0);
_vm->_cursor->showCursor();
@@ -933,9 +944,8 @@ void Mechanical::fortressSimulation_run() {
} else {
VideoHandle holo = _fortressSimulationHolo->playMovie();
- double oldRate = _vm->_video->getVideoRate(holo).toDouble();
-
- uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(holo), 600).totalNumberOfFrames();
+ double oldRate = holo->getRate().toDouble();
+ uint32 moviePosition = Audio::Timestamp(holo->getTime(), 600).totalNumberOfFrames();
int32 positionInQuarter = 900 - (moviePosition + 900) % 1800;
@@ -968,15 +978,15 @@ void Mechanical::fortressSimulation_run() {
newRate = CLIP<double>(newRate, -2.5, 2.5);
- _vm->_video->setVideoRate(holo, Common::Rational((int)(newRate * 1000.0), 1000));
+ holo->setRate(Common::Rational((int)(newRate * 1000.0), 1000));
_gearsWereRunning = true;
} else if (_gearsWereRunning) {
// The fortress has stopped. Set its new position
uint16 simulationPosition = (moviePosition + 900) / 1800 % 4;
- _vm->_video->setVideoRate(holo, 0);
- _vm->_video->seekToTime(holo, Audio::Timestamp(0, 1800 * simulationPosition, 600));
+ holo->setRate(0);
+ holo->seek(Audio::Timestamp(0, 1800 * simulationPosition, 600));
_vm->_sound->playSoundBlocking( _fortressRotationSounds[simulationPosition]);
_gearsWereRunning = false;
diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp
index ca6e7c0ee5..98f0aa5349 100644
--- a/engines/mohawk/myst_stacks/myst.cpp
+++ b/engines/mohawk/myst_stacks/myst.cpp
@@ -51,8 +51,6 @@ Myst::Myst(MohawkEngine_Myst *vm) :
_dockVaultState = 0;
_cabinDoorOpened = 0;
_cabinMatchState = 2;
- _cabinGaugeMovie = NULL_VID_HANDLE;
- _cabinFireMovie = NULL_VID_HANDLE;
_matchBurning = false;
_tree = 0;
_treeAlcove = 0;
@@ -1135,10 +1133,13 @@ void Myst::o_clockWheelsExecute(uint16 op, uint16 var, uint16 argc, uint16 *argv
_vm->_system->delayMillis(500);
// Gears rise up
- VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 33);
- _vm->_video->setVideoBounds(gears, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 650, 600));
- _vm->_video->waitUntilMovieEnds(gears);
+ VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack));
+ if (!gears)
+ error("Failed to open gears movie");
+ gears->moveTo(305, 33);
+ gears->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 650, 600));
+ _vm->_video->waitUntilMovieEnds(gears);
_state.clockTowerBridgeOpen = 1;
_vm->redrawArea(12);
@@ -1147,8 +1148,12 @@ void Myst::o_clockWheelsExecute(uint16 op, uint16 var, uint16 argc, uint16 *argv
_vm->_system->delayMillis(500);
// Gears sink down
- VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 33);
- _vm->_video->setVideoBounds(gears, Audio::Timestamp(0, 700, 600), Audio::Timestamp(0, 1300, 600));
+ VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack));
+ if (!gears)
+ error("Failed to open gears movie");
+
+ gears->moveTo(305, 33);
+ gears->setBounds(Audio::Timestamp(0, 700, 600), Audio::Timestamp(0, 1300, 600));
_vm->_video->waitUntilMovieEnds(gears);
_state.clockTowerBridgeOpen = 0;
@@ -1191,15 +1196,23 @@ void Myst::o_imagerPlayButton(uint16 op, uint16 var, uint16 argc, uint16 *argv)
if (_state.imagerActive) {
// Mountains disappearing
Common::String file = _vm->wrapMovieFilename("vltmntn", kMystStack);
- VideoHandle mountain = _vm->_video->playMovie(file, 159, 96, false);
- _vm->_video->setVideoBounds(mountain, Audio::Timestamp(0, 11180, 600), Audio::Timestamp(0, 16800, 600));
+ VideoHandle mountain = _vm->_video->playMovie(file);
+ if (!mountain)
+ error("Failed to open '%s'", file.c_str());
+
+ mountain->moveTo(159, 96);
+ mountain->setBounds(Audio::Timestamp(0, 11180, 600), Audio::Timestamp(0, 16800, 600));
_state.imagerActive = 0;
} else {
// Mountains appearing
Common::String file = _vm->wrapMovieFilename("vltmntn", kMystStack);
- VideoHandle mountain = _vm->_video->playMovie(file, 159, 96, false);
- _vm->_video->setVideoBounds(mountain, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 11180, 600));
+ VideoHandle mountain = _vm->_video->playMovie(file);
+ if (!mountain)
+ error("Failed to open '%s'", file.c_str());
+
+ mountain->moveTo(159, 96);
+ mountain->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 11180, 600));
_state.imagerActive = 1;
}
@@ -1212,20 +1225,20 @@ void Myst::o_imagerPlayButton(uint16 op, uint16 var, uint16 argc, uint16 *argv)
// Water disappearing
VideoHandle water = _imagerMovie->playMovie();
- _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 4204, 600), Audio::Timestamp(0, 6040, 600));
- _vm->_video->setVideoLooping(water, false);
+ water->setBounds(Audio::Timestamp(0, 4204, 600), Audio::Timestamp(0, 6040, 600));
+ water->setLooping(false);
_state.imagerActive = 0;
} else {
// Water appearing
VideoHandle water = _imagerMovie->playMovie();
- _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 1814, 600));
+ water->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 1814, 600));
_vm->_video->waitUntilMovieEnds(water);
// Water looping
water = _imagerMovie->playMovie();
- _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600));
- _vm->_video->setVideoLooping(water, true);
+ water->setBounds(Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600));
+ water->setLooping(true);
_state.imagerActive = 1;
}
@@ -1902,11 +1915,19 @@ Common::Rational Myst::boilerComputeGaugeRate(uint16 pressure, uint32 delay) {
}
void Myst::boilerResetGauge(const Common::Rational &rate) {
- if (_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ if (!_cabinGaugeMovie || _cabinGaugeMovie->endOfVideo()) {
if (_vm->getCurCard() == 4098) {
- _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96);
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack));
+ if (!_cabinGaugeMovie)
+ error("Failed to open cabingau movie");
+
+ _cabinGaugeMovie->moveTo(243, 96);
} else {
- _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136);
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack));
+ if (!_cabinGaugeMovie)
+ error("Failed to open cabingau movie");
+
+ _cabinGaugeMovie->moveTo(254, 136);
}
}
@@ -1914,10 +1935,10 @@ void Myst::boilerResetGauge(const Common::Rational &rate) {
if (rate > 0)
goTo = Audio::Timestamp(0, 0, 600);
else
- goTo = _vm->_video->getDuration(_cabinGaugeMovie);
+ goTo = _cabinGaugeMovie->getDuration();
- _vm->_video->seekToTime(_cabinGaugeMovie, goTo);
- _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
+ _cabinGaugeMovie->seek(goTo);
+ _cabinGaugeMovie->setRate(rate);
}
void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
@@ -1931,10 +1952,10 @@ void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(8098, 49152);
- if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ if (_cabinGaugeMovie && !_cabinGaugeMovie->endOfVideo()) {
uint16 delay = treeNextMoveDelay(_state.cabinValvePosition);
Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay);
- _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
+ _cabinGaugeMovie->setRate(rate);
}
} else if (_state.cabinValvePosition > 0)
@@ -2006,10 +2027,10 @@ void Myst::o_boilerDecreasePressureStop(uint16 op, uint16 var, uint16 argc, uint
if (_state.cabinValvePosition > 0)
_vm->_sound->replaceBackgroundMyst(8098, 49152);
- if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) {
+ if (_cabinGaugeMovie && !_cabinGaugeMovie->endOfVideo()) {
uint16 delay = treeNextMoveDelay(_state.cabinValvePosition);
Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay);
- _vm->_video->setVideoRate(_cabinGaugeMovie, rate);
+ _cabinGaugeMovie->setRate(rate);
}
} else {
@@ -2117,7 +2138,7 @@ void Myst::tree_run() {
// Check if alcove is accessible
treeSetAlcoveAccessible();
- if (_cabinGaugeMovie != NULL_VID_HANDLE) {
+ if (_cabinGaugeMovie) {
Common::Rational rate = boilerComputeGaugeRate(pressure, delay);
boilerResetGauge(rate);
}
@@ -2246,13 +2267,22 @@ void Myst::rocketCheckSolution() {
// Book appearing
Common::String movieFile = _vm->wrapMovieFilename("selenbok", kMystStack);
- _rocketLinkBook = _vm->_video->playMovie(movieFile, 224, 41);
- _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 660, 600));
+ _rocketLinkBook = _vm->_video->playMovie(movieFile);
+ if (!_rocketLinkBook)
+ error("Failed to open '%s'", movieFile.c_str());
+
+ _rocketLinkBook->moveTo(224, 41);
+ _rocketLinkBook->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 660, 600));
_vm->_video->waitUntilMovieEnds(_rocketLinkBook);
// Book looping closed
- _rocketLinkBook = _vm->_video->playMovie(movieFile, 224, 41, true);
- _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 660, 600), Audio::Timestamp(0, 3500, 600));
+ _rocketLinkBook = _vm->_video->playMovie(movieFile);
+ if (!_rocketLinkBook)
+ error("Failed to open '%s'", movieFile.c_str());
+
+ _rocketLinkBook->moveTo(224, 41);
+ _rocketLinkBook->setLooping(true);
+ _rocketLinkBook->setBounds(Audio::Timestamp(0, 660, 600), Audio::Timestamp(0, 3500, 600));
_tempVar = 1;
}
@@ -2367,7 +2397,7 @@ void Myst::o_rocketOpenBook(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Rocket open link book", op);
// Flyby movie
- _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 3500, 600), Audio::Timestamp(0, 13100, 600));
+ _rocketLinkBook->setBounds(Audio::Timestamp(0, 3500, 600), Audio::Timestamp(0, 13100, 600));
// Set linkable
_tempVar = 2;
@@ -2889,8 +2919,12 @@ void Myst::clockGearForwardOneStep(uint16 gear) {
// Set video bounds
uint16 gearPosition = _clockGearsPositions[gear] - 1;
- _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack), x[gear], y[gear]);
- _vm->_video->setVideoBounds(_clockGearsVideos[gear],
+ _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack));
+ if (!_clockGearsVideos[gear])
+ error("Failed to open %s movie", videos[gear]);
+
+ _clockGearsVideos[gear]->moveTo(x[gear], y[gear]);
+ _clockGearsVideos[gear]->setBounds(
Audio::Timestamp(0, startTime[gearPosition], 600),
Audio::Timestamp(0, endTime[gearPosition], 600));
}
@@ -2902,8 +2936,12 @@ void Myst::clockWeightDownOneStep() {
// Set video bounds
if (updateVideo) {
- _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0);
- _vm->_video->setVideoBounds(_clockWeightVideo,
+ _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack));
+ if (!_clockWeightVideo)
+ error("Failed to open cl1wlfch movie");
+
+ _clockWeightVideo->moveTo(124, 0);
+ _clockWeightVideo->setBounds(
Audio::Timestamp(0, _clockWeightPosition, 600),
Audio::Timestamp(0, _clockWeightPosition + 246, 600));
}
@@ -2931,7 +2969,7 @@ void Myst::o_clockLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv)
// Let movies stop playing
for (uint i = 0; i < ARRAYSIZE(videos); i++) {
VideoHandle handle = _vm->_video->findVideoHandle(_vm->wrapMovieFilename(videos[i], kMystStack));
- if (handle != NULL_VID_HANDLE)
+ if (handle)
_vm->_video->delayUntilMovieEnds(handle);
}
@@ -2956,8 +2994,12 @@ void Myst::clockGearsCheckSolution() {
// Make weight go down
_vm->_sound->replaceSoundMyst(9113);
- _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0);
- _vm->_video->setVideoBounds(_clockWeightVideo,
+ _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack));
+ if (!_clockWeightVideo)
+ error("Failed to open cl1wlfch movie");
+
+ _clockWeightVideo->moveTo(124, 0);
+ _clockWeightVideo->setBounds(
Audio::Timestamp(0, _clockWeightPosition, 600),
Audio::Timestamp(0, 2214, 600));
_vm->_video->waitUntilMovieEnds(_clockWeightVideo);
@@ -2968,7 +3010,7 @@ void Myst::clockGearsCheckSolution() {
_vm->_sound->replaceSoundMyst(7113);
// Gear opening video
- _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225);
+ _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cl1wggat", kMystStack), 195, 225);
_state.gearsOpen = 1;
_vm->redrawArea(40);
@@ -3011,7 +3053,7 @@ void Myst::clockReset() {
// Let movies stop playing
for (uint i = 0; i < ARRAYSIZE(videos); i++) {
VideoHandle handle = _vm->_video->findVideoHandle(_vm->wrapMovieFilename(videos[i], kMystStack));
- if (handle != NULL_VID_HANDLE)
+ if (handle)
_vm->_video->delayUntilMovieEnds(handle);
}
@@ -3024,9 +3066,13 @@ void Myst::clockReset() {
_vm->_sound->replaceSoundMyst(7113);
// Gear closing movie
- VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225);
- _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle));
- _vm->_video->setVideoRate(handle, -1);
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack));
+ if (!handle)
+ error("Failed to open cl1wggat movie");
+
+ handle->moveTo(195, 225);
+ handle->seek(handle->getDuration());
+ handle->setRate(-1);
_vm->_video->waitUntilMovieEnds(handle);
// Redraw gear
@@ -3038,11 +3084,15 @@ void Myst::clockReset() {
}
void Myst::clockResetWeight() {
- _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0);
+ _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack));
+ if (!_clockWeightVideo)
+ error("Failed to open cl1wlfch movie");
+
+ _clockWeightVideo->moveTo(124, 0);
// Play the movie backwards, weight going up
- _vm->_video->seekToTime(_clockWeightVideo, Audio::Timestamp(0, _clockWeightPosition, 600));
- _vm->_video->setVideoRate(_clockWeightVideo, -1);
+ _clockWeightVideo->seek(Audio::Timestamp(0, _clockWeightPosition, 600));
+ _clockWeightVideo->setRate(-1);
// Reset position
_clockWeightPosition = 0;
@@ -3057,8 +3107,12 @@ void Myst::clockResetGear(uint16 gear) {
// Set video bounds, gears going to 3
uint16 gearPosition = _clockGearsPositions[gear] - 1;
if (gearPosition != 2) {
- _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack), x[gear], y[gear]);
- _vm->_video->setVideoBounds(_clockGearsVideos[gear],
+ _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack));
+ if (!_clockGearsVideos[gear])
+ error("Failed to open gears movie");
+
+ _clockGearsVideos[gear]->moveTo(x[gear], y[gear]);
+ _clockGearsVideos[gear]->setBounds(
Audio::Timestamp(0, time[gearPosition], 600),
Audio::Timestamp(0, time[2], 600));
}
@@ -3289,8 +3343,8 @@ void Myst::imager_run() {
if (_state.imagerActive && _state.imagerSelection == 67) {
VideoHandle water = _imagerMovie->playMovie();
- _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600));
- _vm->_video->setVideoLooping(water, true);
+ water->setBounds(Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600));
+ water->setLooping(true);
}
}
@@ -3402,8 +3456,11 @@ void Myst::gullsFly1_run() {
else
x = _vm->_rnd->getRandomNumber(160) + 260;
- _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), x, 0);
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack));
+ if (!handle)
+ error("Failed to open gulls movie");
+ handle->moveTo(x, 0);
_gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334;
}
}
@@ -3548,8 +3605,11 @@ void Myst::gullsFly2_run() {
if (time > _gullsNextTime) {
uint16 video = _vm->_rnd->getRandomNumber(3);
if (video != 3) {
- _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), 424, 0);
-
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack));
+ if (!handle)
+ error("Failed to open gulls movie");
+
+ handle->moveTo(424, 0);
_gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334;
}
}
@@ -3580,31 +3640,41 @@ void Myst::o_boilerMovies_init(uint16 op, uint16 var, uint16 argc, uint16 *argv)
void Myst::boilerFireInit() {
if (_vm->getCurCard() == 4098) {
- _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279, true);
- _vm->_video->pauseMovie(_cabinFireMovie, true);
+ _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack));
+ if (!_cabinFireMovie)
+ error("Failed to open cabfire movie");
+
+ _cabinFireMovie->moveTo(240, 279);
+ _cabinFireMovie->setLooping(true);
+ _cabinFireMovie->pause(true);
_vm->redrawArea(305);
boilerFireUpdate(true);
} else {
if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition >= 1) {
- _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244, true);
+ _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack));
+ if (!_cabinFireMovie)
+ error("Failed to open cabfirfr movie");
+
+ _cabinFireMovie->moveTo(254, 244);
+ _cabinFireMovie->setLooping(true);
}
}
}
void Myst::boilerFireUpdate(bool init) {
- uint position = _vm->_video->getTime(_cabinFireMovie);
+ uint position = _cabinFireMovie->getTime();
if (_state.cabinPilotLightLit == 1) {
if (_state.cabinValvePosition == 0) {
if (position > (uint)Audio::Timestamp(0, 200, 600).msecs() || init) {
- _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600));
- _vm->_video->pauseMovie(_cabinFireMovie, false);
+ _cabinFireMovie->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600));
+ _cabinFireMovie->pause(false);
}
} else {
if (position < (uint)Audio::Timestamp(0, 200, 600).msecs() || init) {
- _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600));
- _vm->_video->pauseMovie(_cabinFireMovie, false);
+ _cabinFireMovie->setBounds(Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600));
+ _cabinFireMovie->pause(false);
}
}
}
@@ -3612,15 +3682,23 @@ void Myst::boilerFireUpdate(bool init) {
void Myst::boilerGaugeInit() {
if (_vm->getCurCard() == 4098) {
- _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96);
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack));
+ if (!_cabinFireMovie)
+ error("Failed to open cabingau movie");
+
+ _cabinFireMovie->moveTo(243, 96);
} else {
- _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136);
+ _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack));
+ if (!_cabinFireMovie)
+ error("Failed to open cabcgfar movie");
+
+ _cabinFireMovie->moveTo(254, 136);
}
Audio::Timestamp frame;
if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition > 12)
- frame = _vm->_video->getDuration(_cabinGaugeMovie);
+ frame = _cabinGaugeMovie->getDuration();
else
frame = Audio::Timestamp(0, 0, 600);
@@ -3680,18 +3758,27 @@ void Myst::greenBook_run() {
_vm->_sound->stopSound();
_vm->_sound->pauseBackgroundMyst();
+ VideoHandle book = _vm->_video->playMovie(file);
+ if (!book)
+ error("Failed to open '%s'", file.c_str());
+
+ book->moveTo(314, 76);
+
if (_globals.ending != 4) {
_tempVar = 2;
- _vm->_video->playMovie(file, 314, 76);
} else {
- VideoHandle book = _vm->_video->playMovie(file, 314, 76, true);
- _vm->_video->setVideoBounds(book, Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600));
+ book->setBounds(Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600));
+ book->setLooping(true);
_tempVar = 0;
}
} else if (_tempVar == 2 && !_vm->_video->isVideoPlaying()) {
- VideoHandle book = _vm->_video->playMovie(file, 314, 76);
- _vm->_video->setVideoBounds(book, Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600));
- _vm->_video->setVideoLooping(book, true);
+ VideoHandle book = _vm->_video->playMovie(file);
+ if (!book)
+ error("Failed to open '%s'", file.c_str());
+
+ book->moveTo(314, 76);
+ book->setBounds(Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600));
+ book->setLooping(true);
_tempVar = 0;
}
}
@@ -3714,8 +3801,11 @@ void Myst::gullsFly3_run() {
if (video != 3) {
uint16 x = _vm->_rnd->getRandomNumber(280) + 135;
- _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), x, 0);
+ VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack));
+ if (!handle)
+ error("Failed to open gulls movie");
+ handle->moveTo(x, 0);
_gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334;
}
}
@@ -3750,8 +3840,8 @@ void Myst::o_treeEntry_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
debugC(kDebugScript, "Opcode %d: Exit boiler card", op);
- _cabinGaugeMovie = NULL_VID_HANDLE;
- _cabinFireMovie = NULL_VID_HANDLE;
+ _cabinGaugeMovie = VideoHandle();
+ _cabinFireMovie = VideoHandle();
}
void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
diff --git a/engines/mohawk/myst_stacks/stoneship.cpp b/engines/mohawk/myst_stacks/stoneship.cpp
index d8dbeef641..1113ceeac9 100644
--- a/engines/mohawk/myst_stacks/stoneship.cpp
+++ b/engines/mohawk/myst_stacks/stoneship.cpp
@@ -425,8 +425,12 @@ void Stoneship::o_cabinBookMovie(uint16 op, uint16 var, uint16 argc, uint16 *arg
uint16 startTime = argv[0];
uint16 endTime = argv[1];
- VideoHandle book = _vm->_video->playMovie(_vm->wrapMovieFilename("bkroom", kStoneshipStack), 159, 99);
- _vm->_video->setVideoBounds(book, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
+ VideoHandle book = _vm->_video->playMovie(_vm->wrapMovieFilename("bkroom", kStoneshipStack));
+ if (!book)
+ error("Failed to open bkroom movie");
+
+ book->moveTo(159, 99);
+ book->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600));
_vm->_video->waitUntilMovieEnds(book);
}
@@ -597,9 +601,9 @@ void Stoneship::o_hologramPlayback(uint16 op, uint16 var, uint16 argc, uint16 *a
if (_hologramTurnedOn) {
if (_hologramDisplayPos)
endPoint = _hologramDisplayPos;
- _vm->_video->setVideoBounds(displayMovie, Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600));
+ displayMovie->setBounds(Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600));
} else {
- _vm->_video->setVideoBounds(displayMovie, Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600));
+ displayMovie->setBounds(Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600));
}
_vm->_video->delayUntilMovieEnds(displayMovie);
@@ -673,29 +677,45 @@ void Stoneship::o_chestValveVideos(uint16 op, uint16 var, uint16 argc, uint16 *a
if (_state.chestValveState) {
// Valve closing
- VideoHandle valve = _vm->_video->playMovie(movie, 97, 267);
- _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600));
+ VideoHandle valve = _vm->_video->playMovie(movie);
+ if (!valve)
+ error("Failed to open '%s'", movie.c_str());
+
+ valve->moveTo(97, 267);
+ valve->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600));
_vm->_video->waitUntilMovieEnds(valve);
} else if (_state.chestWaterState) {
// Valve opening, spilling water
- VideoHandle valve = _vm->_video->playMovie(movie, 97, 267);
- _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 350, 600), Audio::Timestamp(0, 650, 600));
+ VideoHandle valve = _vm->_video->playMovie(movie);
+ if (!valve)
+ error("Failed to open '%s'", movie.c_str());
+
+ valve->moveTo(97, 267);
+ valve->setBounds(Audio::Timestamp(0, 350, 600), Audio::Timestamp(0, 650, 600));
_vm->_video->waitUntilMovieEnds(valve);
_vm->_sound->playSound(3132);
for (uint i = 0; i < 25; i++) {
- valve = _vm->_video->playMovie(movie, 97, 267);
- _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 650, 600), Audio::Timestamp(0, 750, 600));
+ valve = _vm->_video->playMovie(movie);
+ if (!valve)
+ error("Failed to open '%s'", movie.c_str());
+
+ valve->moveTo(97, 267);
+ valve->setBounds(Audio::Timestamp(0, 650, 600), Audio::Timestamp(0, 750, 600));
_vm->_video->waitUntilMovieEnds(valve);
}
_vm->_sound->resumeBackgroundMyst();
} else {
// Valve opening
- VideoHandle valve = _vm->_video->playMovie(movie, 97, 267);
- _vm->_video->seekToTime(valve, Audio::Timestamp(0, 350, 600));
- _vm->_video->setVideoRate(valve, -1);
+ VideoHandle valve = _vm->_video->playMovie(movie);
+ if (!valve)
+ error("Failed to open '%s'", movie.c_str());
+
+ valve->moveTo(97, 267);
+ valve->seek(Audio::Timestamp(0, 350, 600));
+ valve->setRate(-1);
_vm->_video->waitUntilMovieEnds(valve);
}
}
@@ -716,14 +736,22 @@ void Stoneship::o_trapLockOpen(uint16 op, uint16 var, uint16 argc, uint16 *argv)
Common::String movie = _vm->wrapMovieFilename("openloc", kStoneshipStack);
- VideoHandle lock = _vm->_video->playMovie(movie, 187, 71);
- _vm->_video->setVideoBounds(lock, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 750, 600));
+ VideoHandle lock = _vm->_video->playMovie(movie);
+ if (!lock)
+ error("Failed to open '%s'", movie.c_str());
+
+ lock->moveTo(187, 71);
+ lock->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 750, 600));
_vm->_video->waitUntilMovieEnds(lock);
_vm->_sound->playSound(2143);
- lock = _vm->_video->playMovie(movie, 187, 71);
- _vm->_video->setVideoBounds(lock, Audio::Timestamp(0, 750, 600), Audio::Timestamp(0, 10000, 600));
+ lock = _vm->_video->playMovie(movie);
+ if (!lock)
+ error("Failed to open '%s'", movie.c_str());
+
+ lock->moveTo(187, 71);
+ lock->setBounds(Audio::Timestamp(0, 750, 600), Audio::Timestamp(0, 10000, 600));
_vm->_video->waitUntilMovieEnds(lock);
if (_state.pumpState != 4)
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
index a7fe12b9e1..898f68c581 100644
--- a/engines/mohawk/riven.cpp
+++ b/engines/mohawk/riven.cpp
@@ -828,7 +828,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) {
VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1);
uint32 timerTime = 500;
- if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) {
+ if (!oldHandle || oldHandle->endOfVideo()) {
uint32 &sunnerTime = vm->_vars["jsunnertime"];
if (sunnerTime == 0) {
@@ -836,7 +836,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) {
} else if (sunnerTime < vm->getTotalPlayTime()) {
VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(1, 3));
- timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000;
+ timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -858,7 +858,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) {
VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1);
uint32 timerTime = 500;
- if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) {
+ if (!oldHandle || oldHandle->endOfVideo()) {
uint32 &sunnerTime = vm->_vars["jsunnertime"];
if (sunnerTime == 0) {
@@ -874,7 +874,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) {
VideoHandle handle = vm->_video->playMovieRiven(movie);
- timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000;
+ timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -896,7 +896,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) {
VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1);
uint32 timerTime = 500;
- if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) {
+ if (!oldHandle || oldHandle->endOfVideo()) {
uint32 &sunnerTime = vm->_vars["jsunnertime"];
if (sunnerTime == 0) {
@@ -904,7 +904,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) {
} else if (sunnerTime < vm->getTotalPlayTime()) {
VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(3, 5));
- timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
+ timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -926,7 +926,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) {
VideoHandle oldHandle = vm->_video->findVideoHandleRiven(3);
uint32 timerTime = 500;
- if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) {
+ if (!oldHandle || oldHandle->endOfVideo()) {
uint32 &sunnerTime = vm->_vars["jsunnertime"];
if (sunnerTime == 0) {
@@ -938,7 +938,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) {
vm->_video->activateMLST(mlstID, vm->getCurCard());
VideoHandle handle = vm->_video->playMovieRiven(mlstID);
- timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
+ timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000;
}
sunnerTime = timerTime + vm->getTotalPlayTime();
@@ -969,7 +969,7 @@ void MohawkEngine_Riven::installCardTimer() {
}
void MohawkEngine_Riven::doVideoTimer(VideoHandle handle, bool force) {
- assert(handle != NULL_VID_HANDLE);
+ assert(handle);
uint16 id = _scriptMan->getStoredMovieOpcodeID();
@@ -977,7 +977,7 @@ void MohawkEngine_Riven::doVideoTimer(VideoHandle handle, bool force) {
return;
// Run the opcode if we can at this point
- if (force || _video->getTime(handle) >= _scriptMan->getStoredMovieOpcodeTime())
+ if (force || handle->getTime() >= _scriptMan->getStoredMovieOpcodeTime())
_scriptMan->runStoredMovieOpcode();
}
@@ -1003,7 +1003,7 @@ void MohawkEngine_Riven::checkSunnerAlertClick() {
// If the alert video is no longer playing, we have nothing left to do
VideoHandle handle = _video->findVideoHandleRiven(1);
- if (handle == NULL_VID_HANDLE || _video->endOfVideo(handle))
+ if (!handle || handle->endOfVideo())
return;
sunners = 1;
diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp
index cda0683028..00075039fe 100644
--- a/engines/mohawk/riven_external.cpp
+++ b/engines/mohawk/riven_external.cpp
@@ -229,7 +229,7 @@ void RivenExternal::runCredits(uint16 video, uint32 delay) {
VideoHandle videoHandle = _vm->_video->findVideoHandleRiven(video);
while (!_vm->shouldQuit() && _vm->_gfx->getCurCreditsImage() <= 320) {
- if (_vm->_video->getCurFrame(videoHandle) >= (int32)_vm->_video->getFrameCount(videoHandle) - 1) {
+ if (videoHandle->getCurFrame() >= (int32)videoHandle->getFrameCount() - 1) {
if (nextCreditsFrameStart == 0) {
// Set us up to start after delay ms
nextCreditsFrameStart = _vm->_system->getMillis() + delay;
@@ -265,10 +265,10 @@ void RivenExternal::runDomeCheck() {
// Check if we clicked while the golden frame was showing
VideoHandle video = _vm->_video->findVideoHandleRiven(1);
- assert(video != NULL_VID_HANDLE);
+ assert(video);
- int32 curFrame = _vm->_video->getCurFrame(video);
- int32 frameCount = _vm->_video->getFrameCount(video);
+ int32 curFrame = video->getCurFrame();
+ int32 frameCount = video->getFrameCount();
// The final frame of the video is the 'golden' frame (double meaning: the
// frame that is the magic one is the one with the golden symbol) but we
@@ -857,8 +857,12 @@ void RivenExternal::xbupdateboiler(uint16 argc, uint16 *argv) {
_vm->_video->playMovieRiven(7);
}
} else {
- _vm->_video->disableMovieRiven(7);
- _vm->_video->disableMovieRiven(8);
+ VideoHandle handle = _vm->_video->findVideoHandleRiven(7);
+ if (handle)
+ handle->setEnabled(false);
+ handle = _vm->_video->findVideoHandleRiven(8);
+ if (handle)
+ handle->setEnabled(false);
}
}
@@ -1149,8 +1153,8 @@ void RivenExternal::lowerPins() {
// Play the video of the pins going down
VideoHandle handle = _vm->_video->playMovieRiven(upMovie);
- assert(handle != NULL_VID_HANDLE);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600));
+ assert(handle);
+ handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600));
_vm->_video->waitUntilMovieEnds(handle);
upMovie = 0;
@@ -1181,8 +1185,8 @@ void RivenExternal::xgrotatepins(uint16 argc, uint16 *argv) {
// Play the video of the pins rotating
VideoHandle handle = _vm->_video->playMovieRiven(_vm->_vars["gupmoov"]);
- assert(handle != NULL_VID_HANDLE);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 1215, 600));
+ assert(handle);
+ handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 1215, 600));
_vm->_video->waitUntilMovieEnds(handle);
}
@@ -1265,9 +1269,9 @@ void RivenExternal::xgpincontrols(uint16 argc, uint16 *argv) {
// Actually play the movie
VideoHandle handle = _vm->_video->playMovieRiven(pinMovieCodes[imagePos - 1]);
- assert(handle != NULL_VID_HANDLE);
+ assert(handle);
uint32 startTime = 9630 - pinPos * 600;
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600));
+ handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600));
_vm->_video->waitUntilMovieEnds(handle);
// Update the relevant variables
@@ -1343,8 +1347,8 @@ void RivenExternal::xgrviewer(uint16 argc, uint16 *argv) {
// Now play the movie
VideoHandle handle = _vm->_video->playMovieRiven(1);
- assert(handle != NULL_VID_HANDLE);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600));
+ assert(handle);
+ handle->setBounds(Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600));
_vm->_video->waitUntilMovieEnds(handle);
// Set the new position and let the card's scripts take over again
@@ -1412,8 +1416,8 @@ void RivenExternal::xglviewer(uint16 argc, uint16 *argv) {
// Now play the movie
VideoHandle handle = _vm->_video->playMovieRiven(1);
- assert(handle != NULL_VID_HANDLE);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600));
+ assert(handle);
+ handle->setBounds(Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600));
_vm->_video->waitUntilMovieEnds(handle);
// Set the new position to the variable
@@ -1467,7 +1471,7 @@ static void catherineViewerIdleTimer(MohawkEngine_Riven *vm) {
VideoHandle videoHandle = vm->_video->playMovieRiven(30);
// Reset the timer
- vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle).msecs() + vm->_rnd->getRandomNumber(60) * 1000);
+ vm->installTimer(&catherineViewerIdleTimer, videoHandle->getDuration().msecs() + vm->_rnd->getRandomNumber(60) * 1000);
}
void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) {
@@ -1507,7 +1511,7 @@ void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) {
_vm->_video->activateMLST(cathMovie, _vm->getCurCard());
VideoHandle videoHandle = _vm->_video->playMovieRiven(30);
- timeUntilNextMovie = _vm->_video->getDuration(videoHandle).msecs() + _vm->_rnd->getRandomNumber(60) * 1000;
+ timeUntilNextMovie = videoHandle->getDuration().msecs() + _vm->_rnd->getRandomNumber(60) * 1000;
} else {
// Otherwise, just redraw the imager
timeUntilNextMovie = _vm->_rnd->getRandomNumberRng(10, 20) * 1000;
@@ -1986,7 +1990,7 @@ void RivenExternal::xschool280_playwhark(uint16 argc, uint16 *argv) {
Audio::Timestamp startTime = Audio::Timestamp(0, (11560 / 19) * (*posVar), 600);
*posVar += number; // Adjust to the end
Audio::Timestamp endTime = Audio::Timestamp(0, (11560 / 19) * (*posVar), 600);
- _vm->_video->setVideoBounds(handle, startTime, endTime);
+ handle->setBounds(startTime, endTime);
_vm->_video->waitUntilMovieEnds(handle);
if (*posVar > 19) {
@@ -2059,7 +2063,7 @@ void RivenExternal::xbookclick(uint16 argc, uint16 *argv) {
debug(0, "\tHotspot = %d -> %d", argv[3], hotspotMap[argv[3] - 1]);
// Just let the video play while we wait until Gehn opens the trap book for us
- while (_vm->_video->getTime(video) < startTime && !_vm->shouldQuit()) {
+ while (video->getTime() < startTime && !_vm->shouldQuit()) {
if (_vm->_video->updateMovies())
_vm->_system->updateScreen();
@@ -2084,7 +2088,7 @@ void RivenExternal::xbookclick(uint16 argc, uint16 *argv) {
// OK, Gehn has opened the trap book and has asked us to go in. Let's watch
// and see what the player will do...
- while (_vm->_video->getTime(video) < endTime && !_vm->shouldQuit()) {
+ while (video->getTime() < endTime && !_vm->shouldQuit()) {
bool updateScreen = _vm->_video->updateMovies();
Common::Event event;
@@ -2335,7 +2339,7 @@ static void rebelPrisonWindowTimer(MohawkEngine_Riven *vm) {
VideoHandle handle = vm->_video->playMovieRiven(movie);
// Ensure the next video starts after this one ends
- uint32 timeUntilNextVideo = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000;
+ uint32 timeUntilNextVideo = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000;
// Save the time in case we leave the card and return
vm->_vars["rvillagetime"] = timeUntilNextVideo + vm->getTotalPlayTime();
@@ -2434,7 +2438,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) {
static const uint32 timeIntervals[] = { 4320, 3440, 2560, 1760, 880, 0 };
uint16 movieCode = telescopeCover ? 1 : 2;
VideoHandle handle = _vm->_video->playMovieRiven(movieCode);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, timeIntervals[telescopePos], 600), Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600));
+ handle->setBounds(Audio::Timestamp(0, timeIntervals[telescopePos], 600), Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600));
_vm->_sound->playSound(14); // Play the moving sound
_vm->_video->waitUntilMovieEnds(handle);
@@ -2467,7 +2471,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) {
static const uint32 timeIntervals[] = { 0, 800, 1680, 2560, 3440, 4320 };
uint16 movieCode = _vm->_vars["ttelecover"] ? 4 : 5;
VideoHandle handle = _vm->_video->playMovieRiven(movieCode);
- _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600), Audio::Timestamp(0, timeIntervals[telescopePos], 600));
+ handle->setBounds(Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600), Audio::Timestamp(0, timeIntervals[telescopePos], 600));
_vm->_sound->playSound(14); // Play the moving sound
_vm->_video->waitUntilMovieEnds(handle);
@@ -2569,26 +2573,47 @@ void RivenExternal::xt7500_checkmarbles(uint16 argc, uint16 *argv) {
void RivenExternal::xt7600_setupmarbles(uint16 argc, uint16 *argv) {
// Draw the small marbles when we're a step away from the waffle
- uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, "*tsmallred");
+
+ // Convert from marble X coordinate to screen X coordinate
+ static const uint16 xPosOffsets[] = {
+ 246, 245, 244, 243, 243, 241, 240, 240, 239, 238, 237, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 226, 225
+ };
+
+ // Convert from marble Y coordinate to screen Y coordinate
+ static const uint16 yPosOffsets[] = {
+ 261, 263, 265, 267, 268, 270, 272, 274, 276, 278, 281, 284, 285, 288, 290, 293, 295, 298, 300, 303, 306, 309, 311, 314, 316
+ };
+
+ // Handle spacing for y coordinates due to the angle
+ static const double yAdjusts[] = {
+ 4.56, 4.68, 4.76, 4.84, 4.84, 4.96, 5.04, 5.04, 5.12, 5.2, 5.28, 5.28, 5.36, 5.44, 5.4, 5.6, 5.72, 5.8, 5.88, 5.96, 6.04, 6.12, 6.2, 6.2, 6.28
+ };
+
+ // Waffle state of 0 is up, 1 down
bool waffleDown = _vm->_vars["twaffle"] != 0;
// Note that each of the small marble images is exactly 4x2
+ // The original seems to scale the marble images from extras.mhk, but
+ // we're using the pre-scaled images in the stack.
+ uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, "*tsmallred");
for (uint16 i = 0; i < kMarbleCount; i++) {
- uint32 &var = _vm->_vars[s_marbleNames[i]];
+ uint32 var = _vm->_vars[s_marbleNames[i]];
if (var == 0) {
// The marble is still in its initial place
// (Note that this is still drawn even if the waffle is down)
- int marbleX = 376 + i * 2;
- int marbleY = 253 + i * 4;
- _vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight);
+ static const uint16 defaultX[] = { 375, 377, 379, 381, 383, 385 };
+ static const uint16 defaultY[] = { 253, 257, 261, 265, 268, 273 };
+ _vm->_gfx->copyImageToScreen(baseBitmapId + i, defaultX[i], defaultY[i], defaultX[i] + kSmallMarbleWidth, defaultY[i] + kSmallMarbleHeight);
} else if (waffleDown) {
// The marble is on the grid and the waffle is down
// (Nothing to draw here)
} else {
// The marble is on the grid and the waffle is up
- // TODO: Draw them onto the grid
+ int marbleX = (int)floor(getMarbleX(var) * yAdjusts[getMarbleY(var)] + xPosOffsets[getMarbleY(var)] + 0.5);
+ int marbleY = yPosOffsets[getMarbleY(var)];
+ _vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight);
}
}
}
diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp
index 29ee5cd50b..caa235ec8b 100644
--- a/engines/mohawk/riven_scripts.cpp
+++ b/engines/mohawk/riven_scripts.cpp
@@ -493,7 +493,9 @@ void RivenScript::changeStack(uint16 op, uint16 argc, uint16 *argv) {
// Command 28: disable a movie
void RivenScript::disableMovie(uint16 op, uint16 argc, uint16 *argv) {
- _vm->_video->disableMovieRiven(argv[0]);
+ VideoHandle handle = _vm->_video->findVideoHandleRiven(argv[0]);
+ if (handle)
+ handle->setEnabled(false);
}
// Command 29: disable all movies
@@ -503,7 +505,9 @@ void RivenScript::disableAllMovies(uint16 op, uint16 argc, uint16 *argv) {
// Command 31: enable a movie
void RivenScript::enableMovie(uint16 op, uint16 argc, uint16 *argv) {
- _vm->_video->enableMovieRiven(argv[0]);
+ VideoHandle handle = _vm->_video->findVideoHandleRiven(argv[0]);
+ if (handle)
+ handle->setEnabled(true);
}
// Command 32: play foreground movie - blocking (movie_id)
diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp
index 3f27e4a1a4..ff4a69cd28 100644
--- a/engines/mohawk/video.cpp
+++ b/engines/mohawk/video.cpp
@@ -24,6 +24,7 @@
#include "mohawk/resource.h"
#include "mohawk/video.h"
+#include "common/algorithm.h"
#include "common/debug.h"
#include "common/events.h"
#include "common/textconsole.h"
@@ -37,19 +38,110 @@
namespace Mohawk {
-void VideoEntry::clear() {
- video = 0;
- x = 0;
- y = 0;
- loop = false;
- enabled = false;
- start = Audio::Timestamp(0, 1);
- filename.clear();
- id = -1;
+VideoEntry::VideoEntry() : _video(0), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) {
}
-bool VideoEntry::endOfVideo() {
- return !video || video->endOfVideo();
+VideoEntry::VideoEntry(Video::VideoDecoder *video, const Common::String &fileName) : _video(video), _fileName(fileName), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) {
+}
+
+VideoEntry::VideoEntry(Video::VideoDecoder *video, int id) : _video(video), _id(id), _x(0), _y(0), _loop(false), _enabled(true) {
+}
+
+VideoEntry::~VideoEntry() {
+ close();
+}
+
+void VideoEntry::close() {
+ delete _video;
+ _video = 0;
+}
+
+bool VideoEntry::endOfVideo() const {
+ return !isOpen() || _video->endOfVideo();
+}
+
+int VideoEntry::getCurFrame() const {
+ assert(_video);
+ return _video->getCurFrame();
+}
+
+uint32 VideoEntry::getFrameCount() const {
+ assert(_video);
+ return _video->getFrameCount();
+}
+
+uint32 VideoEntry::getTime() const {
+ assert(_video);
+ return _video->getTime();
+}
+
+Audio::Timestamp VideoEntry::getDuration() const {
+ assert(_video);
+ return _video->getDuration();
+}
+
+Common::Rational VideoEntry::getRate() const {
+ assert(_video);
+ return _video->getRate();
+}
+
+void VideoEntry::center() {
+ assert(_video);
+ _x = (g_system->getWidth() - _video->getWidth()) / 2;
+ _y = (g_system->getHeight() - _video->getHeight()) / 2;
+}
+
+void VideoEntry::setBounds(const Audio::Timestamp &startTime, const Audio::Timestamp &endTime) {
+ assert(_video);
+ _start = startTime;
+ _video->setEndTime(endTime);
+ _video->seek(startTime);
+}
+
+void VideoEntry::seek(const Audio::Timestamp &time) {
+ assert(_video);
+ _video->seek(time);
+}
+
+void VideoEntry::setRate(const Common::Rational &rate) {
+ assert(_video);
+ _video->setRate(rate);
+}
+
+void VideoEntry::pause(bool isPaused) {
+ assert(_video);
+ _video->pauseVideo(isPaused);
+}
+
+void VideoEntry::start() {
+ assert(_video);
+ _video->start();
+}
+
+void VideoEntry::stop() {
+ assert(_video);
+ _video->stop();
+}
+
+bool VideoEntry::isPlaying() const {
+ assert(_video);
+ return _video->isPlaying();
+}
+
+int VideoEntry::getVolume() const {
+ assert(_video);
+ return _video->getVolume();
+}
+
+void VideoEntry::setVolume(int volume) {
+ assert(_video);
+ _video->setVolume(CLIP(volume, 0, 255));
+}
+
+VideoHandle::VideoHandle(VideoEntryPtr ptr) : _ptr(ptr) {
+}
+
+VideoHandle::VideoHandle(const VideoHandle &handle) : _ptr(handle._ptr) {
}
VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) {
@@ -62,41 +154,42 @@ VideoManager::~VideoManager() {
}
void VideoManager::pauseVideos() {
- for (uint16 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].video)
- _videoStreams[i]->pauseVideo(true);
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ (*it)->pause(true);
}
void VideoManager::resumeVideos() {
- for (uint16 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].video)
- _videoStreams[i]->pauseVideo(false);
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ (*it)->pause(false);
}
void VideoManager::stopVideos() {
- for (uint16 i = 0; i < _videoStreams.size(); i++)
- delete _videoStreams[i].video;
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ (*it)->close();
- _videoStreams.clear();
+ _videos.clear();
}
-void VideoManager::playMovieBlocking(const Common::String &filename, uint16 x, uint16 y, bool clearScreen) {
- VideoHandle videoHandle = createVideoHandle(filename, x, y, false);
- if (videoHandle == NULL_VID_HANDLE)
+void VideoManager::playMovieBlocking(const Common::String &fileName, uint16 x, uint16 y, bool clearScreen) {
+ VideoEntryPtr ptr = open(fileName);
+ if (!ptr)
return;
+ ptr->moveTo(x, y);
+
// Clear screen if requested
if (clearScreen) {
_vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0));
_vm->_system->updateScreen();
}
- waitUntilMovieEnds(videoHandle);
+ ptr->start();
+ waitUntilMovieEnds(ptr);
}
-void VideoManager::playMovieBlockingCentered(const Common::String &filename, bool clearScreen) {
- VideoHandle videoHandle = createVideoHandle(filename, 0, 0, false);
- if (videoHandle == NULL_VID_HANDLE)
+void VideoManager::playMovieBlockingCentered(const Common::String &fileName, bool clearScreen) {
+ VideoEntryPtr ptr = open(fileName);
+ if (!ptr)
return;
// Clear screen if requested
@@ -105,19 +198,22 @@ void VideoManager::playMovieBlockingCentered(const Common::String &filename, boo
_vm->_system->updateScreen();
}
- _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2;
- _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2;
-
- waitUntilMovieEnds(videoHandle);
+ ptr->center();
+ ptr->start();
+ waitUntilMovieEnds(ptr);
}
void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) {
- if (videoHandle == NULL_VID_HANDLE)
+ if (!videoHandle)
return;
+ // Sanity check
+ if (videoHandle._ptr->isLooping())
+ error("Called waitUntilMovieEnds() on a looping video");
+
bool continuePlaying = true;
- while (!_videoStreams[videoHandle].endOfVideo() && !_vm->shouldQuit() && continuePlaying) {
+ while (!videoHandle->endOfVideo() && !_vm->shouldQuit() && continuePlaying) {
if (updateMovies())
_vm->_system->updateScreen();
@@ -149,12 +245,22 @@ void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) {
_vm->_system->delayMillis(10);
}
- delete _videoStreams[videoHandle].video;
- _videoStreams[videoHandle].clear();
+ // Ensure it's removed
+ removeEntry(videoHandle._ptr);
}
void VideoManager::delayUntilMovieEnds(VideoHandle videoHandle) {
- while (!_videoStreams[videoHandle].endOfVideo() && !_vm->shouldQuit()) {
+ // FIXME: Why is this separate from waitUntilMovieEnds?
+ // It seems to only cut out the event loop (which is bad).
+
+ if (!videoHandle)
+ return;
+
+ // Sanity check
+ if (videoHandle._ptr->isLooping())
+ error("Called delayUntilMovieEnds() on a looping video");
+
+ while (!videoHandle->endOfVideo() && !_vm->shouldQuit()) {
if (updateMovies())
_vm->_system->updateScreen();
@@ -162,73 +268,59 @@ void VideoManager::delayUntilMovieEnds(VideoHandle videoHandle) {
_vm->_system->delayMillis(10);
}
- delete _videoStreams[videoHandle].video;
- _videoStreams[videoHandle].clear();
+ // Ensure it's removed
+ removeEntry(videoHandle._ptr);
}
-VideoHandle VideoManager::playMovie(const Common::String &filename, int16 x, int16 y, bool loop) {
- VideoHandle videoHandle = createVideoHandle(filename, x, y, loop);
- if (videoHandle == NULL_VID_HANDLE)
- return NULL_VID_HANDLE;
-
- // Center x if requested
- if (x < 0)
- _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2;
-
- // Center y if requested
- if (y < 0)
- _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2;
+VideoHandle VideoManager::playMovie(const Common::String &fileName) {
+ VideoEntryPtr ptr = open(fileName);
+ if (!ptr)
+ return VideoHandle();
- return videoHandle;
+ ptr->start();
+ return ptr;
}
-VideoHandle VideoManager::playMovie(uint16 id, int16 x, int16 y, bool loop) {
- VideoHandle videoHandle = createVideoHandle(id, x, y, loop);
- if (videoHandle == NULL_VID_HANDLE)
- return NULL_VID_HANDLE;
+VideoHandle VideoManager::playMovie(uint16 id) {
+ VideoEntryPtr ptr = open(id);
+ if (!ptr)
+ return VideoHandle();
- // Center x if requested
- if (x < 0)
- _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2;
-
- // Center y if requested
- if (y < 0)
- _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2;
-
- return videoHandle;
+ ptr->start();
+ return ptr;
}
bool VideoManager::updateMovies() {
bool updateScreen = false;
- for (uint32 i = 0; i < _videoStreams.size() && !_vm->shouldQuit(); i++) {
- // Skip deleted videos
- if (!_videoStreams[i].video)
- continue;
-
- // Remove any videos that are over
- if (_videoStreams[i].endOfVideo()) {
- if (_videoStreams[i].loop) {
- _videoStreams[i]->seek(_videoStreams[i].start);
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); ) {
+ // Check of the video has reached the end
+ if ((*it)->endOfVideo()) {
+ if ((*it)->isLooping()) {
+ // Seek back if looping
+ (*it)->seek((*it)->getStart());
} else {
- // Check the video time one last time before deleting it
- _vm->doVideoTimer(i, true);
- delete _videoStreams[i].video;
- _videoStreams[i].clear();
+ // Done; close and continue on
+ (*it)->close();
+ it = _videos.erase(it);
continue;
}
}
- // Nothing more to do if we're paused
- if (_videoStreams[i]->isPaused())
+ Video::VideoDecoder *video = (*it)->_video;
+
+ // Ignore paused videos
+ if (video->isPaused()) {
+ it++;
continue;
+ }
// Check if we need to draw a frame
- if (_videoStreams[i]->needsUpdate()) {
- const Graphics::Surface *frame = _videoStreams[i]->decodeNextFrame();
+ if (video->needsUpdate()) {
+ const Graphics::Surface *frame = video->decodeNextFrame();
Graphics::Surface *convertedFrame = 0;
- if (frame && _videoStreams[i].enabled) {
+ if (frame && (*it)->isEnabled()) {
Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat();
if (frame->format != pixelFormat) {
@@ -236,25 +328,25 @@ bool VideoManager::updateMovies() {
// support in the codec. Set _enableDither if shows up.
if (pixelFormat.bytesPerPixel == 1) {
warning("Cannot convert high color video frame to 8bpp");
- delete _videoStreams[i].video;
- _videoStreams[i].clear();
+ (*it)->close();
+ it = _videos.erase(it);
continue;
}
// Convert to the current screen format
- convertedFrame = frame->convertTo(pixelFormat, _videoStreams[i]->getPalette());
+ convertedFrame = frame->convertTo(pixelFormat, video->getPalette());
frame = convertedFrame;
- } else if (pixelFormat.bytesPerPixel == 1 && _videoStreams[i]->hasDirtyPalette()) {
+ } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) {
// Set the palette when running in 8bpp mode only
// Don't do this for Myst, which has its own per-stack handling
if (_vm->getGameType() != GType_MYST)
- _vm->_system->getPaletteManager()->setPalette(_videoStreams[i]->getPalette(), 0, 256);
+ _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256);
}
// Clip the width/height to make sure we stay on the screen (Myst does this a few times)
- uint16 width = MIN<int32>(_videoStreams[i]->getWidth(), _vm->_system->getWidth() - _videoStreams[i].x);
- uint16 height = MIN<int32>(_videoStreams[i]->getHeight(), _vm->_system->getHeight() - _videoStreams[i].y);
- _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, _videoStreams[i].x, _videoStreams[i].y, width, height);
+ uint16 width = MIN<int32>(video->getWidth(), _vm->_system->getWidth() - (*it)->getX());
+ uint16 height = MIN<int32>(video->getHeight(), _vm->_system->getHeight() - (*it)->getY());
+ _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, (*it)->getX(), (*it)->getY(), width, height);
// We've drawn something to the screen, make sure we update it
updateScreen = true;
@@ -268,7 +360,10 @@ bool VideoManager::updateMovies() {
}
// Check the video time
- _vm->doVideoTimer(i, false);
+ _vm->doVideoTimer(*it, false);
+
+ // Remember to increase the iterator
+ it++;
}
// Return true if we need to update the screen
@@ -323,251 +418,165 @@ void VideoManager::clearMLST() {
}
VideoHandle VideoManager::playMovieRiven(uint16 id) {
- for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ for (uint16 i = 0; i < _mlstRecords.size(); i++) {
if (_mlstRecords[i].code == id) {
debug(1, "Play tMOV %d (non-blocking) at (%d, %d) %s, Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0 ? "looping" : "non-looping", _mlstRecords[i].volume);
- return createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0, _mlstRecords[i].volume);
+
+ VideoEntryPtr ptr = open(_mlstRecords[i].movieID);
+ if (ptr) {
+ ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top);
+ ptr->setLooping(_mlstRecords[i].loop != 0);
+ ptr->setVolume(_mlstRecords[i].volume);
+ ptr->start();
+ }
+
+ return ptr;
}
+ }
- return NULL_VID_HANDLE;
+ return VideoHandle();
}
void VideoManager::playMovieBlockingRiven(uint16 id) {
- for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ for (uint16 i = 0; i < _mlstRecords.size(); i++) {
if (_mlstRecords[i].code == id) {
debug(1, "Play tMOV %d (blocking) at (%d, %d), Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].volume);
- VideoHandle videoHandle = createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, false);
- waitUntilMovieEnds(videoHandle);
+ VideoEntryPtr ptr = open(_mlstRecords[i].movieID);
+ ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top);
+ ptr->setVolume(_mlstRecords[i].volume);
+ ptr->start();
+ waitUntilMovieEnds(ptr);
return;
}
+ }
}
void VideoManager::stopMovieRiven(uint16 id) {
debug(2, "Stopping movie %d", id);
- for (uint16 i = 0; i < _mlstRecords.size(); i++)
- if (_mlstRecords[i].code == id)
- for (uint16 j = 0; j < _videoStreams.size(); j++)
- if (_mlstRecords[i].movieID == _videoStreams[j].id) {
- delete _videoStreams[j].video;
- _videoStreams[j].clear();
- return;
- }
-}
-
-void VideoManager::enableMovieRiven(uint16 id) {
- debug(2, "Enabling movie %d", id);
- for (uint16 i = 0; i < _mlstRecords.size(); i++)
- if (_mlstRecords[i].code == id)
- for (uint16 j = 0; j < _videoStreams.size(); j++)
- if (_mlstRecords[i].movieID == _videoStreams[j].id) {
- _videoStreams[j].enabled = true;
- return;
- }
-}
-
-void VideoManager::disableMovieRiven(uint16 id) {
- debug(2, "Disabling movie %d", id);
- for (uint16 i = 0; i < _mlstRecords.size(); i++)
- if (_mlstRecords[i].code == id)
- for (uint16 j = 0; j < _videoStreams.size(); j++)
- if (_mlstRecords[i].movieID == _videoStreams[j].id) {
- _videoStreams[j].enabled = false;
- return;
- }
+ VideoHandle handle = findVideoHandleRiven(id);
+ if (handle)
+ removeEntry(handle._ptr);
}
void VideoManager::disableAllMovies() {
debug(2, "Disabling all movies");
- for (uint16 i = 0; i < _videoStreams.size(); i++)
- _videoStreams[i].enabled = false;
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ (*it)->setEnabled(false);
}
-VideoHandle VideoManager::createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop, uint16 volume) {
- // First, check to see if that video is already playing
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].id == id)
- return i;
+VideoEntryPtr VideoManager::open(uint16 id) {
+ // If this video is already playing, return that handle
+ VideoHandle oldHandle = findVideoHandle(id);
+ if (oldHandle._ptr)
+ return oldHandle._ptr;
// Otherwise, create a new entry
- Video::QuickTimeDecoder *decoder = new Video::QuickTimeDecoder();
- decoder->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id));
- decoder->loadStream(_vm->getResource(ID_TMOV, id));
- decoder->setVolume((volume >= 256) ? 255 : volume);
-
- VideoEntry entry;
- entry.clear();
- entry.video = decoder;
- entry.x = x;
- entry.y = y;
- entry.id = id;
- entry.loop = loop;
- entry.enabled = true;
+ Video::QuickTimeDecoder *video = new Video::QuickTimeDecoder();
+ video->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id));
+ video->loadStream(_vm->getResource(ID_TMOV, id));
+
+ // Create the entry
+ VideoEntryPtr entry(new VideoEntry(video, id));
// Enable dither if necessary
checkEnableDither(entry);
- entry->start();
+ // Add it to the video list
+ _videos.push_back(entry);
- // Search for any deleted videos so we can take a formerly used slot
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (!_videoStreams[i].video) {
- _videoStreams[i] = entry;
- return i;
- }
-
- // Otherwise, just add it to the list
- _videoStreams.push_back(entry);
- return _videoStreams.size() - 1;
+ return entry;
}
-VideoHandle VideoManager::createVideoHandle(const Common::String &filename, uint16 x, uint16 y, bool loop, byte volume) {
- // First, check to see if that video is already playing
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].filename == filename)
- return i;
+VideoEntryPtr VideoManager::open(const Common::String &fileName) {
+ // If this video is already playing, return that entry
+ VideoHandle oldHandle = findVideoHandle(fileName);
+ if (oldHandle._ptr)
+ return oldHandle._ptr;
// Otherwise, create a new entry
- VideoEntry entry;
- entry.clear();
- entry.video = new Video::QuickTimeDecoder();
- entry.x = x;
- entry.y = y;
- entry.filename = filename;
- entry.loop = loop;
- entry.enabled = true;
-
- Common::File *file = new Common::File();
- if (!file->open(filename)) {
- delete file;
- return NULL_VID_HANDLE;
+ Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
+ if (!stream)
+ return VideoEntryPtr();
+
+ Video::VideoDecoder *video = new Video::QuickTimeDecoder();
+ if (!video->loadStream(stream)) {
+ // FIXME: Better error handling
+ delete video;
+ return VideoEntryPtr();
}
- entry->loadStream(file);
+ // Create the entry
+ VideoEntryPtr entry(new VideoEntry(video, fileName));
// Enable dither if necessary
checkEnableDither(entry);
- entry->setVolume(volume);
- entry->start();
-
- // Search for any deleted videos so we can take a formerly used slot
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (!_videoStreams[i].video) {
- _videoStreams[i] = entry;
- return i;
- }
+ // Add it to the video list
+ _videos.push_back(entry);
- // Otherwise, just add it to the list
- _videoStreams.push_back(entry);
- return _videoStreams.size() - 1;
+ return entry;
}
VideoHandle VideoManager::findVideoHandleRiven(uint16 id) {
for (uint16 i = 0; i < _mlstRecords.size(); i++)
if (_mlstRecords[i].code == id)
- for (uint32 j = 0; j < _videoStreams.size(); j++)
- if (_videoStreams[j].video && _mlstRecords[i].movieID == _videoStreams[j].id)
- return j;
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ if ((*it)->getID() == _mlstRecords[i].movieID)
+ return *it;
- return NULL_VID_HANDLE;
+ return VideoHandle();
}
VideoHandle VideoManager::findVideoHandle(uint16 id) {
- if (!id)
- return NULL_VID_HANDLE;
+ if (id == 0)
+ return VideoHandle();
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].video && _videoStreams[i].id == id)
- return i;
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ if ((*it)->getID() == id)
+ return *it;
- return NULL_VID_HANDLE;
+ return VideoHandle();
}
-VideoHandle VideoManager::findVideoHandle(const Common::String &filename) {
- if (filename.empty())
- return NULL_VID_HANDLE;
+VideoHandle VideoManager::findVideoHandle(const Common::String &fileName) {
+ if (fileName.empty())
+ return VideoHandle();
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (_videoStreams[i].video && _videoStreams[i].filename.equalsIgnoreCase(filename))
- return i;
-
- return NULL_VID_HANDLE;
-}
-
-int VideoManager::getCurFrame(VideoHandle handle) {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getCurFrame();
-}
-
-uint32 VideoManager::getFrameCount(VideoHandle handle) {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getFrameCount();
-}
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ if ((*it)->getFileName().equalsIgnoreCase(fileName))
+ return *it;
-uint32 VideoManager::getTime(VideoHandle handle) {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getTime();
-}
-
-Audio::Timestamp VideoManager::getDuration(VideoHandle handle) {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getDuration();
-}
-
-bool VideoManager::endOfVideo(VideoHandle handle) {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle].endOfVideo();
+ return VideoHandle();
}
bool VideoManager::isVideoPlaying() {
- for (uint32 i = 0; i < _videoStreams.size(); i++)
- if (!_videoStreams[i].endOfVideo())
+ for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
+ if (!(*it)->endOfVideo())
return true;
return false;
}
-void VideoManager::setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle].start = start;
- _videoStreams[handle]->setEndTime(end);
- _videoStreams[handle]->seek(start);
-}
-
-void VideoManager::drawVideoFrame(VideoHandle handle, Audio::Timestamp time) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle]->seek(time);
+void VideoManager::drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time) {
+ // FIXME: This should be done separately from the "playing"
+ // videos eventually.
+ assert(handle);
+ handle->seek(time);
updateMovies();
- delete _videoStreams[handle].video;
- _videoStreams[handle].clear();
-}
-
-void VideoManager::seekToTime(VideoHandle handle, Audio::Timestamp time) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle]->seek(time);
-}
-
-void VideoManager::setVideoLooping(VideoHandle handle, bool loop) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle].loop = loop;
-}
-
-Common::Rational VideoManager::getVideoRate(VideoHandle handle) const {
- assert(handle != NULL_VID_HANDLE);
- return _videoStreams[handle]->getRate();
+ handle->close();
}
-void VideoManager::setVideoRate(VideoHandle handle, const Common::Rational &rate) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle]->setRate(rate);
+VideoManager::VideoList::iterator VideoManager::findEntry(VideoEntryPtr ptr) {
+ return Common::find(_videos.begin(), _videos.end(), ptr);
}
-void VideoManager::pauseMovie(VideoHandle handle, bool pause) {
- assert(handle != NULL_VID_HANDLE);
- _videoStreams[handle]->pauseVideo(pause);
+void VideoManager::removeEntry(VideoEntryPtr ptr) {
+ VideoManager::VideoList::iterator it = findEntry(ptr);
+ if (it != _videos.end())
+ _videos.erase(it);
}
-void VideoManager::checkEnableDither(VideoEntry &entry) {
+void VideoManager::checkEnableDither(VideoEntryPtr &entry) {
// If we're not dithering, bail out
if (!_enableDither)
return;
@@ -575,13 +584,13 @@ void VideoManager::checkEnableDither(VideoEntry &entry) {
// Set the palette
byte palette[256 * 3];
g_system->getPaletteManager()->grabPalette(palette, 0, 256);
- entry->setDitheringPalette(palette);
+ entry->_video->setDitheringPalette(palette);
- if (entry->getPixelFormat().bytesPerPixel != 1) {
- if (entry.filename.empty())
- error("Failed to set dither for video %d", entry.id);
+ if (entry->_video->getPixelFormat().bytesPerPixel != 1) {
+ if (entry->getFileName().empty())
+ error("Failed to set dither for video tMOV %d", entry->getID());
else
- error("Failed to set dither for video %s", entry.filename.c_str());
+ error("Failed to set dither for video %s", entry->getFileName().c_str());
}
}
diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h
index deb09afe6b..106a32f8e2 100644
--- a/engines/mohawk/video.h
+++ b/engines/mohawk/video.h
@@ -23,9 +23,17 @@
#ifndef MOHAWK_VIDEO_H
#define MOHAWK_VIDEO_H
+#include "audio/timestamp.h"
#include "common/array.h"
+#include "common/list.h"
+#include "common/noncopyable.h"
+#include "common/ptr.h"
+#include "common/rational.h"
#include "graphics/pixelformat.h"
-#include "video/video_decoder.h"
+
+namespace Video {
+class VideoDecoder;
+}
namespace Mohawk {
@@ -43,29 +51,254 @@ struct MLSTRecord {
uint16 u1;
};
-struct VideoEntry {
+/**
+ * A video monitored by the VideoManager
+ */
+class VideoEntry : private Common::NonCopyable {
+ // The private members should be able to be manipulated by VideoManager
+ friend class VideoManager;
+
+private:
+ // Hide the destructor/constructor
+ // Only VideoManager should be allowed
+ VideoEntry();
+ VideoEntry(Video::VideoDecoder *video, const Common::String &fileName);
+ VideoEntry(Video::VideoDecoder *video, int id);
+
+public:
+ ~VideoEntry();
+
+ /**
+ * Convenience implicit cast to bool
+ */
+ operator bool() const { return isOpen(); }
+
+ /**
+ * Is the video open?
+ */
+ bool isOpen() const { return _video != 0; }
+
+ /**
+ * Close the video
+ */
+ void close();
+
+ /**
+ * Has the video reached its end?
+ */
+ bool endOfVideo() const;
+
+ /**
+ * Get the X position of where the video is displayed
+ */
+ uint16 getX() const { return _x; }
+
+ /**
+ * Get the Y position of where the video is displayed
+ */
+ uint16 getY() const { return _y; }
+
+ /**
+ * Is the video looping?
+ */
+ bool isLooping() const { return _loop; }
+
+ /**
+ * Is the video enabled? (Drawing to the screen)
+ */
+ bool isEnabled() const { return _enabled; }
+
+ /**
+ * Get the start time of the video bounds
+ */
+ const Audio::Timestamp &getStart() const { return _start; }
+
+ /**
+ * Get the file name of the video, or empty if by ID
+ */
+ const Common::String &getFileName() const { return _fileName; }
+
+ /**
+ * Get the ID of the video, or -1 if by file name
+ */
+ int getID() const { return _id; }
+
+ /**
+ * Get the current frame of the video
+ */
+ int getCurFrame() const;
+
+ /**
+ * Get the frame count of the video
+ */
+ uint32 getFrameCount() const;
+
+ /**
+ * Get the current time position of the video
+ */
+ uint32 getTime() const;
+
+ /**
+ * Get the duration of the video
+ */
+ Audio::Timestamp getDuration() const;
+
+ /**
+ * Get the current playback rate of the videos
+ */
+ Common::Rational getRate() const;
+
+ /**
+ * Move the x position of the video
+ */
+ void setX(uint16 x) { _x = x; }
+
+ /**
+ * Move the y position of the video
+ */
+ void setY(uint16 y) { _y = y; }
+
+ /**
+ * Move the video to the specified coordinates
+ */
+ void moveTo(uint16 x, uint16 y) { setX(x); setY(y); }
+
+ /**
+ * Center the video on the screen
+ */
+ void center();
+
+ /**
+ * Set the start time when using video bounds
+ */
+ void setStart(const Audio::Timestamp &time) { _start = time; }
+
+ /**
+ * Set the video to loop (true) or not (false)
+ */
+ void setLooping(bool loop) { _loop = loop; }
+
+ /**
+ * Set the video's enabled status
+ */
+ void setEnabled(bool enabled) { _enabled = enabled; }
+
+ /**
+ * Set the bounds of the video
+ *
+ * This automatically seeks to the start time
+ */
+ void setBounds(const Audio::Timestamp &startTime, const Audio::Timestamp &endTime);
+
+ /**
+ * Seek to the given time
+ */
+ void seek(const Audio::Timestamp &time);
+
+ /**
+ * Set the playback rate
+ */
+ void setRate(const Common::Rational &rate);
+
+ /**
+ * Pause the video
+ */
+ void pause(bool isPaused);
+
+ /**
+ * Start playing the video
+ */
+ void start();
+
+ /**
+ * Stop playing the video
+ */
+ void stop();
+
+ /**
+ * Is the video playing?
+ */
+ bool isPlaying() const;
+
+ /**
+ * Get the volume of the video
+ */
+ int getVolume() const;
+
+ /**
+ * Set the volume of the video
+ */
+ void setVolume(int volume);
+
+private:
+ // Non-changing variables
+ Video::VideoDecoder *_video;
+ Common::String _fileName; // External video files
+ int _id; // Internal Mohawk files
+
// Playback variables
- Video::VideoDecoder *video;
- uint16 x;
- uint16 y;
- bool loop;
- bool enabled;
- Audio::Timestamp start;
-
- // Identification
- Common::String filename; // External video files
- int id; // Internal Mohawk files
-
- // Helper functions
- Video::VideoDecoder *operator->() const { assert(video); return video; } // TODO: Remove this eventually
- void clear();
- bool endOfVideo();
+ uint16 _x;
+ uint16 _y;
+ bool _loop;
+ bool _enabled;
+ Audio::Timestamp _start;
};
-typedef int32 VideoHandle;
+typedef Common::SharedPtr<VideoEntry> VideoEntryPtr;
-enum {
- NULL_VID_HANDLE = -1
+/**
+ * A handle for manipulating a video
+ */
+class VideoHandle {
+ // The private members should be able to be manipulated by VideoManager
+ friend class VideoManager;
+
+public:
+ /**
+ * Default constructor
+ */
+ VideoHandle() {}
+
+ /**
+ * Copy constructor
+ */
+ VideoHandle(const VideoHandle &handle);
+
+ /**
+ * Is this handle pointing to a valid video entry?
+ */
+ bool isValid() const { return _ptr && _ptr->isOpen(); }
+
+ /**
+ * Convenience implicit cast to bool
+ */
+ operator bool() const { return isValid(); }
+
+ /**
+ * Simple equality operator
+ */
+ bool operator==(const VideoHandle &other) const { return _ptr.get() == other._ptr.get(); }
+
+ /**
+ * Simple inequality operator
+ */
+ bool operator!=(const VideoHandle &other) const { return !(*this == other); }
+
+ /**
+ * Convenience operator-> override to give direct access to the VideoEntry
+ */
+ VideoEntryPtr operator->() const { return _ptr; }
+
+private:
+ /**
+ * Constructor for internal VideoManager use
+ */
+ VideoHandle(VideoEntryPtr ptr);
+
+ /**
+ * The video entry this is associated with
+ */
+ VideoEntryPtr _ptr;
};
class VideoManager {
@@ -76,8 +309,8 @@ public:
// Generic movie functions
void playMovieBlocking(const Common::String &filename, uint16 x = 0, uint16 y = 0, bool clearScreen = false);
void playMovieBlockingCentered(const Common::String &filename, bool clearScreen = true);
- VideoHandle playMovie(const Common::String &filename, int16 x = -1, int16 y = -1, bool loop = false);
- VideoHandle playMovie(uint16 id, int16 x = -1, int16 y = -1, bool loop = false);
+ VideoHandle playMovie(const Common::String &filename);
+ VideoHandle playMovie(uint16 id);
bool updateMovies();
void pauseVideos();
void resumeVideos();
@@ -87,31 +320,18 @@ public:
// Riven-related functions
void activateMLST(uint16 mlstId, uint16 card);
void clearMLST();
- void enableMovieRiven(uint16 id);
- void disableMovieRiven(uint16 id);
void disableAllMovies();
VideoHandle playMovieRiven(uint16 id);
- void stopMovieRiven(uint16 id);
void playMovieBlockingRiven(uint16 id);
VideoHandle findVideoHandleRiven(uint16 id);
+ void stopMovieRiven(uint16 id);
// Handle functions
VideoHandle findVideoHandle(uint16 id);
- VideoHandle findVideoHandle(const Common::String &filename);
- int getCurFrame(VideoHandle handle);
- uint32 getFrameCount(VideoHandle handle);
- uint32 getTime(VideoHandle handle);
- Audio::Timestamp getDuration(VideoHandle videoHandle);
- bool endOfVideo(VideoHandle handle);
- void setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end);
- void drawVideoFrame(VideoHandle handle, Audio::Timestamp time);
- void seekToTime(VideoHandle handle, Audio::Timestamp time);
- void setVideoLooping(VideoHandle handle, bool loop);
- Common::Rational getVideoRate(VideoHandle handle) const;
- void setVideoRate(VideoHandle handle, const Common::Rational &rate);
- void waitUntilMovieEnds(VideoHandle videoHandle);
- void delayUntilMovieEnds(VideoHandle videoHandle);
- void pauseMovie(VideoHandle videoHandle, bool pause);
+ VideoHandle findVideoHandle(const Common::String &fileName);
+ void waitUntilMovieEnds(VideoHandle handle);
+ void delayUntilMovieEnds(VideoHandle handle);
+ void drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time);
private:
MohawkEngine *_vm;
@@ -120,14 +340,19 @@ private:
Common::Array<MLSTRecord> _mlstRecords;
// Keep tabs on any videos playing
- Common::Array<VideoEntry> _videoStreams;
+ typedef Common::List<VideoEntryPtr> VideoList;
+ VideoList _videos;
- VideoHandle createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop, uint16 volume = 0xff);
- VideoHandle createVideoHandle(const Common::String &filename, uint16 x, uint16 y, bool loop, byte volume = 0xff);
+ // Utility functions for managing entries
+ VideoEntryPtr open(uint16 id);
+ VideoEntryPtr open(const Common::String &fileName);
+
+ VideoList::iterator findEntry(VideoEntryPtr ptr);
+ void removeEntry(VideoEntryPtr ptr);
// Dithering control
bool _enableDither;
- void checkEnableDither(VideoEntry &entry);
+ void checkEnableDither(VideoEntryPtr &entry);
};
} // End of namespace Mohawk
diff --git a/engines/mortevielle/detection_tables.h b/engines/mortevielle/detection_tables.h
index d244d15365..26611d4271 100644
--- a/engines/mortevielle/detection_tables.h
+++ b/engines/mortevielle/detection_tables.h
@@ -110,6 +110,23 @@ static const MortevielleGameDescription MortevielleGameDescriptions[] = {
}, Common::DE_DEU, kUseEngineDataFile
},
+ // French, provided by ultrapingu in bug ref #6575
+ {
+ {
+ "mortevielle",
+ "",
+ {
+ {"menu.mor", 0, "3fef0a3f8fca99fdcb6dbca8cbcef46f", 160},
+ {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744},
+ AD_LISTEND
+ },
+ Common::FR_FRA,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO0()
+ }, Common::FR_FRA, kUseOriginalData
+ },
+
{ AD_TABLE_END_MARKER , Common::EN_ANY, kUseEngineDataFile}
};
diff --git a/engines/mortevielle/menu.cpp b/engines/mortevielle/menu.cpp
index c0b81b252a..b788ce9a71 100644
--- a/engines/mortevielle/menu.cpp
+++ b/engines/mortevielle/menu.cpp
@@ -682,8 +682,13 @@ void Menu::initMenu() {
if (!menuLoaded) {
// Load menu from game data using the original language
if (_vm->getOriginalLanguage() == Common::FR_FRA) {
- if (!f.open("menufr.mor"))
- error("Missing file - menufr.mor");
+ if (f.exists("menufr.mor")) {
+ if (!f.open("menufr.mor"))
+ error("Missing file - menufr.mor");
+ } else {
+ if (!f.open("menu.mor"))
+ error("Missing file - menu.mor");
+ }
} else { // Common::DE_DEU
if (!f.open("menual.mor"))
error("Missing file - menual.mor");
diff --git a/engines/parallaction/adlib.cpp b/engines/parallaction/adlib.cpp
index 7c1dd1681f..568ad190aa 100644
--- a/engines/parallaction/adlib.cpp
+++ b/engines/parallaction/adlib.cpp
@@ -25,7 +25,7 @@
#include "audio/fmopl.h"
#include "audio/mpu401.h"
-#include "audio/softsynth/emumidi.h"
+#include "audio/mididrv.h"
namespace Parallaction {
@@ -270,11 +270,13 @@ struct MelodicVoice {
int8 _octave;
};
-class AdLibDriver : public MidiDriver_Emulated {
+class AdLibDriver : public MidiDriver {
public:
- AdLibDriver(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) {
+ AdLibDriver(Audio::Mixer *mixer) {
for (uint i = 0; i < 16; ++i)
_channels[i].init(this, i);
+
+ _isOpen = false;
}
int open();
@@ -282,11 +284,13 @@ public:
void send(uint32 b);
MidiChannel *allocateChannel();
MidiChannel *getPercussionChannel() { return &_channels[9]; }
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
- bool isStereo() const { return false; }
- int getRate() const { return _mixer->getOutputRate(); }
-
- void generateSamples(int16 *buf, int len);
+ virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+ }
protected:
OPL::OPL *_opl;
@@ -320,6 +324,13 @@ protected:
void muteMelodicVoice(uint8 voice);
void initVoices();
+
+private:
+ void onTimer();
+
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+ bool _isOpen;
};
MidiDriver *createAdLibDriver() {
@@ -348,10 +359,10 @@ int AdLibDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
- MidiDriver_Emulated::open();
+ _isOpen = true;
_opl = OPL::Config::create();
- _opl->init(getRate());
+ _opl->init();
_opl->writeReg(0x1, 0x20); // set bit 5 (enable all waveforms)
// Reset the OPL registers.
@@ -364,7 +375,7 @@ int AdLibDriver::open() {
initVoices();
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _opl->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::onTimer));
return 0;
}
@@ -373,7 +384,6 @@ void AdLibDriver::close() {
return;
_isOpen = false;
- _mixer->stopHandle(_mixerSoundHandle);
delete _opl;
}
@@ -777,9 +787,9 @@ MidiChannel *AdLibDriver::allocateChannel() {
return NULL;
}
-void AdLibDriver::generateSamples(int16 *buf, int len) {
- memset(buf, 0, sizeof(int16) * len);
- _opl->readBuffer(buf, len);
+void AdLibDriver::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
}
void AdLibDriver::initVoices() {
diff --git a/engines/pegasus/sound.cpp b/engines/pegasus/sound.cpp
index 5b437b81d4..ddcb2be010 100644
--- a/engines/pegasus/sound.cpp
+++ b/engines/pegasus/sound.cpp
@@ -59,7 +59,15 @@ void Sound::initFromAIFFFile(const Common::String &fileName) {
return;
}
- _stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
+ Audio::RewindableAudioStream *stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
+
+ _stream = dynamic_cast<Audio::SeekableAudioStream *>(stream);
+
+ if (!_stream) {
+ delete stream;
+ warning("AIFF stream '%s' is not seekable", fileName.c_str());
+ return;
+ }
}
void Sound::initFromQuickTime(const Common::String &fileName) {
diff --git a/engines/queen/midiadlib.cpp b/engines/queen/midiadlib.cpp
index 25175c21d7..f5bc0f4d58 100644
--- a/engines/queen/midiadlib.cpp
+++ b/engines/queen/midiadlib.cpp
@@ -23,118 +23,29 @@
#include "common/endian.h"
#include "common/textconsole.h"
-#include "audio/fmopl.h"
-#include "audio/softsynth/emumidi.h"
+#include "engines/queen/midiadlib.h"
namespace Queen {
-class AdLibMidiDriver : public MidiDriver_Emulated {
-public:
-
- AdLibMidiDriver(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { _adlibWaveformSelect = 0; }
- ~AdLibMidiDriver() {}
-
- // MidiDriver
- int open();
- void close();
- void send(uint32 b);
- void metaEvent(byte type, byte *data, uint16 length);
- MidiChannel *allocateChannel() { return 0; }
- MidiChannel *getPercussionChannel() { return 0; }
-
- // AudioStream
- bool isStereo() const { return false; }
- int getRate() const { return _mixer->getOutputRate(); }
-
- // MidiDriver_Emulated
- void generateSamples(int16 *buf, int len);
-
-private:
-
- void handleMidiEvent0x90_NoteOn(int channel, int param1, int param2);
- void handleSequencerSpecificMetaEvent1(int channel, const uint8 *data);
- void handleSequencerSpecificMetaEvent2(uint8 value);
- void handleSequencerSpecificMetaEvent3(uint8 value);
-
- void adlibWrite(uint8 port, uint8 value);
- void adlibSetupCard();
- void adlibSetupChannels(int fl);
- void adlibResetAmpVibratoRhythm(int am, int vib, int kso);
- void adlibResetChannels();
- void adlibSetAmpVibratoRhythm();
- void adlibSetCSMKeyboardSplit();
- void adlibSetNoteMul(int mul);
- void adlibSetWaveformSelect(int fl);
- void adlibSetPitchBend(int channel, int range);
- void adlibPlayNote(int channel);
- uint8 adlibPlayNoteHelper(int channel, int note1, int note2, int oct);
- void adlibTurnNoteOff(int channel);
- void adlibTurnNoteOn(int channel, int note);
- void adlibSetupChannelFromSequence(int channel, const uint8 *src, int fl);
- void adlibSetupChannel(int channel, const uint16 *src, int fl);
- void adlibSetNoteVolume(int channel, int volume);
- void adlibSetupChannelHelper(int channel);
- void adlibSetChannel0x40(int channel);
- void adlibSetChannel0xC0(int channel);
- void adlibSetChannel0x60(int channel);
- void adlibSetChannel0x80(int channel);
- void adlibSetChannel0x20(int channel);
- void adlibSetChannel0xE0(int channel);
-
- FM_OPL *_opl;
- int _midiNumberOfChannels;
- int _adlibNoteMul;
- int _adlibWaveformSelect;
- int _adlibAMDepthEq48;
- int _adlibVibratoDepthEq14;
- int _adlibRhythmEnabled;
- int _adlibKeyboardSplitOn;
- int _adlibVibratoRhythm;
- uint8 _midiChannelsFreqTable[9];
- uint8 _adlibChannelsLevelKeyScalingTable[11];
- uint8 _adlibSetupChannelSequence1[14 * 18];
- uint16 _adlibSetupChannelSequence2[14];
- int16 _midiChannelsNote2Table[9];
- uint8 _midiChannelsNote1Table[9];
- uint8 _midiChannelsOctTable[9];
- uint16 _adlibChannelsVolume[11];
- uint16 _adlibMetaSequenceData[28];
-
- static const uint8 _adlibChannelsMappingTable1[];
- static const uint8 _adlibChannelsNoFeedback[];
- static const uint8 _adlibChannelsMappingTable2[];
- static const uint8 _adlibChannelsMappingTable3[];
- static const uint8 _adlibChannelsKeyScalingTable1[];
- static const uint8 _adlibChannelsKeyScalingTable2[];
- static const uint8 _adlibChannelsVolumeTable[];
- static const uint8 _adlibInitSequenceData1[];
- static const uint8 _adlibInitSequenceData2[];
- static const uint8 _adlibInitSequenceData3[];
- static const uint8 _adlibInitSequenceData4[];
- static const uint8 _adlibInitSequenceData5[];
- static const uint8 _adlibInitSequenceData6[];
- static const uint8 _adlibInitSequenceData7[];
- static const uint8 _adlibInitSequenceData8[];
- static const int16 _midiChannelsNoteTable[];
- static const int16 _midiNoteFreqTable[];
-};
-
int AdLibMidiDriver::open() {
- MidiDriver_Emulated::open();
- _opl = makeAdLibOPL(getRate());
+ _isOpen = true;
+ _opl = OPL::Config::create();
+ if (!_opl || !_opl->init())
+ error("Failed to create OPL");
+
adlibSetupCard();
for (int i = 0; i < 11; ++i) {
_adlibChannelsVolume[i] = 0;
adlibSetNoteVolume(i, 0);
adlibTurnNoteOff(i);
}
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ _opl->start(new Common::Functor0Mem<void, AdLibMidiDriver>(this, &AdLibMidiDriver::onTimer));
return 0;
}
void AdLibMidiDriver::close() {
- _mixer->stopHandle(_mixerSoundHandle);
- OPLDestroy(_opl);
+ delete _opl;
}
void AdLibMidiDriver::send(uint32 b) {
@@ -164,6 +75,11 @@ void AdLibMidiDriver::send(uint32 b) {
}
}
+void AdLibMidiDriver::setVolume(uint32 volume) {
+ for (int i = 0; i < _midiNumberOfChannels; ++i)
+ adlibSetChannelVolume(i, volume * 64 / 256 + 64);
+}
+
void AdLibMidiDriver::metaEvent(byte type, byte *data, uint16 length) {
int event = 0;
if (length > 4 && READ_BE_UINT32(data) == 0x3F00) {
@@ -192,9 +108,14 @@ void AdLibMidiDriver::metaEvent(byte type, byte *data, uint16 length) {
warning("Unhandled meta event %d len %d", event, length);
}
-void AdLibMidiDriver::generateSamples(int16 *data, int len) {
- memset(data, 0, sizeof(int16) * len);
- YM3812UpdateOne(_opl, data, len);
+void AdLibMidiDriver::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+}
+
+void AdLibMidiDriver::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
}
void AdLibMidiDriver::handleSequencerSpecificMetaEvent1(int channel, const uint8 *data) {
@@ -238,7 +159,7 @@ void AdLibMidiDriver::handleMidiEvent0x90_NoteOn(int channel, int param1, int pa
}
void AdLibMidiDriver::adlibWrite(uint8 port, uint8 value) {
- OPLWriteReg(_opl, port, value);
+ _opl->writeReg(port, value);
}
void AdLibMidiDriver::adlibSetupCard() {
@@ -253,6 +174,7 @@ void AdLibMidiDriver::adlibSetupCard() {
_midiChannelsFreqTable[i] = 0;
}
memset(_adlibChannelsLevelKeyScalingTable, 127, 11);
+ memset(_adlibChannelsVolumeTable, 128, 11);
adlibSetupChannels(0);
adlibResetAmpVibratoRhythm(0, 0, 0);
adlibSetNoteMul(1);
@@ -448,6 +370,11 @@ void AdLibMidiDriver::adlibSetNoteVolume(int channel, int volume) {
}
}
+void AdLibMidiDriver::adlibSetChannelVolume(int channel, uint8 volume) {
+ if (channel < (_adlibRhythmEnabled ? 11 : 9))
+ _adlibChannelsVolumeTable[channel] = volume;
+}
+
void AdLibMidiDriver::adlibSetupChannelHelper(int channel) {
adlibSetAmpVibratoRhythm();
adlibSetCSMKeyboardSplit();
@@ -558,10 +485,6 @@ const uint8 AdLibMidiDriver::_adlibChannelsKeyScalingTable2[] = {
0, 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11, 12, 15, 16, 255, 14, 255, 17, 255, 13, 255
};
-const uint8 AdLibMidiDriver::_adlibChannelsVolumeTable[] = {
- 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128
-};
-
const uint8 AdLibMidiDriver::_adlibInitSequenceData1[] = {
1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0
};
@@ -617,8 +540,4 @@ const int16 AdLibMidiDriver::_midiNoteFreqTable[] = {
-363, -361, -359, -356, -354, -351, -349, -347, -344, -342, -339, -337
};
-MidiDriver *C_Player_CreateAdLibMidiDriver(Audio::Mixer *mixer) {
- return new AdLibMidiDriver(mixer);
-}
-
} // End of namespace Queen
diff --git a/engines/queen/midiadlib.h b/engines/queen/midiadlib.h
new file mode 100644
index 0000000000..8692e51840
--- /dev/null
+++ b/engines/queen/midiadlib.h
@@ -0,0 +1,128 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/fmopl.h"
+#include "audio/mididrv.h"
+
+namespace Queen {
+
+class AdLibMidiDriver : public MidiDriver {
+public:
+
+ AdLibMidiDriver() {
+ _adlibWaveformSelect = 0;
+ _isOpen = false;
+ }
+
+ ~AdLibMidiDriver() {}
+
+ // MidiDriver
+ int open();
+ void close();
+ void send(uint32 b);
+ void metaEvent(byte type, byte *data, uint16 length);
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+ void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
+
+ void setVolume(uint32 volume);
+
+private:
+
+ void handleMidiEvent0x90_NoteOn(int channel, int param1, int param2);
+ void handleSequencerSpecificMetaEvent1(int channel, const uint8 *data);
+ void handleSequencerSpecificMetaEvent2(uint8 value);
+ void handleSequencerSpecificMetaEvent3(uint8 value);
+
+ void adlibWrite(uint8 port, uint8 value);
+ void adlibSetupCard();
+ void adlibSetupChannels(int fl);
+ void adlibResetAmpVibratoRhythm(int am, int vib, int kso);
+ void adlibResetChannels();
+ void adlibSetAmpVibratoRhythm();
+ void adlibSetCSMKeyboardSplit();
+ void adlibSetNoteMul(int mul);
+ void adlibSetWaveformSelect(int fl);
+ void adlibSetPitchBend(int channel, int range);
+ void adlibPlayNote(int channel);
+ uint8 adlibPlayNoteHelper(int channel, int note1, int note2, int oct);
+ void adlibTurnNoteOff(int channel);
+ void adlibTurnNoteOn(int channel, int note);
+ void adlibSetupChannelFromSequence(int channel, const uint8 *src, int fl);
+ void adlibSetupChannel(int channel, const uint16 *src, int fl);
+ void adlibSetNoteVolume(int channel, int volume);
+ void adlibSetChannelVolume(int channel, uint8 volume);
+ void adlibSetupChannelHelper(int channel);
+ void adlibSetChannel0x40(int channel);
+ void adlibSetChannel0xC0(int channel);
+ void adlibSetChannel0x60(int channel);
+ void adlibSetChannel0x80(int channel);
+ void adlibSetChannel0x20(int channel);
+ void adlibSetChannel0xE0(int channel);
+
+ void onTimer();
+
+ OPL::OPL *_opl;
+ int _midiNumberOfChannels;
+ int _adlibNoteMul;
+ int _adlibWaveformSelect;
+ int _adlibAMDepthEq48;
+ int _adlibVibratoDepthEq14;
+ int _adlibRhythmEnabled;
+ int _adlibKeyboardSplitOn;
+ int _adlibVibratoRhythm;
+ uint8 _midiChannelsFreqTable[9];
+ uint8 _adlibChannelsLevelKeyScalingTable[11];
+ uint8 _adlibSetupChannelSequence1[14 * 18];
+ uint16 _adlibSetupChannelSequence2[14];
+ int16 _midiChannelsNote2Table[9];
+ uint8 _midiChannelsNote1Table[9];
+ uint8 _midiChannelsOctTable[9];
+ uint16 _adlibChannelsVolume[11];
+ uint16 _adlibMetaSequenceData[28];
+ uint8 _adlibChannelsVolumeTable[11];
+
+ bool _isOpen;
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+
+ static const uint8 _adlibChannelsMappingTable1[];
+ static const uint8 _adlibChannelsNoFeedback[];
+ static const uint8 _adlibChannelsMappingTable2[];
+ static const uint8 _adlibChannelsMappingTable3[];
+ static const uint8 _adlibChannelsKeyScalingTable1[];
+ static const uint8 _adlibChannelsKeyScalingTable2[];
+ static const uint8 _adlibInitSequenceData1[];
+ static const uint8 _adlibInitSequenceData2[];
+ static const uint8 _adlibInitSequenceData3[];
+ static const uint8 _adlibInitSequenceData4[];
+ static const uint8 _adlibInitSequenceData5[];
+ static const uint8 _adlibInitSequenceData6[];
+ static const uint8 _adlibInitSequenceData7[];
+ static const uint8 _adlibInitSequenceData8[];
+ static const int16 _midiChannelsNoteTable[];
+ static const int16 _midiNoteFreqTable[];
+};
+
+} // End of namespace Queen
diff --git a/engines/queen/music.cpp b/engines/queen/music.cpp
index 93d6527622..9f74aab915 100644
--- a/engines/queen/music.cpp
+++ b/engines/queen/music.cpp
@@ -23,6 +23,7 @@
#include "common/config-manager.h"
#include "common/events.h"
+#include "queen/midiadlib.h"
#include "queen/music.h"
#include "queen/queen.h"
#include "queen/resource.h"
@@ -33,8 +34,6 @@
namespace Queen {
-extern MidiDriver *C_Player_CreateAdLibMidiDriver(Audio::Mixer *);
-
MidiMusic::MidiMusic(QueenEngine *vm)
: _isPlaying(false), _isLooping(false),
_randomLoop(false), _masterVolume(192),
@@ -69,7 +68,7 @@ MidiMusic::MidiMusic(QueenEngine *vm)
// if (READ_LE_UINT16(_musicData + 2) != infoOffset) {
// defaultAdLibVolume = _musicData[infoOffset];
// }
- _driver = C_Player_CreateAdLibMidiDriver(vm->_mixer);
+ _driver = new AdLibMidiDriver();
} else {
_driver = MidiDriver::createMidi(dev);
if (_nativeMT32) {
@@ -117,6 +116,9 @@ void MidiMusic::setVolume(int volume) {
if (_channelsTable[i])
_channelsTable[i]->volume(_channelsVolume[i] * _masterVolume / 255);
}
+
+ if (_adlib)
+ static_cast<AdLibMidiDriver*>(_driver)->setVolume(volume);
}
void MidiMusic::playSong(uint16 songNum) {
diff --git a/engines/saga/detection_tables.h b/engines/saga/detection_tables.h
index 2f72e7a13c..98009326ae 100644
--- a/engines/saga/detection_tables.h
+++ b/engines/saga/detection_tables.h
@@ -192,9 +192,9 @@ static const SAGAGameDescription gameDescriptions[] = {
ADGF_DEMO,
GUIO1(GUIO_NOSPEECH)
},
- GID_ITE, // Game id
- GF_OLD_ITE_DOS, // features
- ITE_DEFAULT_SCENE, // Starting scene number
+ GID_ITE,
+ GF_ITE_DOS_DEMO,
+ ITE_DEFAULT_SCENE,
&ITEDemo_Resources,
ARRAYSIZE(ITEDEMO_GameFonts),
ITEDEMO_GameFonts,
@@ -393,6 +393,33 @@ static const SAGAGameDescription gameDescriptions[] = {
NULL,
},
+ // Inherit the earth - German Wyrmkeep combined Windows/Mac/Linux CD
+
+ // Supplied by user nicode in bug #6428.
+ // Contains voices.rsc instead of "Inherit the Earth Voices".
+ {
+ {
+ "ite",
+ "Multi-OS CD Version",
+ {
+ {"ite.rsc", GAME_RESOURCEFILE, "420e09cfdbb4db12baefd4bc81d8e154", 8925349},
+ {"scripts.rsc", GAME_SCRIPTFILE, "a891405405edefc69c9d6c420c868b84", -1},
+ { NULL, 0, NULL, 0}
+ },
+ Common::DE_DEU,
+ Common::kPlatformUnknown,
+ ADGF_CD,
+ GUIO0()
+ },
+ GID_ITE,
+ 0,
+ ITE_DEFAULT_SCENE,
+ &ITE_Resources,
+ ARRAYSIZE(ITE_GameFonts),
+ ITE_GameFonts,
+ NULL,
+ },
+
// Inherit the earth - Italian Wyrmkeep combined Windows/Mac/Linux CD (fan translation)
// version is different from the other Wyrmkeep re-releases in that it does
diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp
index fc28d2372f..dc3f55e8c1 100644
--- a/engines/saga/introproc_ihnm.cpp
+++ b/engines/saga/introproc_ihnm.cpp
@@ -68,7 +68,7 @@ int Scene::IHNMStartProc() {
// Play the title music
_vm->_music->play(1, MUSIC_NORMAL);
// Play title screen
- playTitle(2, 17);
+ playTitle(2, _vm->_music->isAdlib() ? 20 : 27);
}
}
} else {
@@ -150,7 +150,7 @@ bool Scene::checkKey() {
break;
case Common::EVENT_KEYDOWN:
// Don't react to modifier keys alone. The original did
- // non, and the user may want to change scaler without
+ // not, and the user may want to change scaler without
// terminating the intro.
if (event.kbd.ascii)
res = true;
diff --git a/engines/saga/introproc_ite.cpp b/engines/saga/introproc_ite.cpp
index 0b129dbcc0..3c10cbe1dd 100644
--- a/engines/saga/introproc_ite.cpp
+++ b/engines/saga/introproc_ite.cpp
@@ -59,6 +59,11 @@ namespace Saga {
#define RID_ITE_FAIREPATH_SCENE 1564
#define RID_ITE_FAIRETENT_SCENE 1567
+// Intro scenes - DOS demo
+#define RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO 298
+#define RID_ITE_CAVE_SCENE_DOS_DEMO 302
+#define RID_ITE_VALLEY_SCENE_DOS_DEMO 310
+
// ITE intro music
#define MUSIC_INTRO 9
#define MUSIC_TITLE_THEME 10
@@ -75,22 +80,24 @@ LoadSceneParams ITE_IntroList[] = {
{RID_ITE_FAIRETENT_SCENE, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}
};
-int Scene::ITEStartProc() {
- size_t scenesCount;
- size_t i;
+LoadSceneParams ITE_DOS_Demo_IntroList[] = {
+ {RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_CAVE_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroCaveDemoProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
+ {RID_ITE_VALLEY_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
+};
+int Scene::ITEStartProc() {
LoadSceneParams firstScene;
LoadSceneParams tempScene;
+ bool dosDemo = (_vm->getFeatures() & GF_ITE_DOS_DEMO);
+ int scenesCount = (!dosDemo) ? ARRAYSIZE(ITE_IntroList) : ARRAYSIZE(ITE_DOS_Demo_IntroList);
- scenesCount = ARRAYSIZE(ITE_IntroList);
-
- for (i = 0; i < scenesCount; i++) {
- tempScene = ITE_IntroList[i];
+ for (int i = 0; i < scenesCount; i++) {
+ tempScene = (!dosDemo) ? ITE_IntroList[i] : ITE_DOS_Demo_IntroList[i];
tempScene.sceneDescriptor = _vm->_resource->convertResourceId(tempScene.sceneDescriptor);
_vm->_scene->queueScene(tempScene);
}
-
firstScene.loadFlag = kLoadBySceneNumber;
firstScene.sceneDescriptor = _vm->getStartSceneNumber();
firstScene.sceneSkipTarget = true;
@@ -437,6 +444,53 @@ int Scene::ITEIntroCaveCommonProc(int param, int caveScene) {
return 0;
}
+int Scene::ITEIntroCaveDemoProc(int param) {
+ Event event;
+ EventColumns *eventColumns = NULL;
+
+ switch (param) {
+ case SCENE_BEGIN:
+ // Begin palette cycling animation for candles
+ event.type = kEvTOneshot;
+ event.code = kPalAnimEvent;
+ event.op = kEventCycleStart;
+ event.time = 0;
+ eventColumns = _vm->_events->chain(eventColumns, event);
+
+ // Queue narrator dialogue list
+ for (int i = 0; i < 11; i++) {
+ // Play voice
+ event.type = kEvTOneshot;
+ event.code = kVoiceEvent;
+ event.op = kEventPlay;
+ event.param = i;
+ event.time = _vm->_sndRes->getVoiceLength(i);
+ _vm->_events->chain(eventColumns, event);
+ }
+
+ // End scene after last dialogue over
+ event.type = kEvTOneshot;
+ event.code = kSceneEvent;
+ event.op = kEventEnd;
+ event.time = INTRO_VOICE_PAD;
+ _vm->_events->chain(eventColumns, event);
+
+ break;
+ case SCENE_END:
+ break;
+
+ default:
+ warning("Illegal scene procedure parameter");
+ break;
+ }
+
+ return 0;
+}
+
+int Scene::SC_ITEIntroCaveDemoProc(int param, void *refCon) {
+ return ((Scene *)refCon)->ITEIntroCaveDemoProc(param);
+}
+
// Handles first introductory cave painting scene
int Scene::SC_ITEIntroCave1Proc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 1);
diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp
index d20882ca26..663f5991d0 100644
--- a/engines/saga/music.cpp
+++ b/engines/saga/music.cpp
@@ -31,6 +31,7 @@
#include "audio/mididrv.h"
#include "audio/midiparser.h"
#include "audio/midiparser_qt.h"
+#include "audio/miles.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
#include "common/file.h"
@@ -42,24 +43,51 @@ namespace Saga {
#define MUSIC_SUNSPOT 26
MusicDriver::MusicDriver() : _isGM(false) {
-
- MidiPlayer::createDriver();
-
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
_driverType = MidiDriver::getMusicType(dev);
+ switch (_driverType) {
+ case MT_ADLIB:
+ if (Common::File::exists("INSTR.AD") && Common::File::exists("INSTR.OPL")) {
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_AdLib_create("INSTR.AD", "INSTR.OPL");
+ } else if (Common::File::exists("SAMPLE.AD") && Common::File::exists("SAMPLE.OPL")) {
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+ } else {
+ _milesAudioMode = false;
+ MidiPlayer::createDriver();
+ }
+ break;
+ case MT_MT32:
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ default:
+ _milesAudioMode = false;
+ MidiPlayer::createDriver();
+ break;
+ }
+
int retValue = _driver->open();
if (retValue == 0) {
- if (_nativeMT32)
- _driver->sendMT32Reset();
- else
- _driver->sendGMReset();
+ if (_driverType != MT_ADLIB) {
+ if (_driverType == MT_MT32 || _nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+ }
_driver->setTimerCallback(this, &timerCallback);
}
}
void MusicDriver::send(uint32 b) {
+ if (_milesAudioMode) {
+ _driver->send(b);
+ return;
+ }
+
if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
// Remap MT32 instruments to General Midi
b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
@@ -249,7 +277,11 @@ void Music::play(uint32 resourceId, MusicFlags flags) {
debug(2, "Music::play %d, %d", resourceId, flags);
- if (isPlaying() && _trackNumber == resourceId) {
+ if (isPlaying() && _trackNumber == resourceId)
+ return;
+
+ if (_vm->getFeatures() & GF_ITE_DOS_DEMO) {
+ warning("TODO: Music::play %d, %d for ITE DOS demo", resourceId, flags);
return;
}
diff --git a/engines/saga/music.h b/engines/saga/music.h
index 2106fb6fa6..2e7cc4c5ec 100644
--- a/engines/saga/music.h
+++ b/engines/saga/music.h
@@ -61,6 +61,7 @@ public:
protected:
MusicType _driverType;
bool _isGM;
+ bool _milesAudioMode;
};
class Music {
@@ -79,6 +80,8 @@ public:
void setVolume(int volume, int time = 1);
int getVolume() { return _currentVolume; }
+ bool isAdlib() const { return _player->isAdlib(); }
+
Common::Array<int32> _songTable;
private:
diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp
index 3d38b3ea52..b94bb66bb4 100644
--- a/engines/saga/saga.cpp
+++ b/engines/saga/saga.cpp
@@ -117,6 +117,9 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc)
SearchMan.addSubDirectoryMatching(gameDataDir, "music");
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
+ // Location of Miles audio files (sample.ad and sample.opl) in IHNM
+ SearchMan.addSubDirectoryMatching(gameDataDir, "drivers");
+
// The Multi-OS version puts the voices file in the root directory of
// the CD. The rest of the data files are in game/itedata
SearchMan.addSubDirectoryMatching(gameDataDir, "game/itedata");
@@ -634,6 +637,9 @@ void SagaEngine::syncSoundSettings() {
}
void SagaEngine::pauseEngineIntern(bool pause) {
+ if (!_render || !_music)
+ return;
+
bool engineIsPaused = (_render->getFlags() & RF_RENDERPAUSE);
if (engineIsPaused == pause)
return;
diff --git a/engines/saga/saga.h b/engines/saga/saga.h
index 6077e55094..9c7b2f5295 100644
--- a/engines/saga/saga.h
+++ b/engines/saga/saga.h
@@ -137,9 +137,7 @@ enum GameFileTypes {
enum GameFeatures {
GF_ITE_FLOPPY = 1 << 0,
-#if 0
- GF_OLD_ITE_DOS = 1 << 1, // Currently unused
-#endif
+ GF_ITE_DOS_DEMO = 1 << 1,
GF_EXTRA_ITE_CREDITS = 1 << 2,
GF_8BIT_UNSIGNED_PCM = 1 << 3
};
diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp
index 4fa15d09e5..efd4c371b1 100644
--- a/engines/saga/scene.cpp
+++ b/engines/saga/scene.cpp
@@ -866,15 +866,13 @@ void Scene::loadSceneDescriptor(uint32 resourceId) {
_sceneDescription.reset();
- if (resourceId == 0) {
+ if (resourceId == 0)
return;
- }
_vm->_resource->loadResource(_sceneContext, resourceId, sceneDescriptorData);
+ ByteArrayReadStreamEndian readS(sceneDescriptorData, _sceneContext->isBigEndian());
- if (sceneDescriptorData.size() == 16) {
- ByteArrayReadStreamEndian readS(sceneDescriptorData, _sceneContext->isBigEndian());
-
+ if (sceneDescriptorData.size() == 14 || sceneDescriptorData.size() == 16) {
_sceneDescription.flags = readS.readSint16();
_sceneDescription.resourceListResourceId = readS.readSint16();
_sceneDescription.endSlope = readS.readSint16();
@@ -882,7 +880,10 @@ void Scene::loadSceneDescriptor(uint32 resourceId) {
_sceneDescription.scriptModuleNumber = readS.readUint16();
_sceneDescription.sceneScriptEntrypointNumber = readS.readUint16();
_sceneDescription.startScriptEntrypointNumber = readS.readUint16();
- _sceneDescription.musicResourceId = readS.readSint16();
+ if (sceneDescriptorData.size() == 16)
+ _sceneDescription.musicResourceId = readS.readSint16();
+ } else {
+ warning("Scene::loadSceneDescriptor: Unknown scene descriptor data size (%d)", sceneDescriptorData.size());
}
}
diff --git a/engines/saga/scene.h b/engines/saga/scene.h
index 410713c5d5..1a710cfe9c 100644
--- a/engines/saga/scene.h
+++ b/engines/saga/scene.h
@@ -400,12 +400,14 @@ class Scene {
static int SC_ITEIntroTreeHouseProc(int param, void *refCon);
static int SC_ITEIntroFairePathProc(int param, void *refCon);
static int SC_ITEIntroFaireTentProc(int param, void *refCon);
+ static int SC_ITEIntroCaveDemoProc(int param, void *refCon);
private:
EventColumns *queueIntroDialogue(EventColumns *eventColumns, int n_dialogues, const IntroDialogue dialogue[]);
EventColumns *queueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]);
int ITEIntroAnimProc(int param);
int ITEIntroCaveCommonProc(int param, int caveScene);
+ int ITEIntroCaveDemoProc(int param);
int ITEIntroValleyProc(int param);
int ITEIntroTreeHouseProc(int param);
int ITEIntroFairePathProc(int param);
diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp
index 94b26c8da3..3cc6586432 100644
--- a/engines/saga/script.cpp
+++ b/engines/saga/script.cpp
@@ -977,19 +977,15 @@ void Script::opSpeak(SCRIPTOP_PARAMS) {
// now data contains last string index
-#if 0
- if (_vm->getFeatures() & GF_OLD_ITE_DOS) { // special ITE dos
+ if (_vm->getFeatures() & GF_ITE_DOS_DEMO) {
if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) &&
(iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_END - RID_SCENE1_VOICE_START + 288))) {
sampleResourceId = RID_SCENE1_VOICE_START + iparam1 - 288;
}
} else {
-#endif
if (thread->_voiceLUT->size() > uint16(first))
sampleResourceId = (*thread->_voiceLUT)[uint16(first)];
-#if 0
}
-#endif
if (sampleResourceId < 0 || sampleResourceId > 4000)
sampleResourceId = -1;
diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp
index 39578e96f0..b8d03c9c08 100644
--- a/engines/saga/sndres.cpp
+++ b/engines/saga/sndres.cpp
@@ -327,9 +327,18 @@ bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buff
result = true;
} break;
case kSoundAIFF: {
- Audio::SeekableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES);
- buffer.stream = audStream;
- buffer.streamLength = audStream->getLength();
+ Audio::RewindableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES);
+ Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audStream);
+
+ if (!seekStream) {
+ warning("AIFF file is not seekable");
+ delete audStream;
+ result = false;
+ break;
+ }
+
+ buffer.stream = seekStream;
+ buffer.streamLength = seekStream->getLength();
result = true;
} break;
case kSoundVOC: {
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 9a41127f6d..bac9b3467a 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -371,8 +371,8 @@ static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_EGA_UNDITHER,
{
- _s("EGA undithering"),
- _s("Enable undithering in EGA games"),
+ _s("Skip EGA dithering pass (full color backgrounds)"),
+ _s("Skip dithering pass in EGA games, graphics are shown with full colors"),
"disable_dithering",
false
}
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 7cadcfc27e..55305c4b42 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -2445,6 +2445,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Lighthouse - Japanese DOS (from m_kiewitz)
+ // Executable scanning reports "3.000.000", VERSION file reports "1.0C"
+ {"lighthouse", "", {
+ {"resmap.001", 0, "18e0ac1597fe1cf6dc663118fe983e3b", 7885},
+ {"ressci.001", 0, "14e922c47b92156377cb49e241691792", 99573473},
+ {"resmap.002", 0, "723fc742c623d8933e5753a264324cb0", 7657},
+ {"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94627469},
+ AD_LISTEND},
+ Common::JA_JPN, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Lighthouse - Spanish DOS (from jvprat)
// Executable scanning reports "3.000.000", VERSION file reports "1.1"
{"lighthouse", "", {
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index c56eb09482..61ac76d0a7 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -933,6 +933,11 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics
g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals
break;
+ case GID_PQ2:
+ // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875).
+ // It gets disabled in the game's death screen.
+ g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
+ break;
default:
break;
}
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 6034378ef6..36e33ccfa6 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -121,8 +121,8 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
//
// TODO: Remove this once such a mechanism is in place
if (script->size > 65535)
- error("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't "
- "be handled at the moment, thus stopping", script_nr, script->size);
+ warning("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't "
+ "be fully handled at the moment", script_nr, script->size);
}
uint extraLocalsWorkaround = 0;
@@ -1086,9 +1086,14 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) {
const byte *seeker = getSci3ObjectsPointer();
while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) {
- reg_t reg = make_reg(segmentId, seeker - _buf);
- Object *obj = scriptObjInit(reg);
+ // We call setSegment and setOffset directly here, instead of using
+ // make_reg, as in large scripts, seeker - _buf can be larger than
+ // a 16-bit integer
+ reg_t reg;
+ reg.setSegment(segmentId);
+ reg.setOffset(seeker - _buf);
+ Object *obj = scriptObjInit(reg);
obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0));
seeker += READ_SCI11ENDIAN_UINT16(seeker + 2);
}
diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp
index ccc7a4389a..c75580a077 100644
--- a/engines/sci/graphics/transitions.cpp
+++ b/engines/sci/graphics/transitions.cpp
@@ -124,6 +124,10 @@ void GfxTransitions::setup(int16 number, bool blackoutFlag) {
}
}
+// Checks, if current time is lower than expected time of the current frame
+// If current time is higher, then we have to assume that the current system isn't capable
+// of either rendering frames that fast or has 60hz V'Sync enabled, which is why we drop frames
+// in those cases, so that transitions work as fast as expected.
bool GfxTransitions::doCreateFrame(uint32 shouldBeAtMsec) {
uint32 msecPos = g_system->getMillis() - _transitionStartTime;
@@ -132,12 +136,16 @@ bool GfxTransitions::doCreateFrame(uint32 shouldBeAtMsec) {
return false;
}
-void GfxTransitions::updateScreenAndWait(uint32 shouldBeAtMsec) {
+void GfxTransitions::updateScreen() {
Common::Event ev;
while (g_system->getEventManager()->pollEvent(ev)) {} // discard all events
g_system->updateScreen();
+}
+
+void GfxTransitions::updateScreenAndWait(uint32 shouldBeAtMsec) {
+ updateScreen();
// if we have still some time left, delay accordingly
uint32 msecPos = g_system->getMillis() - _transitionStartTime;
if (shouldBeAtMsec > msecPos)
@@ -257,6 +265,9 @@ void GfxTransitions::doTransition(int16 number, bool blackoutFlag) {
warning("Transitions: ID %d not implemented", number);
setNewScreen(blackoutFlag);
}
+ // Just to make sure that the current frame is shown in case we skipped the last update-call b/c of timing
+ updateScreen();
+ debugC(kDebugLevelGraphics, "Transition took %d milliseconds", g_system->getMillis() - _transitionStartTime);
}
void GfxTransitions::setNewPalette(bool blackoutFlag) {
@@ -348,7 +359,9 @@ void GfxTransitions::pixelation(bool blackoutFlag) {
copyRectToScreen(pixelRect, blackoutFlag);
if ((stepNr & 0x3FF) == 0) {
msecCount += 9;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
stepNr++;
} while (mask != 0x40);
@@ -372,7 +385,9 @@ void GfxTransitions::blocks(bool blackoutFlag) {
copyRectToScreen(blockRect, blackoutFlag);
if ((stepNr & 7) == 0) {
msecCount += 5;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
stepNr++;
} while (mask != 0x40);
@@ -392,7 +407,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) {
copyRectToScreen(newScreenRect, blackoutFlag);
if ((stepNr & 1) == 0) {
msecCount += 2;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
stepNr++;
newScreenRect.translate(-1, 0);
@@ -405,7 +422,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) {
copyRectToScreen(newScreenRect, blackoutFlag);
if ((stepNr & 1) == 0) {
msecCount += 2;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
stepNr++;
newScreenRect.translate(1, 0);
@@ -417,7 +436,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) {
while (newScreenRect.top >= _picRect.top) {
copyRectToScreen(newScreenRect, blackoutFlag);
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
stepNr++;
newScreenRect.translate(0, -1);
}
@@ -428,7 +449,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) {
while (newScreenRect.bottom <= _picRect.bottom) {
copyRectToScreen(newScreenRect, blackoutFlag);
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
stepNr++;
newScreenRect.translate(0, 1);
}
@@ -534,7 +557,6 @@ void GfxTransitions::scroll(int16 number) {
// Copy over final position just in case
_screen->copyRectToScreen(newScreenRect);
- g_system->updateScreen();
}
// Vertically displays new screen starting from center - works on _picRect area
@@ -552,7 +574,9 @@ void GfxTransitions::verticalRollFromCenter(bool blackoutFlag) {
copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(-1, 0);
copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(1, 0);
msecCount += 3;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
@@ -567,7 +591,9 @@ void GfxTransitions::verticalRollToCenter(bool blackoutFlag) {
copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(1, 0);
copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(-1, 0);
msecCount += 3;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
@@ -586,7 +612,9 @@ void GfxTransitions::horizontalRollFromCenter(bool blackoutFlag) {
copyRectToScreen(upperRect, blackoutFlag); upperRect.translate(0, -1);
copyRectToScreen(lowerRect, blackoutFlag); lowerRect.translate(0, 1);
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
@@ -601,7 +629,9 @@ void GfxTransitions::horizontalRollToCenter(bool blackoutFlag) {
copyRectToScreen(upperRect, blackoutFlag); upperRect.translate(0, 1);
copyRectToScreen(lowerRect, blackoutFlag); lowerRect.translate(0, -1);
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
@@ -633,7 +663,9 @@ void GfxTransitions::diagonalRollFromCenter(bool blackoutFlag) {
copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(-1, 0); leftRect.top--; leftRect.bottom++;
copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(1, 0); rightRect.top--; rightRect.bottom++;
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
@@ -652,7 +684,9 @@ void GfxTransitions::diagonalRollToCenter(bool blackoutFlag) {
copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(1, 0);
copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(-1, 0);
msecCount += 4;
- updateScreenAndWait(msecCount);
+ if (doCreateFrame(msecCount)) {
+ updateScreenAndWait(msecCount);
+ }
}
}
diff --git a/engines/sci/graphics/transitions.h b/engines/sci/graphics/transitions.h
index ae9ca4b48a..05842a4d2a 100644
--- a/engines/sci/graphics/transitions.h
+++ b/engines/sci/graphics/transitions.h
@@ -89,6 +89,7 @@ private:
void diagonalRollFromCenter(bool blackoutFlag);
void diagonalRollToCenter(bool blackoutFlag);
bool doCreateFrame(uint32 shouldBeAtMsec);
+ void updateScreen();
void updateScreenAndWait(uint32 shouldBeAtMsec);
GfxScreen *_screen;
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index 8e35d6b055..fb9a3f17b9 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -391,18 +391,13 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32
} else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKTAG('F','O','R','M')) {
// AIFF detected
Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO);
+ Audio::RewindableAudioStream *rewindStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES);
+ audioSeekStream = dynamic_cast<Audio::SeekableAudioStream *>(rewindStream);
- // Calculate samplelen from AIFF header
- int waveSize = 0, waveRate = 0;
- byte waveFlags = 0;
- bool ret = Audio::loadAIFFFromStream(*waveStream, waveSize, waveRate, waveFlags);
- if (!ret)
- error("Failed to load AIFF from stream");
-
- *sampleLen = (waveFlags & Audio::FLAG_16BITS ? waveSize >> 1 : waveSize) * 60 / waveRate;
-
- waveStream->seek(0, SEEK_SET);
- audioStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES);
+ if (!audioSeekStream) {
+ warning("AIFF file is not seekable");
+ delete rewindStream;
+ }
} else if (audioRes->size > 14 && READ_BE_UINT16(audioRes->data) == 1 && READ_BE_UINT16(audioRes->data + 2) == 1
&& READ_BE_UINT16(audioRes->data + 4) == 5 && READ_BE_UINT32(audioRes->data + 10) == 0x00018051) {
// Mac snd detected
diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp
index fcfda2f532..4f557be95e 100644
--- a/engines/sci/sound/drivers/adlib.cpp
+++ b/engines/sci/sound/drivers/adlib.cpp
@@ -27,7 +27,7 @@
#include "common/textconsole.h"
#include "audio/fmopl.h"
-#include "audio/softsynth/emumidi.h"
+#include "audio/mididrv.h"
#include "sci/resource.h"
#include "sci/sound/drivers/mididriver.h"
@@ -43,29 +43,30 @@ namespace Sci {
// FIXME: We don't seem to be sending the polyphony init data, so disable this for now
#define ADLIB_DISABLE_VOICE_MAPPING
-class MidiDriver_AdLib : public MidiDriver_Emulated {
+class MidiDriver_AdLib : public MidiDriver {
public:
enum {
kVoices = 9,
kRhythmKeys = 62
};
- MidiDriver_AdLib(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0) { }
+ MidiDriver_AdLib(Audio::Mixer *mixer) :_playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0), _isOpen(false) { }
virtual ~MidiDriver_AdLib() { }
// MidiDriver
+ int open() { return -1; } // Dummy implementation (use openAdLib)
int openAdLib(bool isSCI0);
void close();
void send(uint32 b);
MidiChannel *allocateChannel() { return NULL; }
MidiChannel *getPercussionChannel() { return NULL; }
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
- // AudioStream
- bool isStereo() const { return _stereo; }
- int getRate() const { return _mixer->getOutputRate(); }
+ // MidiDriver
+ void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
- // MidiDriver_Emulated
- void generateSamples(int16 *buf, int len);
+ void onTimer();
void setVolume(byte volume);
void playSwitch(bool play);
@@ -133,6 +134,7 @@ private:
bool _stereo;
bool _isSCI0;
OPL::OPL *_opl;
+ bool _isOpen;
bool _playSwitch;
int _masterVolume;
Channel _channels[MIDI_CHANNELS];
@@ -140,6 +142,9 @@ private:
byte *_rhythmKeyMap;
Common::Array<AdLibPatch> _patches;
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+
void loadInstrument(const byte *ins);
void voiceOn(int voice, int note, int velocity);
void voiceOff(int voice);
@@ -215,14 +220,12 @@ static const int ym3812_note[13] = {
};
int MidiDriver_AdLib::openAdLib(bool isSCI0) {
- int rate = _mixer->getOutputRate();
-
_stereo = STEREO;
debug(3, "ADLIB: Starting driver in %s mode", (isSCI0 ? "SCI0" : "SCI1"));
_isSCI0 = isSCI0;
- _opl = OPL::Config::create(isStereo() ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
+ _opl = OPL::Config::create(_stereo ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
// Try falling back to mono, thus plain OPL2 emualtor, when no Dual OPL2 is available.
if (!_opl && _stereo) {
@@ -233,22 +236,24 @@ int MidiDriver_AdLib::openAdLib(bool isSCI0) {
if (!_opl)
return -1;
- _opl->init(rate);
+ if (!_opl->init()) {
+ delete _opl;
+ _opl = nullptr;
+ return -1;
+ }
setRegister(0xBD, 0);
setRegister(0x08, 0);
setRegister(0x01, 0x20);
- MidiDriver_Emulated::open();
+ _isOpen = true;
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
+ _opl->start(new Common::Functor0Mem<void, MidiDriver_AdLib>(this, &MidiDriver_AdLib::onTimer));
return 0;
}
void MidiDriver_AdLib::close() {
- _mixer->stopHandle(_mixerSoundHandle);
-
delete _opl;
delete[] _rhythmKeyMap;
}
@@ -325,10 +330,14 @@ void MidiDriver_AdLib::send(uint32 b) {
}
}
-void MidiDriver_AdLib::generateSamples(int16 *data, int len) {
- if (isStereo())
- len <<= 1;
- _opl->readBuffer(data, len);
+void MidiDriver_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+}
+
+void MidiDriver_AdLib::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
// Increase the age of the notes
for (int i = 0; i < kVoices; i++) {
@@ -684,7 +693,7 @@ void MidiDriver_AdLib::setVelocityReg(int regOffset, int velocity, int kbScaleLe
if (!_playSwitch)
velocity = 0;
- if (isStereo()) {
+ if (_stereo) {
int velLeft = velocity;
int velRight = velocity;
@@ -734,7 +743,7 @@ void MidiDriver_AdLib::setRegister(int reg, int value, int channels) {
_opl->write(0x221, value);
}
- if (isStereo()) {
+ if (_stereo) {
if (channels & kRightChannel) {
_opl->write(0x222, reg);
_opl->write(0x223, value);
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index d42a7251d9..5a994cb699 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -306,9 +306,9 @@ static const GameSettings gameVariantsTable[] = {
// Humongous Entertainment Scumm Version 7.2
{"airport", "", 0, GID_HEGAME, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
+ {"farm", "", 0, GID_HEGAME, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
// Changed o_getResourceSize to cover all resource types
- {"farm", "", 0, GID_HEGAME, 6, 73, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
{"puttzoo", "", 0, GID_PUTTZOO, 6, 73, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
{"puttzoo", "HE 72", 0, GID_PUTTZOO, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
{"puttzoo", "HE 98.5", 0, GID_PUTTZOO, 6, 98, MDT_NONE, GF_USE_KEY | GF_HE_985, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)},
diff --git a/engines/scumm/players/player_ad.cpp b/engines/scumm/players/player_ad.cpp
index adcda68e10..4d4be2c3c2 100644
--- a/engines/scumm/players/player_ad.cpp
+++ b/engines/scumm/players/player_ad.cpp
@@ -27,6 +27,7 @@
#include "scumm/saveload.h"
#include "audio/fmopl.h"
+#include "audio/mixer.h"
#include "common/textconsole.h"
#include "common/config-manager.h"
@@ -35,26 +36,19 @@ namespace Scumm {
#define AD_CALLBACK_FREQUENCY 472
-Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
- : _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) {
+Player_AD::Player_AD(ScummEngine *scumm)
+ : _vm(scumm) {
_opl2 = OPL::Config::create();
- if (!_opl2->init(_rate)) {
+ if (!_opl2->init()) {
error("Could not initialize OPL2 emulator");
}
- _samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY;
- _samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY;
- _samplesTillCallback = 0;
- _samplesTillCallbackRemainder = 0;
-
memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable));
writeReg(0x01, 0x00);
writeReg(0xBD, 0x00);
writeReg(0x08, 0x00);
writeReg(0x01, 0x20);
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
_engineMusicTimer = 0;
_soundPlaying = -1;
@@ -78,11 +72,11 @@ Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
_musicVolume = _sfxVolume = 255;
_isSeeking = false;
+
+ _opl2->start(new Common::Functor0Mem<void, Player_AD>(this, &Player_AD::onTimer), AD_CALLBACK_FREQUENCY);
}
Player_AD::~Player_AD() {
- _mixer->stopHandle(_soundHandle);
-
stopAllSounds();
Common::StackLock lock(_mutex);
delete _opl2;
@@ -244,36 +238,14 @@ void Player_AD::saveLoadWithSerializer(Serializer *ser) {
}
}
-int Player_AD::readBuffer(int16 *buffer, const int numSamples) {
+void Player_AD::onTimer() {
Common::StackLock lock(_mutex);
- int len = numSamples;
-
- while (len > 0) {
- if (!_samplesTillCallback) {
- if (_curOffset) {
- updateMusic();
- }
-
- updateSfx();
-
- _samplesTillCallback = _samplesPerCallback;
- _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
- if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) {
- ++_samplesTillCallback;
- _samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY;
- }
- }
-
- const int samplesToRead = MIN(len, _samplesTillCallback);
- _opl2->readBuffer(buffer, samplesToRead);
-
- buffer += samplesToRead;
- len -= samplesToRead;
- _samplesTillCallback -= samplesToRead;
+ if (_curOffset) {
+ updateMusic();
}
- return numSamples;
+ updateSfx();
}
void Player_AD::setupVolume() {
diff --git a/engines/scumm/players/player_ad.h b/engines/scumm/players/player_ad.h
index 63a8503f47..63fda3cc7c 100644
--- a/engines/scumm/players/player_ad.h
+++ b/engines/scumm/players/player_ad.h
@@ -26,7 +26,6 @@
#include "scumm/music.h"
#include "audio/audiostream.h"
-#include "audio/mixer.h"
#include "common/mutex.h"
@@ -41,9 +40,9 @@ class ScummEngine;
/**
* Sound output for v3/v4 AdLib data.
*/
-class Player_AD : public MusicEngine, public Audio::AudioStream {
+class Player_AD : public MusicEngine {
public:
- Player_AD(ScummEngine *scumm, Audio::Mixer *mixer);
+ Player_AD(ScummEngine *scumm);
virtual ~Player_AD();
// MusicEngine API
@@ -56,18 +55,12 @@ public:
virtual void saveLoadWithSerializer(Serializer *ser);
- // AudioStream API
- virtual int readBuffer(int16 *buffer, const int numSamples);
- virtual bool isStereo() const { return false; }
- virtual bool endOfData() const { return false; }
- virtual int getRate() const { return _rate; }
+ // Timer callback
+ void onTimer();
private:
ScummEngine *const _vm;
Common::Mutex _mutex;
- Audio::Mixer *const _mixer;
- const int _rate;
- Audio::SoundHandle _soundHandle;
void setupVolume();
int _musicVolume;
@@ -75,11 +68,6 @@ private:
OPL::OPL *_opl2;
- int _samplesPerCallback;
- int _samplesPerCallbackRemainder;
- int _samplesTillCallback;
- int _samplesTillCallbackRemainder;
-
int _soundPlaying;
int32 _engineMusicTimer;
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 1cbefee105..a836cf12bc 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun May 10 14:23:43 2015
+ This file was generated by the md5table tool on Sun Jun 28 03:19:52 2015
DO NOT EDIT MANUALLY!
*/
@@ -272,7 +272,7 @@ static const MD5Table md5table[] = {
{ "5c21fc49aee8f46e58fef21579e614a1", "thinker1", "", "", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "5c9cecbd2952ccec14c9ecebf5822a34", "puttzoo", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformIOS },
{ "5d88b9d6a88e6f8e90cded9d01b7f082", "loom", "VGA", "VGA", 8307, Common::EN_ANY, Common::kPlatformDOS },
- { "5dda73606533d66a4c3f4f9ea6e842af", "farm", "", "", 87061, Common::RU_RUS, Common::kPlatformWindows },
+ { "5dda73606533d66a4c3f4f9ea6e842af", "farm", "HE 73", "", 87061, Common::RU_RUS, Common::kPlatformWindows },
{ "5e8fb66971a60e523e5afbc4c129c0e8", "socks", "HE 85", "", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "5ebb57234b2fe5c5dff641e00184ad81", "freddi", "HE 73", "", -1, Common::FR_FRA, Common::kPlatformWindows },
{ "5fbe557049892eb4b709d90916ec97ca", "indy3", "EGA", "EGA", 5361, Common::EN_ANY, Common::kPlatformDOS },
@@ -465,7 +465,7 @@ static const MD5Table md5table[] = {
{ "a194f15f51ee62badab74b9e7da97693", "baseball2001", "", "Demo", 20507, Common::EN_ANY, Common::kPlatformUnknown },
{ "a197a87ae77f3b3333f09a7a2c448fe2", "freddi", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "a22af0ad0e3126d19d22707b0267a37d", "balloon", "HE 80", "", -1, Common::NL_NLD, Common::kPlatformWindows },
- { "a2386da005672cbd5136f4f27a626c5f", "farm", "", "", 87061, Common::NL_NLD, Common::kPlatformWindows },
+ { "a2386da005672cbd5136f4f27a626c5f", "farm", "HE 73", "", 87061, Common::NL_NLD, Common::kPlatformWindows },
{ "a28135a7ade38cc0208b04507c46efd1", "spyfox", "HE 99", "", -1, Common::DE_DEU, Common::kPlatformUnknown },
{ "a2bb6aa0537402c1b3c2ea899ccef64b", "lost", "HE 99", "Demo", 15540, Common::EN_ANY, Common::kPlatformWindows },
{ "a3036878840720fbefa41e6965fa4a0a", "samnmax", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformDOS },
@@ -480,7 +480,7 @@ static const MD5Table md5table[] = {
{ "a654fb60c3b67d6317a7894ffd9f25c5", "pajama3", "", "Demo", -1, Common::EN_USA, Common::kPlatformUnknown },
{ "a71014c53a6d18c66ef2ea0ee42328e9", "PuttTime", "HE 99", "Mini Game", 18458, Common::NL_NLD, Common::kPlatformWindows },
{ "a7cacad9c40c4dc9e1812abf6c8af9d5", "puttcircus", "", "Demo", -1, Common::EN_ANY, Common::kPlatformUnknown },
- { "a85856675429fe88051744f755b72f93", "farm", "", "", -1, Common::EN_ANY, Common::kPlatformWindows },
+ { "a85856675429fe88051744f755b72f93", "farm", "HE 73", "", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "a86f9c49355579c30d4a55b477c0d869", "baseball2001", "", "", -1, Common::EN_ANY, Common::kPlatformUnknown },
{ "a8fcc3084ad5e3e569722755f205b1ef", "pajama3", "", "Mini Game", 13911, Common::DE_DEU, Common::kPlatformWindows },
{ "a9543ef0d79bcb47cd76ec197ad0a967", "puttmoon", "", "", -1, Common::EN_ANY, Common::kPlatform3DO },
@@ -650,7 +650,7 @@ static const MD5Table md5table[] = {
{ "ee785fe2569bc9965526e774f7ab86f1", "spyfox", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformMacintosh },
{ "ee8cfeb76e55d43a01c25e0865a9db76", "puttrace", "HE 98", "Demo", 13135, Common::NL_NLD, Common::kPlatformMacintosh },
{ "eea4d9ac2fb6f145945a308e8866915b", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 },
- { "eeb606c2d2ec877a712a9f20c10bcdda", "farm", "", "", 87034, Common::NL_NLD, Common::kPlatformMacintosh },
+ { "eeb606c2d2ec877a712a9f20c10bcdda", "farm", "HE 73", "", 87034, Common::NL_NLD, Common::kPlatformMacintosh },
{ "ef347474f3c7be3b29584eaa133cca05", "samnmax", "Floppy", "Floppy", -1, Common::FR_FRA, Common::kPlatformDOS },
{ "ef71a322b6530ac45b1a070f7c0795f7", "moonbase", "Demo", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "ef74d9071d4e564b037cb44bd6774de7", "fbear", "HE 62", "", -1, Common::HE_ISR, Common::kPlatformDOS },
@@ -679,7 +679,7 @@ static const MD5Table md5table[] = {
{ "faa89ab5e67ba4eebb4399f584f7490c", "pajama3", "", "Mini Game", 13911, Common::FR_FRA, Common::kPlatformWindows },
{ "fb66aa42de21675116346213f176a366", "monkey", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformAmiga },
{ "fbb697d89d2beca87360a145f467bdae", "PuttTime", "HE 90", "Demo", -1, Common::DE_DEU, Common::kPlatformUnknown },
- { "fbbbb38a81fc9d6a61d509278390a290", "farm", "", "", -1, Common::EN_ANY, Common::kPlatformMacintosh },
+ { "fbbbb38a81fc9d6a61d509278390a290", "farm", "HE 73", "", -1, Common::EN_ANY, Common::kPlatformMacintosh },
{ "fbdd947d21e8f5bac6d6f7a316af1c5a", "spyfox", "", "Demo", 15693, Common::EN_ANY, Common::kPlatformUnknown },
{ "fc53ce0e5f6562b1c1e1b4b8203acafb", "samnmax", "Floppy", "Floppy", -1, Common::ES_ESP, Common::kPlatformDOS },
{ "fc6b6148e80d67939d9a18697c0f626a", "monkey", "EGA", "EGA", 8367, Common::DE_DEU, Common::kPlatformDOS },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 99b4e695bb..24d676a1ff 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -316,6 +316,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)
_NES_lastTalkingActor = 0;
_NES_talkColor = 0;
_keepText = false;
+ _msgCount = 0;
_costumeLoader = NULL;
_costumeRenderer = NULL;
_2byteFontPtr = 0;
@@ -1904,7 +1905,7 @@ void ScummEngine::setupMusic(int midi) {
// EGA/VGA. However, we support multi MIDI for that game and we cannot
// support this with the Player_AD code at the moment. The reason here
// is that multi MIDI is supported internally by our iMuse output.
- _musicEngine = new Player_AD(this, _mixer);
+ _musicEngine = new Player_AD(this);
} else if (_game.version >= 3 && _game.heversion <= 62) {
MidiDriver *nativeMidiDriver = 0;
MidiDriver *adlibMidiDriver = 0;
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index 30b4d61880..6e0adc3ff3 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -1182,6 +1182,7 @@ protected:
byte _charsetBuffer[512];
bool _keepText;
+ byte _msgCount;
int _nextLeft, _nextTop;
diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp
index d60c4c6a50..3049fbcf62 100644
--- a/engines/scumm/string.cpp
+++ b/engines/scumm/string.cpp
@@ -283,6 +283,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) {
switch (c) {
case 1:
c = 13; // new line
+ _msgCount = _screenWidth;
endLoop = true;
break;
case 2:
@@ -293,6 +294,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) {
case 3:
_haveMsg = (_game.version >= 7) ? 1 : 0xFF;
_keepText = false;
+ _msgCount = 0;
endLoop = true;
break;
case 8:
@@ -573,6 +575,9 @@ void ScummEngine::CHARSET_1() {
#endif
restoreCharsetBg();
}
+ _msgCount = 0;
+ } else if (_game.version <= 2) {
+ _talkDelay += _msgCount * _defaultTalkDelay;
}
if (_game.version > 3) {
@@ -600,6 +605,7 @@ void ScummEngine::CHARSET_1() {
// End of text reached, set _haveMsg accordingly
_haveMsg = (_game.version >= 7) ? 2 : 1;
_keepText = false;
+ _msgCount = 0;
break;
}
@@ -648,6 +654,7 @@ void ScummEngine::CHARSET_1() {
}
if (_game.version <= 3) {
_charset->printChar(c, false);
+ _msgCount += 1;
} else {
if (_game.features & GF_16BIT_COLOR) {
// HE games which use sprites for subtitles
diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp
index 21d63633d3..bbf7c913b7 100644
--- a/engines/sherlock/animation.cpp
+++ b/engines/sherlock/animation.cpp
@@ -31,7 +31,7 @@ static const int NO_FRAMES = FRAMES_END;
Animation::Animation(SherlockEngine *vm) : _vm(vm) {
}
-bool Animation::play(const Common::String &filename, int minDelay, int fade,
+bool Animation::play(const Common::String &filename, bool intro, int minDelay, int fade,
bool setPalette, int speed) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
@@ -39,7 +39,7 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade,
int soundNumber = 0;
// Check for any any sound frames for the given animation
- const int *soundFrames = checkForSoundFrames(filename);
+ const int *soundFrames = checkForSoundFrames(filename, intro);
// Add on the VDX extension
Common::String vdxName = filename + ".vdx";
@@ -102,12 +102,19 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade,
if (frameNumber++ == *soundFrames) {
++soundNumber;
++soundFrames;
- Common::String fname = _soundLibraryFilename.empty() ?
- Common::String::format("%s%01d", filename.c_str(), soundNumber) :
- Common::String::format("%s%02d", filename.c_str(), soundNumber);
+
+ Common::String sampleFilename;
+
+ if (!intro) {
+ // regular animation, append 1-digit number
+ sampleFilename = Common::String::format("%s%01d", filename.c_str(), soundNumber);
+ } else {
+ // intro animation, append 2-digit number
+ sampleFilename = Common::String::format("%s%02d", filename.c_str(), soundNumber);
+ }
if (sound._voices)
- sound.playSound(fname, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str());
+ sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str());
}
events.wait(speed * 3);
@@ -133,6 +140,133 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade,
return !skipped && !_vm->shouldQuit();
}
+bool Animation::play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey,
+ int speed) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Sound &sound = *_vm->_sound;
+ int soundNumber = 0;
+
+ bool fadeActive = false;
+ uint16 fadeLimitColor = 0;
+ uint16 fadeLimitColorRed = 0;
+ uint16 fadeLimitColorGreen = 0;
+ uint16 fadeLimitColorBlue = 0;
+
+ // Check for any any sound frames for the given animation
+ const int *soundFrames = checkForSoundFrames(filename, intro);
+
+ // Add the VDX extension
+ Common::String indexName = "prologue/" + filename + ".3dx";
+
+ // Load the animation
+ Common::File *indexStream = new Common::File();
+
+ if (!indexStream->open(indexName)) {
+ warning("unable to open %s\n", indexName.c_str());
+ return false;
+ }
+
+ // Load initial image
+ Common::String graphicsName = "prologue/" + filename + ".3da";
+ ImageFile3DO images(graphicsName, kImageFile3DOType_Animation);
+
+ events.wait(minDelay);
+
+ if (fadeFromGrey) {
+ fadeActive = true;
+ fadeLimitColor = 0xCE59; // RGB565: 25, 50, 25 -> "grey"
+ }
+
+ int frameNumber = 0;
+ Common::Point pt;
+ bool skipped = false;
+ while (!_vm->shouldQuit()) {
+ // Get the next sprite to display
+ int imageFrame = indexStream->readSint16BE();
+
+ if (imageFrame == -2) {
+ // End of animation reached
+ break;
+ } else if (imageFrame != -1) {
+ // Read position from either animation stream or the sprite frame itself
+ if (imageFrame < 0) {
+ imageFrame += 32768;
+ pt.x = indexStream->readUint16BE();
+ pt.y = indexStream->readUint16BE();
+ } else {
+ pt = images[imageFrame]._offset;
+ }
+
+ // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame,
+ // since we don't want the offsets in the image file to be used, just the explicit position we specify
+ if (!fadeActive) {
+ screen.transBlitFrom(images[imageFrame]._frame, pt);
+ } else {
+ // Fade active, blit to backbuffer1
+ screen._backBuffer1.transBlitFrom(images[imageFrame]._frame, pt);
+ }
+ } else {
+ // At this point, either the sprites for the frame has been complete, or there weren't any sprites
+ // at all to draw for the frame
+
+ if (fadeActive) {
+ // process fading
+ screen.blitFrom3DOcolorLimit(fadeLimitColor);
+
+ if (!fadeLimitColor) {
+ // we are at the end, so stop
+ fadeActive = false;
+ } else {
+ // decrease limit color
+ fadeLimitColorRed = fadeLimitColor & 0xF800;
+ fadeLimitColorGreen = fadeLimitColor & 0x07E0;
+ fadeLimitColorBlue = fadeLimitColor & 0x001F;
+ if (fadeLimitColorRed)
+ fadeLimitColor -= 0x0800;
+ if (fadeLimitColorGreen)
+ fadeLimitColor -= 0x0040; // -2 because we are using RGB565, sherlock uses RGB555
+ if (fadeLimitColorBlue)
+ fadeLimitColor -= 0x0001;
+ }
+ }
+
+ // Check if we've reached a frame with sound
+ if (frameNumber++ == *soundFrames) {
+ ++soundNumber;
+ ++soundFrames;
+
+ Common::String sampleFilename;
+
+ // append 1-digit number
+ sampleFilename = Common::String::format("prologue/sounds/%s%01d", filename.c_str(), soundNumber);
+
+ if (sound._voices)
+ sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100); // no sound library
+ }
+ events.wait(speed * 3);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ if (keyState.keycode == Common::KEYCODE_ESCAPE ||
+ keyState.keycode == Common::KEYCODE_SPACE) {
+ skipped = true;
+ break;
+ }
+ } else if (events._pressed) {
+ skipped = true;
+ break;
+ }
+ }
+
+ events.clearEvents();
+ sound.stopSound();
+ delete indexStream;
+
+ return !skipped && !_vm->shouldQuit();
+}
+
void Animation::setPrologueNames(const char *const *names, int count) {
for (int idx = 0; idx < count; ++idx, ++names) {
_prologueNames.push_back(*names);
@@ -163,10 +297,11 @@ void Animation::setTitleFrames(const int *frames, int count, int maxFrames) {
}
}
-const int *Animation::checkForSoundFrames(const Common::String &filename) {
+const int *Animation::checkForSoundFrames(const Common::String &filename, bool intro) {
const int *frames = &NO_FRAMES;
- if (_soundLibraryFilename.empty()) {
+ if (!intro) {
+ // regular animation is playing
for (uint idx = 0; idx < _prologueNames.size(); ++idx) {
if (filename.equalsIgnoreCase(_prologueNames[idx])) {
frames = &_prologueFrames[idx][0];
@@ -174,6 +309,7 @@ const int *Animation::checkForSoundFrames(const Common::String &filename) {
}
}
} else {
+ // intro-animation is playing
for (uint idx = 0; idx < _titleNames.size(); ++idx) {
if (filename.equalsIgnoreCase(_titleNames[idx])) {
frames = &_titleFrames[idx][0];
diff --git a/engines/sherlock/animation.h b/engines/sherlock/animation.h
index b7811d3fa8..f3c95d4027 100644
--- a/engines/sherlock/animation.h
+++ b/engines/sherlock/animation.h
@@ -45,10 +45,11 @@ private:
/**
* Checks for whether an animation is being played that has associated sound
*/
- const int *checkForSoundFrames(const Common::String &filename);
+ const int *checkForSoundFrames(const Common::String &filename, bool intro);
public:
Common::String _soundLibraryFilename;
Common::String _gfxLibraryFilename;
+
public:
Animation(SherlockEngine *vm);
@@ -75,7 +76,9 @@ public:
/**
* Play a full-screen animation
*/
- bool play(const Common::String &filename, int minDelay, int fade, bool setPalette, int speed);
+ bool play(const Common::String &filename, bool intro, int minDelay, int fade, bool setPalette, int speed);
+
+ bool play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, int speed);
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp
index cfbea2bc24..50293db648 100644
--- a/engines/sherlock/debugger.cpp
+++ b/engines/sherlock/debugger.cpp
@@ -22,12 +22,44 @@
#include "sherlock/debugger.h"
#include "sherlock/sherlock.h"
+#include "sherlock/music.h"
+#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "sherlock/scalpel/scalpel_debugger.h"
+#include "sherlock/tattoo/tattoo_debugger.h"
+#include "audio/mixer.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+#include "common/str-array.h"
namespace Sherlock {
+Debugger *Debugger::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_RoseTattoo)
+ return new Tattoo::TattooDebugger(vm);
+ else
+ return new Scalpel::ScalpelDebugger(vm);
+}
+
Debugger::Debugger(SherlockEngine *vm) : GUI::Debugger(), _vm(vm) {
- registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
- registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
+ _showAllLocations = LOC_DISABLED;
+
+ registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
+ registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
+ registerCmd("song", WRAP_METHOD(Debugger, cmdSong));
+ registerCmd("songs", WRAP_METHOD(Debugger, cmdListSongs));
+ registerCmd("listfiles", WRAP_METHOD(Debugger, cmdListFiles));
+ registerCmd("dumpfile", WRAP_METHOD(Debugger, cmdDumpFile));
+ registerCmd("locations", WRAP_METHOD(Debugger, cmdLocations));
+}
+
+void Debugger::postEnter() {
+ if (!_3doPlayMovieFile.empty()) {
+ Scalpel3DOMoviePlay(_3doPlayMovieFile.c_str(), Common::Point(0, 0));
+
+ _3doPlayMovieFile.clear();
+ }
+
+ _vm->pauseEngine(false);
}
int Debugger::strToInt(const char *s) {
@@ -56,4 +88,79 @@ bool Debugger::cmdScene(int argc, const char **argv) {
}
}
+bool Debugger::cmdSong(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: song <name>\n");
+ return true;
+ }
+
+ Common::StringArray songs;
+ _vm->_music->getSongNames(songs);
+
+ for (uint i = 0; i < songs.size(); i++) {
+ if (songs[i].equalsIgnoreCase(argv[1])) {
+ _vm->_music->loadSong(songs[i]);
+ return false;
+ }
+ }
+
+ debugPrintf("Invalid song. Use the 'songs' command to see which ones are available.\n");
+ return true;
+}
+
+bool Debugger::cmdListSongs(int argc, const char **argv) {
+ Common::StringArray songs;
+ _vm->_music->getSongNames(songs);
+ debugPrintColumns(songs);
+ return true;
+}
+
+bool Debugger::cmdListFiles(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: listfiles <resource file>\n");
+ return true;
+ }
+ Common::StringArray files;
+ _vm->_res->getResourceNames(Common::String(argv[1]), files);
+ debugPrintColumns(files);
+ return true;
+}
+
+bool Debugger::cmdDumpFile(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: dumpfile <resource name>\n");
+ return true;
+ }
+
+ Common::SeekableReadStream *s = _vm->_res->load(argv[1]);
+ if (!s) {
+ debugPrintf("Invalid resource.\n");
+ return true;
+ }
+
+ byte *buffer = new byte[s->size()];
+ s->read(buffer, s->size());
+
+ Common::DumpFile dumpFile;
+ dumpFile.open(argv[1]);
+
+ dumpFile.write(buffer, s->size());
+ dumpFile.flush();
+ dumpFile.close();
+
+ delete[] buffer;
+
+ debugPrintf("Resource %s has been dumped to disk.\n", argv[1]);
+
+ return true;
+}
+
+bool Debugger::cmdLocations(int argc, const char **argv) {
+ _showAllLocations = LOC_REFRESH;
+
+ debugPrintf("Now showing all map locations\n");
+ return false;
+}
+
+
} // End of namespace Sherlock
diff --git a/engines/sherlock/debugger.h b/engines/sherlock/debugger.h
index e6a3aba828..bcc4448c32 100644
--- a/engines/sherlock/debugger.h
+++ b/engines/sherlock/debugger.h
@@ -30,10 +30,10 @@ namespace Sherlock {
class SherlockEngine;
+enum AllLocations { LOC_REFRESH = -1, LOC_DISABLED = 0, LOC_ALL = 1 };
+
class Debugger : public GUI::Debugger {
private:
- SherlockEngine *_vm;
-
/**
* Converts a decimal or hexadecimal string into a number
*/
@@ -43,9 +43,42 @@ private:
* Switch to another scene
*/
bool cmdScene(int argc, const char **argv);
+
+ /**
+ * Plays a song
+ */
+ bool cmdSong(int argc, const char **argv);
+
+ /**
+ * Lists all available songs
+ */
+ bool cmdListSongs(int argc, const char **argv);
+
+ /**
+ * Lists all files in a library (use at your own risk)
+ */
+ bool cmdListFiles(int argc, const char **argv);
+
+ /**
+ * Dumps a file to disk
+ */
+ bool cmdDumpFile(int argc, const char **argv);
+
+ /**
+ * Show all locations on the map
+ */
+ bool cmdLocations(int argc, const char **argv);
+protected:
+ SherlockEngine *_vm;
+ Common::String _3doPlayMovieFile;
+public:
+ AllLocations _showAllLocations;
public:
Debugger(SherlockEngine *vm);
virtual ~Debugger() {}
+ static Debugger *init(SherlockEngine *vm);
+
+ void postEnter();
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp
index ea68d79a1b..5a94b3485f 100644
--- a/engines/sherlock/detection.cpp
+++ b/engines/sherlock/detection.cpp
@@ -44,6 +44,10 @@ Common::Platform SherlockEngine::getPlatform() const {
return _gameDescription->desc.platform;
}
+Common::Language SherlockEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
} // End of namespace Sherlock
static const PlainGameDescriptor sherlockGames[] = {
@@ -58,6 +62,7 @@ static const PlainGameDescriptor sherlockGames[] = {
#define GAMEOPTION_HELP_STYLE GUIO_GAMEOPTIONS3
#define GAMEOPTION_PORTRAITS_ON GUIO_GAMEOPTIONS4
#define GAMEOPTION_WINDOW_STYLE GUIO_GAMEOPTIONS5
+#define GAMEOPTION_TRANSPARENT_WINDOWS GUIO_GAMEOPTIONS6
static const ADExtraGuiOptionsMap optionsList[] = {
{
@@ -110,6 +115,16 @@ static const ADExtraGuiOptionsMap optionsList[] = {
}
},
+ {
+ GAMEOPTION_TRANSPARENT_WINDOWS,
+ {
+ _s("Transparent windows"),
+ _s("Show windows with a partially transparent background"),
+ "transparent_windows",
+ true
+ }
+ },
+
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/sherlock/detection_tables.h b/engines/sherlock/detection_tables.h
index 208b6710af..e2b5a3dce9 100644
--- a/engines/sherlock/detection_tables.h
+++ b/engines/sherlock/detection_tables.h
@@ -40,6 +40,75 @@ static const SherlockGameDescription gameDescriptions[] = {
},
{
+ // Case of the Serrated Scalpel - German CD (from multilingual CD)
+ // Provided by m_kiewitz
+ {
+ "scalpel",
+ 0, {
+ {"talk.lib", 0, "40a5f9f37c0e0d2ad48d8f44d8e393c9", 284278},
+ {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465},
+ AD_LISTEND},
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - German
+ // Provided by mgerhardy
+ {
+ "scalpel",
+ 0, {
+ {"talk.lib", 0, "44652e54172e13b1b075b1ef7d89de24", 284043},
+ {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465},
+ AD_LISTEND},
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - Spanish CD (from multilingual CD)
+ // Provided by m_kiewitz
+ {
+ "scalpel",
+ 0, {
+ {"talk.lib", 0, "27697804b637a7f3b77234bf16f15dce", 171419},
+ {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465},
+ AD_LISTEND},
+ Common::ES_ESP,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - English 3DO
+ {
+ "scalpel",
+ 0,
+ AD_ENTRY1s("talk.lib", "20f74a29f2db6475e85b029ac9fc03bc", 240610),
+ Common::EN_ANY,
+ Common::kPlatform3DO,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
// Case of the Serrated Scalpel - Interactive English Demo
// Provided by Strangerke
{
@@ -79,7 +148,7 @@ static const SherlockGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSTABLE,
- GUIO0()
+ GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS)
},
GType_RoseTattoo
},
@@ -94,7 +163,7 @@ static const SherlockGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSTABLE,
- GUIO0()
+ GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS)
},
GType_RoseTattoo,
},
@@ -109,7 +178,7 @@ static const SherlockGameDescription gameDescriptions[] = {
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_UNSTABLE,
- GUIO0()
+ GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS)
},
GType_RoseTattoo,
},
diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp
index b238605e13..079ea0e663 100644
--- a/engines/sherlock/events.cpp
+++ b/engines/sherlock/events.cpp
@@ -27,6 +27,7 @@
#include "graphics/cursorman.h"
#include "sherlock/sherlock.h"
#include "sherlock/events.h"
+#include "sherlock/surface.h"
namespace Sherlock {
@@ -41,6 +42,9 @@ Events::Events(SherlockEngine *vm): _vm(vm) {
_pressed = _released = false;
_rightPressed = _rightReleased = false;
_oldButtons = _oldRightButton = false;
+ _firstPress = false;
+ _waitCounter = 0;
+ _frameRate = 0;
if (_vm->_interactiveFl)
loadCursors("rmouse.vgs");
@@ -54,35 +58,102 @@ void Events::loadCursors(const Common::String &filename) {
hideCursor();
delete _cursorImages;
- _cursorImages = new ImageFile(filename);
+ if (!IS_3DO) {
+ // PC
+ _cursorImages = new ImageFile(filename);
+ } else {
+ // 3DO
+ _cursorImages = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat);
+ }
_cursorId = INVALID_CURSOR;
}
void Events::setCursor(CursorId cursorId) {
- if (cursorId == _cursorId)
+ if (cursorId == _cursorId || _waitCounter > 0)
return;
- _cursorId = cursorId;
+ int hotspotX, hotspotY;
+
+ if (cursorId == MAGNIFY) {
+ hotspotX = 8;
+ hotspotY = 8;
+ } else {
+ hotspotX = 0;
+ hotspotY = 0;
+ }
// Set the cursor data
Graphics::Surface &s = (*_cursorImages)[cursorId]._frame;
- setCursor(s);
+ setCursor(s, hotspotX, hotspotY);
+
+ _cursorId = cursorId;
}
-void Events::setCursor(const Graphics::Surface &src) {
- CursorMan.replaceCursor(src.getPixels(), src.w, src.h, 0, 0, 0xff);
+void Events::setCursor(const Graphics::Surface &src, int hotspotX, int hotspotY) {
+ _cursorId = INVALID_CURSOR;
+ _hotspotPos = Common::Point(hotspotX, hotspotY);
+
+ if (!IS_3DO) {
+ // PC 8-bit palettized
+ CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0xff);
+ } else {
+ // 3DO RGB565
+ CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0x0000, false, &src.format);
+ }
showCursor();
}
+void Events::setCursor(CursorId cursorId, const Common::Point &cursorPos, const Graphics::Surface &surface) {
+ _cursorId = cursorId;
+
+ // Get the standard cursor frame
+ Graphics::Surface &cursorImg = (*_cursorImages)[cursorId]._frame;
+
+ // If the X pos for the cursor image is -100, this is a special value to indicate
+ // the cursor should be horizontally centered
+ Common::Point cursorPt = cursorPos;
+ if (cursorPos.x == -100)
+ cursorPt.x = (surface.w - cursorImg.w) / 2;
+
+ // Figure total bounds needed for cursor image and passed image
+ Common::Rect bounds(surface.w, surface.h);
+ bounds.extend(Common::Rect(cursorPt.x, cursorPt.y, cursorPt.x + cursorImg.w, cursorPt.y + cursorImg.h));
+ Common::Rect r = bounds;
+ r.moveTo(0, 0);
+
+ // Form a single surface containing both frames
+ Surface s(r.width(), r.height());
+ s.fill(TRANSPARENCY);
+
+ // Draw the passed image
+ Common::Point drawPos;
+ if (cursorPt.x < 0)
+ drawPos.x = -cursorPt.x;
+ if (cursorPt.y < 0)
+ drawPos.y = -cursorPt.y;
+ s.blitFrom(surface, Common::Point(drawPos.x, drawPos.y));
+
+ // Draw the cursor image
+ drawPos = Common::Point(MAX(cursorPt.x, (int16)0), MAX(cursorPt.y, (int16)0));
+ s.transBlitFrom(cursorImg, Common::Point(drawPos.x, drawPos.y));
+
+ // Set up hotspot position for cursor, adjusting for cursor image's position within the surface
+ Common::Point hotspot;
+ if (cursorId == MAGNIFY)
+ hotspot = Common::Point(8, 8);
+ hotspot += drawPos;
+ // Set the cursor
+ setCursor(s.getRawSurface(), hotspot.x, hotspot.y);
+}
+
void Events::animateCursorIfNeeded() {
if (_cursorId >= WAIT && _cursorId < (WAIT + 3)) {
- CursorId newId = (WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1);
+ CursorId newId = (_cursorId == WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1);
setCursor(newId);
}
}
-
void Events::showCursor() {
CursorMan.showMouse(true);
}
@@ -99,16 +170,14 @@ bool Events::isCursorVisible() const {
return CursorMan.isVisible();
}
-void Events::moveMouse(const Common::Point &pt) {
- g_system->warpMouse(pt.x, pt.y);
-}
-
void Events::pollEvents() {
checkForNextFrameCounter();
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
- // Handle keypress
+ _mousePos = event.mouse;
+
+ // Handle events
switch (event.type) {
case Common::EVENT_QUIT:
case Common::EVENT_RTL:
@@ -149,10 +218,29 @@ void Events::pollEventsAndWait() {
g_system->delayMillis(10);
}
+void Events::warpMouse(const Common::Point &pt) {
+ _mousePos = pt - _vm->_screen->_currentScroll;
+ g_system->warpMouse(_mousePos.x, _mousePos.y);
+}
+
+void Events::warpMouse() {
+ Screen &screen = *_vm->_screen;
+ warpMouse(Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2,
+ screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2));
+}
+
+Common::Point Events::mousePos() const {
+ return _vm->_screen->_currentScroll + _mousePos;
+}
+
+void Events::setFrameRate(int newRate) {
+ _frameRate = newRate;
+}
+
bool Events::checkForNextFrameCounter() {
// Check for next game frame
uint32 milli = g_system->getMillis();
- if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) {
+ if ((milli - _priorFrameTime) >= (1000 / _frameRate)) {
++_frameCounter;
_priorFrameTime = milli;
@@ -168,12 +256,42 @@ bool Events::checkForNextFrameCounter() {
return false;
}
-Common::Point Events::mousePos() const {
- return g_system->getEventManager()->getMousePos();
-}
-
Common::KeyState Events::getKey() {
- return _pendingKeys.pop();
+ Common::KeyState keyState = _pendingKeys.pop();
+
+ switch (keyState.keycode) {
+ case Common::KEYCODE_KP1:
+ keyState.keycode = Common::KEYCODE_END;
+ break;
+ case Common::KEYCODE_KP2:
+ keyState.keycode = Common::KEYCODE_DOWN;
+ break;
+ case Common::KEYCODE_KP3:
+ keyState.keycode = Common::KEYCODE_PAGEDOWN;
+ break;
+ case Common::KEYCODE_KP4:
+ keyState.keycode = Common::KEYCODE_LEFT;
+ break;
+ case Common::KEYCODE_KP6:
+ keyState.keycode = Common::KEYCODE_RIGHT;
+ break;
+ case Common::KEYCODE_KP7:
+ keyState.keycode = Common::KEYCODE_HOME;
+ break;
+ case Common::KEYCODE_KP8:
+ keyState.keycode = Common::KEYCODE_UP;
+ break;
+ case Common::KEYCODE_KP9:
+ keyState.keycode = Common::KEYCODE_PAGEUP;
+ break;
+ case Common::KEYCODE_KP_ENTER:
+ keyState.keycode = Common::KEYCODE_RETURN;
+ break;
+ default:
+ break;
+ }
+
+ return keyState;
}
void Events::clearEvents() {
@@ -182,6 +300,7 @@ void Events::clearEvents() {
_pressed = _released = false;
_rightPressed = _rightReleased = false;
_oldButtons = _oldRightButton = false;
+ _firstPress = false;
}
void Events::clearKeyboard() {
@@ -189,7 +308,7 @@ void Events::clearKeyboard() {
}
void Events::wait(int numFrames) {
- uint32 totalMilli = numFrames * 1000 / GAME_FRAME_RATE;
+ uint32 totalMilli = numFrames * 1000 / _frameRate;
delay(totalMilli);
}
@@ -199,7 +318,7 @@ bool Events::delay(uint32 time, bool interruptable) {
// For really short periods, simply delay by the desired amount
pollEvents();
g_system->delayMillis(time);
- bool result = !(interruptable && (kbHit() || _pressed));
+ bool result = !(interruptable && (kbHit() || _pressed || _vm->shouldQuit()));
clearEvents();
return result;
@@ -212,17 +331,19 @@ bool Events::delay(uint32 time, bool interruptable) {
while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) {
pollEventsAndWait();
- if (interruptable && (kbHit() || _pressed)) {
+ if (interruptable && (kbHit() || _mouseButtons)) {
clearEvents();
return false;
}
}
- return true;
+ return !_vm->shouldQuit();
}
}
void Events::setButtonState() {
+ _firstPress = ((_mouseButtons & 1) && !_pressed) || ((_mouseButtons & 2) && !_rightPressed);
+
_released = _rightReleased = false;
if (_mouseButtons & LEFT_BUTTON)
_pressed = _oldButtons = true;
@@ -246,4 +367,14 @@ bool Events::checkInput() {
return kbHit() || _pressed || _released || _rightPressed || _rightReleased;
}
+void Events::incWaitCounter() {
+ setCursor(WAIT);
+ ++_waitCounter;
+}
+
+void Events::decWaitCounter() {
+ assert(_waitCounter > 0);
+ --_waitCounter;
+}
+
} // End of namespace Sherlock
diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h
index b35109fefe..e13ef18822 100644
--- a/engines/sherlock/events.h
+++ b/engines/sherlock/events.h
@@ -26,11 +26,11 @@
#include "common/scummsys.h"
#include "common/events.h"
#include "common/stack.h"
-#include "sherlock/resources.h"
+#include "sherlock/image_file.h"
namespace Sherlock {
-#define GAME_FRAME_RATE 60
+#define GAME_FRAME_RATE 30
#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE)
enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, EXIT_ZONES_START = 5, INVALID_CURSOR = -1 };
@@ -44,6 +44,9 @@ private:
uint32 _priorFrameTime;
ImageFile *_cursorImages;
int _mouseButtons;
+ Common::Point _mousePos;
+ int _waitCounter;
+ uint _frameRate;
/**
* Check whether it's time to display the next screen frame
@@ -57,7 +60,9 @@ public:
bool _rightReleased;
bool _oldButtons;
bool _oldRightButton;
+ bool _firstPress;
Common::Stack<Common::KeyState> _pendingKeys;
+ Common::Point _hotspotPos;
public:
Events(SherlockEngine *vm);
~Events();
@@ -75,7 +80,12 @@ public:
/**
* Set the cursor to show from a passed frame
*/
- void setCursor(const Graphics::Surface &src);
+ void setCursor(const Graphics::Surface &src, int hotspotX = 0, int hotspotY = 0);
+
+ /**
+ * Set both a standard cursor as well as an inventory item above it
+ */
+ void setCursor(CursorId cursorId, const Common::Point &cursorPos, const Graphics::Surface &surface);
/**
* Animates the mouse cursor if the Wait cursor is showing
@@ -103,11 +113,6 @@ public:
bool isCursorVisible() const;
/**
- * Move the mouse
- */
- void moveMouse(const Common::Point &pt);
-
- /**
* Check for any pending events
*/
void pollEvents();
@@ -119,12 +124,38 @@ public:
void pollEventsAndWait();
/**
+ * Move the mouse cursor
+ */
+ void warpMouse(const Common::Point &pt);
+
+ /**
+ * Move the mouse cursor to the center of the screen
+ */
+ void warpMouse();
+
+ /**
* Get the current mouse position
*/
+ Common::Point screenMousePos() const { return _mousePos; }
+
+ /**
+ * Get the current mouse position within the scene, adjusted by the scroll position
+ */
Common::Point mousePos() const;
+ /**
+ * Override the default frame rate
+ */
+ void setFrameRate(int newRate);
+
+ /**
+ * Return the current game frame number
+ */
uint32 getFrameCounter() const { return _frameCounter; }
+ /**
+ * Returns true if there's a pending keyboard key
+ */
bool kbHit() const { return !_pendingKeys.empty(); }
/**
@@ -164,6 +195,16 @@ public:
* Checks to see to see if a key or a mouse button is pressed.
*/
bool checkInput();
+
+ /**
+ * Increment the wait counter
+ */
+ void incWaitCounter();
+
+ /**
+ * Decrement the wait counter
+ */
+ void decWaitCounter();
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp
new file mode 100644
index 0000000000..cbee944120
--- /dev/null
+++ b/engines/sherlock/fixed_text.cpp
@@ -0,0 +1,38 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/fixed_text.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+
+namespace Sherlock {
+
+FixedText *FixedText::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelFixedText(vm);
+ else
+ return new Tattoo::TattooFixedText(vm);
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/fixed_text.h b/engines/sherlock/fixed_text.h
new file mode 100644
index 0000000000..40444f4052
--- /dev/null
+++ b/engines/sherlock/fixed_text.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_FIXED_TEXT_H
+#define SHERLOCK_FIXED_TEXT_H
+
+#include "common/scummsys.h"
+#include "common/language.h"
+
+namespace Sherlock {
+
+#define FIXED(MSG) _vm->_fixedText->getText(kFixedText_##MSG)
+
+enum FixedTextActionId {
+ kFixedTextAction_Invalid = -1,
+ kFixedTextAction_Open = 0,
+ kFixedTextAction_Close,
+ kFixedTextAction_Move,
+ kFixedTextAction_Pick,
+ kFixedTextAction_Use
+};
+
+class SherlockEngine;
+
+class FixedText {
+protected:
+ SherlockEngine *_vm;
+
+ FixedText(SherlockEngine *vm) : _vm(vm) {}
+public:
+ static FixedText *init(SherlockEngine *vm);
+ virtual ~FixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId) = 0;
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) = 0;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
new file mode 100644
index 0000000000..482e795b6d
--- /dev/null
+++ b/engines/sherlock/fonts.cpp
@@ -0,0 +1,211 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/platform.h"
+#include "sherlock/fonts.h"
+#include "sherlock/image_file.h"
+#include "sherlock/surface.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+SherlockEngine *Fonts::_vm;
+ImageFile *Fonts::_font;
+int Fonts::_fontNumber;
+int Fonts::_fontHeight;
+int Fonts::_widestChar;
+uint16 Fonts::_charCount;
+byte Fonts::_yOffsets[255];
+
+void Fonts::setVm(SherlockEngine *vm) {
+ _vm = vm;
+ _font = nullptr;
+ _charCount = 0;
+}
+
+void Fonts::free() {
+ delete _font;
+}
+
+void Fonts::setFont(int fontNum) {
+ _fontNumber = fontNum;
+
+ // Discard previous font
+ delete _font;
+
+ Common::String fontFilename;
+
+ if (_vm->getPlatform() != Common::kPlatform3DO) {
+ // PC
+ // use FONT[number].VGS, which is a regular sherlock graphic file
+ fontFilename = Common::String::format("FONT%d.VGS", fontNum + 1);
+
+ // load font data
+ _font = new ImageFile(fontFilename);
+ } else {
+ // 3DO
+ switch (fontNum) {
+ case 0:
+ case 1:
+ fontFilename = "helvetica14.font";
+ break;
+ case 2:
+ fontFilename = "darts.font";
+ break;
+ default:
+ error("setFont(): unsupported 3DO font number");
+ }
+
+ // load font data
+ _font = new ImageFile3DO(fontFilename, kImageFile3DOType_Font);
+ }
+
+ _charCount = _font->size();
+
+ // Iterate through the frames to find the widest and tallest font characters
+ _fontHeight = _widestChar = 0;
+ for (uint idx = 0; idx < _charCount; ++idx) {
+ _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h);
+ _widestChar = MAX((uint16)_widestChar, (*_font)[idx]._frame.w);
+ }
+
+ // Initialize the Y offset table for the extended character set
+ for (int idx = 0; idx < 255; ++idx) {
+ _yOffsets[idx] = 0;
+
+ if (IS_ROSE_TATTOO) {
+ if ((idx >= 129 && idx < 135) || (idx >= 136 && idx < 143) || (idx >= 147 && idx < 155) ||
+ (idx >= 156 && idx < 165))
+ _yOffsets[idx] = 1;
+ else if ((idx >= 143 && idx < 146) || idx == 165)
+ _yOffsets[idx] = 2;
+ }
+ }
+}
+
+inline byte Fonts::translateChar(byte c) {
+ switch (c) {
+ case ' ':
+ return 0; // translate to first actual character
+ case 225:
+ // This was done in the German interpreter
+ // SH1: happens, when talking to the kid in the 2nd room
+ // SH2: happens, when looking at the newspaper right at the start in the backalley
+ // Special handling for 0xE1 (German Sharp-S character)
+ if (IS_ROSE_TATTOO) {
+ return 136; // it got translated to this for SH2
+ }
+ return 135; // and this for SH1
+ default:
+ if (IS_SERRATED_SCALPEL) {
+ if (c >= 0x80) { // German SH1 version did this, but not German SH2
+ c--;
+ }
+ // Spanish SH1 did this (reverse engineered code)
+ //if ((c >= 0xA0) && (c <= 0xAD) || (c == 0x82)) {
+ // c--;
+ //}
+ }
+ assert(c > 32); // anything above space is allowed
+ return c - 33;
+ }
+}
+
+void Fonts::writeString(Surface *surface, const Common::String &str,
+ const Common::Point &pt, int overrideColor) {
+ Common::Point charPos = pt;
+
+ if (!_font)
+ return;
+
+ for (const char *curCharPtr = str.c_str(); *curCharPtr; ++curCharPtr) {
+ byte curChar = *curCharPtr;
+
+ if (curChar == ' ') {
+ charPos.x += 5; // hardcoded space
+ continue;
+ }
+ curChar = translateChar(curChar);
+
+ assert(curChar < _charCount);
+ ImageFrame &frame = (*_font)[curChar];
+ surface->transBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor);
+ charPos.x += frame._frame.w + 1;
+ }
+}
+
+int Fonts::stringWidth(const Common::String &str) {
+ int width = 0;
+
+ if (!_font)
+ return 0;
+
+ for (const char *c = str.c_str(); *c; ++c)
+ width += charWidth(*c);
+
+ return width;
+}
+
+int Fonts::stringHeight(const Common::String &str) {
+ int height = 0;
+
+ if (!_font)
+ return 0;
+
+ for (const char *c = str.c_str(); *c; ++c)
+ height = MAX(height, charHeight(*c));
+
+ return height;
+}
+
+int Fonts::charWidth(unsigned char c) {
+ byte curChar;
+
+ if (!_font)
+ return 0;
+
+ if (c == ' ') {
+ return 5; // hardcoded space
+ }
+ curChar = translateChar(c);
+
+ if (curChar < _charCount)
+ return (*_font)[curChar]._frame.w + 1;
+ return 0;
+}
+
+int Fonts::charHeight(unsigned char c) {
+ byte curChar;
+
+ if (!_font)
+ return 0;
+
+ // Space is supposed to be handled like the first actual character (which is decimal 33)
+ curChar = translateChar(c);
+
+ assert(curChar < _charCount);
+ const ImageFrame &img = (*_font)[curChar];
+ return img._height + img._offset.y + 1;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h
new file mode 100644
index 0000000000..a527cc73c0
--- /dev/null
+++ b/engines/sherlock/fonts.h
@@ -0,0 +1,105 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_FONTS_H
+#define SHERLOCK_FONTS_H
+
+#include "common/rect.h"
+#include "common/platform.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+class ImageFile;
+class Surface;
+
+class Fonts {
+private:
+ static ImageFile *_font;
+ static byte _yOffsets[255];
+protected:
+ static SherlockEngine *_vm;
+ static int _fontNumber;
+ static int _fontHeight;
+ static int _widestChar;
+ static uint16 _charCount;
+
+ static void writeString(Surface *surface, const Common::String &str,
+ const Common::Point &pt, int overrideColor = 0);
+
+ static inline byte translateChar(byte c);
+public:
+ /**
+ * Initialise the font manager
+ */
+ static void setVm(SherlockEngine *vm);
+
+ /**
+ * Frees the font manager
+ */
+ static void free();
+
+ /**
+ * Set the font to use for writing text on the screen
+ */
+ void setFont(int fontNum);
+
+ /**
+ * Returns the width of a string in pixels
+ */
+ int stringWidth(const Common::String &str);
+
+ /**
+ * Returns the height of a string in pixels (i.e. the tallest displayed character)
+ */
+ int stringHeight(const Common::String &str);
+
+ /**
+ * Returns the width of a character in pixels
+ */
+ int charWidth(unsigned char c);
+
+ /**
+ * Returns the width of a character in pixels
+ */
+ int charHeight(unsigned char c);
+
+ /**
+ * Return the font height
+ */
+ int fontHeight() const { return _fontHeight; }
+
+ /**
+ * Return the width of the widest character in the font
+ */
+ int widestChar() const { return _widestChar; }
+
+ /**
+ * Return the currently active font number
+ */
+ int fontNumber() const { return _fontNumber; }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/image_file.cpp b/engines/sherlock/image_file.cpp
new file mode 100644
index 0000000000..3d881eb655
--- /dev/null
+++ b/engines/sherlock/image_file.cpp
@@ -0,0 +1,1098 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/image_file.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+
+namespace Sherlock {
+
+SherlockEngine *ImageFile::_vm;
+
+void ImageFile::setVm(SherlockEngine *vm) {
+ _vm = vm;
+}
+
+ImageFile::ImageFile() {
+}
+
+ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) {
+ Common::SeekableReadStream *stream = _vm->_res->load(name);
+
+ Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
+ load(*stream, skipPal, animImages);
+
+ delete stream;
+}
+
+ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) {
+ Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
+ load(stream, skipPal, false);
+}
+
+ImageFile::~ImageFile() {
+ for (uint idx = 0; idx < size(); ++idx)
+ (*this)[idx]._frame.free();
+}
+
+void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) {
+ loadPalette(stream);
+
+ int streamSize = stream.size();
+ while (stream.pos() < streamSize) {
+ ImageFrame frame;
+ frame._width = stream.readUint16LE() + 1;
+ frame._height = stream.readUint16LE() + 1;
+ frame._paletteBase = stream.readByte();
+
+ if (animImages) {
+ // Animation cutscene image files use a 16-bit x offset
+ frame._offset.x = stream.readUint16LE();
+ frame._rleEncoded = (frame._offset.x & 0xff) == 1;
+ frame._offset.y = stream.readByte();
+ } else {
+ // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset
+ frame._rleEncoded = stream.readByte() == 1;
+ frame._offset.x = stream.readByte();
+ frame._offset.y = stream.readByte();
+ }
+
+ frame._rleEncoded = !skipPalette && frame._rleEncoded;
+
+ if (frame._paletteBase) {
+ // Nibble packed frame data
+ frame._size = (frame._width * frame._height) / 2;
+ } else if (frame._rleEncoded) {
+ // This size includes the header size, which we subtract
+ frame._size = stream.readUint16LE() - 11;
+ frame._rleMarker = stream.readByte();
+ } else {
+ // Uncompressed data
+ frame._size = frame._width * frame._height;
+ }
+
+ // Load data for frame and decompress it
+ byte *data = new byte[frame._size + 4];
+ stream.read(data, frame._size);
+ Common::fill(data + frame._size, data + frame._size + 4, 0);
+ frame.decompressFrame(data, IS_ROSE_TATTOO);
+ delete[] data;
+
+ push_back(frame);
+ }
+}
+
+void ImageFile::loadPalette(Common::SeekableReadStream &stream) {
+ // Check for palette
+ uint16 width = stream.readUint16LE() + 1;
+ uint16 height = stream.readUint16LE() + 1;
+ byte paletteBase = stream.readByte();
+ byte rleEncoded = stream.readByte();
+ byte offsetX = stream.readByte();
+ byte offsetY = stream.readByte();
+ uint32 palSignature = 0;
+
+ if ((width == 390) && (height == 2) && (!paletteBase) && (!rleEncoded) && (!offsetX) && (!offsetY)) {
+ // We check for these specific values
+ // We can't do "width * height", because at least the first German+Spanish menu bar is 60 x 13
+ // which is 780, which is the size of the palette. We obviously don't want to detect it as palette.
+
+ // As another security measure, we also check for the signature text
+ palSignature = stream.readUint32BE();
+ if (palSignature != MKTAG('V', 'G', 'A', ' ')) {
+ // signature mismatch, rewind
+ stream.seek(-12, SEEK_CUR);
+ return;
+ }
+ // Found palette, so read it in
+ stream.seek(8, SEEK_CUR); // Skip over the rest of the signature text "VGA palette"
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx)
+ _palette[idx] = VGA_COLOR_TRANS(stream.readByte());
+ } else {
+ // Not a palette, so rewind to start of frame data for normal frame processing
+ stream.seek(-8, SEEK_CUR);
+ }
+}
+
+void ImageFrame::decompressFrame(const byte *src, bool isRoseTattoo) {
+ _frame.create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
+ byte *dest = (byte *)_frame.getPixels();
+ Common::fill(dest, dest + _width * _height, 0xff);
+
+ if (_paletteBase) {
+ // Nibble-packed
+ for (uint idx = 0; idx < _size; ++idx, ++src) {
+ *dest++ = *src & 0xF;
+ *dest++ = (*src >> 4);
+ }
+ } else if (_rleEncoded && isRoseTattoo) {
+ // Rose Tattoo run length encoding doesn't use the RLE marker byte
+ for (int yp = 0; yp < _height; ++yp) {
+ int xSize = _width;
+ while (xSize > 0) {
+ // Skip a given number of pixels
+ byte skip = *src++;
+ dest += skip;
+ xSize -= skip;
+ if (!xSize)
+ break;
+
+ // Get a run length, and copy the following number of pixels
+ int rleCount = *src++;
+ xSize -= rleCount;
+ while (rleCount-- > 0)
+ *dest++ = *src++;
+ }
+ assert(xSize == 0);
+ }
+ } else if (_rleEncoded) {
+ // RLE encoded
+ int frameSize = _width * _height;
+ while (frameSize > 0) {
+ if (*src == _rleMarker) {
+ byte rleColor = src[1];
+ byte rleCount = src[2];
+ src += 3;
+ frameSize -= rleCount;
+ while (rleCount--)
+ *dest++ = rleColor;
+ } else {
+ *dest++ = *src++;
+ --frameSize;
+ }
+ }
+ assert(frameSize == 0);
+ } else {
+ // Uncompressed frame
+ Common::copy(src, src + _width * _height, dest);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+int ImageFrame::sDrawXSize(int scaleVal) const {
+ int width = _width;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --width;
+
+ int result = width * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawYSize(int scaleVal) const {
+ int height = _height;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --height;
+
+ int result = height * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawXOffset(int scaleVal) const {
+ if (scaleVal == SCALE_THRESHOLD)
+ return _offset.x;
+
+ int width = _offset.x;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --width;
+
+ int result = width * SCALE_THRESHOLD / scale;
+ if (scaleVal > SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawYOffset(int scaleVal) const {
+ if (scaleVal == SCALE_THRESHOLD)
+ return _offset.y;
+
+ int height = _offset.y;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --height;
+
+ int result = height * SCALE_THRESHOLD / scale;
+ if (scaleVal > SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+// *******************************************************
+
+/*----------------------------------------------------------------*/
+
+SherlockEngine *ImageFile3DO::_vm;
+
+void ImageFile3DO::setVm(SherlockEngine *vm) {
+ _vm = vm;
+}
+
+ImageFile3DO::ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType) {
+#if 0
+ Common::File *dataStream = new Common::File();
+
+ if (!dataStream->open(name)) {
+ error("unable to open %s\n", name.c_str());
+ }
+#endif
+ Common::SeekableReadStream *dataStream = _vm->_res->load(name);
+
+ switch(imageFile3DOType) {
+ case kImageFile3DOType_Animation:
+ loadAnimationFile(*dataStream);
+ break;
+ case kImageFile3DOType_Cel:
+ case kImageFile3DOType_CelAnimation:
+ load3DOCelFile(*dataStream);
+ break;
+ case kImageFile3DOType_RoomFormat:
+ load3DOCelRoomData(*dataStream);
+ break;
+ case kImageFile3DOType_Font:
+ loadFont(*dataStream);
+ break;
+ default:
+ error("unknown Imagefile-3DO-Type");
+ break;
+ }
+
+ delete dataStream;
+}
+
+ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData) {
+ if (!isRoomData) {
+ load(stream, isRoomData);
+ } else {
+ load3DOCelRoomData(stream);
+ }
+}
+
+ImageFile3DO::~ImageFile3DO() {
+ // already done in ImageFile destructor
+ //for (uint idx = 0; idx < size(); ++idx)
+ // (*this)[idx]._frame.free();
+}
+
+void ImageFile3DO::load(Common::SeekableReadStream &stream, bool isRoomData) {
+ uint32 headerId = 0;
+
+ if (isRoomData) {
+ load3DOCelRoomData(stream);
+ return;
+ }
+
+ headerId = stream.readUint32BE();
+ assert(!stream.eos());
+
+ // Seek back to the start
+ stream.seek(-4, SEEK_CUR);
+
+ // Identify type of file
+ switch (headerId) {
+ case MKTAG('C', 'C', 'B', ' '):
+ case MKTAG('A', 'N', 'I', 'M'):
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim)
+ load3DOCelFile(stream);
+ break;
+
+ default:
+ // Sherlock animation file (.3da files)
+ loadAnimationFile(stream);
+ break;
+ }
+}
+
+// 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us
+inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) {
+ byte red = (pixel3DO >> 10) & 0x1F;
+ byte green = (pixel3DO >> 5) & 0x1F;
+ byte blue = pixel3DO & 0x1F;
+
+ return ((red << 11) | (green << 6) | (blue));
+}
+
+void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream) {
+ uint32 streamLeft = stream.size() - stream.pos();
+ uint32 celDataSize = 0;
+
+ while (streamLeft > 0) {
+ ImageFrame frame;
+
+ // We expect a basic header of 8 bytes
+ if (streamLeft < 8)
+ error("load3DOAnimationFile: expected animation header, not enough bytes");
+
+ celDataSize = stream.readUint16BE();
+
+ frame._width = stream.readUint16BE() + 1; // 2 bytes BE width
+ frame._height = stream.readByte() + 1; // 1 byte BE height
+ frame._paletteBase = 0;
+
+ frame._rleEncoded = true; // always compressed
+ if (frame._width & 0x8000) {
+ frame._width &= 0x7FFF;
+ celDataSize += 0x10000;
+ }
+
+ frame._offset.x = stream.readUint16BE();
+ frame._offset.y = stream.readByte();
+ frame._size = 0;
+ // Got header
+ streamLeft -= 8;
+
+ // cel data follows
+ if (streamLeft < celDataSize)
+ error("load3DOAnimationFile: expected cel data, not enough bytes");
+
+ //
+ // Load data for frame and decompress it
+ byte *data = new byte[celDataSize];
+ stream.read(data, celDataSize);
+ streamLeft -= celDataSize;
+
+ // always 16 bits per pixel (RGB555)
+ decompress3DOCelFrame(frame, data, celDataSize, 16, NULL);
+
+ delete[] data;
+
+ push_back(frame);
+ }
+}
+
+static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = {
+ 0, 1, 2, 4, 6, 8, 16, 0
+};
+
+// Reads a 3DO .cel/.anim file
+void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) {
+ int32 streamSize = stream.size();
+ int32 chunkStartPos = 0;
+ uint32 chunkTag = 0;
+ uint32 chunkSize = 0;
+ byte *chunkDataPtr = NULL;
+
+ // ANIM chunk (animation header for animation files)
+ bool animFound = false;
+ uint32 animVersion = 0;
+ uint32 animType = 0;
+ uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header
+ // CCB chunk (cel control block)
+ bool ccbFound = false;
+ uint32 ccbVersion = 0;
+ uint32 ccbFlags = 0;
+ bool ccbFlags_compressed = false;
+ uint16 ccbPPMP0 = 0;
+ uint16 ccbPPMP1 = 0;
+ uint32 ccbPRE0 = 0;
+ uint16 ccbPRE0_height = 0;
+ byte ccbPRE0_bitsPerPixel = 0;
+ uint32 ccbPRE1 = 0;
+ uint16 ccbPRE1_width = 0;
+ uint32 ccbWidth = 0;
+ uint32 ccbHeight = 0;
+ // pixel lookup table
+ bool plutFound = false;
+ uint32 plutCount = 0;
+ ImageFile3DOPixelLookupTable plutRGBlookupTable;
+
+ memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable));
+
+ while (!stream.err() && (stream.pos() < streamSize)) {
+ chunkStartPos = stream.pos();
+ chunkTag = stream.readUint32BE();
+ chunkSize = stream.readUint32BE();
+
+ if (stream.eos() || stream.err())
+ break;
+
+ if (chunkSize < 8)
+ error("load3DOCelFile: Invalid chunk size");
+
+ uint32 dataSize = chunkSize - 8;
+
+ switch (chunkTag) {
+ case MKTAG('A', 'N', 'I', 'M'):
+ // animation header
+ assert(dataSize >= 24);
+
+ if (animFound)
+ error("load3DOCelFile: multiple ANIM chunks not supported");
+
+ animFound = true;
+ animVersion = stream.readUint32BE();
+ animType = stream.readUint32BE();
+ animFrameCount = stream.readUint32BE();
+ // UINT32 - framerate (0x2000 in walk.anim???)
+ // UINT32 - starting frame (0 for walk.anim)
+ // UINT32 - number of loops (0 for walk.anim)
+
+ if (animVersion != 0)
+ error("load3DOCelFile: Unsupported animation file version");
+ if (animType != 1)
+ error("load3DOCelFile: Only single CCB animation files are supported");
+ break;
+
+ case MKTAG('C', 'C', 'B', ' '):
+ // CEL control block
+ assert(dataSize >= 72);
+
+ if (ccbFound)
+ error("load3DOCelFile: multiple CCB chunks not supported");
+
+ ccbFound = true;
+ ccbVersion = stream.readUint32BE();
+ ccbFlags = stream.readUint32BE();
+ stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
+ stream.skip(8 * 4); // skip over 8 offset fields
+ ccbPPMP0 = stream.readUint16BE();
+ ccbPPMP1 = stream.readUint16BE();
+ ccbPRE0 = stream.readUint32BE();
+ ccbPRE1 = stream.readUint32BE();
+ ccbWidth = stream.readUint32BE();
+ ccbHeight = stream.readUint32BE();
+
+ if (ccbVersion != 0)
+ error("load3DOCelFile: Unsupported CCB version");
+
+ if (ccbFlags & 0x200) // bit 9
+ ccbFlags_compressed = true;
+
+ // bit 5 of ccbFlags defines how RGB-black (0, 0, 0) will get treated
+ // = false -> RGB-black is treated as transparent
+ // = true -> RGB-black is treated as actual black
+ // atm we are always treating it as transparent
+ // it seems this bit is not set for any data of Sherlock Holmes
+
+ // PRE0 first 3 bits define how many bits per encoded pixel are used
+ ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
+ if (!ccbPRE0_bitsPerPixel)
+ error("load3DOCelFile: Invalid CCB PRE0 bits per pixel");
+
+ ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
+ ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
+ assert(ccbPRE0_height == ccbHeight);
+ assert(ccbPRE1_width == ccbWidth);
+ break;
+
+ case MKTAG('P', 'L', 'U', 'T'):
+ // pixel lookup table
+ // optional, not required for at least 16-bit pixel data
+ assert(dataSize >= 6);
+
+ if (!ccbFound)
+ error("load3DOCelFile: PLUT chunk found without CCB chunk");
+ if (plutFound)
+ error("load3DOCelFile: multiple PLUT chunks currently not supported");
+
+ plutFound = true;
+ plutCount = stream.readUint32BE();
+ // table follows, each entry is 16bit RGB555
+ assert(dataSize >= 4 + (plutCount * 2)); // security check
+ assert(plutCount <= 256); // security check
+
+ assert(plutCount <= 32); // PLUT should never contain more than 32 entries
+
+ for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
+ plutRGBlookupTable.pixelColor[plutColorNr] = stream.readUint16BE();
+ }
+
+ if (ccbPRE0_bitsPerPixel == 8) {
+ // In case we are getting 8-bits per pixel, we calculate the shades accordingly
+ // I'm not 100% sure if the calculation is correct. It's difficult to find information
+ // on this topic.
+ // The map uses this type of cel
+ assert(plutCount == 32); // And we expect 32 entries inside PLUT chunk
+
+ uint16 plutColorRGB = 0;
+ for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
+ plutColorRGB = plutRGBlookupTable.pixelColor[plutColorNr];
+
+ // Extract RGB values
+ byte plutColorRed = (plutColorRGB >> 10) & 0x1F;
+ byte plutColorGreen = (plutColorRGB >> 5) & 0x1F;
+ byte plutColorBlue = plutColorRGB & 0x1F;
+
+ byte shadeMultiplier = 2;
+ for (uint32 plutShadeNr = 1; plutShadeNr < 8; plutShadeNr++) {
+ uint16 shadedColorRGB;
+ byte shadedColorRed = (plutColorRed * shadeMultiplier) >> 3;
+ byte shadedColorGreen = (plutColorGreen * shadeMultiplier) >> 3;
+ byte shadedColorBlue = (plutColorBlue * shadeMultiplier) >> 3;
+
+ shadedColorRed = CLIP<byte>(shadedColorRed, 0, 0x1F);
+ shadedColorGreen = CLIP<byte>(shadedColorGreen, 0, 0x1F);
+ shadedColorBlue = CLIP<byte>(shadedColorBlue, 0, 0x1F);
+ shadedColorRGB = (shadedColorRed << 10) | (shadedColorGreen << 5) | shadedColorBlue;
+
+ plutRGBlookupTable.pixelColor[plutColorNr + (plutShadeNr << 5)] = shadedColorRGB;
+ shadeMultiplier++;
+ }
+ }
+ }
+ break;
+
+ case MKTAG('X', 'T', 'R', 'A'):
+ // Unknown contents, occurs right before PDAT
+ break;
+
+ case MKTAG('P', 'D', 'A', 'T'): {
+ // pixel data for one frame
+ // may be compressed or uncompressed pixels
+
+ if (ccbPRE0_bitsPerPixel != 16) {
+ // We require a pixel lookup table in case bits-per-pixel is lower than 16
+ if (!plutFound)
+ error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found");
+ } else {
+ // But we don't like it in case bits-per-pixel is 16 and we find one
+ if (plutFound)
+ error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well");
+ }
+ // read data into memory
+ chunkDataPtr = new byte[dataSize];
+
+ stream.read(chunkDataPtr, dataSize);
+
+ // Set up frame
+ ImageFrame imageFrame;
+
+ imageFrame._width = ccbWidth;
+ imageFrame._height = ccbHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = 0;
+ imageFrame._offset.y = 0;
+ imageFrame._rleEncoded = ccbFlags_compressed;
+ imageFrame._size = 0;
+
+ // Decompress/copy this frame
+ if (!plutFound) {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, NULL);
+ } else {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, &plutRGBlookupTable);
+ }
+
+ delete[] chunkDataPtr;
+
+ push_back(imageFrame);
+ break;
+ }
+
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // unknown contents
+ break;
+
+ default:
+ error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag));
+ }
+
+ // Seek to end of chunk
+ stream.seek(chunkStartPos + chunkSize);
+ }
+
+ // Warning below being used to silence unused variable warnings for now
+ warning("TODO: Remove %d %d %d", animFrameCount, ccbPPMP0, ccbPPMP1);
+}
+
+// Reads 3DO .cel data (room file format)
+void ImageFile3DO::load3DOCelRoomData(Common::SeekableReadStream &stream) {
+ uint32 streamLeft = stream.size() - stream.pos();
+ uint16 roomDataHeader_size = 0;
+ byte roomDataHeader_offsetX = 0;
+ byte roomDataHeader_offsetY = 0;
+
+ // CCB chunk (cel control block)
+ uint32 ccbFlags = 0;
+ bool ccbFlags_compressed = false;
+ uint16 ccbPPMP0 = 0;
+ uint16 ccbPPMP1 = 0;
+ uint32 ccbPRE0 = 0;
+ uint16 ccbPRE0_height = 0;
+ byte ccbPRE0_bitsPerPixel = 0;
+ uint32 ccbPRE1 = 0;
+ uint16 ccbPRE1_width = 0;
+ uint32 ccbWidth = 0;
+ uint32 ccbHeight = 0;
+ // cel data
+ uint32 celDataSize = 0;
+
+ while (streamLeft > 0) {
+ // We expect at least 8 bytes basic header
+ if (streamLeft < 8)
+ error("load3DOCelRoomData: expected room data header, not enough bytes");
+
+ // 3DO sherlock holmes room data header
+ stream.skip(4); // Possibly UINT16 width, UINT16 height?!?!
+ roomDataHeader_size = stream.readUint16BE();
+ roomDataHeader_offsetX = stream.readByte();
+ roomDataHeader_offsetY = stream.readByte();
+ streamLeft -= 8;
+
+ // We expect the header size specified in the basic header to be at least a raw CCB
+ if (roomDataHeader_size < 68)
+ error("load3DOCelRoomData: header size is too small");
+ // Check, that enough bytes for CCB are available
+ if (streamLeft < 68)
+ error("load3DOCelRoomData: expected raw cel control block, not enough bytes");
+
+ // 3DO raw cel control block
+ ccbFlags = stream.readUint32BE();
+ stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
+ stream.skip(8 * 4); // skip over 8 offset fields
+ ccbPPMP0 = stream.readUint16BE();
+ ccbPPMP1 = stream.readUint16BE();
+ ccbPRE0 = stream.readUint32BE();
+ ccbPRE1 = stream.readUint32BE();
+ ccbWidth = stream.readUint32BE();
+ ccbHeight = stream.readUint32BE();
+
+ if (ccbFlags & 0x200) // bit 9
+ ccbFlags_compressed = true;
+
+ // PRE0 first 3 bits define how many bits per encoded pixel are used
+ ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
+ if (!ccbPRE0_bitsPerPixel)
+ error("load3DOCelRoomData: Invalid CCB PRE0 bits per pixel");
+
+ ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
+ ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
+ assert(ccbPRE0_height == ccbHeight);
+ assert(ccbPRE1_width == ccbWidth);
+
+ if (ccbPRE0_bitsPerPixel != 16) {
+ // We currently support 16-bits per pixel in here
+ error("load3DOCelRoomData: bits per pixel < 16?!?!?");
+ }
+ // Got the raw CCB
+ streamLeft -= 68;
+
+ // cel data follows
+ // size field does not include the 8 byte header
+ celDataSize = roomDataHeader_size - 68;
+
+ if (streamLeft < celDataSize)
+ error("load3DOCelRoomData: expected cel data, not enough bytes");
+
+ // read data into memory
+ byte *celDataPtr = new byte[celDataSize];
+
+ stream.read(celDataPtr, celDataSize);
+ streamLeft -= celDataSize;
+
+ // Set up frame
+ {
+ ImageFrame imageFrame;
+
+ imageFrame._width = ccbWidth;
+ imageFrame._height = ccbHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = roomDataHeader_offsetX;
+ imageFrame._offset.y = roomDataHeader_offsetY;
+ imageFrame._rleEncoded = ccbFlags_compressed;
+ imageFrame._size = 0;
+
+ // Decompress/copy this frame
+ decompress3DOCelFrame(imageFrame, celDataPtr, celDataSize, ccbPRE0_bitsPerPixel, NULL);
+
+ delete[] celDataPtr;
+
+ push_back(imageFrame);
+ }
+ }
+
+ // Suppress compiler warning
+ warning("ccbPPMP0 = %d, ccbPPMP1 = %d", ccbPPMP0, ccbPPMP1);
+}
+
+static uint16 imagefile3DO_cel_bitsMask[17] = {
+ 0,
+ 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+};
+
+// gets [bitCount] bits from dataPtr, going from MSB to LSB
+inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) {
+ byte resultBitsLeft = bitCount;
+ uint16 result = 0;
+ byte currentByte = *dataPtr;
+
+ // Get bits of current byte
+ while (resultBitsLeft) {
+ if (resultBitsLeft < dataBitsLeft) {
+ // we need less than we have left
+ result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft];
+ dataBitsLeft -= resultBitsLeft;
+ resultBitsLeft = 0;
+
+ } else {
+ // we need as much as we have left or more
+ resultBitsLeft -= dataBitsLeft;
+ result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft;
+
+ // Go to next byte
+ dataPtr++;
+ dataBitsLeft = 8;
+ if (resultBitsLeft) {
+ currentByte = *dataPtr;
+ }
+ }
+ }
+ return result;
+}
+
+// decompress/copy 3DO cel data
+void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) {
+ frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ uint16 *dest = (uint16 *)frame._frame.getPixels();
+ Common::fill(dest, dest + frame._width * frame._height, 0);
+
+ int frameHeightLeft = frame._height;
+ int frameWidthLeft = frame._width;
+ uint16 pixelCount = 0;
+ uint16 pixel = 0;
+
+ const byte *srcLineStart = dataPtr;
+ const byte *srcLineData = dataPtr;
+ byte srcLineDataBitsLeft = 0;
+ uint16 lineDWordSize = 0;
+ uint16 lineByteSize = 0;
+
+ if (bitsPerPixel == 16) {
+ // Must not use pixel lookup table on 16-bits-per-pixel data
+ assert(!pixelLookupTable);
+ }
+
+ if (frame._rleEncoded) {
+ // compressed
+ byte compressionType = 0;
+ byte compressionPixels = 0;
+
+ while (frameHeightLeft > 0) {
+ frameWidthLeft = frame._width;
+
+ if (bitsPerPixel >= 8) {
+ lineDWordSize = READ_BE_UINT16(srcLineStart);
+ srcLineData = srcLineStart + 2;
+ } else {
+ lineDWordSize = *srcLineStart;
+ srcLineData = srcLineStart + 1;
+ }
+ srcLineDataBitsLeft = 8;
+
+ lineDWordSize += 2;
+ lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line
+
+ // debug
+ //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize);
+
+ while (frameWidthLeft > 0) {
+ // get 2 bits -> compressionType
+ // get 6 bits -> pixel count (0 = 1 pixel)
+ compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft);
+ // 6 bits == length (0 = 1 pixel)
+ compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1;
+
+ if (!compressionType) // end of line
+ break;
+
+ switch(compressionType) {
+ case 1: // simple copy
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
+ }
+ break;
+ case 2: // transparent
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = 0;
+ }
+ break;
+ case 3: // duplicate pixels
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ pixel = convertPixel(pixel);
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = pixel;
+ }
+ break;
+ default:
+ break;
+ }
+ frameWidthLeft -= compressionPixels;
+ }
+
+ assert(frameWidthLeft >= 0);
+
+ if (frameWidthLeft > 0) {
+ // still pixels left? skip them
+ dest += frameWidthLeft;
+ }
+
+ frameHeightLeft--;
+
+ // Seek to next line start
+ srcLineStart += lineByteSize;
+ }
+ } else {
+ // uncompressed
+ srcLineDataBitsLeft = 8;
+ lineDWordSize = ((frame._width * bitsPerPixel) + 31) >> 5;
+ lineByteSize = lineDWordSize * 4;
+ uint32 totalExpectedSize = lineByteSize * frame._height;
+
+ assert(totalExpectedSize <= dataSize); // security check
+
+ while (frameHeightLeft > 0) {
+ srcLineData = srcLineStart;
+ frameWidthLeft = frame._width;
+
+ while (frameWidthLeft > 0) {
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
+
+ frameWidthLeft--;
+ }
+ frameHeightLeft--;
+
+ // Seek to next line start
+ srcLineStart += lineByteSize;
+ }
+ }
+}
+
+// Reads Sherlock Holmes 3DO font file
+void ImageFile3DO::loadFont(Common::SeekableReadStream &stream) {
+ uint32 streamSize = stream.size();
+ uint32 header_offsetWidthTable = 0;
+ uint32 header_offsetBitsTable = 0;
+ uint32 header_fontHeight = 0;
+ uint32 header_bytesPerLine = 0;
+ uint32 header_maxChar = 0;
+ uint32 header_charCount = 0;
+
+ byte *widthTablePtr = NULL;
+ uint32 bitsTableSize = 0;
+ byte *bitsTablePtr = NULL;
+
+ stream.skip(2); // Unknown bytes
+ stream.skip(2); // Unknown bytes (0x000E)
+ header_offsetWidthTable = stream.readUint32BE();
+ header_offsetBitsTable = stream.readUint32BE();
+ stream.skip(4); // Unknown bytes (0x00000004)
+ header_fontHeight = stream.readUint32BE();
+ header_bytesPerLine = stream.readUint32BE();
+ header_maxChar = stream.readUint32BE();
+
+ assert(header_maxChar <= 255);
+ header_charCount = header_maxChar + 1;
+
+ // Allocate memory for width table
+ widthTablePtr = new byte[header_charCount];
+
+ stream.seek(header_offsetWidthTable);
+ stream.read(widthTablePtr, header_charCount);
+
+ // Allocate memory for the bits
+ assert(header_offsetBitsTable < streamSize); // Security check
+ bitsTableSize = streamSize - header_offsetBitsTable;
+ bitsTablePtr = new byte[bitsTableSize];
+ stream.read(bitsTablePtr, bitsTableSize);
+
+ // Now extract all characters
+ uint16 curChar = 0;
+ const byte *curBitsLinePtr = bitsTablePtr;
+ const byte *curBitsPtr = NULL;
+ byte curBitsLeft = 0;
+ uint32 curCharHeightLeft = 0;
+ uint32 curCharWidthLeft = 0;
+ byte curBits = 0;
+ byte curBitsReversed = 0;
+ byte curPosX = 0;
+
+ assert(bitsTableSize >= (header_maxChar * header_fontHeight * header_bytesPerLine)); // Security
+
+ // first frame needs to be "!" (33 decimal)
+ // our font code is subtracting 33 from the actual character code
+ curBitsLinePtr += (33 * (header_fontHeight * header_bytesPerLine));
+
+ for (curChar = 33; curChar < header_charCount; curChar++) {
+ // create frame
+ {
+ ImageFrame imageFrame;
+
+ imageFrame._width = widthTablePtr[curChar];
+ imageFrame._height = header_fontHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = 0;
+ imageFrame._offset.y = 0;
+ imageFrame._rleEncoded = false;
+ imageFrame._size = 0;
+
+ // Extract pixels
+ imageFrame._frame.create(imageFrame._width, imageFrame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ uint16 *dest = (uint16 *)imageFrame._frame.getPixels();
+ Common::fill(dest, dest + imageFrame._width * imageFrame._height, 0);
+
+ curCharHeightLeft = header_fontHeight;
+ while (curCharHeightLeft) {
+ curCharWidthLeft = widthTablePtr[curChar];
+ curBitsPtr = curBitsLinePtr;
+ curBitsLeft = 8;
+ curPosX = 0;
+
+ while (curCharWidthLeft) {
+ if (!(curPosX & 1)) {
+ curBits = *curBitsPtr >> 4;
+ } else {
+ curBits = *curBitsPtr & 0x0F;
+ curBitsPtr++;
+ }
+ // doing this properly is complicated
+ // the 3DO has built-in anti-aliasing
+ // this here at least results in somewhat readable text
+ // TODO: make it better
+ if (curBits) {
+ curBitsReversed = (curBits >> 3) | ((curBits & 0x04) >> 1) | ((curBits & 0x02) << 1) | ((curBits & 0x01) << 3);
+ curBits = 20 - curBits;
+ }
+
+ byte curIntensity = curBits;
+ *dest = (curIntensity << 11) | (curIntensity << 6) | curIntensity;
+ dest++;
+
+ curCharWidthLeft--;
+ curPosX++;
+ }
+
+ curCharHeightLeft--;
+ curBitsLinePtr += header_bytesPerLine;
+ }
+
+ push_back(imageFrame);
+ }
+ }
+
+ // Warning below being used to silence unused variable warnings for now
+ warning("TODO: Remove %d %d", curBitsLeft, curBitsReversed);
+
+ delete[] bitsTablePtr;
+ delete[] widthTablePtr;
+}
+
+/*----------------------------------------------------------------*/
+
+StreamingImageFile::StreamingImageFile() {
+ _frameNumber = 0;
+ _stream = nullptr;
+ _flags = 0;
+ _scaleVal = 0;
+ _zPlacement = 0;
+ _compressed = false;
+ _active = false;
+}
+
+StreamingImageFile::~StreamingImageFile() {
+ close();
+}
+
+void StreamingImageFile::load(Common::SeekableReadStream *stream, bool compressed) {
+ _stream = stream;
+ _compressed = compressed;
+ _frameNumber = -1;
+ _active = true;
+}
+
+void StreamingImageFile::close() {
+ delete _stream;
+ _stream = nullptr;
+ _frameNumber = -1;
+ _active = false;
+}
+
+bool StreamingImageFile::getNextFrame() {
+ // Don't proceed if we're already at the end of the stream
+ if (_stream->pos() >= _stream->size()) {
+ _active = false;
+ return false;
+ }
+
+ // Increment frame number
+ ++_frameNumber;
+
+ // If necessary, decompress the next frame
+ Common::SeekableReadStream *frameStream = _stream;
+ if (_compressed) {
+ uint32 inSize = _stream->readUint32LE();
+ Resources::decompressLZ(*_stream, _buffer, STREAMING_BUFFER_SIZE, inSize);
+ frameStream = new Common::MemoryReadStream(_buffer, 11, DisposeAfterUse::NO);
+ }
+
+ // Load the data for the frame
+ _imageFrame._width = frameStream->readUint16LE() + 1;
+ _imageFrame._height = frameStream->readUint16LE() + 1;
+ _imageFrame._paletteBase = frameStream->readByte();
+ _imageFrame._rleEncoded = frameStream->readByte() == 1;
+ _imageFrame._offset.x = frameStream->readByte();
+ _imageFrame._offset.y = frameStream->readByte();
+ _imageFrame._size = frameStream->readUint16LE() - 11;
+ _imageFrame._rleMarker = frameStream->readByte();
+
+ // Decode the frame
+ if (_compressed) {
+ delete frameStream;
+ _imageFrame.decompressFrame(_buffer + 11, true);
+ } else {
+ byte *data = new byte[_imageFrame._size];
+ _stream->read(data, _imageFrame._size);
+ _imageFrame.decompressFrame(_buffer + 11, true);
+ delete[] data;
+ }
+
+ return true;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/image_file.h b/engines/sherlock/image_file.h
new file mode 100644
index 0000000000..da260ab30b
--- /dev/null
+++ b/engines/sherlock/image_file.h
@@ -0,0 +1,208 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_IMAGE_FILE_H
+#define SHERLOCK_IMAGE_FILE_H
+
+#include "common/array.h"
+#include "common/file.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+struct ImageFrame {
+ uint32 _size;
+ uint16 _width, _height;
+ int _paletteBase;
+ bool _rleEncoded;
+ Common::Point _offset;
+ byte _rleMarker;
+ Graphics::Surface _frame;
+
+ /**
+ * Decompress a single frame for the sprite
+ */
+ void decompressFrame(const byte *src, bool isRoseTattoo);
+
+ /**
+ * Return the frame width adjusted by a specified scale amount
+ */
+ int sDrawXSize(int scaleVal) const;
+
+ /**
+ * Return the frame height adjusted by a specified scale amount
+ */
+ int sDrawYSize(int scaleVal) const;
+
+ /**
+ * Return the frame offset x adjusted by a specified scale amount
+ */
+ int sDrawXOffset(int scaleVal) const;
+
+ /**
+ * Return the frame offset y adjusted by a specified scale amount
+ */
+ int sDrawYOffset(int scaleVal) const;
+};
+
+class ImageFile : public Common::Array<ImageFrame> {
+private:
+ static SherlockEngine *_vm;
+
+ /**
+ * Load the data of the sprite
+ */
+ void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages);
+
+ /**
+ * Gets the palette at the start of the sprite file
+ */
+ void loadPalette(Common::SeekableReadStream &stream);
+public:
+ byte _palette[256 * 3];
+public:
+ ImageFile();
+ ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false);
+ ImageFile(Common::SeekableReadStream &stream, bool skipPal = false);
+ ~ImageFile();
+ static void setVm(SherlockEngine *vm);
+};
+
+enum ImageFile3DOType {
+ kImageFile3DOType_Animation = 0,
+ kImageFile3DOType_Cel = 1,
+ kImageFile3DOType_CelAnimation = 2,
+ kImageFile3DOType_RoomFormat = 3,
+ kImageFile3DOType_Font = 4
+};
+
+struct ImageFile3DOPixelLookupTable {
+ uint16 pixelColor[256];
+};
+
+class ImageFile3DO : public ImageFile { // Common::Array<ImageFrame> {
+private:
+ static SherlockEngine *_vm;
+
+ /**
+ * Load the data of the sprite
+ */
+ void load(Common::SeekableReadStream &stream, bool isRoomData);
+
+ /**
+ * convert pixel RGB values from RGB555 to RGB565
+ */
+ inline uint16 convertPixel(uint16 pixel3DO);
+
+ /**
+ * Load 3DO cel file
+ */
+ void load3DOCelFile(Common::SeekableReadStream &stream);
+
+ /**
+ * Load 3DO cel data (room file format)
+ */
+ void load3DOCelRoomData(Common::SeekableReadStream &stream);
+
+ inline uint16 celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft);
+
+ /**
+ * Decompress a single frame of a 3DO cel file
+ */
+ void decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable);
+
+ /**
+ * Load animation graphics file
+ */
+ void loadAnimationFile(Common::SeekableReadStream &stream);
+
+ /**
+ * Load Sherlock Holmes 3DO font file
+ */
+ void loadFont(Common::SeekableReadStream &stream);
+
+public:
+ ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType);
+ ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData = false);
+ ~ImageFile3DO();
+ static void setVm(SherlockEngine *vm);
+};
+
+#define STREAMING_BUFFER_SIZE 65536
+
+class StreamingImageFile {
+private:
+ int _frameNumber;
+ Common::SeekableReadStream *_stream;
+ byte _buffer[STREAMING_BUFFER_SIZE];
+ bool _compressed;
+ bool _active;
+public:
+ ImageFrame _imageFrame;
+
+ Common::Point _position; // Animation position
+ Common::Rect _oldBounds; // Bounds of previous frame
+ Common::Rect _removeBounds; // Remove area for just drawn frame
+
+ int _flags; // Flags
+ int _scaleVal; // Specifies the scale amount
+ int _zPlacement; // Used by doBgAnim for determining Z order
+public:
+ StreamingImageFile();
+ ~StreamingImageFile();
+
+ /**
+ * Initialize reading of the specified stream
+ */
+ void load(Common::SeekableReadStream *stream, bool compressed);
+
+ /**
+ * Close the streamining image file
+ */
+ void close();
+
+ /**
+ * Get the next frame of the file
+ */
+ bool getNextFrame();
+
+ /**
+ * Returns whether there are any remaining frames or not
+ */
+ bool active() const { return _active; }
+
+ /**
+ * Return the current frame number
+ */
+ int frameNumber() const { return _frameNumber; }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp
index a8ecb64102..d0982542f2 100644
--- a/engines/sherlock/inventory.cpp
+++ b/engines/sherlock/inventory.cpp
@@ -22,34 +22,48 @@
#include "sherlock/inventory.h"
#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_inventory.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo_inventory.h"
namespace Sherlock {
InventoryItem::InventoryItem(int requiredFlag, const Common::String &name,
const Common::String &description, const Common::String &examine) :
- _requiredFlag(requiredFlag), _name(name), _description(description),
+ _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description),
_examine(examine), _lookFlag(0) {
}
-void InventoryItem::synchronize(Common::Serializer &s) {
+InventoryItem::InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine, const Common::String &verbName) :
+ _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description),
+ _examine(examine), _lookFlag(0) {
+ _verb._verb = verbName;
+}
+
+void InventoryItem::synchronize(Serializer &s) {
s.syncAsSint16LE(_requiredFlag);
s.syncAsSint16LE(_lookFlag);
s.syncString(_name);
s.syncString(_description);
s.syncString(_examine);
+ _verb.synchronize(s);
}
/*----------------------------------------------------------------*/
+Inventory *Inventory::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelInventory(vm);
+ else
+ return new Tattoo::TattooInventory(vm);
+}
+
Inventory::Inventory(SherlockEngine *vm) : Common::Array<InventoryItem>(), _vm(vm) {
- Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr);
_invGraphicsLoaded = false;
_invIndex = 0;
_holdings = 0;
_invMode = INVMODE_EXIT;
- for (int i = 0; i < 6; ++i)
- _invShapes[i] = nullptr;
}
Inventory::~Inventory() {
@@ -64,50 +78,31 @@ void Inventory::freeInv() {
}
void Inventory::freeGraphics() {
- for (uint idx = 0; idx < MAX_VISIBLE_INVENTORY; ++idx)
+ int count = _invShapes.size();
+ for (int idx = 0; idx < count; ++idx)
delete _invShapes[idx];
+ _invShapes.clear();
+ _invShapes.resize(count);
- Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr);
_invGraphicsLoaded = false;
}
-void Inventory::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 streamSize = stream->size();
- while (stream->pos() < streamSize) {
- Common::String name;
- char c;
- while ((c = stream->readByte()) != 0)
- name += c;
-
- _names.push_back(name);
- }
-
- delete stream;
-
- loadGraphics();
-}
-
void Inventory::loadGraphics() {
if (_invGraphicsLoaded)
return;
- // Default all inventory slots to empty
- Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr);
-
- for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) {
+ for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < (int)_invShapes.size(); ++idx) {
// Get the name of the item to be displayed, figure out its accompanying
// .VGS file with its picture, and then load it
int invNum = findInv((*this)[idx]._name);
- Common::String fName = Common::String::format("item%02d.vgs", invNum + 1);
+ Common::String filename = Common::String::format("item%02d.vgs", invNum + 1);
- _invShapes[idx - _invIndex] = new ImageFile(fName);
+ if (!IS_3DO) {
+ // PC
+ _invShapes[idx - _invIndex] = new ImageFile(filename);
+ } else {
+ _invShapes[idx - _invIndex] = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat);
+ }
}
_invGraphicsLoaded = true;
@@ -123,226 +118,6 @@ int Inventory::findInv(const Common::String &name) {
error("Couldn't find inventory item - %s", name.c_str());
}
-void Inventory::putInv(InvSlamMode slamIt) {
- Screen &screen = *_vm->_screen;
- UserInterface &ui = *_vm->_ui;
-
- // If an inventory item has disappeared (due to using it or giving it),
- // a blank space slot may have appeared. If so, adjust the inventory
- if (_invIndex > 0 && _invIndex > (_holdings - 6)) {
- --_invIndex;
- freeGraphics();
- loadGraphics();
- }
-
- if (slamIt != SLAM_SECONDARY_BUFFER) {
- screen.makePanel(Common::Rect(6, 163, 54, 197));
- screen.makePanel(Common::Rect(58, 163, 106, 197));
- screen.makePanel(Common::Rect(110, 163, 158, 197));
- screen.makePanel(Common::Rect(162, 163, 210, 197));
- screen.makePanel(Common::Rect(214, 163, 262, 197));
- screen.makePanel(Common::Rect(266, 163, 314, 197));
- }
-
- // Iterate through displaying up to 6 objects at a time
- for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) {
- int itemNum = idx - _invIndex;
- Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1;
- Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194);
-
- // Draw the background
- if (idx == ui._selector) {
- bb.fillRect(r, 235);
- } else if (slamIt == SLAM_SECONDARY_BUFFER) {
- bb.fillRect(r, BUTTON_MIDDLE);
- }
-
- // Draw the item image
- ImageFrame &frame = (*_invShapes[itemNum])[0];
- bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2),
- 163 + ((33 - frame._height) / 2)));
- }
-
- if (slamIt == SLAM_DISPLAY)
- screen.slamArea(6, 163, 308, 34);
-
- if (slamIt != SLAM_SECONDARY_BUFFER)
- ui.clearInfo();
-
- if (slamIt == 0) {
- invCommands(0);
- } else if (slamIt == SLAM_SECONDARY_BUFFER) {
- screen._backBuffer = &screen._backBuffer2;
- invCommands(0);
- screen._backBuffer = &screen._backBuffer1;
- }
-}
-
-void Inventory::drawInventory(InvNewMode mode) {
- Screen &screen = *_vm->_screen;
- UserInterface &ui = *_vm->_ui;
- InvNewMode tempMode = mode;
-
- loadInv();
-
- if (mode == INVENTORY_DONT_DISPLAY) {
- screen._backBuffer = &screen._backBuffer2;
- }
-
- // Draw the window background
- Surface &bb = *screen._backBuffer;
- bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
- bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10,
- SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH,
- SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2),
- INV_BACKGROUND);
-
- // Draw the buttons
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[0][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[0][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[1][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[1][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[1][2] - screen.stringWidth("Look") / 2, "Look");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[2][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[2][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[2][2] - screen.stringWidth("Use") / 2, "Use");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[3][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[3][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[3][2] - screen.stringWidth("Give") / 2, "Give");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[4][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[4][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[4][2], "^^");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[5][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[5][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[5][2], "^");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[6][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[6][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[6][2], "_");
- screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[7][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[7][1],
- CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[7][2], "__");
-
- if (tempMode == INVENTORY_DONT_DISPLAY)
- mode = LOOK_INVENTORY_MODE;
- _invMode = (InvMode)mode;
-
- if (mode != PLAIN_INVENTORY) {
- ui._oldKey = Scalpel::INVENTORY_COMMANDS[(int)mode];
- } else {
- ui._oldKey = -1;
- }
-
- invCommands(0);
- putInv(SLAM_DONT_DISPLAY);
-
- if (tempMode != INVENTORY_DONT_DISPLAY) {
- if (!ui._slideWindows) {
- screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
- } else {
- ui.summonWindow(false, CONTROLS_Y1);
- }
-
- ui._windowOpen = true;
- } else {
- // Reset the screen back buffer to the first buffer now that drawing is done
- screen._backBuffer = &screen._backBuffer1;
- }
-
- assert(IS_SERRATED_SCALPEL);
- ((Scalpel::ScalpelUserInterface *)_vm->_ui)->_oldUse = -1;
-}
-
-void Inventory::invCommands(bool slamIt) {
- Screen &screen = *_vm->_screen;
- UserInterface &ui = *_vm->_ui;
-
- if (slamIt) {
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1),
- _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
- true, "Exit");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1),
- _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
- true, "Look");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1),
- _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- true, "Use");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1),
- _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- true, "Give");
- screen.print(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1),
- _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
- "^^");
- screen.print(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1),
- _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
- "^");
- screen.print(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1),
- (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
- "_");
- screen.print(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1),
- (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
- "__");
- if (_invMode != INVMODE_LOOK)
- ui.clearInfo();
- } else {
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1),
- _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- false, "Exit");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1),
- _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- false, "Look");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1),
- _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- false, "Use");
- screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1),
- _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
- false, "Give");
- screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1),
- _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
- "^^");
- screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1),
- _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
- "^");
- screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1),
- (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
- "_");
- screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1),
- (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
- "__");
- }
-}
-
-void Inventory::highlight(int index, byte color) {
- Screen &screen = *_vm->_screen;
- Surface &bb = *screen._backBuffer;
- int slot = index - _invIndex;
- ImageFrame &frame = (*_invShapes[slot])[0];
-
- bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color);
- bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2),
- 163 + ((33 - frame._height) / 2)));
- screen.slamArea(8 + slot * 52, 165, 44, 30);
-}
-
-void Inventory::refreshInv() {
- if (IS_ROSE_TATTOO)
- return;
-
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- Scalpel::ScalpelUserInterface &ui = *(Scalpel::ScalpelUserInterface *)_vm->_ui;
-
- ui._invLookFlag = true;
- freeInv();
-
- ui._infoFlag = true;
- ui.clearInfo();
-
- screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y),
- Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
- ui.examine();
-
- if (!talk._talkToAbort) {
- screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y));
- loadInv();
- }
-}
-
int Inventory::putNameInInventory(const Common::String &name) {
Scene &scene = *_vm->_scene;
int matches = 0;
@@ -425,7 +200,7 @@ void Inventory::copyToInventory(Object &obj) {
invItem._description = obj._description;
invItem._examine = obj._examine;
invItem._lookFlag = obj._lookFlag;
- invItem._requiredFlag = obj._requiredFlag;
+ invItem._requiredFlag = obj._requiredFlag[0];
insert_at(_holdings, invItem);
++_holdings;
@@ -450,7 +225,7 @@ int Inventory::deleteItemFromInventory(const Common::String &name) {
return 1;
}
-void Inventory::synchronize(Common::Serializer &s) {
+void Inventory::synchronize(Serializer &s) {
s.syncAsSint16LE(_holdings);
uint count = size();
diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h
index 02f570f5da..057e923e5d 100644
--- a/engines/sherlock/inventory.h
+++ b/engines/sherlock/inventory.h
@@ -25,15 +25,13 @@
#include "common/scummsys.h"
#include "common/array.h"
-#include "common/serializer.h"
#include "common/str-array.h"
#include "sherlock/objects.h"
#include "sherlock/resources.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
-#define MAX_VISIBLE_INVENTORY 6
-
enum InvMode {
INVMODE_EXIT = 0,
INVMODE_LOOK = 1,
@@ -62,18 +60,24 @@ struct InventoryItem {
Common::String _examine;
int _lookFlag;
- InventoryItem() : _requiredFlag(0), _lookFlag(0) {}
+ // Rose Tattoo fields
+ int _requiredFlag1;
+ UseType _verb;
+
+ InventoryItem() : _requiredFlag(0), _lookFlag(0), _requiredFlag1(0) {}
InventoryItem(int requiredFlag, const Common::String &name,
const Common::String &description, const Common::String &examine);
+ InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine, const Common::String &verbName);
/**
* Synchronize the data for an inventory item
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
};
class Inventory : public Common::Array<InventoryItem> {
-private:
+protected:
SherlockEngine *_vm;
Common::StringArray _names;
@@ -82,7 +86,7 @@ private:
*/
void copyToInventory(Object &obj);
public:
- ImageFile *_invShapes[MAX_VISIBLE_INVENTORY];
+ Common::Array<ImageFile *> _invShapes;
bool _invGraphicsLoaded;
InvMode _invMode;
int _invIndex;
@@ -93,8 +97,9 @@ public:
*/
void freeGraphics();
public:
+ static Inventory *init(SherlockEngine *vm);
Inventory(SherlockEngine *vm);
- ~Inventory();
+ virtual ~Inventory();
/**
* Free inventory data
@@ -102,12 +107,6 @@ public:
void freeInv();
/**
- * 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();
-
- /**
* Load the list of names of graphics for the inventory
*/
void loadGraphics();
@@ -119,32 +118,6 @@ public:
int findInv(const Common::String &name);
/**
- * Display the character's inventory. The slamIt parameter specifies:
- */
- void putInv(InvSlamMode slamIt);
-
- /**
- * Put the game into inventory mode and open the interface window.
- */
- void drawInventory(InvNewMode flag);
-
- /**
- * Prints the line of inventory commands at the top of an inventory window with
- * the correct highlighting
- */
- void invCommands(bool slamIt);
-
- /**
- * Set the highlighting color of a given inventory item
- */
- void highlight(int index, byte color);
-
- /**
- * Support method for refreshing the display of the inventory
- */
- void refreshInv();
-
- /**
* Adds a shape from the scene to the player's inventory
*/
int putNameInInventory(const Common::String &name);
@@ -163,7 +136,13 @@ public:
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv() = 0;
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
index b4b05da991..334cc05abf 100644
--- a/engines/sherlock/journal.cpp
+++ b/engines/sherlock/journal.cpp
@@ -21,113 +21,280 @@
*/
#include "sherlock/journal.h"
-#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_journal.h"
namespace Sherlock {
-#define JOURNAL_BUTTONS_Y 178
-#define LINES_PER_PAGE 11
-#define JOURNAL_SEARCH_LEFT 15
-#define JOURNAL_SEARCH_TOP 186
-#define JOURNAL_SEARCH_RIGHT 296
-#define JOURNAL_SEACRH_MAX_CHARS 50
-
-// Positioning of buttons in the journal view
-static const int JOURNAL_POINTS[9][3] = {
- { 6, 68, 37 },
- { 69, 131, 100 },
- { 132, 192, 162 },
- { 193, 250, 221 },
- { 251, 313, 281 },
- { 6, 82, 44 },
- { 83, 159, 121 },
- { 160, 236, 198 },
- { 237, 313, 275 }
-};
-
-static const int SEARCH_POINTS[3][3] = {
- { 51, 123, 86 },
- { 124, 196, 159 },
- { 197, 269, 232 }
+static const int TATTOO_LINE_SPACING[17] = {
+ 21, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 20, 20, 20, 21
};
/*----------------------------------------------------------------*/
+Journal *Journal::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelJournal(vm);
+ else
+ return new Tattoo::TattooJournal(vm);
+}
+
Journal::Journal(SherlockEngine *vm) : _vm(vm) {
- // Initialize fields
- _maxPage = 0;
- _index = 0;
- _sub = 0;
_up = _down = false;
+ _index = 0;
_page = 1;
-
- if (_vm->_interactiveFl) {
- // Load the journal directory and location names
- loadJournalLocations();
- }
+ _maxPage = 0;
+ _sub = 0;
}
-void Journal::record(int converseNum, int statementNum, bool replyOnly) {
- int saveIndex = _index;
- int saveSub = _sub;
+bool Journal::drawJournal(int direction, int howFar) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int topLineY = IS_SERRATED_SCALPEL ? 37 : 103 - screen.charHeight('A');
+ int yp = topLineY;
+ int startPage = _page;
+ bool endJournal = false;
+ bool firstOccurance = true;
+ bool searchSuccessful = false;
+ bool endFlag = false;
+ int lineNum = 0;
+ int savedIndex;
+ int temp;
+ const char *matchP;
+ int width;
- // Record the entry into the list
- _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly));
- _index = _journal.size() - 1;
+ talk._converseNum = -1;
+ _down = true;
- // Load the text for the new entry to get the number of lines it will have
- loadJournalFile(true);
+ do {
+ // Get the number of lines for the current journal entry
+ loadJournalFile(false);
+ if (_lines.empty()) {
+ // Entry has no text, so it must be a stealth eny. Move onto further journal entries
+ // until an entry with text is found
+ if (++_index == (int)_journal.size()) {
+ endJournal = true;
+ } else {
+ _sub = 0;
+ loadJournalFile(false);
+ }
+ }
+ } while (!endJournal && _lines.empty());
- // Restore old state
- _index = saveIndex;
- _sub = saveSub;
+ // Check if there no further pages with text until the end of the journal
+ if (endJournal) {
+ // If moving forward or backwards, clear the page before printing
+ if (direction)
+ drawFrame();
- // If new lines were added to the ournal, update the total number of lines
- // the journal continues
- if (!_lines.empty()) {
- _maxPage += _lines.size();
- } else {
- // No lines in entry, so remove the new entry from the journal
- _journal.remove_at(_journal.size() - 1);
+ if (IS_SERRATED_SCALPEL)
+ screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, fixedText.getText(Scalpel::kFixedText_Journal_Page), _page);
+ else
+ screen.gPrint(Common::Point(530, 72), COL_PEN_COLOR, fixedText.getText(Tattoo::kFixedText_Page), _page);
+
+ return false;
}
-}
-void Journal::loadJournalLocations() {
- Resources &res = *_vm->_res;
+ // If the journal page is being changed, set the wait cursor
+ if (direction)
+ events.setCursor(WAIT);
+
+ switch (direction) {
+ case 1:
+ case 4:
+ // Move backwards howFar number of lines unless either the start of the journal is reached,
+ // or a searched for keyword is found
+ do {
+ // Move backwards through the journal file a line at a time
+ if (--_sub < 0) {
+ do {
+ if (--_index < 0) {
+ _index = 0;
+ _sub = 0;
+ endJournal = true;
+ }
+ else {
+ loadJournalFile(false);
+ _sub = _lines.size() - 1;
+ }
+ } while (!endJournal && _lines.empty());
+ }
- _directory.clear();
+ // If it's search mode, check each line for the given keyword
+ if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) {
+ Common::String line = _lines[_sub];
+ line.toUppercase();
+ if (strstr(line.c_str(), _find.c_str()) != nullptr) {
+ // Found a match. Reset howFar so that the start of page that the match
+ // was found on will be displayed
+ searchSuccessful = true;
+ howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE;
+ }
+ }
- Common::SeekableReadStream *dir = res.load("talk.lib");
- dir->skip(4); // Skip header
+ ++lineNum;
+ } while (lineNum < howFar && !endJournal);
- // Get the numer of entries
- _directory.resize(dir->readUint16LE());
+ if (!_index && !_sub)
+ _page = 1;
+ else
+ _page -= howFar / LINES_PER_PAGE;
+ break;
- // Read in each entry
- char buffer[17];
- for (uint idx = 0; idx < _directory.size(); ++idx) {
- dir->read(buffer, 17);
- buffer[16] = '\0';
+ case 2:
+ case 3:
+ // Move howFar lines ahead unless the end of the journal is reached,
+ // or a searched for keyword is found
+ for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) {
+ // Handle animating mouse cursor
+ events.animateCursorIfNeeded();
- _directory[idx] = Common::String(buffer);
- }
+ lineNum = 0;
+ savedIndex = _index;
+ int savedSub = _sub;
- delete dir;
+ // Move a single page ahead
+ do {
+ // If in search mode, check for keyword
+ if (direction >= 3 && _page != startPage) {
+ Common::String line = _lines[_sub];
+ line.toUppercase();
+ if (strstr(line.c_str(), _find.c_str()) != nullptr)
+ searchSuccessful = true;
+ }
- // Load in the locations stored in journal.txt
- Common::SeekableReadStream *loc = res.load("journal.txt");
+ // Move forwards a line at a time, unless search word was found
+ if (!searchSuccessful) {
+ if (++_sub == (int)_lines.size()) {
+ // Reached end of page
+ do {
+ if (++_index == (int)_journal.size()) {
+ _index = savedIndex;
+ _sub = savedSub;
+ loadJournalFile(false);
+ endJournal = true;
+ } else {
+ _sub = 0;
+ loadJournalFile(false);
+ }
+ } while (!endJournal && _lines.empty());
+ }
- _locations.clear();
- while (loc->pos() < loc->size()) {
- Common::String line;
- char c;
- while ((c = loc->readByte()) != 0)
- line += c;
+ ++lineNum;
+ }
+ } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful);
- _locations.push_back(line);
+ if (!endJournal && !searchSuccessful)
+ // Move to next page
+ ++_page;
+
+ if (searchSuccessful) {
+ // Search found, so show top of the page it was found on
+ _index = savedIndex;
+ _sub = savedSub;
+ loadJournalFile(false);
+ }
+ }
+ break;
+
+ default:
+ break;
}
- delete loc;
+ if (direction) {
+ events.setCursor(ARROW);
+ drawFrame();
+ }
+
+ if (IS_SERRATED_SCALPEL)
+ screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, fixedText.getText(Scalpel::kFixedText_Journal_Page), _page);
+ else
+ screen.gPrint(Common::Point(530, 72), COL_PEN_COLOR, fixedText.getText(Tattoo::kFixedText_Page), _page);
+
+ temp = _sub;
+ savedIndex = _index;
+ lineNum = 0;
+
+ do {
+ bool inc = true;
+
+ // If there wasn't any line to print at the top of the page, we won't need to
+ // increment the y position
+ if (_lines[temp].empty() && yp == topLineY)
+ inc = false;
+
+ // If there's a searched for keyword in the line, it will need to be highlighted
+ if (searchSuccessful && firstOccurance) {
+ // Check if line has the keyword
+ Common::String line = _lines[temp];
+ line.toUppercase();
+ if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) {
+ matchP = _lines[temp].c_str() + (matchP - line.c_str());
+ firstOccurance = false;
+
+ // Print out the start of the line before the matching keyword
+ Common::String lineStart(_lines[temp].c_str(), matchP);
+ if (lineStart.hasPrefix("@")) {
+ width = screen.stringWidth(lineStart.c_str() + 1);
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", lineStart.c_str() + 1);
+ } else {
+ width = screen.stringWidth(lineStart.c_str());
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", lineStart.c_str());
+ }
+
+ // Print out the found keyword
+ Common::String lineMatch(matchP, matchP + _find.size());
+ byte fgColor = IS_SERRATED_SCALPEL ? (byte)Scalpel::INV_FOREGROUND : (byte)Tattoo::INV_FOREGROUND;
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), fgColor, "%s", lineMatch.c_str());
+ width += screen.stringWidth(lineMatch.c_str());
+
+ // Print remainder of line
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), COL_PEN_COLOR, "%s", matchP + _find.size());
+ } else if (_lines[temp].hasPrefix("@")) {
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
+ } else {
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+ }
+ } else {
+ if (_lines[temp].hasPrefix("@")) {
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
+ } else {
+ screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+ }
+ }
+
+ if (++temp == (int)_lines.size()) {
+ // Move to next page
+ do {
+ if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) {
+ ++_index;
+ loadJournalFile(false);
+ temp = 0;
+ } else {
+ if (_index == ((int)_journal.size() - 1))
+ _down = false;
+ endFlag = true;
+ }
+ } while (!endFlag && _lines.empty());
+ }
+
+ if (inc) {
+ // Move to next line
+ yp += IS_SERRATED_SCALPEL ? 13 : TATTOO_LINE_SPACING[lineNum];
+ ++lineNum;
+ }
+ } while (lineNum < LINES_PER_PAGE && !endFlag);
+
+ _index = savedIndex;
+ _up = _index || _sub;
+
+ return direction >= 3 && searchSuccessful;
}
void Journal::loadJournalFile(bool alreadyLoaded) {
@@ -144,7 +311,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
Common::String locStr(dirFilename.c_str() + 4, dirFilename.c_str() + 6);
int newLocation = atoi(locStr.c_str());
- // If not flagged as alrady loaded, load the conversation into script variables
+ // If not flagged as already loaded, load the conversation into script variables
if (!alreadyLoaded) {
// See if the file to be used is already loaded
if (journalEntry._converseNum != talk._converseNum) {
@@ -154,7 +321,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
// Find the person being referred to
talk._talkTo = -1;
for (int idx = 0; idx < (int)people._characters.size(); ++idx) {
- Common::String portrait = people[idx]._portrait;
+ Common::String portrait = people._characters[idx]._portrait;
Common::String numStr(portrait.c_str(), portrait.c_str() + 4);
if (locStr == numStr) {
@@ -186,7 +353,10 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
if (newLocation != oldLocation) {
// Add in scene title
- journalString = "@" + _locations[newLocation - 1] + ":";
+ journalString = "@";
+ if (IS_SERRATED_SCALPEL || newLocation - 1 < 100)
+ journalString += _locations[newLocation - 1];
+ journalString += ":";
// See if title can fit into a single line, or requires splitting on 2 lines
int width = screen.stringWidth(journalString.c_str() + 1);
@@ -216,16 +386,12 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
else
journalString += "said to ";
- switch (talk._talkTo) {
- case 1:
+ if (talk._talkTo == 1) {
journalString += "me";
- break;
- case 2:
+ } else if ((talk._talkTo == 2 && IS_SERRATED_SCALPEL) || (talk._talkTo == 18 && IS_ROSE_TATTOO)) {
journalString += "the Inspector";
- break;
- default:
+ } else {
journalString += people._characters[talk._talkTo]._name;
- break;
}
journalString += ", \"";
@@ -239,14 +405,28 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
bool commentFlag = false;
bool commentJustPrinted = false;
const byte *replyP = (const byte *)statement._reply.c_str();
+ const int inspectorId = (IS_SERRATED_SCALPEL) ? 2 : 18;
+ int beforeLastSpeakerChange = journalString.size();
+ bool justChangedSpeaker = true;
while (*replyP) {
byte c = *replyP++;
+ if (IS_ROSE_TATTOO) {
+ // Ignore commented out data
+ if (c == '/' && *(replyP + 1) == '*') {
+ replyP++; // skip *
+ while (*replyP++ != '*') {} // empty loop on purpose
+ replyP++; // skip /
+ c = *replyP;
+ }
+ }
+
// Is it a control character?
if (c < opcodes[0]) {
// Nope. Set flag for allowing control codes to insert spaces
ctrlSpace = true;
+ justChangedSpeaker = false;
assert(c >= ' ');
// Check for embedded comments
@@ -282,7 +462,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
} else {
if (talk._talkTo == 1)
journalString += "I";
- else if (talk._talkTo == 2)
+ else if (talk._talkTo == inspectorId)
journalString += "The Inspector";
else
journalString += people._characters[talk._talkTo]._name;
@@ -312,6 +492,15 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
commentJustPrinted = false;
}
} else if (c == opcodes[OP_SWITCH_SPEAKER]) {
+ if (IS_ROSE_TATTOO) {
+ // If the speaker has just changed, then no text has just been added
+ // from the last speaker, so remove the initial "Person said" text
+ if (justChangedSpeaker)
+ journalString = Common::String(journalString.c_str(), journalString.c_str() + beforeLastSpeakerChange);
+
+ justChangedSpeaker = true;
+ }
+
if (!startOfReply) {
if (!commentFlag && !commentJustPrinted)
journalString += "\"\n";
@@ -324,12 +513,14 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
startOfReply = false;
c = *replyP++ - 1;
+ if (IS_ROSE_TATTOO)
+ replyP++;
if (c == 0)
journalString += "Holmes";
else if (c == 1)
journalString += "I";
- else if (c == 2)
+ else if (c == inspectorId)
journalString += "the Inspector";
else
journalString += people._characters[c]._name;
@@ -345,42 +536,119 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
else
journalString += " said, \"";
} else {
- // Control code, so move past it and any parameters
- if (c == opcodes[OP_RUN_CANIMATION] || c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
- c == opcodes[OP_PAUSE] || c == opcodes[OP_PAUSE_WITHOUT_CONTROL] ||
+ if (IS_SERRATED_SCALPEL) {
+ // Control code, so move past it and any parameters
+ if (c == opcodes[OP_RUN_CANIMATION] ||
+ c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
+ c == opcodes[OP_PAUSE] ||
+ c == opcodes[OP_PAUSE_WITHOUT_CONTROL] ||
c == opcodes[OP_WALK_TO_CANIMATION]) {
- // These commands have a single parameter
- ++replyP;
-
- } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) {
- replyP += (replyP[0] & 127) + replyP[1] + 2;
-
- } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) {
- replyP += 4;
-
- } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) {
- replyP += 2;
-
- } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] ||
- c == opcodes[OP_CALL_TALK_FILE]) {
- replyP += 8;
- break;
-
- } else if (c == opcodes[OP_TOGGLE_OBJECT] || c == opcodes[OP_ADD_ITEM_TO_INVENTORY] ||
- c == opcodes[OP_SET_OBJECT] || c == opcodes[OP_DISPLAY_INFO_LINE] ||
- c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) {
- replyP += (*replyP & 127) + 1;
-
- } else if (c == opcodes[OP_GOTO_SCENE]) {
- replyP += 5;
-
- } else if (c == opcodes[OP_CARRIAGE_RETURN]) {
- journalString += "\n";
+ // These commands have a single parameter
+ ++replyP;
+ } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) {
+ replyP += (replyP[0] & 127) + replyP[1] + 2;
+ } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) {
+ replyP += 4;
+ } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) {
+ replyP += 2;
+ } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] ||
+ c == opcodes[OP_CALL_TALK_FILE]) {
+ replyP += 8;
+ break;
+ } else if (
+ c == opcodes[OP_TOGGLE_OBJECT] ||
+ c == opcodes[OP_ADD_ITEM_TO_INVENTORY] ||
+ c == opcodes[OP_SET_OBJECT] ||
+ c == opcodes[OP_DISPLAY_INFO_LINE] ||
+ c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) {
+ replyP += (*replyP & 127) + 1;
+ } else if (c == opcodes[OP_GOTO_SCENE]) {
+ replyP += 5;
+ } else if (c == opcodes[OP_END_TEXT_WINDOW]) {
+ journalString += "\n";
+ }
+ } else {
+ if (c == opcodes[OP_RUN_CANIMATION] ||
+ c == opcodes[OP_PAUSE] ||
+ c == opcodes[OP_MOUSE_OFF_ON] ||
+ c == opcodes[OP_SET_WALK_CONTROL] ||
+ c == opcodes[OP_PAUSE_WITHOUT_CONTROL] ||
+ c == opcodes[OP_WALK_TO_CANIMATION] ||
+ c == opcodes[OP_TURN_NPC_OFF] ||
+ c == opcodes[OP_TURN_NPC_ON] ||
+ c == opcodes[OP_RESTORE_PEOPLE_SEQUENCE])
+ ++replyP;
+ else if (
+ c == opcodes[OP_SET_TALK_SEQUENCE] ||
+ c == opcodes[OP_SET_FLAG] ||
+ c == opcodes[OP_WALK_NPC_TO_CANIM] ||
+ c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_CANIM] ||
+ c == opcodes[OP_NPC_PATH_LABEL] ||
+ c == opcodes[OP_PATH_GOTO_LABEL])
+ replyP += 2;
+ else if (
+ c == opcodes[OP_SET_NPC_PATH_PAUSE] ||
+ c == opcodes[OP_NPC_PATH_PAUSE_TAKING_NOTES] ||
+ c == opcodes[OP_NPC_PATH_PAUSE_LOOKING_HOLMES] ||
+ c == opcodes[OP_NPC_VERB_CANIM])
+ replyP += 3;
+ else if (
+ c == opcodes[OP_SET_SCENE_ENTRY_FLAG] ||
+ c == opcodes[OP_PATH_IF_FLAG_GOTO_LABEL])
+ replyP += 4;
+ else if (
+ c == opcodes[OP_WALK_TO_COORDS])
+ replyP += 5;
+ else if (
+ c == opcodes[OP_WALK_NPC_TO_COORDS] ||
+ c == opcodes[OP_GOTO_SCENE] ||
+ c == opcodes[OP_SET_NPC_PATH_DEST] ||
+ c == opcodes[OP_SET_NPC_POSITION])
+ replyP += 6;
+ else if (
+ c == opcodes[OP_PLAY_SONG] ||
+ c == opcodes[OP_NEXT_SONG])
+ replyP += 8;
+ else if (
+ c == opcodes[OP_CALL_TALK_FILE] ||
+ c == opcodes[OP_SET_NPC_TALK_FILE] ||
+ c == opcodes[OP_NPC_WALK_GRAPHICS])
+ replyP += 9;
+ else if (
+ c == opcodes[OP_NPC_VERB_SCRIPT])
+ replyP += 10;
+ else if (
+ c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_COORDS])
+ replyP += 11;
+ else if (
+ c == opcodes[OP_NPC_VERB] ||
+ c == opcodes[OP_NPC_VERB_TARGET])
+ replyP += 14;
+ else if (
+ c == opcodes[OP_ADJUST_OBJ_SEQUENCE])
+ replyP += (replyP[0] & 127) + replyP[1] + 2;
+ else if (
+ c == opcodes[OP_TOGGLE_OBJECT] ||
+ c == opcodes[OP_ADD_ITEM_TO_INVENTORY] ||
+ c == opcodes[OP_SET_OBJECT] ||
+ c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY])
+ replyP += (*replyP & 127) + 1;
+ else if (
+ c == opcodes[OP_END_TEXT_WINDOW]) {
+ journalString += '\n';
+ } else if (
+ c == opcodes[OP_NPC_DESC_ON_OFF]) {
+ replyP++;
+ while (replyP[0] && replyP[0] != opcodes[OP_NPC_DESC_ON_OFF])
+ replyP++;
+ } else if (
+ c == opcodes[OP_SET_NPC_INFO_LINE])
+ replyP += replyP[1] + 2;
}
// Put a space in the output for a control character, unless it's
// immediately coming after another control character
- if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_CARRIAGE_RETURN] &&
+ if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_END_TEXT_WINDOW] &&
!commentJustPrinted) {
journalString += " ";
ctrlSpace = false;
@@ -391,6 +659,9 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
if (!startOfReply && !commentJustPrinted)
journalString += '"';
+ if (IS_ROSE_TATTOO && justChangedSpeaker)
+ journalString = Common::String(journalString.c_str(), journalString.c_str() + beforeLastSpeakerChange);
+
// Finally finished building the journal text. Need to process the text to
// word wrap it to fit on-screen. The resulting lines are stored in the
// _lines array
@@ -431,727 +702,38 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
}
}
-void Journal::drawJournalFrame() {
- Resources &res = *_vm->_res;
- Screen &screen = *_vm->_screen;
- byte palette[PALETTE_SIZE];
-
- // Load in the journal background
- Common::SeekableReadStream *bg = res.load("journal.lbv");
- bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
- bg->read(palette, PALETTE_SIZE);
- delete bg;
-
- // Translate the palette for display
- for (int idx = 0; idx < PALETTE_SIZE; ++idx)
- palette[idx] = VGA_COLOR_TRANS(palette[idx]);
-
- // Set the palette and print the title
- screen.setPalette(palette);
- screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "Watson's Journal");
- screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "Watson's Journal");
-
- // Draw the buttons
- screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y,
- JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10),
- JOURNAL_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y,
- JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10),
- JOURNAL_POINTS[1][2] - screen.stringWidth("Back 10") / 2, "Back 10");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y,
- JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10),
- JOURNAL_POINTS[2][2] - screen.stringWidth("Up") / 2, "Up");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y,
- JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10),
- JOURNAL_POINTS[3][2] - screen.stringWidth("Down") / 2, "Down");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y,
- JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10),
- JOURNAL_POINTS[4][2] - screen.stringWidth("Ahead 10") / 2, "Ahead 10");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11,
- JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21),
- JOURNAL_POINTS[5][2] - screen.stringWidth("Search") / 2, "Search");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11,
- JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21),
- JOURNAL_POINTS[6][2] - screen.stringWidth("First Page") / 2, "First Page");
- screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11,
- JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21),
- JOURNAL_POINTS[7][2] - screen.stringWidth("Last Page") / 2, "Last Page");
-
- // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM
- screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11,
- JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21),
- JOURNAL_POINTS[8][2] - screen.stringWidth("Print Text") / 2, "Print Text");
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11),
- COMMAND_NULL, false, "Print Text");
-}
-
-void Journal::drawInterface() {
- Screen &screen = *_vm->_screen;
-
- drawJournalFrame();
-
- if (_journal.empty()) {
- _up = _down = 0;
- } else {
- drawJournal(0, 0);
- }
-
- doArrows();
-
- // Show the entire screen
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-}
-
-void Journal::doArrows() {
- Screen &screen = *_vm->_screen;
- byte color;
-
- color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL;
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, "Back 10");
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, "Up");
-
- color = _down ? COMMAND_FOREGROUND : COMMAND_NULL;
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, "Down");
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, "Ahead 10");
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, "Last Page");
-
- color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL;
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, "Search");
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, "Print Text");
-
- color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL;
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, "First Page");
-}
-
-bool Journal::drawJournal(int direction, int howFar) {
- Events &events = *_vm->_events;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- int yp = 37;
- int startPage = _page;
- bool endJournal = false;
- bool firstOccurance = true;
- bool searchSuccessful = false;
- bool endFlag = false;
- int lineNum = 0;
- int savedIndex;
- int temp;
- const char *matchP;
- int width;
-
- talk._converseNum = -1;
- _down = true;
-
- do {
- // Get the number of lines for the current journal entry
- loadJournalFile(false);
- if (_lines.empty()) {
- // Entry has no text, so it must be a stealth eny. Move onto further journal entries
- // until an entry with text is found
- if (++_index == (int)_journal.size()) {
- endJournal = true;
- } else {
- _sub = 0;
- loadJournalFile(false);
- }
- }
- } while (!endJournal && _lines.empty());
-
- // Check if there no further pages with text until the end of the journal
- if (endJournal) {
- // If moving forward or backwards, clear the page before printing
- if (direction)
- drawJournalFrame();
-
- screen.gPrint(Common::Point(235, 21), PEN_COLOR, "Page %d", _page);
- return false;
- }
-
- // If the journal page is being changed, set the wait cursor
- if (direction)
- events.setCursor(WAIT);
-
- switch (direction) {
- case 1:
- case 4:
- // Move backwards howFar number of lines unless either the start of the journal is reached,
- // or a searched for keyword is found
- do {
- // Animate the glass mouse cursor
- int cursorNum = (int)events.getCursor() + 1;
- if (cursorNum > (WAIT + 2))
- cursorNum = WAIT;
- events.setCursor((CursorId)cursorNum);
-
- // Move backwards through the journal file a line at a time
- if (--_sub < 0) {
- do {
- if (--_index < 0) {
- _index = 0;
- _sub = 0;
- endJournal = true;
- }
- else {
- loadJournalFile(false);
- _sub = _lines.size() - 1;
- }
- } while (!endJournal && _lines.empty());
- }
-
- // If it's search mode, check each line for the given keyword
- if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) {
- Common::String line = _lines[_sub];
- line.toUppercase();
- if (strstr(line.c_str(), _find.c_str()) != nullptr) {
- // Found a match. Reset howFar so that the start of page that the match
- // was found on will be displayed
- searchSuccessful = true;
- howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE;
- }
- }
-
- ++lineNum;
- } while (lineNum < howFar && !endJournal);
-
- if (!_index && !_sub)
- _page = 1;
- else
- _page -= howFar / LINES_PER_PAGE;
- break;
-
- case 2:
- case 3:
- // Move howFar lines ahead unless the end of the journal is reached,
- // or a searched for keyword is found
- for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) {
- // Handle animating mouse cursor
- int cursorNum = (int)events.getCursor() + 1;
- if (cursorNum >(WAIT + 2))
- cursorNum = WAIT;
- events.setCursor((CursorId)cursorNum);
-
- lineNum = 0;
- savedIndex = _index;
- int savedSub = _sub;
-
- // Move a single page ahead
- do {
- // If in search mode, check for keyword
- if (direction >= 3 && _page != startPage) {
- Common::String line = _lines[_sub];
- line.toUppercase();
- if (strstr(line.c_str(), _find.c_str()) != nullptr)
- searchSuccessful = true;
- }
-
- // Move forwards a line at a time, unless search word was found
- if (!searchSuccessful) {
- if (++_sub == (int)_lines.size()) {
- // Reached end of page
- do {
- if (++_index == (int)_journal.size()) {
- _index = savedIndex;
- _sub = savedSub;
- loadJournalFile(false);
- endJournal = true;
- } else {
- _sub = 0;
- loadJournalFile(false);
- }
- } while (!endJournal && _lines.empty());
- }
-
- ++lineNum;
- }
- } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful);
-
- if (!endJournal && !searchSuccessful)
- // Move to next page
- ++_page;
-
- if (searchSuccessful) {
- // Search found, so show top of the page it was found on
- _index = savedIndex;
- _sub = savedSub;
- loadJournalFile(false);
- }
- }
- break;
-
- default:
- break;
- }
-
- if (direction) {
- events.setCursor(ARROW);
- drawJournalFrame();
- }
-
- screen.gPrint(Common::Point(235, 21), PEN_COLOR, "Page %d", _page);
-
- temp = _sub;
- savedIndex = _index;
- lineNum = 0;
-
- do {
- bool inc = true;
-
- // If there wasn't any line to print at the top of the page, we won't need to
- // increment the y position
- if (_lines[temp].empty() && yp == 37)
- inc = false;
-
- // If there's a searched for keyword in the line, it will need to be highlighted
- if (searchSuccessful && firstOccurance) {
- // Check if line has the keyword
- Common::String line = _lines[temp];
- line.toUppercase();
- if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) {
- matchP = _lines[temp].c_str() + (matchP - line.c_str());
- firstOccurance = false;
-
- // Print out the start of the line before the matching keyword
- Common::String lineStart(_lines[temp].c_str(), matchP);
- if (lineStart.hasPrefix("@")) {
- width = screen.stringWidth(lineStart.c_str() + 1);
- screen.gPrint(Common::Point(53, yp), 15, "%s", lineStart.c_str() + 1);
- } else {
- width = screen.stringWidth(lineStart.c_str());
- screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", lineStart.c_str());
- }
-
- // Print out the found keyword
- Common::String lineMatch(matchP, matchP + _find.size());
- screen.gPrint(Common::Point(53 + width, yp), INV_FOREGROUND, "%s", lineMatch.c_str());
- width += screen.stringWidth(lineMatch.c_str());
-
- // Print remainder of line
- screen.gPrint(Common::Point(53 + width, yp), PEN_COLOR, "%s", matchP + _find.size());
- } else if (_lines[temp].hasPrefix("@")) {
- screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1);
- } else {
- screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str());
- }
- } else {
- if (_lines[temp].hasPrefix("@")) {
- screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1);
- } else {
- screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str());
- }
- }
-
- if (++temp == (int)_lines.size()) {
- // Move to next page
- do {
- if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) {
- ++_index;
- loadJournalFile(false);
- temp = 0;
- } else {
- if (_index == ((int)_journal.size() - 1))
- _down = false;
- endFlag = true;
- }
- } while (!endFlag && _lines.empty());
- }
-
- if (inc) {
- // Move to next line
- ++lineNum;
- yp += 13;
- }
- } while (lineNum < LINES_PER_PAGE && !endFlag);
-
- _index = savedIndex;
- _up = _index || _sub;
-
- return direction >= 3 && searchSuccessful;
-}
-
-JournalButton Journal::getHighlightedButton(const Common::Point &pt) {
- if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y &&
- pt.y < (JOURNAL_BUTTONS_Y + 10))
- return BTN_EXIT;
-
- if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y &&
- pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1)
- return BTN_BACK10;
-
- if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y &&
- pt.y < (JOURNAL_BUTTONS_Y + 10) && _up)
- return BTN_UP;
-
- if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y &&
- pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
- return BTN_DOWN;
-
- if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y &&
- pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
- return BTN_AHEAD110;
-
- if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
- pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
- return BTN_SEARCH;
-
- if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
- pt.y < (JOURNAL_BUTTONS_Y + 20) && _up)
- return BTN_FIRST_PAGE;
-
- if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
- pt.y < (JOURNAL_BUTTONS_Y + 20) && _down)
- return BTN_LAST_PAGE;
-
- if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
- pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
- return BTN_PRINT_TEXT;
-
- return BTN_NONE;
-}
-
-bool Journal::handleEvents(int key) {
- Events &events = *_vm->_events;
- Screen &screen = *_vm->_screen;
- bool doneFlag = false;
-
- Common::Point pt = events.mousePos();
- JournalButton btn = getHighlightedButton(pt);
- byte color;
-
- if (events._pressed || events._released) {
- // Exit button
- color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, "Exit");
-
- // Back 10 button
- if (btn == BTN_BACK10) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Back 10");
- } else if (_page > 1) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Back 10");
- }
-
- // Up button
- if (btn == BTN_UP) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Up");
- } else if (_up) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Up");
- }
-
- // Down button
- if (btn == BTN_DOWN) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Down");
- } else if (_down) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Down");
- }
-
- // Ahead 10 button
- if (btn == BTN_AHEAD110) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Ahead 10");
- } else if (_down) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Ahead 10");
- }
-
- // Search button
- if (btn == BTN_SEARCH) {
- color = COMMAND_HIGHLIGHTED;
- } else if (_journal.empty()) {
- color = COMMAND_NULL;
- } else {
- color = COMMAND_FOREGROUND;
- }
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, "Search");
-
- // First Page button
- if (btn == BTN_FIRST_PAGE) {
- color = COMMAND_HIGHLIGHTED;
- } else if (_up) {
- color = COMMAND_FOREGROUND;
- } else {
- color = COMMAND_NULL;
- }
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, "First Page");
-
- // Last Page button
- if (btn == BTN_LAST_PAGE) {
- color = COMMAND_HIGHLIGHTED;
- } else if (_down) {
- color = COMMAND_FOREGROUND;
- } else {
- color = COMMAND_NULL;
- }
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, "Last Page");
-
- // Print Text button
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, "Print Text");
- }
-
- if (btn == BTN_EXIT && events._released) {
- // Exit button pressed
- doneFlag = true;
-
- } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) {
- // Scrolll up 10 pages
- if (_page < 11)
- drawJournal(1, (_page - 1) * LINES_PER_PAGE);
- else
- drawJournal(1, 10 * LINES_PER_PAGE);
-
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) {
- // Scroll up
- drawJournal(1, LINES_PER_PAGE);
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) {
- // Scroll down
- drawJournal(2, LINES_PER_PAGE);
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) {
- // Scroll down 10 pages
- if ((_page + 10) > _maxPage)
- drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
- else
- drawJournal(2, 10 * LINES_PER_PAGE);
-
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) {
- screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search");
- bool notFound = false;
-
- do {
- int dir;
- if ((dir = getSearchString(notFound)) != 0) {
- int savedIndex = _index;
- int savedSub = _sub;
- int savedPage = _page;
-
- if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) {
- _index = savedIndex;
- _sub = savedSub;
- _page = savedPage;
-
- drawJournalFrame();
- drawJournal(0, 0);
- notFound = true;
- } else {
- doneFlag = true;
- }
-
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
- } else {
- doneFlag = true;
- }
- } while (!doneFlag);
- doneFlag = false;
-
- } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) {
- // First page
- _index = _sub = 0;
- _up = _down = false;
- _page = 1;
-
- drawJournalFrame();
- drawJournal(0, 0);
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) {
- // Last page
- if ((_page + 10) > _maxPage)
- drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
- else
- drawJournal(2, 1000 * LINES_PER_PAGE);
-
- doArrows();
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
- }
-
- events.wait(2);
-
- return doneFlag;
-}
-
-int Journal::getSearchString(bool printError) {
- enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD };
-
- Events &events = *_vm->_events;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- int xp;
- int yp = 174;
- bool flag = false;
- Common::String name;
- int done = 0;
- byte color;
-
- // Draw search panel
- screen.makePanel(Common::Rect(6, 171, 313, 199));
- screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10),
- SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
- screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10),
- SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, "Backward");
- screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10),
- SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, "Forward");
- screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, yp),
- COMMAND_FOREGROUND, "E");
- screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, yp),
- COMMAND_FOREGROUND, "B");
- screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, yp),
- COMMAND_FOREGROUND, "F");
-
- screen.makeField(Common::Rect(12, 185, 307, 196));
-
- screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM);
- screen.vLine(12, 185, 195, BUTTON_BOTTOM);
- screen.hLine(13, 195, 306, BUTTON_TOP);
- screen.hLine(306, 186, 195, BUTTON_TOP);
-
- if (printError) {
- screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth("Text Not Found !")) / 2, 185),
- INV_FOREGROUND, "Text Not Found !");
- } else if (!_find.empty()) {
- // There's already a search term, display it already
- screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
- name = _find;
- }
-
- screen.slamArea(6, 171, 307, 28);
-
- if (printError) {
- // Give time for user to see the message
- events.setButtonState();
- for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) {
- events.pollEvents();
- events.setButtonState();
- events.wait(2);
- }
-
- events.clearKeyboard();
- screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE);
-
- if (!_find.empty()) {
- screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
- name = _find;
- }
+void Journal::record(int converseNum, int statementNum, bool replyOnly) {
+ int saveIndex = _index;
+ int saveSub = _sub;
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ if (IS_3DO) {
+ // there seems to be no journal in the 3DO version
+ return;
}
- xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name);
- yp = JOURNAL_SEARCH_TOP;
-
- do {
- events._released = false;
- Button found = BTN_NONE;
-
- while (!_vm->shouldQuit() && !events.kbHit() && !events._released) {
- found = BTN_NONE;
- if (talk._talkToAbort)
- return 0;
-
- // Check if key or mouse button press has occurred
- events.setButtonState();
- Common::Point pt = events.mousePos();
-
- flag = !flag;
- screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE);
-
- if (events._pressed || events._released) {
- if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) {
- found = BTN_EXIT;
- color = COMMAND_HIGHLIGHTED;
- } else {
- color = COMMAND_FOREGROUND;
- }
- screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, 175), color, "Exit");
-
- if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) {
- found = BTN_BACKWARD;
- color = COMMAND_HIGHLIGHTED;
- } else {
- color = COMMAND_FOREGROUND;
- }
- screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, 175), color, "Backward");
-
- if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) {
- found = BTN_FORWARD;
- color = COMMAND_HIGHLIGHTED;
- } else {
- color = COMMAND_FOREGROUND;
- }
- screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, 175), color, "Forward");
- }
-
- events.wait(2);
- }
+ // Record the entry into the list
+ _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly));
+ _index = _journal.size() - 1;
- if (events.kbHit()) {
- Common::KeyState keyState = events.getKey();
-
- if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) {
- screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE);
- xp -= screen.charWidth(name.lastChar());
- screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND);
- name.deleteLastChar();
-
- } else if (keyState.keycode == Common::KEYCODE_RETURN) {
- done = 1;
-
- } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
- screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
- done = -1;
-
- } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT &&
- name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) {
- char ch = toupper(keyState.ascii);
- screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
- screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch);
- xp += screen.charWidth(ch);
- name += ch;
- }
- }
+ // Load the text for the new entry to get the number of lines it will have
+ loadJournalFile(true);
- if (events._released) {
- switch (found) {
- case BTN_EXIT:
- done = -1; break;
- case BTN_BACKWARD:
- done = 2; break;
- case BTN_FORWARD:
- done = 1; break;
- default:
- break;
- }
- }
- } while (!done && !_vm->shouldQuit());
+ // Restore old state
+ _index = saveIndex;
+ _sub = saveSub;
- if (done != -1) {
- _find = name;
+ // If new lines were added to the ournal, update the total number of lines
+ // the journal continues
+ if (!_lines.empty()) {
+ _maxPage += _lines.size();
} else {
- done = 0;
+ // No lines in entry, so remove the new entry from the journal
+ _journal.remove_at(_journal.size() - 1);
}
-
- // Redisplay the journal screen
- drawJournalFrame();
- drawJournal(0, 0);
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
-
- return done;
}
-void Journal::resetPosition() {
- _index = _sub = _up = _down = 0;
- _page = 1;
-}
-void Journal::synchronize(Common::Serializer &s) {
+void Journal::synchronize(Serializer &s) {
s.syncAsSint16LE(_index);
s.syncAsSint16LE(_sub);
s.syncAsSint16LE(_page);
diff --git a/engines/sherlock/journal.h b/engines/sherlock/journal.h
index d62b8338c0..a8fec104f4 100644
--- a/engines/sherlock/journal.h
+++ b/engines/sherlock/journal.h
@@ -26,20 +26,18 @@
#include "common/scummsys.h"
#include "common/array.h"
#include "common/rect.h"
-#include "common/serializer.h"
#include "common/str-array.h"
#include "common/stream.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
-#define JOURNAL_MAX_WIDTH 230
+#define LINES_PER_PAGE (IS_SERRATED_SCALPEL ? 11 : 17)
+#define JOURNAL_MAX_WIDTH (IS_SERRATED_SCALPEL ? 230 : 422)
#define JOURNAL_MAX_CHARS 80
+#define JOURNAL_LEFT_X (IS_SERRATED_SCALPEL ? 53 : 156)
-enum JournalButton {
- BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH,
- BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT
-};
-
+class SherlockEngine;
struct JournalEntry {
int _converseNum;
@@ -51,26 +49,21 @@ struct JournalEntry {
_converseNum(converseNum), _statementNum(statementNum), _replyOnly(replyOnly) {}
};
-class SherlockEngine;
-
class Journal {
-private:
+protected:
SherlockEngine *_vm;
- Common::Array<JournalEntry> _journal;
Common::StringArray _directory;
Common::StringArray _locations;
+ Common::Array<JournalEntry> _journal;
Common::StringArray _lines;
- int _maxPage;
- int _index;
- int _sub;
bool _up, _down;
+ int _index;
int _page;
+ int _maxPage;
+ int _sub;
Common::String _find;
- /**
- * Load the list of location names that the journal will make reference to
- */
- void loadJournalLocations();
+ Journal(SherlockEngine *vm);
/**
* Loads the description for the current display index in the journal, and then
@@ -79,59 +72,35 @@ private:
* first time, or being reloaded
*/
void loadJournalFile(bool alreadyLoaded);
+public:
+ static Journal *init(SherlockEngine *vm);
+ virtual ~Journal() {}
/**
- * Display the arrows that can be used to scroll up and down pages
- */
- void doArrows();
-
- /**
- * Displays a page of the journal at the current index
- */
+ * Displays a page of the journal at the current index
+ */
bool drawJournal(int direction, int howFar);
/**
- * Show the search submenu and allow the player to enter a search string
+ * Synchronize the data for a savegame
*/
- int getSearchString(bool printError);
-
+ void synchronize(Serializer &s);
+public:
/**
* Draw the journal background, frame, and interface buttons
*/
- void drawJournalFrame();
+ virtual void drawFrame() = 0;
/**
- * Returns the button, if any, that is under the specified position
+ * Reset viewing position to the start of the journal
*/
- JournalButton getHighlightedButton(const Common::Point &pt);
-public:
- Journal(SherlockEngine *vm);
+ virtual void resetPosition() {}
/**
* 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);
-
- /**
- * Display the journal
- */
- void drawInterface();
-
- /**
- * Handle events whilst the journal is being displayed
- */
- bool handleEvents(int key);
-
- /**
- * Reset viewing position to the start of the journal
- */
- void resetPosition();
-
- /**
- * Synchronize the data for a savegame
- */
- void synchronize(Common::Serializer &s);
+ virtual void record(int converseNum, int statementNum, bool replyOnly = false);
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/map.cpp b/engines/sherlock/map.cpp
index ffbca3fb42..fbccaf244f 100644
--- a/engines/sherlock/map.cpp
+++ b/engines/sherlock/map.cpp
@@ -20,546 +20,33 @@
*
*/
+#include "common/system.h"
#include "sherlock/map.h"
#include "sherlock/sherlock.h"
-#include "common/system.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/tattoo/tattoo_map.h"
namespace Sherlock {
-MapPaths::MapPaths() {
- _numLocations = 0;
-}
-
-void MapPaths::load(int numLocations, Common::SeekableReadStream &s) {
- _numLocations = numLocations;
- _paths.resize(_numLocations * _numLocations);
-
- for (int idx = 0; idx < (numLocations * numLocations); ++idx) {
- Common::Array<byte> &path = _paths[idx];
- int v;
-
- do {
- v = s.readByte();
- path.push_back(v);
- } while (v && v < 254);
- }
-}
-
-const byte *MapPaths::getPath(int srcLocation, int destLocation) {
- return &_paths[srcLocation * _numLocations + destLocation][0];
-}
-
-/*----------------------------------------------------------------*/
-
-Map::Map(SherlockEngine *vm): _vm(vm), _topLine(g_system->getWidth(), 12) {
- _active = false;
- _mapCursors = nullptr;
- _shapes = nullptr;
- _iconShapes = nullptr;
- _point = 0;
- _placesShown = false;
- _cursorIndex = -1;
- _drawMap = false;
- _overPos = Common::Point(13000, 12600);
- _charPoint = 0;
- _oldCharPoint = 0;
- _frameChangeFlag = false;
-
- // Initialise the initial walk sequence set
- _walkSequences.resize(MAX_HOLMES_SEQUENCE);
- for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) {
- _walkSequences[idx]._sequences.resize(MAX_FRAME);
- Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0);
- }
-
- if (!_vm->isDemo())
- loadData();
-}
-
-void Map::loadPoints(int count, const int *xList, const int *yList, const int *transList) {
- for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) {
- _points.push_back(MapEntry(*xList, *yList, *transList));
- }
-}
-
-void Map::loadSequences(int count, const byte *seq) {
- for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME)
- Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]);
-}
-
-void Map::loadData() {
- // TODO: Remove this
- if (_vm->getGameID() == GType_RoseTattoo)
- return;
-
- // Load the list of location names
- Common::SeekableReadStream *txtStream = _vm->_res->load(
- _vm->getGameID() == GType_SerratedScalpel ? "chess.txt" : "map.txt");
-
- int streamSize = txtStream->size();
- while (txtStream->pos() < streamSize) {
- Common::String line;
- char c;
- while ((c = txtStream->readByte()) != '\0')
- line += c;
-
- _locationNames.push_back(line);
- }
-
- delete txtStream;
-
- // Load the path data
- Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth");
-
- // Get routes between different locations on the map
- _paths.load(31, *pathStream);
-
- // Load in the co-ordinates that the paths refer to
- _pathPoints.resize(208);
- for (uint idx = 0; idx < _pathPoints.size(); ++idx) {
- _pathPoints[idx].x = pathStream->readSint16LE();
- _pathPoints[idx].y = pathStream->readSint16LE();
- }
-
- delete pathStream;
-}
-
-int Map::show() {
- Events &events = *_vm->_events;
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
- Common::Point lDrawn(-1, -1);
- bool changed = false, exitFlag = false;
- _active = true;
-
- // Set font and custom cursor for the map
- int oldFont = screen.fontNumber();
- screen.setFont(0);
-
- // Initial screen clear
- screen._backBuffer1.clear();
- screen.clear();
-
- // Load the entire map
- ImageFile bigMap("bigmap.vgs");
- screen.setPalette(bigMap._palette);
-
- // Load need sprites
- setupSprites();
-
- screen._backBuffer1.blitFrom(bigMap[0], Common::Point(-_bigPos.x, -_bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
-
- _drawMap = true;
- _charPoint = -1;
- _point = -1;
- people[AL]._position = _lDrawnPos = _overPos;
-
- // Show place icons
- showPlaces();
- saveTopLine();
- _placesShown = true;
-
- // Keep looping until either a location is picked, or the game is ended
- while (!_vm->shouldQuit() && !exitFlag) {
- events.pollEventsAndWait();
- events.setButtonState();
-
- // Keyboard handling
- if (events.kbHit()) {
- Common::KeyState keyState = events.getKey();
-
- if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) {
- // Both space and enter simulate a mouse release
- events._pressed = false;
- events._released = true;
- events._oldButtons = 0;
- }
- }
-
- // Ignore scrolling attempts until the screen is drawn
- if (!_drawMap) {
- Common::Point pt = events.mousePos();
-
- // Check for vertical map scrolling
- if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) {
- if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10))
- _bigPos.y += 10;
- else
- _bigPos.y -= 10;
-
- changed = true;
- }
-
- // Check for horizontal map scrolling
- if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) {
- if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10))
- _bigPos.x += 15;
- else
- _bigPos.x -= 15;
-
- changed = true;
- }
- }
-
- if (changed) {
- // Map has scrolled, so redraw new map view
- changed = false;
-
- screen._backBuffer1.blitFrom(bigMap[0], Common::Point(-_bigPos.x, -_bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
- screen._backBuffer1.blitFrom(bigMap[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
-
- showPlaces();
- _placesShown = false;
-
- saveTopLine();
- _savedPos.x = -1;
- updateMap(true);
- } else if (!_drawMap) {
- if (!_placesShown) {
- showPlaces();
- _placesShown = true;
- }
-
- if (_cursorIndex == 0) {
- Common::Point pt = events.mousePos();
- highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y));
- }
- updateMap(false);
- }
-
- if ((events._released || events._rightReleased) && _point != -1) {
- if (people[AL]._walkCount == 0) {
- people._walkDest = _points[_point] + Common::Point(4, 9);
- _charPoint = _point;
-
- // Start walking to selected location
- walkTheStreets();
-
- // Show wait cursor
- _cursorIndex = 1;
- events.setCursor((*_mapCursors)[_cursorIndex]._frame);
- }
- }
-
- // Check if a scene has beeen selected and we've finished "moving" to it
- if (people[AL]._walkCount == 0) {
- if (_charPoint >= 1 && _charPoint < (int)_points.size())
- exitFlag = true;
- }
-
- if (_drawMap) {
- _drawMap = false;
-
- if (screen._fadeStyle)
- screen.randomTransition();
- else
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
- }
-
- // Wait for a frame
- events.wait(1);
- }
-
- freeSprites();
- _overPos = people[AL]._position;
-
- // Reset font
- screen.setFont(oldFont);
-
- _active = false;
- return _charPoint;
-}
-
-void Map::setupSprites() {
- Events &events = *_vm->_events;
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
- _savedPos.x = -1;
-
- _mapCursors = new ImageFile("omouse.vgs");
- _cursorIndex = 0;
- events.setCursor((*_mapCursors)[_cursorIndex]._frame);
-
- _shapes = new ImageFile("mapicon.vgs");
- _iconShapes = new ImageFile("overicon.vgs");
- _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height);
- Person &p = people[AL];
- p._description = " ";
- p._type = CHARACTER;
- p._position = Common::Point(12400, 5000);
- p._sequenceNumber = 0;
- p._images = _shapes;
- p._imageFrame = nullptr;
- p._frameNumber = 0;
- p._delta = Common::Point(0, 0);
- p._oldSize = Common::Point(0, 0);
- p._oldSize = Common::Point(0, 0);
- p._misc = 0;
- p._walkCount = 0;
- p._allow = 0;
- p._noShapeSize = Common::Point(0, 0);
- p._goto = Common::Point(28000, 15000);
- p._status = 0;
- p._walkSequences = _walkSequences;
- p.setImageFrame();
- scene._bgShapes.clear();
-}
-
-void Map::freeSprites() {
- delete _mapCursors;
- delete _shapes;
- delete _iconShapes;
- _iconSave.free();
-}
-
-void Map::showPlaces() {
- Screen &screen = *_vm->_screen;
-
- for (uint idx = 0; idx < _points.size(); ++idx) {
- const MapEntry &pt = _points[idx];
-
- if (pt.x != 0 && pt.y != 0) {
- if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH
- && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) {
- if (_vm->readFlags(idx)) {
- screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate],
- Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12));
- }
- }
- }
- }
-}
-
-void Map::saveTopLine() {
- _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12));
-}
-
-void Map::eraseTopLine() {
- Screen &screen = *_vm->_screen;
- screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0));
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h());
-}
-
-void Map::showPlaceName(int idx, bool highlighted) {
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
-
- Common::String name = _locationNames[idx];
- int width = screen.stringWidth(name);
-
- if (!_cursorIndex) {
- saveIcon(people[AL]._imageFrame, _lDrawnPos);
-
- bool flipped = people[AL]._sequenceNumber == MAP_DOWNLEFT || people[AL]._sequenceNumber == MAP_LEFT
- || people[AL]._sequenceNumber == MAP_UPLEFT;
- screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, _lDrawnPos, flipped);
- }
-
- if (highlighted) {
- int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2;
- screen.gPrint(Common::Point(xp + 2, 2), 0, "%s", name.c_str());
- screen.gPrint(Common::Point(xp + 1, 1), 0, "%s", name.c_str());
- screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str());
-
- screen.slamArea(xp, 0, width + 2, 15);
- }
-}
-
-void Map::updateMap(bool flushScreen) {
- Events &events = *_vm->_events;
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
- Common::Point osPos = _savedPos;
- Common::Point osSize = _savedSize;
- Common::Point hPos;
-
- if (_cursorIndex >= 1) {
- if (++_cursorIndex > (1 + 8))
- _cursorIndex = 1;
-
- events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame);
- }
-
- if (!_drawMap && !flushScreen)
- restoreIcon();
+Map *Map::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelMap(vm);
else
- _savedPos.x = -1;
-
- people[AL].adjustSprite();
-
- _lDrawnPos.x = hPos.x = people[AL]._position.x / 100 - _bigPos.x;
- _lDrawnPos.y = hPos.y = people[AL]._position.y / 100 - people[AL].frameHeight() - _bigPos.y;
-
- // Draw the person icon
- saveIcon(people[AL]._imageFrame, hPos);
- if (people[AL]._sequenceNumber == MAP_DOWNLEFT || people[AL]._sequenceNumber == MAP_LEFT
- || people[AL]._sequenceNumber == MAP_UPLEFT)
- screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, hPos, true);
- else
- screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, hPos, false);
-
- if (flushScreen) {
- screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
- } else if (!_drawMap) {
- if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT)
- screen.flushImage(people[AL]._imageFrame, Common::Point(people[AL]._position.x / 100 - _bigPos.x,
- people[AL]._position.y / 100 - people[AL].frameHeight() - _bigPos.y),
- &people[AL]._oldPosition.x, &people[AL]._oldPosition.y, &people[AL]._oldSize.x, &people[AL]._oldSize.y);
-
- if (osPos.x != -1)
- screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y);
- }
+ return new Tattoo::TattooMap(vm);
}
-void Map::walkTheStreets() {
- People &people = *_vm->_people;
- Common::Array<Common::Point> tempPath;
-
- // Get indexes into the path lists for the start and destination scenes
- int start = _points[_oldCharPoint]._translate;
- int dest = _points[_charPoint]._translate;
-
- // Get pointer to start of path
- const byte *path = _paths.getPath(start, dest);
-
- // Add in destination position
- people._walkTo.clear();
- Common::Point destPos = people._walkDest;
-
- // Check for any intermediate points between the two locations
- if (path[0] || _charPoint > 50 || _oldCharPoint > 50) {
- people[AL]._sequenceNumber = -1;
-
- if (_charPoint == 51 || _oldCharPoint == 51) {
- people.setWalking();
- } else {
- bool reversePath = false;
-
- // Check for moving the path backwards or forwards
- if (path[0] == 255) {
- reversePath = true;
- SWAP(start, dest);
- path = _paths.getPath(start, dest);
- }
-
- do {
- int idx = *path++;
- tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4));
- } while (*path != 254);
-
- // Load up the path to use
- people._walkTo.clear();
-
- if (reversePath) {
- for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx)
- people._walkTo.push(tempPath[idx]);
- } else {
- for (int idx = 0; idx < (int)tempPath.size(); ++idx)
- people._walkTo.push(tempPath[idx]);
- }
-
- people._walkDest = people._walkTo.pop() + Common::Point(12, 6);
- people.setWalking();
- }
- } else {
- people[AL]._walkCount = 0;
- }
-
- // Store the final destination icon position
- people._walkTo.push(destPos);
-}
-
-void Map::saveIcon(ImageFrame *src, const Common::Point &pt) {
- Screen &screen = *_vm->_screen;
- Common::Point size(src->_width, src->_height);
- Common::Point pos = pt;
-
- if (pos.x < 0) {
- size.x += pos.x;
- pos.x = 0;
- }
-
- if (pos.y < 0) {
- size.y += pos.y;
- pos.y = 0;
- }
-
- if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH)
- size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH;
-
- if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT)
- size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT;
-
- if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) {
- // Flag as the area not needing to be saved
- _savedPos.x = -1;
- return;
- }
-
- assert(size.x <= _iconSave.w() && size.y <= _iconSave.h());
- _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0),
- Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y));
- _savedPos = pos;
- _savedSize = size;
+Map::Map(SherlockEngine *vm) : _vm(vm) {
+ _charPoint = _oldCharPoint = 0;
+ _active = _frameChangeFlag = false;
}
-void Map::restoreIcon() {
- Screen &screen = *_vm->_screen;
-
- if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH
- && _savedPos.y < SHERLOCK_SCREEN_HEIGHT)
- screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y));
-}
-
-void Map::highlightIcon(const Common::Point &pt) {
- int oldPoint = _point;
-
- // Iterate through the icon list
- bool done = false;
- for (int idx = 0; idx < (int)_points.size(); ++idx) {
- const MapEntry &entry = _points[idx];
-
- // Check whether the mouse is over a given icon
- if (entry.x != 0 && entry.y != 0) {
- if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) {
- done = true;
-
- if (_point != idx && _vm->readFlags(idx)) {
- // Changed to a new valid (visible) location
- eraseTopLine();
- showPlaceName(idx, true);
- _point = idx;
- }
- }
- }
- }
-
- if (!done) {
- // No icon was highlighted
- if (_point != -1) {
- // No longer highlighting previously highlighted icon, so erase it
- showPlaceName(_point, false);
- eraseTopLine();
- }
-
- _point = -1;
- } else if (oldPoint != -1 && oldPoint != _point) {
- showPlaceName(oldPoint, false);
- eraseTopLine();
- }
-}
-
-void Map::synchronize(Common::Serializer &s) {
- s.syncAsSint16LE(_bigPos.x);
- s.syncAsSint16LE(_bigPos.y);
- s.syncAsSint16LE(_overPos.x);
+void Map::synchronize(Serializer &s) {
+ s.syncAsSint32LE(_bigPos.x);
+ s.syncAsSint32LE(_bigPos.y);
+ s.syncAsSint32LE(_overPos.x);
s.syncAsSint16LE(_overPos.y);
s.syncAsSint16LE(_oldCharPoint);
}
+
} // End of namespace Sherlock
diff --git a/engines/sherlock/map.h b/engines/sherlock/map.h
index e0c7d038c4..104f5e9c8a 100644
--- a/engines/sherlock/map.h
+++ b/engines/sherlock/map.h
@@ -23,156 +23,37 @@
#ifndef SHERLOCK_MAP_H
#define SHERLOCK_MAP_H
-#include "common/scummsys.h"
-#include "common/array.h"
-#include "common/rect.h"
-#include "common/serializer.h"
-#include "common/str.h"
-#include "common/str-array.h"
-#include "sherlock/surface.h"
#include "sherlock/objects.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
class SherlockEngine;
-struct MapEntry : Common::Point {
- int _translate;
-
- MapEntry() : Common::Point(), _translate(-1) {}
-
- MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {}
-};
-
-class MapPaths {
-private:
- int _numLocations;
- Common::Array< Common::Array<byte> > _paths;
-
-public:
- MapPaths();
-
- /**
- * Load the data for the paths between locations on the map
- */
- void load(int numLocations, Common::SeekableReadStream &s);
-
- /**
- * Get the path between two locations on the map
- */
- const byte *getPath(int srcLocation, int destLocation);
-};
-
class Map {
-private:
+protected:
SherlockEngine *_vm;
- Common::Array<MapEntry> _points; // Map locations for each scene
- Common::StringArray _locationNames;
- MapPaths _paths;
- Common::Array<Common::Point> _pathPoints;
- Common::Point _savedPos;
- Common::Point _savedSize;
- Surface _topLine;
- ImageFile *_mapCursors;
- ImageFile *_shapes;
- ImageFile *_iconShapes;
- WalkSequences _walkSequences;
- Point32 _lDrawnPos;
- int _point;
- bool _placesShown;
- int _cursorIndex;
- bool _drawMap;
- Surface _iconSave;
-private:
- /**
- * Load data needed for the map
- */
- void loadData();
-
- /**
- * Load and initialize all the sprites that are needed for the map display
- */
- void setupSprites();
- /**
- * Free the sprites and data used by the map
- */
- void freeSprites();
-
- /**
- * Draws an icon for every place that's currently known
- */
- void showPlaces();
-
- /**
- * Makes a copy of the top rows of the screen that are used to display location names
- */
- void saveTopLine();
-
- /**
- * Erases anything shown in the top line by restoring the previously saved original map background
- */
- void eraseTopLine();
-
- /**
- * Prints the name of the specified icon
- */
- void showPlaceName(int idx, bool highlighted);
-
- /**
- * Update all on-screen sprites to account for any scrolling of the map
- */
- void updateMap(bool flushScreen);
-
- /**
- * Handle moving icon for player from their previous location on the map to a destination location
- */
- void walkTheStreets();
-
- /**
- * Save the area under the player's icon
- */
- void saveIcon(ImageFrame *src, const Common::Point &pt);
-
- /**
- * Restore the area under the player's icon
- */
- void restoreIcon();
-
- /**
- * Handles highlighting map icons, showing their names
- */
- void highlightIcon(const Common::Point &pt);
+ Map(SherlockEngine *vm);
public:
- bool _active;
Point32 _overPos;
Point32 _bigPos;
int _charPoint, _oldCharPoint;
+ bool _active;
bool _frameChangeFlag;
public:
- Map(SherlockEngine *vm);
-
- const MapEntry &operator[](int idx) { return _points[idx]; }
-
- /**
- * Loads the list of points for locations on the map for each scene
- */
- void loadPoints(int count, const int *xList, const int *yList, const int *transList);
-
- /**
- * Load the sequence data for player icon animations
- */
- void loadSequences(int count, const byte *seq);
+ static Map *init(SherlockEngine *vm);
+ virtual ~Map() {}
/**
* Show the map
*/
- int show();
+ virtual int show() = 0;
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk
index 1a38d56511..7fa7896691 100644
--- a/engines/sherlock/module.mk
+++ b/engines/sherlock/module.mk
@@ -1,21 +1,57 @@
MODULE := engines/sherlock
MODULE_OBJS = \
- scalpel/darts.o \
scalpel/scalpel.o \
+ scalpel/3do/movie_decoder.o \
scalpel/drivers/adlib.o \
+ scalpel/drivers/mt32.o \
scalpel/tsage/logo.o \
scalpel/tsage/resources.o \
+ scalpel/scalpel_darts.o \
+ scalpel/scalpel_debugger.o \
+ scalpel/scalpel_fixed_text.o \
+ scalpel/scalpel_inventory.o \
+ scalpel/scalpel_journal.o \
+ scalpel/scalpel_map.o \
+ scalpel/scalpel_people.o \
+ scalpel/scalpel_saveload.o \
scalpel/scalpel_scene.o \
+ scalpel/scalpel_screen.o \
+ scalpel/scalpel_talk.o \
scalpel/scalpel_user_interface.o \
scalpel/settings.o \
tattoo/tattoo.o \
+ tattoo/tattoo_darts.o \
+ tattoo/tattoo_debugger.o \
+ tattoo/tattoo_fixed_text.o \
+ tattoo/tattoo_inventory.o \
+ tattoo/tattoo_journal.o \
+ tattoo/tattoo_map.o \
+ tattoo/tattoo_people.o \
+ tattoo/tattoo_resources.o \
tattoo/tattoo_scene.o \
+ tattoo/tattoo_talk.o \
tattoo/tattoo_user_interface.o \
+ tattoo/widget_base.o \
+ tattoo/widget_credits.o \
+ tattoo/widget_files.o \
+ tattoo/widget_foolscap.o \
+ tattoo/widget_inventory.o \
+ tattoo/widget_lab.o \
+ tattoo/widget_options.o \
+ tattoo/widget_password.o \
+ tattoo/widget_quit.o \
+ tattoo/widget_talk.o \
+ tattoo/widget_text.o \
+ tattoo/widget_tooltip.o \
+ tattoo/widget_verbs.o \
animation.o \
debugger.o \
detection.o \
events.o \
+ fixed_text.o \
+ fonts.o \
+ image_file.o \
inventory.o \
journal.o \
map.o \
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp
index 0735f41d9a..7802bf5eeb 100644
--- a/engines/sherlock/music.cpp
+++ b/engines/sherlock/music.cpp
@@ -20,10 +20,16 @@
*
*/
+#include "common/algorithm.h"
#include "common/config-manager.h"
+#include "common/mutex.h"
#include "sherlock/sherlock.h"
#include "sherlock/music.h"
#include "sherlock/scalpel/drivers/mididriver.h"
+// for Miles Audio (Sherlock Holmes 2)
+#include "audio/miles.h"
+// for 3DO digital music
+#include "audio/decoders/aiff.h"
namespace Sherlock {
@@ -57,32 +63,21 @@ MidiParser_SH::MidiParser_SH() {
_beats = 0;
_lastEvent = 0;
_trackEnd = nullptr;
+
+ _musData = nullptr;
+ _musDataSize = 0;
+}
+
+MidiParser_SH::~MidiParser_SH() {
+ Common::StackLock lock(_mutex);
+ unloadMusic();
+ _driver = NULL;
}
void MidiParser_SH::parseNextEvent(EventInfo &info) {
-// warning("parseNextEvent");
+ Common::StackLock lock(_mutex);
- // An attempt to remap MT32 instruments to GMIDI. Only partially successful, it still
- // does not sound even close to the real MT32. Oddly enough, on the actual hardware MT32
- // and SB sound very differently.
- static const byte mt32Map[128] = {
- 0, 1, 0, 2, 4, 4, 5, 3, /* 0-7 */
- 16, 17, 18, 16, 16, 19, 20, 21, /* 8-15 */
- 6, 6, 6, 7, 7, 7, 8, 112, /* 16-23 */
- 62, 62, 63, 63 , 38, 38, 39, 39, /* 24-31 */
- 88, 95, 52, 98, 97, 99, 14, 54, /* 32-39 */
- 102, 96, 53, 102, 81, 100, 14, 80, /* 40-47 */
- 48, 48, 49, 45, 41, 40, 42, 42, /* 48-55 */
- 43, 46, 45, 24, 25, 28, 27, 104, /* 56-63 */
- 32, 32, 34, 33, 36, 37, 35, 35, /* 64-71 */
- 79, 73, 72, 72, 74, 75, 64, 65, /* 72-79 */
- 66, 67, 71, 71, 68, 69, 70, 22, /* 80-87 */
- 56, 59, 57, 57, 60, 60, 58, 61, /* 88-95 */
- 61, 11, 11, 98, 14, 9, 14, 13, /* 96-103 */
- 12, 107, 107, 77, 78, 78, 76, 76, /* 104-111 */
- 47, 117, 127, 118, 118, 116, 115, 119, /* 112-119 */
- 115, 112, 55, 124, 123, 0, 14, 117 /* 120-127 */
- };
+// warning("parseNextEvent");
// there is no delta right at the start of the music data
// this order is essential, otherwise notes will get delayed or even go missing
@@ -102,8 +97,6 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
case 0xC: { // program change
int idx = *_position._playPos++;
info.basic.param1 = idx & 0x7f;
- // don't do this here, it breaks adlib
- //info.basic.param1 = mt32Map[idx & 0x7f]; // remap MT32 to GM
info.basic.param2 = 0;
}
break;
@@ -151,16 +144,16 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
}
} else if (info.event == 0xFC) {
// Official End-Of-Track signal
- warning("System META event 0xFC");
+ debugC(kDebugLevelMusic, "Music: System META event 0xFC");
byte type = *(_position._playPos++);
switch (type) {
case 0x80: // end of track, triggers looping
- warning("META event triggered looping");
+ debugC(kDebugLevelMusic, "Music: META event triggered looping");
jumpToTick(0, true, true, false);
break;
case 0x81: // end of track, stop playing
- warning("META event triggered music stop");
+ debugC(kDebugLevelMusic, "Music: META event triggered music stop");
stopPlaying();
unloadMusic();
break;
@@ -179,20 +172,26 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
}// switch (info.command())
}
-bool MidiParser_SH::loadMusic(byte *data, uint32 size) {
- warning("loadMusic");
+bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) {
+ Common::StackLock lock(_mutex);
+
+ debugC(kDebugLevelMusic, "Music: loadMusic()");
unloadMusic();
- byte *headerPtr = data;
- byte *pos = data;
+ _musData = musData;
+ _musDataSize = musDataSize;
+
+ byte *headerPtr = _musData + 12; // skip over the already checked SPACE header
+ byte *pos = headerPtr;
+
uint16 headerSize = READ_LE_UINT16(headerPtr);
- assert(headerSize == 0x7F);
+ assert(headerSize == 0x7F); // Security check
// Skip over header
pos += headerSize;
_lastEvent = 0;
- _trackEnd = data + size;
+ _trackEnd = _musData + _musDataSize;
_numTracks = 1;
_tracks[0] = pos;
@@ -204,45 +203,152 @@ bool MidiParser_SH::loadMusic(byte *data, uint32 size) {
return true;
}
+void MidiParser_SH::unloadMusic() {
+ Common::StackLock lock(_mutex);
+
+ if (_musData) {
+ delete[] _musData;
+ _musData = NULL;
+ _musDataSize = 0;
+ }
+
+ MidiParser::unloadMusic();
+}
+
/*----------------------------------------------------------------*/
Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
+ _midiDriver = NULL;
+ _midiParser = NULL;
+ _musicType = MT_NULL;
+ _musicPlaying = false;
+ _musicOn = false;
+ _midiOption = false;
+ _musicVolume = 0;
+
+ if (IS_3DO) {
+ // 3DO - uses digital samples for music
+ _musicOn = true;
+ return;
+ }
+
if (_vm->_interactiveFl)
_vm->_res->addToCache("MUSIC.LIB");
- MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ MidiDriver::DeviceHandle dev;
- _musicType = MidiDriver::getMusicType(dev);
+ if (IS_SERRATED_SCALPEL) {
+ // Serrated Scalpel: used an internal Electronic Arts .MUS music engine
+ _midiParser = new MidiParser_SH();
+ dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+ _musicType = MidiDriver::getMusicType(dev);
- _driver = NULL;
-
- switch (_musicType) {
- case MT_ADLIB:
- _driver = MidiDriver_AdLib_create();
- break;
- default:
- _driver = MidiDriver::createMidi(dev);
- break;
+ switch (_musicType) {
+ case MT_ADLIB:
+ _midiDriver = MidiDriver_SH_AdLib_create();
+ break;
+ case MT_MT32:
+ _midiDriver = MidiDriver_MT32_create();
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _midiDriver = MidiDriver_MT32_create();
+ _musicType = MT_MT32;
+ }
+ break;
+ default:
+ // Create default one
+ // I guess we shouldn't do this anymore
+ //_midiDriver = MidiDriver::createMidi(dev);
+ break;
+ }
+ } else {
+ // Rose Tattooo: seems to use Miles Audio 3
+ _midiParser = MidiParser::createParser_XMIDI();
+ dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ _musicType = MidiDriver::getMusicType(dev);
+
+ switch (_musicType) {
+ case MT_ADLIB:
+ // SAMPLE.AD -> regular AdLib instrument data
+ // SAMPLE.OPL -> OPL-3 instrument data
+ // although in case of Rose Tattoo both files are exactly the same
+ _midiDriver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+ break;
+ case MT_MT32:
+ // Sherlock Holmes 2 does not have a MT32 timbre file
+ _midiDriver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _midiDriver = Audio::MidiDriver_Miles_MT32_create("");
+ _musicType = MT_MT32;
+ } else {
+ _midiDriver = MidiDriver::createMidi(dev);
+ _musicType = MT_GM;
+ }
+ break;
+ default:
+ // Do not create anything
+ break;
+ }
}
- if (_driver) {
- assert(_driver);
-
- int ret = _driver->open();
+ if (_midiDriver) {
+ int ret = _midiDriver->open();
if (ret == 0) {
- _driver->sendGMReset();
- _driver->setTimerCallback(&_midiParser, &_midiParser.timerCallback);
+ // Reset is done inside our MIDI driver
+ _midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback);
+ }
+ _midiParser->setMidiDriver(_midiDriver);
+ _midiParser->setTimerRate(_midiDriver->getBaseTempo());
+
+ if (IS_SERRATED_SCALPEL) {
+ if (_musicType == MT_MT32) {
+ // Upload patches
+ Common::SeekableReadStream *MT32driverStream = _vm->_res->load("MTHOM.DRV", "MUSIC.LIB");
+
+ if (!MT32driverStream)
+ error("Music: could not load MTHOM.DRV, critical");
+
+ byte *MT32driverData = new byte[MT32driverStream->size()];
+ int32 MT32driverDataSize = MT32driverStream->size();
+ assert(MT32driverData);
+
+ MT32driverStream->read(MT32driverData, MT32driverDataSize);
+ delete MT32driverStream;
+
+ assert(MT32driverDataSize > 12);
+ byte *MT32driverDataPtr = MT32driverData + 12;
+ MT32driverDataSize -= 12;
+
+ MidiDriver_MT32_uploadPatches(_midiDriver, MT32driverDataPtr, MT32driverDataSize);
+ delete[] MT32driverData;
+ }
}
- _midiParser.setMidiDriver(_driver);
- _midiParser.setTimerRate(_driver->getBaseTempo());
+
+ _musicOn = true;
}
+}
- _musicPlaying = false;
- _musicOn = true;
+Music::~Music() {
+ stopMusic();
+ if (_midiDriver) {
+ _midiDriver->setTimerCallback(this, NULL);
+ }
+ if (_midiParser) {
+ _midiParser->stopPlaying();
+ delete _midiParser;
+ _midiParser = nullptr;
+ }
+ if (_midiDriver) {
+ _midiDriver->close();
+ delete _midiDriver;
+ }
}
bool Music::loadSong(int songNumber) {
- warning("loadSong");
+ debugC(kDebugLevelMusic, "Music: loadSong()");
if(songNumber == 100)
songNumber = 55;
@@ -260,21 +366,27 @@ bool Music::loadSong(int songNumber) {
if((songNumber > NUM_SONGS) || (songNumber < 1))
return false;
- Common::String songName = Common::String(SONG_NAMES[songNumber - 1]) + ".MUS";
+ Common::String songName = Common::String(SONG_NAMES[songNumber - 1]);
freeSong(); // free any song that is currently loaded
-
+ stopMusic();
+
if (!playMusic(songName))
return false;
- stopMusic();
startSong();
return true;
}
bool Music::loadSong(const Common::String &songName) {
- warning("TODO: Music::loadSong");
- return false;
+ freeSong(); // free any song that is currently loaded
+ stopMusic();
+
+ if (!playMusic(songName))
+ return false;
+
+ startSong();
+ return true;
}
void Music::syncMusicSettings() {
@@ -285,76 +397,220 @@ bool Music::playMusic(const Common::String &name) {
if (!_musicOn)
return false;
- warning("Sound::playMusic %s", name.c_str());
- Common::SeekableReadStream *stream = _vm->_res->load(name, "MUSIC.LIB");
+ debugC(kDebugLevelMusic, "Music: playMusic('%s')", name.c_str());
- byte *data = new byte[stream->size()];
- int32 dataSize = stream->size();
- assert(data);
+ if (!IS_3DO) {
+ // MIDI based
+ if (!_midiDriver)
+ return false;
- stream->read(data, dataSize);
- delete stream;
+ Common::String midiMusicName = (IS_SERRATED_SCALPEL) ? name + ".MUS" : name + ".XMI";
+ Common::SeekableReadStream *stream = _vm->_res->load(midiMusicName, "MUSIC.LIB");
- // for dumping the music tracks
-#if 0
- Common::DumpFile outFile;
- outFile.open(name + ".RAW");
- outFile.write(data, stream->size());
- outFile.flush();
- outFile.close();
-#endif
+ byte *midiMusicData = new byte[stream->size()];
+ int32 midiMusicDataSize = stream->size();
- if (dataSize < 14) {
- warning("not enough data in music file");
- return false;
- }
+ stream->read(midiMusicData, midiMusicDataSize);
+ delete stream;
- byte *dataPos = data;
- if (memcmp(" ", dataPos, 12)) {
- warning("Expected header not found in music file");
- return false;
- }
- dataPos += 12;
- dataSize -= 12;
+ if (midiMusicDataSize < 14) {
+ warning("Music: not enough data in music file");
+ delete[] midiMusicData;
+ return false;
+ }
- uint16 headerSize = READ_LE_UINT16(dataPos);
- if (headerSize != 0x7F) {
- warning("music header is not as expected");
- return false;
- }
+ byte *dataPos = midiMusicData;
+ uint32 dataSize = midiMusicDataSize;
+
+ if (IS_SERRATED_SCALPEL) {
+ if (memcmp(" ", dataPos, 12)) {
+ warning("Music: expected header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+ dataPos += 12;
+ dataSize -= 12;
+
+ if (dataSize < 0x7F) {
+ warning("Music: expected music header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+
+ uint16 headerSize = READ_LE_UINT16(dataPos);
+ if (headerSize != 0x7F) {
+ warning("Music: header is not as expected");
+ delete[] midiMusicData;
+ return false;
+ }
+ } else {
+ if (memcmp("FORM", dataPos, 4)) {
+ warning("Music: expected header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+ }
+
+ if (IS_SERRATED_SCALPEL) {
+ // Pass the music data to the driver as well
+ // because channel mapping and a few other things inside the header
+ switch (_musicType) {
+ case MT_ADLIB:
+ MidiDriver_SH_AdLib_newMusicData(_midiDriver, dataPos, dataSize);
+ break;
- if (_musicType == MT_ADLIB) {
- if (_driver)
- MidiDriver_AdLib_newMusicData(_driver, dataPos, dataSize);
+ case MT_MT32:
+ MidiDriver_MT32_newMusicData(_midiDriver, dataPos, dataSize);
+ break;
+
+ default:
+ // should never happen
+ break;
+ }
+ }
+
+ _midiParser->loadMusic(midiMusicData, midiMusicDataSize);
+
+ } else {
+ // 3DO: sample based
+ Audio::AudioStream *musicStream;
+ Common::String digitalMusicName = "music/" + name + "_MW22.aifc";
+
+ if (isPlaying()) {
+ _mixer->stopHandle(_digitalMusicHandle);
+ }
+
+ Common::File *digitalMusicFile = new Common::File();
+ if (!digitalMusicFile->open(digitalMusicName)) {
+ warning("playMusic: can not open 3DO music '%s'", digitalMusicName.c_str());
+ return false;
+ }
+
+ // Try to load the given file as AIFF/AIFC
+ musicStream = Audio::makeAIFFStream(digitalMusicFile, DisposeAfterUse::YES);
+ if (!musicStream) {
+ warning("playMusic: can not load 3DO music '%s'", digitalMusicName.c_str());
+ return false;
+ }
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_digitalMusicHandle, musicStream);
}
- _midiParser.loadMusic(dataPos, dataSize);
+ _musicPlaying = true;
return true;
}
void Music::stopMusic() {
- // TODO
- warning("TODO: Sound::stopMusic");
+ freeSong();
+}
+
+void Music::startSong() {
+ // No implementation needed for ScummVM
+}
+
+void Music::freeSong() {
+ if (!IS_3DO) {
+ if (_midiParser->isPlaying())
+ _midiParser->stopPlaying();
+
+ // Free the MIDI MUS data buffer
+ _midiParser->unloadMusic();
+ }
_musicPlaying = false;
}
-void Music::startSong() {
- if (!_musicOn)
- return;
+bool Music::isPlaying() {
+ if (!IS_3DO) {
+ // MIDI based
+ return _midiParser->isPlaying();
+ } else {
+ // 3DO: sample based
+ return _mixer->isSoundHandleActive(_digitalMusicHandle);
+ }
+}
- // TODO
- warning("TODO: Sound::startSong");
- _musicPlaying = true;
+// Returns the current music position in milliseconds
+uint32 Music::getCurrentPosition() {
+ if (!IS_3DO) {
+ // MIDI based
+ return (_midiParser->getTick() * 1000) / 60; // translate tick to millisecond
+ } else {
+ // 3DO: sample based
+ return _mixer->getSoundElapsedTime(_digitalMusicHandle);
+ }
}
-void Music::freeSong() {
- // TODO
- warning("TODO: Sound::freeSong");
+// This is used to wait for the music in certain situations like especially the intro
+// Note: the original game didn't do this, instead it just waited for certain amounts of time
+// We do this, so that the intro graphics + music work together even on faster/slower hardware.
+bool Music::waitUntilMSec(uint32 msecTarget, uint32 msecMax, uint32 additionalDelay, uint32 noMusicDelay) {
+ uint32 msecCurrent = 0;
+
+ if (!isPlaying()) {
+ return _vm->_events->delay(noMusicDelay, true);
+ }
+ while (1) {
+ if (!isPlaying()) { // Music is not playing anymore -> we are done
+ if (additionalDelay > 0) {
+ if (!_vm->_events->delay(additionalDelay, true))
+ return false;
+ }
+ return true;
+ }
+
+ msecCurrent = getCurrentPosition();
+ //warning("waitUntilMSec: %lx", msecCurrent);
+
+ if ((!msecMax) || (msecCurrent <= msecMax)) {
+ if (msecCurrent >= msecTarget) {
+ if (additionalDelay > 0) {
+ if (!_vm->_events->delay(additionalDelay, true))
+ return false;
+ }
+ return true;
+ }
+ }
+ if (!_vm->_events->delay(10, true))
+ return false;
+ }
}
-void Music::waitTimerRoland(uint time) {
- // TODO
- warning("TODO: Sound::waitTimerRoland");
-}} // End of namespace Sherlock
+void Music::setMusicVolume(int volume) {
+ _musicVolume = volume;
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+}
+
+void Music::getSongNames(Common::StringArray &songs) {
+ songs.clear();
+ if (IS_SERRATED_SCALPEL) {
+ if (IS_3DO) {
+ Common::FSDirectory gameDirectory(ConfMan.get("path"));
+ Common::FSDirectory *musicDirectory = gameDirectory.getSubDirectory("music");
+ Common::ArchiveMemberList files;
+
+ musicDirectory->listMatchingMembers(files, "*_mw22.aifc");
+
+ for (Common::ArchiveMemberList::iterator i = files.begin(); i != files.end(); ++i) {
+ Common::String name = (*i)->getName();
+ name.erase(name.size() - 10);
+ songs.push_back(name);
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(SONG_NAMES); i++) {
+ songs.push_back(SONG_NAMES[i]);
+ }
+ }
+ } else {
+ Common::StringArray fileList;
+ _vm->_res->getResourceNames("music.lib", fileList);
+ for (Common::StringArray::iterator i = fileList.begin(); i != fileList.end(); ++i) {
+ if ((*i).matchString("*.XMI", true)) {
+ (*i).erase((*i).size() - 4);
+ songs.push_back(*i);
+ }
+ }
+ }
+ Common::sort(songs.begin(), songs.end());
+}
+} // End of namespace Sherlock
diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h
index 0ebc3e94e2..afd3a429be 100644
--- a/engines/sherlock/music.h
+++ b/engines/sherlock/music.h
@@ -27,40 +27,61 @@
#include "audio/midiparser.h"
//#include "audio/mididrv.h"
#include "sherlock/scalpel/drivers/mididriver.h"
+// for 3DO digital music
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/mutex.h"
+#include "common/str-array.h"
namespace Sherlock {
class SherlockEngine;
class MidiParser_SH : public MidiParser {
+public:
+ MidiParser_SH();
+ ~MidiParser_SH();
+
protected:
- virtual void parseNextEvent(EventInfo &info);
+ Common::Mutex _mutex;
+ void parseNextEvent(EventInfo &info);
uint8 _beats;
uint8 _lastEvent;
byte *_data;
byte *_trackEnd;
+
public:
- MidiParser_SH();
- virtual bool loadMusic(byte *data, uint32 size);
+ bool loadMusic(byte *musData, uint32 musSize);
+ virtual void unloadMusic();
+
+private:
+ byte *_musData;
+ uint32 _musDataSize;
};
class Music {
private:
SherlockEngine *_vm;
Audio::Mixer *_mixer;
- MidiParser_SH _midiParser;
- MidiDriver *_driver;
-
+ MidiParser *_midiParser;
+ MidiDriver *_midiDriver;
+ Audio::SoundHandle _digitalMusicHandle;
+ MusicType _musicType;
+
+ /**
+ * Play the specified music resource
+ */
+ bool playMusic(const Common::String &name);
public:
bool _musicPlaying;
bool _musicOn;
-
-private:
- MusicType _musicType;
-
+ int _musicVolume;
+ bool _midiOption;
+ Common::String _currentSongName, _nextSongName;
public:
Music(SherlockEngine *vm, Audio::Mixer *mixer);
+ ~Music();
/**
* Saves sound-related settings
@@ -86,21 +107,28 @@ public:
* Free any currently loaded song
*/
void freeSong();
-
- /**
- * Play the specified music resource
- */
- bool playMusic(const Common::String &name);
/**
* Stop playing the music
*/
void stopMusic();
- void waitTimerRoland(uint time);
+ bool isPlaying();
+ uint32 getCurrentPosition();
+
+ bool waitUntilMSec(uint32 msecTarget, uint32 maxMSec, uint32 additionalDelay, uint32 noMusicDelay);
+
+ /**
+ * Sets the volume of the MIDI music with a value ranging from 0 to 127
+ */
+ void setMusicVolume(int volume);
+
+ /**
+ * Gets the names of all the songs in the game. Used by the debugger.
+ */
+ void getSongNames(Common::StringArray &songs);
};
} // End of namespace Sherlock
#endif
-
diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp
index 8818f805a5..093f666a46 100644
--- a/engines/sherlock/objects.cpp
+++ b/engines/sherlock/objects.cpp
@@ -20,608 +20,135 @@
*
*/
+#include "common/util.h"
#include "sherlock/objects.h"
-#include "sherlock/sherlock.h"
#include "sherlock/people.h"
#include "sherlock/scene.h"
-#include "common/util.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
#define START_FRAME 0
-#define UPPER_LIMIT 0
-#define LOWER_LIMIT CONTROLS_Y
-#define LEFT_LIMIT 0
-#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH
+#define NUM_ADJUSTED_WALKS 21
// Distance to walk around WALK_AROUND boxes
#define CLEAR_DIST_X 5
#define CLEAR_DIST_Y 0
-SherlockEngine *Sprite::_vm;
-
-void Sprite::clear() {
- _name = "";
- _description = "";
- _examine.clear();
- _pickUp = "";
- _walkSequences.clear();
- _images = nullptr;
- _imageFrame = nullptr;
- _walkCount = 0;
- _allow = 0;
- _frameNumber = _sequenceNumber = 0;
- _position.x = _position.y = 0;
- _delta.x = _delta.y = 0;
- _oldPosition.x = _oldPosition.y = 0;
- _oldSize.x = _oldSize.y = 0;
- _goto.x = _goto.y = 0;
- _type = INVALID;
- _pickUp.clear();
- _noShapeSize.x = _noShapeSize.y = 0;
- _status = 0;
- _misc = 0;
- _numFrames = 0;
- _altImages = nullptr;
- _altSequences = false;
- Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr);
-}
-
-void Sprite::setImageFrame() {
- int frameNum = MAX(_frameNumber, 0);
- int imageNumber = _walkSequences[_sequenceNumber][frameNum];
-
- if (IS_SERRATED_SCALPEL)
- imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2;
- else if (imageNumber > _numFrames)
- imageNumber = 1;
-
- // Get the images to use
- ImageFile *images = _altSequences ? _altImages : _images;
- assert(images);
-
- // Set the frame pointer
- _imageFrame = &(*images)[imageNumber];
-}
-
-void Sprite::adjustSprite() {
- Map &map = *_vm->_map;
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
- Talk &talk = *_vm->_talk;
-
- if (_type == INVALID || (_type == CHARACTER && scene._animating))
- return;
-
- if (!talk._talkCounter && _type == CHARACTER && _walkCount) {
- // Handle active movement for the sprite
- _position += _delta;
- --_walkCount;
-
- if (!_walkCount) {
- // If there any points left for the character to walk to along the
- // route to a destination, then move to the next point
- if (!people._walkTo.empty()) {
- people._walkDest = people._walkTo.pop();
- people.setWalking();
- } else {
- people.gotoStand(*this);
- }
- }
- }
-
- if (_type == CHARACTER && !map._active) {
- if ((_position.y / 100) > LOWER_LIMIT) {
- _position.y = LOWER_LIMIT * 100;
- people.gotoStand(*this);
- }
-
- if ((_position.y / 100) < UPPER_LIMIT) {
- _position.y = UPPER_LIMIT * 100;
- people.gotoStand(*this);
- }
-
- if ((_position.x / 100) < LEFT_LIMIT) {
- _position.x = LEFT_LIMIT * 100;
- people.gotoStand(*this);
- }
- } else if (!map._active) {
- _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT);
- _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT);
- }
-
- if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag))
- ++_frameNumber;
-
- if (_walkSequences[_sequenceNumber][_frameNumber] == 0) {
- switch (_sequenceNumber) {
- case STOP_UP:
- case STOP_DOWN:
- case STOP_LEFT:
- case STOP_RIGHT:
- case STOP_UPRIGHT:
- case STOP_UPLEFT:
- case STOP_DOWNRIGHT:
- case STOP_DOWNLEFT:
- // We're in a stop sequence, so reset back to the last frame, so
- // the character is shown as standing still
- --_frameNumber;
- break;
-
- default:
- // Move 1 past the first frame - we need to compensate, since we
- // already passed the frame increment
- _frameNumber = 1;
- break;
- }
- }
-
- // Update the _imageFrame to point to the new frame's image
- setImageFrame();
+#define ADJUST_COORD(COORD) \
+ if (COORD.x != -1) \
+ COORD.x *= FIXED_INT_MULTIPLIER; \
+ if (COORD.y != -1) \
+ COORD.y *= FIXED_INT_MULTIPLIER
- // Check to see if character has entered an exit zone
- if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) {
- Common::Rect charRect(_position.x / 100 - 5, _position.y / 100 - 2,
- _position.x / 100 + 5, _position.y / 100 + 2);
- Exit *exit = scene.checkForExit(charRect);
-
- if (exit) {
- scene._goToScene = exit->_scene;
-
- if (exit->_people.x != 0) {
- people._hSavedPos = exit->_people;
- people._hSavedFacing = exit->_peopleDir;
-
- if (people._hSavedFacing > 100 && people._hSavedPos.x < 1)
- people._hSavedPos.x = 100;
- }
- }
- }
-}
-
-void Sprite::checkSprite() {
- Events &events = *_vm->_events;
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- Point32 pt;
- Common::Rect objBounds;
- Common::Point spritePt(_position.x / 100, _position.y / 100);
-
- if (!talk._talkCounter && _type == CHARACTER) {
- pt = _walkCount ? _position + _delta : _position;
- pt.x /= 100;
- pt.y /= 100;
-
- for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) {
- Object &obj = scene._bgShapes[idx];
- if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN)
- continue;
-
- if (obj._type == NO_SHAPE) {
- objBounds = Common::Rect(obj._position.x, obj._position.y,
- obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1);
- } else {
- int xp = obj._position.x + obj._imageFrame->_offset.x;
- int yp = obj._position.y + obj._imageFrame->_offset.y;
- objBounds = Common::Rect(xp, yp,
- xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1);
- }
-
- if (objBounds.contains(pt)) {
- if (objBounds.contains(spritePt)) {
- // Current point is already inside the the bounds, so impact occurred
- // on a previous call. So simply do nothing until we're clear of the box
- switch (obj._aType) {
- case TALK_MOVE:
- if (_walkCount) {
- // Holmes is moving
- obj._type = HIDDEN;
- obj.setFlagsAndToggles();
- talk.talkTo(obj._use[0]._target);
- }
- break;
-
- case PAL_CHANGE:
- case PAL_CHANGE2:
- if (_walkCount) {
- int palStart = atoi(obj._use[0]._names[0].c_str()) * 3;
- int palLength = atoi(obj._use[0]._names[1].c_str()) * 3;
- int templ = atoi(obj._use[0]._names[2].c_str()) * 3;
- if (templ == 0)
- templ = 100;
-
- // Ensure only valid palette change data found
- if (palLength > 0) {
- // Figure out how far into the shape Holmes is so that we
- // can figure out what percentage of the original palette
- // to set the current palette to
- int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width();
- palPercent = palPercent * templ / 100;
- if (obj._aType == PAL_CHANGE)
- // Invert percentage
- palPercent = 100 - palPercent;
-
- for (int i = palStart; i < (palStart + palLength); ++i)
- screen._sMap[i] = screen._cMap[i] * palPercent / 100;
-
- events.pollEvents();
- screen.setPalette(screen._sMap);
- }
- }
- break;
-
- case TALK:
- case TALK_EVERY:
- obj._type = HIDDEN;
- obj.setFlagsAndToggles();
- talk.talkTo(obj._use[0]._target);
- break;
-
- default:
- break;
- }
- } else {
- // New impact just occurred
- switch (obj._aType) {
- case BLANK_ZONE:
- // A blank zone masks out all other remaining zones underneath it.
- // If this zone is hit, exit the outer loop so we do not check anymore
- return;
-
- case SOLID:
- case TALK:
- // Stop walking
- if (obj._aType == TALK) {
- obj.setFlagsAndToggles();
- talk.talkTo(obj._use[0]._target);
- } else {
- people.gotoStand(*this);
- }
- break;
-
- case TALK_EVERY:
- if (obj._aType == TALK_EVERY) {
- obj._type = HIDDEN;
- obj.setFlagsAndToggles();
- talk.talkTo(obj._use[0]._target);
- } else {
- people.gotoStand(*this);
- }
- break;
-
- case FLAG_SET:
- obj.setFlagsAndToggles();
- obj._type = HIDDEN;
- break;
-
- case WALK_AROUND:
- if (objBounds.contains(people._walkTo.front())) {
- // Reached zone
- people.gotoStand(*this);
- } else {
- // Destination not within box, walk to best corner
- Common::Point walkPos;
-
- if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) {
- // Impact occurred due to vertical movement. Determine whether to
- // travel to the left or right side
- if (_delta.x > 0)
- // Go to right side
- walkPos.x = objBounds.right + CLEAR_DIST_X;
- else if (_delta.x < 0) {
- // Go to left side
- walkPos.x = objBounds.left - CLEAR_DIST_X;
- } else {
- // Going straight up or down. So choose best side
- if (spritePt.x >= (objBounds.left + objBounds.width() / 2))
- walkPos.x = objBounds.right + CLEAR_DIST_X;
- else
- walkPos.x = objBounds.left - CLEAR_DIST_X;
- }
-
- walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y :
- objBounds.bottom + CLEAR_DIST_Y;
- } else {
- // Impact occurred due to horizontal movement
- if (_delta.y > 0)
- // Go to bottom of box
- walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
- else if (_delta.y < 0)
- // Go to top of box
- walkPos.y = objBounds.top - CLEAR_DIST_Y;
- else {
- // Going straight horizontal, so choose best side
- if (spritePt.y >= (objBounds.top + objBounds.height() / 2))
- walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
- else
- walkPos.y = objBounds.top - CLEAR_DIST_Y;
- }
-
- walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X :
- objBounds.right + CLEAR_DIST_X;
- }
-
- walkPos.x += people[AL]._imageFrame->_frame.w / 2;
- people._walkDest = walkPos;
- people._walkTo.push(walkPos);
- people.setWalking();
- }
- break;
-
- case DELTA:
- _position.x += 200;
- break;
-
- default:
- break;
- }
- }
- }
- }
- }
-}
+SherlockEngine *BaseObject::_vm;
+bool BaseObject::_countCAnimFrames;
/*----------------------------------------------------------------*/
-void WalkSequence::load(Common::SeekableReadStream &s) {
- char buffer[9];
- s.read(buffer, 9);
- _vgsName = Common::String(buffer);
- _horizFlip = s.readByte() != 0;
-
- _sequences.resize(s.readUint16LE());
- s.read(&_sequences[0], _sequences.size());
-}
-
-/*----------------------------------------------------------------*/
-
-WalkSequences &WalkSequences::operator=(const WalkSequences &src) {
- resize(src.size());
- for (uint idx = 0; idx < size(); ++idx) {
- const WalkSequence &wSrc = src[idx];
- WalkSequence &wDest = (*this)[idx];
- wDest._horizFlip = wSrc._horizFlip;
-
- wDest._sequences.resize(wSrc._sequences.size());
- Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]);
- }
-
- return *this;
-}
-
-/*----------------------------------------------------------------*/
-
-void ActionType::load(Common::SeekableReadStream &s) {
- char buffer[12];
-
- _cAnimNum = s.readByte();
- _cAnimSpeed = s.readByte();
- if (_cAnimSpeed & 0x80)
- _cAnimSpeed = -(_cAnimSpeed & 0x7f);
-
- for (int idx = 0; idx < NAMES_COUNT; ++idx) {
- s.read(buffer, 12);
- _names[idx] = Common::String(buffer);
- }
-}
-
-/*----------------------------------------------------------------*/
-
-UseType::UseType() {
- _cAnimNum = _cAnimSpeed = 0;
- _useFlag = 0;
-}
-
-void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
- char buffer[12];
-
- if (isRoseTattoo) {
- s.read(buffer, 12);
- _verb = Common::String(buffer);
- }
-
- _cAnimNum = s.readByte();
- _cAnimSpeed = s.readByte();
- if (_cAnimSpeed & 0x80)
- _cAnimSpeed = -(_cAnimSpeed & 0x7f);
-
- for (int idx = 0; idx < NAMES_COUNT; ++idx) {
- s.read(buffer, 12);
- _names[idx] = Common::String(buffer);
- }
-
- _useFlag = s.readSint16LE();
-
- if (!isRoseTattoo)
- s.skip(6);
-
- s.read(buffer, 12);
- _target = Common::String(buffer);
-}
-
-/*----------------------------------------------------------------*/
-
-SherlockEngine *Object::_vm;
-bool Object::_countCAnimFrames;
-
-void Object::setVm(SherlockEngine *vm) {
+void BaseObject::setVm(SherlockEngine *vm) {
_vm = vm;
_countCAnimFrames = false;
}
-Object::Object() {
- _sequenceOffset = 0;
+BaseObject::BaseObject() {
+ _type = INVALID;
_sequences = nullptr;
_images = nullptr;
_imageFrame = nullptr;
+ _sequenceNumber = 0;
+ _startSeq = 0;
_walkCount = 0;
_allow = 0;
_frameNumber = 0;
- _sequenceNumber = 0;
- _type = INVALID;
- _pickup = 0;
- _defaultCommand = 0;
_lookFlag = 0;
- _pickupFlag = 0;
- _requiredFlag = 0;
+ _requiredFlag[0] = _requiredFlag[1] = 0;
_status = 0;
_misc = 0;
_maxFrames = 0;
_flags = 0;
- _aOpen._cAnimNum = 0;
- _aOpen._cAnimSpeed = 0;
_aType = OBJECT;
_lookFrames = 0;
_seqCounter = 0;
- _lookFacing = 0;
_lookcAnim = 0;
_seqStack = 0;
_seqTo = 0;
_descOffset = 0;
_seqCounter2 = 0;
_seqSize = 0;
-
_quickDraw = 0;
_scaleVal = 0;
- _requiredFlag1 = 0;
_gotoSeq = 0;
_talkSeq = 0;
_restoreSlot = 0;
}
-void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
- char buffer[41];
- s.read(buffer, 12);
- _name = Common::String(buffer);
- s.read(buffer, 41);
- _description = Common::String(buffer);
+bool BaseObject::hasAborts() const {
+ int seqNum = _talkSeq;
- _examine.clear();
- _sequences = nullptr;
- _images = nullptr;
- _imageFrame = nullptr;
-
- s.skip(4);
- _sequenceOffset = s.readUint16LE();
- s.seek(10, SEEK_CUR);
-
- _walkCount = s.readByte();
- _allow = s.readByte();
- _frameNumber = s.readSint16LE();
- _sequenceNumber = s.readSint16LE();
- _position.x = s.readSint16LE();
- _position.y = s.readSint16LE();
- _delta.x = s.readSint16LE();
- _delta.y = s.readSint16LE();
- _type = (SpriteType)s.readUint16LE();
- _oldPosition.x = s.readSint16LE();
- _oldPosition.y = s.readSint16LE();
- _oldSize.x = s.readUint16LE();
- _oldSize.y = s.readUint16LE();
- _goto.x = s.readSint16LE();
- _goto.y = s.readSint16LE();
+ // See if the object is in its regular sequence
+ bool startChecking = !seqNum || _type == CHARACTER;
- _pickup = isRoseTattoo ? 0 : s.readByte();
- _defaultCommand = isRoseTattoo ? 0 : s.readByte();
- _lookFlag = s.readSint16LE();
- _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE();
- _requiredFlag = s.readSint16LE();
- _noShapeSize.x = s.readUint16LE();
- _noShapeSize.y = s.readUint16LE();
- _status = s.readUint16LE();
- _misc = s.readByte();
- _maxFrames = s.readUint16LE();
- _flags = s.readByte();
-
- if (!isRoseTattoo)
- _aOpen.load(s);
-
- _aType = (AType)s.readByte();
- _lookFrames = s.readByte();
- _seqCounter = s.readByte();
- _lookPosition.x = s.readUint16LE();
- _lookPosition.y = isRoseTattoo ? s.readSint16LE() : s.readByte();
- _lookFacing = s.readByte();
- _lookcAnim = s.readByte();
-
- if (!isRoseTattoo)
- _aClose.load(s);
-
- _seqStack = s.readByte();
- _seqTo = s.readByte();
- _descOffset = s.readUint16LE();
- _seqCounter2 = s.readByte();
- _seqSize = s.readUint16LE();
-
- if (isRoseTattoo) {
- for (int idx = 0; idx < 6; ++idx)
- _use[idx].load(s, true);
-
- _quickDraw = s.readByte();
- _scaleVal = s.readUint16LE();
- _requiredFlag1 = s.readSint16LE();
- _gotoSeq = s.readByte();
- _talkSeq = s.readByte();
- _restoreSlot = s.readByte();
- } else {
- s.skip(1);
- _aMove.load(s);
- s.skip(8);
-
- for (int idx = 0; idx < 4; ++idx)
- _use[idx].load(s, false);
- }
-}
-
-void Object::toggleHidden() {
- if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) {
- if (_seqTo != 0)
- _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
- _seqTo = 0;
+ uint idx = 0;
+ do
+ {
+ // Get the Frame value
+ int v = _sequences[idx++];
- if (_images == nullptr || _images->size() == 0)
- // No shape to erase, so flag as hidden
- _type = HIDDEN;
- else
- // Otherwise, flag it to be hidden after it gets erased
- _type = HIDE_SHAPE;
- } else if (_type != INVALID) {
- if (_seqTo != 0)
- _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
- _seqTo = 0;
+ // See if we found an Allow Talk Interrupt Code
+ if (startChecking && v == ALLOW_TALK_CODE)
+ return true;
- _seqCounter = _seqCounter2 = 0;
- _seqStack = 0;
- _frameNumber = -1;
+ // If we've started checking and we've encountered another Talk or Listen Sequence Code,
+ // then we're done checking this sequence because this is where it would repeat
+ if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE))
+ break;
- if (_images == nullptr || _images->size() == 0) {
- _type = NO_SHAPE;
+ // See if we've found the beginning of a Talk Sequence
+ if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) {
+ // If checking was already on and we came across one of these codes, then there couldn't
+ // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done.
+ if (startChecking)
+ break;
+
+ seqNum--;
+ // See if we're at the correct Talk Sequence Number
+ if (!(seqNum & 127))
+ {
+ // Correct Sequence, Start Checking Now
+ startChecking = true;
+ }
} else {
- _type = ACTIVE_BG_SHAPE;
- int idx = _sequences[0];
- if (idx >= _maxFrames)
- // Turn on: set up first frame
- idx = 0;
-
- _imageFrame = &(*_images)[idx];
+ // Move ahead any extra because of special control codes
+ switch (v) {
+ case 0: idx++; break;
+ case MOVE_CODE:
+ case TELEPORT_CODE: idx += 4; break;
+ case CALL_TALK_CODE:idx += 8; break;
+ case HIDE_CODE: idx += 2; break;
+ }
}
- }
+ } while (idx < _seqSize);
+
+ return false;
}
-void Object::checkObject() {
+void BaseObject::checkObject() {
Scene &scene = *_vm->_scene;
Sound &sound = *_vm->_sound;
+ Talk &talk = *_vm->_talk;
int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
bool codeFound;
if (_seqTo) {
byte *ptr = &_sequences[_frameNumber];
if (*ptr == _seqTo) {
- // The sequence is completed
- *ptr = _seqTo + SEQ_TO_CODE + 128; // Reset to normal
+ // The sequence is completed. Reset to normal
+ *ptr = _seqTo + (IS_ROSE_TATTOO ? 0 : SEQ_TO_CODE + 128);
_seqTo = 0;
} else {
// Continue doing sequence
@@ -637,6 +164,11 @@ void Object::checkObject() {
++_frameNumber;
do {
+ if (!_sequences) {
+ warning("checkObject: _sequences is not set");
+ break;
+ }
+
// Check for end of sequence
codeFound = checkEndOfSequence();
@@ -644,20 +176,33 @@ void Object::checkObject() {
codeFound = true;
int v = _sequences[_frameNumber];
- if (v >= 228) {
+ // Check for a Talk or Listen Sequence
+ if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) {
+ if (_gotoSeq) {
+ setObjTalkSequence(_gotoSeq);
+ _gotoSeq = 0;
+ } else {
+ ++_frameNumber;
+ }
+ } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) {
+ if (_talkSeq)
+ setObjTalkSequence(_talkSeq);
+ else
+ setObjSequence(0, false);
+ } else if (v >= GOTO_CODE) {
// Goto code found
- v -= 228;
+ v -= GOTO_CODE;
_seqCounter2 = _seqCounter;
_seqStack = _frameNumber + 1;
setObjSequence(v, false);
} else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) {
codeFound = true;
++_frameNumber;
- v -= SOUND_CODE;
+ v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0);
if (sound._soundOn && !_countCAnimFrames) {
- if (!scene._sounds[v - 1]._name.empty() && sound._digitized)
- sound.playLoadedSound(v - 1, WAIT_RETURN_IMMEDIATELY);
+ if (!scene._sounds[v]._name.empty() && sound._digitized)
+ sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY);
}
} else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) {
// Flip code
@@ -682,28 +227,90 @@ void Object::checkObject() {
default:
break;
}
+ } else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) {
+ _position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]);
+ _position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]);
+
+ _frameNumber += 5;
+ } else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) {
+ Common::String filename;
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_sequences[_frameNumber + 1 + idx] != 1)
+ filename += (char)_sequences[_frameNumber + 1 + idx];
+ else
+ break;
+ }
+
+ _frameNumber += 8;
+ talk.talkTo(filename);
+
+ } else if (IS_ROSE_TATTOO && v == HIDE_CODE) {
+ switch (_sequences[_frameNumber + 2]) {
+ case 1:
+ // Hide Object
+ if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN)
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ case 2:
+ // Activate Object
+ if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN)
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ case 3:
+ // Toggle Object
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ default:
+ break;
+ }
+ _frameNumber += 3;
+
} else {
v -= 128;
- // 68-99 is a squence code
+ // 68-99 is a sequence code
if (v > SEQ_TO_CODE) {
- byte *p = &_sequences[_frameNumber];
- v -= SEQ_TO_CODE; // # from 1-32
- _seqTo = v;
- *p = *(p - 1);
-
- if (*p > 128)
- // If the high bit is set, convert to a real frame
- *p -= (byte)(SEQ_TO_CODE - 128);
-
- if (*p > _seqTo)
- *p -= 1;
- else
- *p += 1;
+ if (IS_ROSE_TATTOO) {
+ ++_frameNumber;
+ byte *p = &_sequences[_frameNumber];
+ _seqTo = *p;
+ *p = *(p - 2);
+
+ if (*p > _seqTo)
+ *p -= 1;
+ else
+ *p += 1;
+
+ --_frameNumber;
+ } else {
+ byte *p = &_sequences[_frameNumber];
+ v -= SEQ_TO_CODE; // # from 1-32
+ _seqTo = v;
+ *p = *(p - 1);
+
+ if (*p > 128)
+ // If the high bit is set, convert to a real frame
+ *p -= (byte)(SEQ_TO_CODE - 128);
+
+ if (*p > _seqTo)
+ *p -= 1;
+ else
+ *p += 1;
+
+ // Will be incremented below to return back to original value
+ --_frameNumber;
+ v = 0;
+ }
+ } else if (IS_ROSE_TATTOO && v == 10) {
+ // Set delta for objects
+ _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]),
+ READ_LE_UINT16(&_sequences[_frameNumber + 3]));
+ _noShapeSize = Common::Point(0, 0);
+ _frameNumber += 4;
- // Will be incremented below to return back to original value
- --_frameNumber;
- v = 0;
} else if (v == 10) {
// Set delta for objects
Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]);
@@ -719,9 +326,10 @@ void Object::checkObject() {
_delta = pt;
_frameNumber += 2;
+
} else if (v < USE_COUNT) {
for (int idx = 0; idx < NAMES_COUNT; ++idx) {
- checkNameForCodes(_use[v]._names[idx], nullptr);
+ checkNameForCodes(_use[v]._names[idx]);
}
if (_use[v]._useFlag)
@@ -734,7 +342,7 @@ void Object::checkObject() {
} while (codeFound);
}
-bool Object::checkEndOfSequence() {
+bool BaseObject::checkEndOfSequence() {
Screen &screen = *_vm->_screen;
int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
bool result = false;
@@ -742,20 +350,26 @@ bool Object::checkEndOfSequence() {
if (_type == REMOVE || _type == INVALID)
return false;
- if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) {
+ if (_frameNumber < 0 || _frameNumber >= checkFrame || _sequences[_frameNumber] == 0) {
result = true;
- if (_frameNumber >= (checkFrame - 1)) {
+ if (_frameNumber < 0 || _frameNumber >= (checkFrame - 1)) {
_frameNumber = START_FRAME;
} else {
// Determine next sequence to use
int seq = _sequences[_frameNumber + 1];
+ // If the object has been turned off, we're going nowhere
+ if (IS_ROSE_TATTOO && (_type == HIDE_SHAPE || _type == HIDDEN || _type == REMOVE))
+ return false;
+
if (seq == 99) {
--_frameNumber;
screen._backBuffer1.transBlitFrom(*_imageFrame, _position);
screen._backBuffer2.transBlitFrom(*_imageFrame, _position);
_type = INVALID;
+ } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) {
+ setObjTalkSequence(_talkSeq);
} else {
setObjSequence(seq, false);
}
@@ -786,10 +400,14 @@ bool Object::checkEndOfSequence() {
return result;
}
-void Object::setObjSequence(int seq, bool wait) {
+void BaseObject::setObjSequence(int seq, bool wait) {
Scene &scene = *_vm->_scene;
int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
+ if (IS_ROSE_TATTOO && (seq == -1 || seq == 255))
+ // This means goto beginning
+ seq = 0;
+
if (seq >= 128) {
// Loop the sequence until the count exceeded
seq -= 128;
@@ -812,6 +430,10 @@ void Object::setObjSequence(int seq, bool wait) {
if (_frameNumber >= checkFrame)
_frameNumber = 0;
+ // For Rose Tattoo, save the starting frame for new sequences
+ if (IS_ROSE_TATTOO)
+ _startSeq = _frameNumber;
+
_seqCounter = 0;
if (_sequences[_frameNumber] == 0)
seq = _sequences[_frameNumber + 1];
@@ -819,12 +441,18 @@ void Object::setObjSequence(int seq, bool wait) {
return;
} else {
// Find beginning of sequence
- do {
- --_frameNumber;
- } while (_frameNumber > 0 && _sequences[_frameNumber] != 0);
+ if (IS_ROSE_TATTOO) {
+ // Use the saved start of the sequence to reset the frame
+ _frameNumber = _startSeq;
+ } else {
+ // For Scalpel, scan backwards from the end of the sequence to find its start
+ do {
+ --_frameNumber;
+ } while (_frameNumber > 0 && _sequences[_frameNumber] != 0);
- if (_frameNumber != 0)
- _frameNumber += 2;
+ if (_frameNumber != 0)
+ _frameNumber += 2;
+ }
return;
}
@@ -837,16 +465,35 @@ void Object::setObjSequence(int seq, bool wait) {
int seqCc = 0;
while (seqCc < seq && idx < checkFrame) {
- ++idx;
- if (_sequences[idx] == 0) {
- ++seqCc;
- idx += 2;
+ if (IS_SERRATED_SCALPEL) {
+ ++idx;
+
+ if (_sequences[idx] == 0) {
+ ++seqCc;
+ idx += 2;
+ }
+ } else {
+ byte s = _sequences[idx];
+
+ if (s == 0) {
+ ++seqCc;
+ ++idx;
+ } else if (s == MOVE_CODE || s == TELEPORT_CODE) {
+ idx += 4;
+ } else if (s == CALL_TALK_CODE) {
+ idx += 8;
+ } else if (s == HIDE_CODE) {
+ idx += 2;
+ }
+
+ ++idx;
}
}
if (idx >= checkFrame)
idx = 0;
_frameNumber = idx;
+ _startSeq = idx;
if (wait) {
seqCc = idx;
@@ -859,8 +506,8 @@ void Object::setObjSequence(int seq, bool wait) {
}
}
-int Object::checkNameForCodes(const Common::String &name, const char *const messages[]) {
- Map &map = *_vm->_map;
+int BaseObject::checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId) {
+ FixedText &fixedText = *_vm->_fixedText;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
@@ -901,13 +548,20 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess
break;
}
+ case 'V':
+ // Do nothing for Verb codes. This is only a flag for Inventory syntax
+ break;
+
default:
if (ch >= '0' && ch <= '9') {
scene._goToScene = atoi(name.c_str() + 1);
- if (scene._goToScene < 97 && map[scene._goToScene].x) {
- map._overPos.x = map[scene._goToScene].x * 100 - 600;
- map._overPos.y = map[scene._goToScene].y * 100 + 900;
+ if (IS_SERRATED_SCALPEL && scene._goToScene < 97) {
+ Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map;
+ if (map[scene._goToScene].x) {
+ map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
+ }
}
const char *p;
@@ -915,24 +569,23 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess
++p;
Common::String s(p, p + 3);
- people._hSavedPos.x = atoi(s.c_str());
+ people._savedPos.x = atoi(s.c_str());
s = Common::String(p + 3, p + 6);
- people._hSavedPos.y = atoi(s.c_str());
+ people._savedPos.y = atoi(s.c_str());
s = Common::String(p + 6, p + 9);
- people._hSavedFacing = atoi(s.c_str());
- if (people._hSavedFacing == 0)
- people._hSavedFacing = 10;
+ people._savedPos._facing = atoi(s.c_str());
+ if (people._savedPos._facing == 0)
+ people._savedPos._facing = 10;
} else if ((p = strchr(name.c_str(), '/')) != nullptr) {
- people._hSavedPos = Common::Point(1, 0);
- people._hSavedFacing = 100 + atoi(p + 1);
+ people._savedPos = PositionFacing(1, 0, 100 + atoi(p + 1));
}
} else {
scene._goToScene = 100;
}
- people[AL]._position = Common::Point(0, 0);
+ people[HOLMES]._position = Point32(0, 0);
break;
}
} else if (name.hasPrefix("!")) {
@@ -940,13 +593,14 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess
int messageNum = atoi(name.c_str() + 1);
ui._infoFlag = true;
ui.clearInfo();
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[messageNum]);
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, messageNum);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
ui._menuCounter = 25;
} else if (name.hasPrefix("@")) {
// Message attached to canimation
ui._infoFlag = true;
ui.clearInfo();
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", name.c_str() + 1);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", name.c_str() + 1);
printed = true;
ui._menuCounter = 25;
}
@@ -954,6 +608,679 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess
return printed;
}
+/*----------------------------------------------------------------*/
+
+void Sprite::clear() {
+ _name = "";
+ _description = "";
+ _examine.clear();
+ _pickUp = "";
+ _walkSequences.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+ _walkCount = 0;
+ _allow = 0;
+ _frameNumber = 0;
+ _position.x = _position.y = 0;
+ _delta.x = _delta.y = 0;
+ _oldPosition.x = _oldPosition.y = 0;
+ _oldSize.x = _oldSize.y = 0;
+ _goto.x = _goto.y = 0;
+ _type = INVALID;
+ _pickUp.clear();
+ _noShapeSize.x = _noShapeSize.y = 0;
+ _status = 0;
+ _misc = 0;
+ _altImages = nullptr;
+ _altSeq = 0;
+ Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr);
+}
+
+void Sprite::setImageFrame() {
+ int frameNum = MAX(_frameNumber, 0);
+ int imageNumber = _walkSequences[_sequenceNumber][frameNum];
+
+ if (IS_SERRATED_SCALPEL)
+ imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2;
+ else if (imageNumber > _maxFrames)
+ imageNumber = 1;
+
+ // Get the images to use
+ ImageFile *images = _altSeq ? _altImages : _images;
+ assert(images);
+
+ if (IS_3DO) {
+ // only do this to the image-array with 110 entries
+ // map uses another image-array and this code
+ if (images->size() == 110) {
+ // 3DO has 110 animation frames inside walk.anim
+ // PC has 55
+ // this adjusts the framenumber accordingly
+ // sort of HACK
+ imageNumber *= 2;
+ }
+ } else if (IS_ROSE_TATTOO) {
+ --imageNumber;
+ }
+
+ // Set the frame pointer
+ _imageFrame = &(*images)[imageNumber];
+}
+
+void Sprite::checkSprite() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Point32 pt;
+ Common::Rect objBounds;
+ Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER);
+
+ if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter))
+ return;
+
+ pt = _walkCount ? _position + _delta : _position;
+ pt.x /= FIXED_INT_MULTIPLIER;
+ pt.y /= FIXED_INT_MULTIPLIER;
+
+ if (IS_ROSE_TATTOO) {
+ checkObject();
+
+ // For Rose Tattoo, we only do the further processing for Sherlock
+ if (this != &people[HOLMES])
+ return;
+ }
+
+ for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) {
+ Object &obj = scene._bgShapes[idx];
+ if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN)
+ continue;
+
+ if (obj._type == NO_SHAPE) {
+ objBounds = Common::Rect(obj._position.x, obj._position.y,
+ obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1);
+ } else {
+ int xp = obj._position.x + obj._imageFrame->_offset.x;
+ int yp = obj._position.y + obj._imageFrame->_offset.y;
+ objBounds = Common::Rect(xp, yp,
+ xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1);
+ }
+
+ if (objBounds.contains(pt)) {
+ if (objBounds.contains(spritePt)) {
+ // Current point is already inside the the bounds, so impact occurred
+ // on a previous call. So simply do nothing until we're clear of the box
+ switch (obj._aType) {
+ case TALK_MOVE:
+ if (_walkCount) {
+ // Holmes is moving
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ }
+ break;
+
+ case PAL_CHANGE:
+ case PAL_CHANGE2:
+ if (_walkCount) {
+ int palStart = atoi(obj._use[0]._names[0].c_str()) * 3;
+ int palLength = atoi(obj._use[0]._names[1].c_str()) * 3;
+ int templ = atoi(obj._use[0]._names[2].c_str()) * 3;
+ if (templ == 0)
+ templ = 100;
+
+ // Ensure only valid palette change data found
+ if (palLength > 0) {
+ // Figure out how far into the shape Holmes is so that we
+ // can figure out what percentage of the original palette
+ // to set the current palette to
+ int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width();
+ palPercent = palPercent * templ / 100;
+ if (obj._aType == PAL_CHANGE)
+ // Invert percentage
+ palPercent = 100 - palPercent;
+
+ for (int i = palStart; i < (palStart + palLength); ++i)
+ screen._sMap[i] = screen._cMap[i] * palPercent / 100;
+
+ events.pollEvents();
+ screen.setPalette(screen._sMap);
+ }
+ }
+ break;
+
+ case TALK:
+ case TALK_EVERY:
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ // New impact just occurred
+ switch (obj._aType) {
+ case BLANK_ZONE:
+ // A blank zone masks out all other remaining zones underneath it.
+ // If this zone is hit, exit the outer loop so we do not check anymore
+ return;
+
+ case SOLID:
+ case TALK:
+ // Stop walking
+ if (obj._aType == TALK) {
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ } else {
+ gotoStand();
+ }
+ break;
+
+ case TALK_EVERY:
+ if (obj._aType == TALK_EVERY) {
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ } else {
+ gotoStand();
+ }
+ break;
+
+ case FLAG_SET:
+ obj.setFlagsAndToggles();
+ obj._type = HIDDEN;
+ break;
+
+ case WALK_AROUND:
+ if (objBounds.contains(people[HOLMES]._walkTo.front())) {
+ // Reached zone
+ gotoStand();
+ } else {
+ // Destination not within box, walk to best corner
+ Common::Point walkPos;
+
+ if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) {
+ // Impact occurred due to vertical movement. Determine whether to
+ // travel to the left or right side
+ if (_delta.x > 0)
+ // Go to right side
+ walkPos.x = objBounds.right + CLEAR_DIST_X;
+ else if (_delta.x < 0) {
+ // Go to left side
+ walkPos.x = objBounds.left - CLEAR_DIST_X;
+ } else {
+ // Going straight up or down. So choose best side
+ if (spritePt.x >= (objBounds.left + objBounds.width() / 2))
+ walkPos.x = objBounds.right + CLEAR_DIST_X;
+ else
+ walkPos.x = objBounds.left - CLEAR_DIST_X;
+ }
+
+ walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y :
+ objBounds.bottom + CLEAR_DIST_Y;
+ } else {
+ // Impact occurred due to horizontal movement
+ if (_delta.y > 0)
+ // Go to bottom of box
+ walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
+ else if (_delta.y < 0)
+ // Go to top of box
+ walkPos.y = objBounds.top - CLEAR_DIST_Y;
+ else {
+ // Going straight horizontal, so choose best side
+ if (spritePt.y >= (objBounds.top + objBounds.height() / 2))
+ walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
+ else
+ walkPos.y = objBounds.top - CLEAR_DIST_Y;
+ }
+
+ walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X :
+ objBounds.right + CLEAR_DIST_X;
+ }
+
+ walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2;
+ people[HOLMES]._walkDest = walkPos;
+ people[HOLMES]._walkTo.push(walkPos);
+ people[HOLMES].setWalking();
+ }
+ break;
+
+ case DELTA:
+ _position.x += 200;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+const Common::Rect Sprite::getOldBounds() const {
+ return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y);
+}
+
+/*----------------------------------------------------------------*/
+
+void WalkSequence::load(Common::SeekableReadStream &s) {
+ char buffer[9];
+ s.read(buffer, 9);
+ _vgsName = Common::String(buffer);
+ _horizFlip = s.readByte() != 0;
+
+ _sequences.resize(s.readUint16LE());
+ s.skip(4); // Skip over pointer field of structure
+
+ s.read(&_sequences[0], _sequences.size());
+}
+
+/*----------------------------------------------------------------*/
+
+WalkSequences &WalkSequences::operator=(const WalkSequences &src) {
+ resize(src.size());
+ for (uint idx = 0; idx < size(); ++idx) {
+ const WalkSequence &wSrc = src[idx];
+ WalkSequence &wDest = (*this)[idx];
+ wDest._horizFlip = wSrc._horizFlip;
+
+ wDest._sequences.resize(wSrc._sequences.size());
+ Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]);
+ }
+
+ return *this;
+}
+
+/*----------------------------------------------------------------*/
+
+ActionType::ActionType() {
+ _cAnimNum = _cAnimSpeed = 0;
+ _useFlag = 0;
+}
+
+void ActionType::load(Common::SeekableReadStream &s) {
+ char buffer[12];
+
+ _cAnimNum = s.readByte();
+ _cAnimSpeed = s.readByte();
+ if (_cAnimSpeed & 0x80)
+ _cAnimSpeed = -(_cAnimSpeed & 0x7f);
+
+ for (int idx = 0; idx < NAMES_COUNT; ++idx) {
+ s.read(buffer, 12);
+ _names[idx] = Common::String(buffer);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+UseType::UseType(): ActionType() {
+}
+
+void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ char buffer[12];
+
+ if (isRoseTattoo) {
+ s.read(buffer, 12);
+ _verb = Common::String(buffer);
+ }
+
+ ActionType::load(s);
+
+ _useFlag = s.readSint16LE();
+
+ if (!isRoseTattoo)
+ s.skip(6);
+
+ s.read(buffer, 12);
+ _target = Common::String(buffer);
+}
+
+void UseType::load3DO(Common::SeekableReadStream &s) {
+ char buffer[12];
+
+ _cAnimNum = s.readByte();
+ _cAnimSpeed = s.readByte();
+ if (_cAnimSpeed & 0x80)
+ _cAnimSpeed = -(_cAnimSpeed & 0x7f);
+
+ for (int idx = 0; idx < NAMES_COUNT; ++idx) {
+ s.read(buffer, 12);
+ _names[idx] = Common::String(buffer);
+ }
+
+ _useFlag = s.readSint16BE();
+
+ s.skip(6);
+
+ s.read(buffer, 12);
+ _target = Common::String(buffer);
+}
+
+void UseType::synchronize(Serializer &s) {
+ s.syncString(_verb);
+ s.syncAsSint16LE(_cAnimNum);
+ s.syncAsSint16LE(_cAnimSpeed);
+ s.syncAsSint16LE(_useFlag);
+
+ for (int idx = 0; idx < 4; ++idx)
+ s.syncString(_names[idx]);
+ s.syncString(_target);
+}
+
+/*----------------------------------------------------------------*/
+
+Object::Object(): BaseObject() {
+ _sequenceOffset = 0;
+ _pickup = 0;
+ _defaultCommand = 0;
+ _pickupFlag = 0;
+}
+
+void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ char buffer[41];
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+ s.read(buffer, 41);
+ _description = Common::String(buffer);
+
+ _examine.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+
+ s.skip(4);
+ _sequenceOffset = s.readUint16LE();
+ s.seek(10, SEEK_CUR);
+
+ _walkCount = s.readByte();
+ _allow = s.readByte();
+ _frameNumber = s.readSint16LE();
+ _sequenceNumber = s.readSint16LE();
+ _position.x = s.readSint16LE();
+ _position.y = s.readSint16LE();
+ _delta.x = s.readSint16LE();
+ _delta.y = s.readSint16LE();
+ _type = (SpriteType)s.readUint16LE();
+ _oldPosition.x = s.readSint16LE();
+ _oldPosition.y = s.readSint16LE();
+ _oldSize.x = s.readUint16LE();
+ _oldSize.y = s.readUint16LE();
+
+ _goto.x = s.readSint16LE();
+ _goto.y = s.readSint16LE();
+ if (!isRoseTattoo) {
+ _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
+ _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
+ }
+
+ _pickup = isRoseTattoo ? 0 : s.readByte();
+ _defaultCommand = isRoseTattoo ? 0 : s.readByte();
+ _lookFlag = s.readSint16LE();
+ _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE();
+ _requiredFlag[0] = s.readSint16LE();
+ _noShapeSize.x = s.readUint16LE();
+ _noShapeSize.y = s.readUint16LE();
+ _status = s.readUint16LE();
+ _misc = s.readByte();
+ _maxFrames = s.readUint16LE();
+ _flags = s.readByte();
+
+ if (!isRoseTattoo)
+ _aOpen.load(s);
+
+ _aType = (AType)s.readByte();
+ _lookFrames = s.readByte();
+ _seqCounter = s.readByte();
+ if (isRoseTattoo) {
+ _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER;
+ _lookPosition.y = s.readSint16LE() * FIXED_INT_MULTIPLIER;
+ } else {
+ _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100;
+ _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER;
+ }
+ _lookPosition._facing = s.readByte();
+ _lookcAnim = s.readByte();
+
+ if (!isRoseTattoo)
+ _aClose.load(s);
+
+ _seqStack = s.readByte();
+ _seqTo = s.readByte();
+ _descOffset = s.readUint16LE();
+ _seqCounter2 = s.readByte();
+ _seqSize = s.readUint16LE();
+
+ if (isRoseTattoo) {
+ for (int idx = 0; idx < 6; ++idx)
+ _use[idx].load(s, true);
+
+ _quickDraw = s.readByte();
+ _scaleVal = s.readUint16LE();
+ _requiredFlag[1] = s.readSint16LE();
+ _gotoSeq = s.readByte();
+ _talkSeq = s.readByte();
+ _restoreSlot = s.readByte();
+ } else {
+ s.skip(1);
+ _aMove.load(s);
+ s.skip(8);
+
+ for (int idx = 0; idx < 4; ++idx)
+ _use[idx].load(s, false);
+ }
+ //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum);
+}
+
+void Object::load3DO(Common::SeekableReadStream &s) {
+ int32 streamStartPos = s.pos();
+ char buffer[41];
+
+ _examine.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+
+ // on 3DO all of this data is reordered!!!
+ // it seems that possibly the 3DO compiler reordered the global struct
+ // 3DO size for 1 object is 588 bytes
+ s.skip(4);
+ _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE
+ s.seek(10, SEEK_CUR);
+
+ // Offset 16
+ _frameNumber = s.readSint16BE();
+ _sequenceNumber = s.readSint16BE();
+ _position.x = s.readSint16BE();
+ _position.y = s.readSint16BE();
+ _delta.x = s.readSint16BE();
+ _delta.y = s.readSint16BE();
+ _type = (SpriteType)s.readUint16BE();
+ _oldPosition.x = s.readSint16BE();
+ _oldPosition.y = s.readSint16BE();
+ _oldSize.x = s.readUint16BE();
+ _oldSize.y = s.readUint16BE();
+
+ _goto.x = s.readSint16BE();
+ _goto.y = s.readSint16BE();
+ _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
+ _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
+
+ // Offset 42
+ warning("pos %d", s.pos());
+
+ // Unverified
+ _lookFlag = s.readSint16BE();
+ _pickupFlag = s.readSint16BE();
+ _requiredFlag[0] = s.readSint16BE();
+ _noShapeSize.x = s.readUint16BE();
+ _noShapeSize.y = s.readUint16BE();
+ _status = s.readUint16BE();
+ // Unverified END
+
+ _maxFrames = s.readUint16BE();
+ // offset 56
+ _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100;
+ // offset 58
+ _descOffset = s.readUint16BE();
+ _seqSize = s.readUint16BE();
+
+ s.skip(2); // boundary filler
+
+ // 288 bytes
+ for (int idx = 0; idx < 4; ++idx) {
+ _use[idx].load3DO(s);
+ s.skip(2); // Filler
+ }
+
+ // 158 bytes
+ _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes
+ s.skip(2); // Boundary filler
+ _aClose.load(s);
+ s.skip(2); // Filler
+ _aMove.load(s);
+ s.skip(2); // Filler
+
+ // offset 508
+ // 3DO: name is at the end
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+ s.read(buffer, 41);
+ _description = Common::String(buffer);
+
+ // Unverified
+ _walkCount = s.readByte();
+ _allow = s.readByte();
+ _pickup = s.readByte();
+ _defaultCommand = s.readByte();
+ // Unverified END
+
+ // Probably those here?!?!
+ _misc = s.readByte();
+ _flags = s.readByte();
+
+ // Unverified
+ _aType = (AType)s.readByte();
+ _lookFrames = s.readByte();
+ _seqCounter = s.readByte();
+ // Unverified END
+
+ _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER;
+ _lookPosition._facing = s.readByte();
+
+ // Unverified
+ _lookcAnim = s.readByte();
+ _seqStack = s.readByte();
+ _seqTo = s.readByte();
+ _seqCounter2 = s.readByte();
+ // Unverified END
+
+ s.skip(12); // Unknown
+
+ //warning("object %s, offset %d", _name.c_str(), streamPos);
+ //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y);
+ //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand);
+ int32 dataSize = s.pos() - streamStartPos;
+ assert(dataSize == 588);
+}
+
+void Object::toggleHidden() {
+ if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) {
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
+ _seqTo = 0;
+
+ if (_images == nullptr || _images->size() == 0)
+ // No shape to erase, so flag as hidden
+ _type = HIDDEN;
+ else
+ // Otherwise, flag it to be hidden after it gets erased
+ _type = HIDE_SHAPE;
+ } else if (_type != INVALID) {
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
+ _seqTo = 0;
+
+ _seqCounter = _seqCounter2 = 0;
+ _seqStack = 0;
+ _frameNumber = -1;
+
+ if (_images == nullptr || _images->size() == 0) {
+ _type = NO_SHAPE;
+ } else {
+ _type = ACTIVE_BG_SHAPE;
+ int idx = _sequences[0];
+ if (idx >= _maxFrames)
+ // Turn on: set up first frame
+ idx = 0;
+
+ _imageFrame = &(*_images)[idx];
+ }
+ }
+}
+
+void Object::setObjTalkSequence(int seq) {
+ Talk &talk = *_vm->_talk;
+
+ // See if we're supposed to restore the object's sequence from the talk sequence stack
+ if (seq == -1) {
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo;
+
+ talk.pullSequence(_restoreSlot);
+ return;
+ }
+
+ assert(_type != CHARACTER);
+
+ talk.pushSequenceEntry(this);
+ int talkSeqNum = seq;
+
+ // Find where the talk sequence data begins in the object
+ int idx = 0;
+ for (;;) {
+ // Get the Frame value
+ byte f = _sequences[idx++];
+
+ // See if we've found the beginning of a Talk Sequence
+ if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) {
+ --seq;
+
+ // See if we're at the correct Talk Sequence Number
+ if (!(seq & 127))
+ {
+ // Correct Sequence, Start Talking Here
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo;
+ _frameNumber = idx;
+ _seqTo = 0;
+ _seqStack = 0;
+ _seqCounter = 0;
+ _seqCounter2 = 0;
+ _talkSeq = talkSeqNum;
+ break;
+ }
+ } else {
+ // Move ahead any extra because of special control codes
+ switch (f) {
+ case 0: idx++; break;
+ case MOVE_CODE:
+ case TELEPORT_CODE: idx += 4; break;
+ case CALL_TALK_CODE: idx += 8; break;
+ case HIDE_CODE: idx += 2; break;
+ }
+ }
+
+ // See if we're out of sequence data
+ if (idx >= (int)_seqSize)
+ break;
+ }
+}
+
void Object::setFlagsAndToggles() {
Scene &scene = *_vm->_scene;
Talk &talk = *_vm->_talk;
@@ -983,7 +1310,22 @@ void Object::adjustObject() {
if (_type == REMOVE)
return;
- _position += _delta;
+ if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) {
+ // The shape position is in pixels, and the delta is in fixed integer amounts
+ int t;
+ _noShapeSize.x += _delta.x;
+ t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10);
+ _noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10);
+ _position.x += t;
+
+ _noShapeSize.y += _delta.y;
+ t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10);
+ _noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10);
+ _position.y += t;
+ } else if (IS_SERRATED_SCALPEL) {
+ // The delta is in whole pixels, so simply adjust the position with it
+ _position += _delta;
+ }
if (_position.y > LOWER_LIMIT)
_position.y = LOWER_LIMIT;
@@ -1001,7 +1343,8 @@ void Object::adjustObject() {
}
}
-int Object::pickUpObject(const char *const messages[]) {
+int Object::pickUpObject(FixedTextActionId fixedTextActionId) {
+ FixedText &fixedText = *_vm->_fixedText;
Inventory &inv = *_vm->_inventory;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
@@ -1014,7 +1357,7 @@ int Object::pickUpObject(const char *const messages[]) {
if (pickup == 99) {
for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
- if (checkNameForCodes(_use[0]._names[idx], nullptr)) {
+ if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) {
if (!talk._talkToAbort)
printed = true;
}
@@ -1030,7 +1373,8 @@ int Object::pickUpObject(const char *const messages[]) {
ui._infoFlag = true;
ui.clearInfo();
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[message]);
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, message);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
ui._menuCounter = 30;
} else {
// Pick it up
@@ -1055,13 +1399,13 @@ int Object::pickUpObject(const char *const messages[]) {
} else {
// Play generic pickup sequence
// Original moved cursor position here
- people.goAllTheWay();
+ people[HOLMES].goAllTheWay();
ui._menuCounter = 25;
ui._temp1 = 1;
}
for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
- if (checkNameForCodes(_use[0]._names[idx], nullptr)) {
+ if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) {
if (!talk._talkToAbort)
printed = true;
}
@@ -1079,7 +1423,7 @@ int Object::pickUpObject(const char *const messages[]) {
Common::String itemName = _description;
itemName.setChar(tolower(itemName[0]), 0);
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Picked up %s", itemName.c_str());
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Picked up %s", itemName.c_str());
ui._menuCounter = 25;
}
}
@@ -1088,7 +1432,7 @@ int Object::pickUpObject(const char *const messages[]) {
}
const Common::Rect Object::getNewBounds() const {
- Common::Point pt = _position;
+ Point32 pt = _position;
if (_imageFrame)
pt += _imageFrame->_offset;
@@ -1107,14 +1451,14 @@ const Common::Rect Object::getOldBounds() const {
/*----------------------------------------------------------------*/
-void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset) {
char buffer[12];
s.read(buffer, 12);
_name = Common::String(buffer);
if (isRoseTattoo) {
Common::fill(&_sequences[0], &_sequences[30], 0);
- _size = s.readUint32LE();
+ _dataSize = s.readUint32LE();
} else {
s.read(_sequences, 30);
}
@@ -1126,33 +1470,85 @@ void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
_flags = s.readByte();
_scaleVal = s.readSint16LE();
} else {
- _size = s.readUint32LE();
+ _dataSize = s.readUint32LE();
_type = (SpriteType)s.readUint16LE();
_flags = s.readByte();
}
- _goto.x = s.readSint16LE();
- _goto.y = s.readSint16LE();
- _gotoDir = s.readSint16LE();
- _teleportPos.x = s.readSint16LE();
- _teleportPos.y = s.readSint16LE();
- _teleportDir = s.readSint16LE();
-}
+ _goto[0].x = s.readSint16LE();
+ _goto[0].y = s.readSint16LE();
+ _goto[0]._facing = s.readSint16LE();
+ ADJUST_COORD(_goto[0]);
-/*----------------------------------------------------------------*/
+ if (isRoseTattoo) {
+ // Get Goto position and facing for second NPC
+ _goto[1].x = s.readSint16LE();
+ _goto[1].y = s.readSint16LE();
+ _goto[1]._facing = s.readSint16LE();
+ ADJUST_COORD(_goto[1]);
+ } else if (_goto[0].x != -1) {
+ // For Serrated Scalpel, adjust the loaded co-ordinates
+ _goto[0].x = _goto[0].x / 100;
+ _goto[0].y = _goto[0].y / 100;
+ }
-CAnimStream::CAnimStream() {
- _stream = nullptr;
- _frameSize = 0;
- _images = nullptr;
- _imageFrame = nullptr;
- _flags = 0;
- _scaleVal = 0;
- _zPlacement = 0;
+ _teleport[0].x = s.readSint16LE();
+ _teleport[0].y = s.readSint16LE();
+ _teleport[0]._facing = s.readSint16LE();
+ ADJUST_COORD(_teleport[0]);
+
+ if (isRoseTattoo) {
+ // Get Teleport position and facing for second NPC
+ _teleport[1].x = s.readSint16LE();
+ _teleport[1].y = s.readSint16LE();
+ _teleport[1]._facing = s.readSint16LE();
+ ADJUST_COORD(_teleport[1]);
+ } else if (_teleport[0].x != -1) {
+ // For Serrated Scalpel, adjust the loaded co-ordinates
+ _teleport[0].x = _teleport[0].x / 100;
+ _teleport[0].y = _teleport[0].y / 100;
+ }
+
+ // Save offset of data, which is actually inside another table inside the room data file
+ // This table is at offset 44 for Serrated Scalpel
+ // TODO: find it for the other game
+ _dataOffset = dataOffset;
}
-void CAnimStream::getNextFrame() {
- // TODO
+void CAnim::load3DO(Common::SeekableReadStream &s, uint32 dataOffset) {
+ // this got reordered on 3DO
+ // maybe it was the 3DO compiler
+
+ _dataSize = s.readUint32BE();
+ // Save offset of data, which is inside another table inside the room data file
+ _dataOffset = dataOffset;
+
+ _position.x = s.readSint16BE();
+ _position.y = s.readSint16BE();
+
+ _type = (SpriteType)s.readUint16BE();
+
+ _goto[0].x = s.readSint16BE();
+ _goto[0].y = s.readSint16BE();
+ _goto[0]._facing = s.readSint16BE();
+
+ _teleport[0].x = s.readSint16BE();
+ _teleport[0].y = s.readSint16BE();
+ _teleport[0]._facing = s.readSint16BE();
+
+ char buffer[12];
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+
+ s.read(_sequences, 30);
+ _flags = s.readByte();
+
+ s.skip(3); // Filler
+
+ _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100;
+ _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100;
+ _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100;
+ _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100;
}
/*----------------------------------------------------------------*/
diff --git a/engines/sherlock/objects.h b/engines/sherlock/objects.h
index d671066a23..38b02aeae8 100644
--- a/engines/sherlock/objects.h
+++ b/engines/sherlock/objects.h
@@ -27,7 +27,9 @@
#include "common/rect.h"
#include "common/str-array.h"
#include "common/str.h"
-#include "sherlock/resources.h"
+#include "sherlock/image_file.h"
+#include "sherlock/fixed_text.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
@@ -76,12 +78,26 @@ enum {
#define MAX_HOLMES_SEQUENCE 16
#define MAX_FRAME 30
-#define FIXED_INT_MULTIPLIER 100
+#define FIXED_INT_MULTIPLIER 1000
// code put into sequences to defines 1-10 type seqs
#define SEQ_TO_CODE 67
#define FLIP_CODE (64 + 128)
#define SOUND_CODE (34 + 128)
+#define HIDE_CODE (7+128) // Code for hiding/unhiding an object from a Sequence
+#define CALL_TALK_CODE (8+128) // Code for call a Talk File from a Sequence
+#define TELEPORT_CODE (9+128) // Code for setting Teleport Data (X,Y)
+#define MOVE_CODE (10+128) // Code for setting Movement Delta (X,Y)
+
+#define GOTO_CODE 228
+#define TALK_SEQ_CODE 252 // Code specifying start of talk sequence frames in a Sequence
+#define TALK_LISTEN_CODE 251 // Code specifying start of talk listen frames in a Sequence
+#define ALLOW_TALK_CODE 250
+
+#define UPPER_LIMIT 0
+#define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT)
+#define LEFT_LIMIT 0
+#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH
class Point32 {
public:
@@ -102,6 +118,17 @@ public:
void operator-=(const Point32 &delta) { x -= delta.x; y -= delta.y; }
};
+class PositionFacing : public Point32 {
+public:
+ int _facing;
+
+ PositionFacing() : Point32(), _facing(0) {}
+ PositionFacing(int xp, int yp, int theFacing) : Point32(xp, yp), _facing(theFacing) {}
+ PositionFacing &operator=(const Point32 &pt) {
+ x = pt.x; y = pt.y;
+ return *this;
+ }
+};
struct WalkSequence {
Common::String _vgsName;
@@ -129,6 +156,9 @@ struct ActionType {
int _cAnimNum;
int _cAnimSpeed;
Common::String _names[NAMES_COUNT];
+ int _useFlag; // Which flag USE will set (if any)
+
+ ActionType();
/**
* Load the data for the action
@@ -136,11 +166,7 @@ struct ActionType {
void load(Common::SeekableReadStream &s);
};
-struct UseType {
- int _cAnimNum;
- int _cAnimSpeed;
- Common::String _names[NAMES_COUNT];
- int _useFlag; // Which flag USE will set (if any)
+struct UseType: public ActionType {
Common::String _target;
Common::String _verb;
@@ -150,66 +176,129 @@ struct UseType {
* Load the data for the UseType
*/
void load(Common::SeekableReadStream &s, bool isRoseTattoo);
-};
+ void load3DO(Common::SeekableReadStream &s);
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+};
-class Sprite {
-private:
+class BaseObject {
+protected:
static SherlockEngine *_vm;
+protected:
+ /**
+ * This will check to see if the object has reached the end of a sequence.
+ * If it has, it switch to whichever next sequence should be started.
+ * @returns true if the end of a sequence was reached
+ */
+ bool checkEndOfSequence();
+
+ /**
+ * Scans through the sequences array and finds the designated sequence.
+ * It then sets the frame number of the start of that sequence
+ */
+ void setObjSequence(int seq, bool wait);
+public:
+ static bool _countCAnimFrames;
+public:
+ SpriteType _type; // Type of object/sprite
+ Common::String _description; // Description lines
+ byte *_sequences; // Holds animation sequences
+ ImageFile *_images; // Sprite images
+ ImageFrame *_imageFrame; // Pointer to shape in the images
+ int _sequenceNumber; // Sequence being used
+ int _startSeq; // Frame sequence starts at
+ int _walkCount; // Walk counter
+ int _allow; // Allowed UI commands
+ int _frameNumber; // Frame number in rame sequence to draw
+ Point32 _position; // Current position
+ Point32 _delta; // Momvement amount
+ Common::Point _oldPosition; // Old position
+ Common::Point _oldSize; // Image's old size
+ Point32 _goto; // Walk destination
+
+ int _lookFlag; // Which flag LOOK will set (if any)
+ int _requiredFlag[2]; // Object will be hidden if not set
+ Common::Point _noShapeSize; // Size of a NO_SHAPE
+ int _status; // Status (open/closed, moved/not)
+ int8 _misc; // Misc field -- use varies with type
+ int _maxFrames; // Number of frames
+ int _flags; // Tells if object can be walked behind
+ AType _aType; // Tells if this is an object, person, talk, etc.
+ int _lookFrames; // How many frames to play of the look anim before pausing
+ int _seqCounter; // How many times this sequence has been executed
+ PositionFacing _lookPosition; // Where to walk when examining object
+ int _lookcAnim;
+ int _seqStack; // Allows gosubs to return to calling frame
+ int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes
+ uint _descOffset; // Tells where description starts in DescText
+ int _seqCounter2; // Counter of calling frame sequence
+ uint _seqSize; // Tells where description starts
+ UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6
+ int _quickDraw; // Flag telling whether to use quick draw routine or not
+ int _scaleVal; // Tells how to scale the sprite
+ int _gotoSeq; // Used by Talk to tell which sequence to goto when able
+ int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen)
+ int _restoreSlot; // Used when talk returns to the previous sequence
+public:
+ BaseObject();
+ virtual ~BaseObject() {}
+ static void setVm(SherlockEngine *vm);
+
+ /**
+ * Returns true if the the object has an Allow Talk Code in the sequence that it's
+ * currently running, specified by the _talkSeq field of the object. If it's 0,
+ * then it's a regular sequence. If it's not 0 but below 128, then it's a Talk Sequence.
+ * If it's above 128, then it's one of the Listen sequences.
+ */
+ bool hasAborts() const;
+
+ /**
+ * Check the state of the object
+ */
+ void checkObject();
+
+ /**
+ * Checks for codes
+ * @param name The name to check for codes
+ * @param messages Provides a lookup list of messages that can be printed
+ * @returns 0 if no codes are found, 1 if codes were found
+ */
+ int checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
+
+ /**
+ * 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.
+ */
+ virtual void setObjTalkSequence(int seq) {}
+};
+
+class Sprite: public BaseObject {
public:
Common::String _name;
- Common::String _description;
Common::String _examine; // Examine in-depth description
Common::String _pickUp; // Message for if you can't pick up object
WalkSequences _walkSequences; // Holds animation sequences
- ImageFile *_images; // Sprite images
- ImageFrame *_imageFrame; // Pointer to shape in the images
- int _walkCount; // Character walk counter
- int _allow; // Allowed menu commands - ObjectAllow
- int _frameNumber; // Frame number in rame sequence to draw
- int _sequenceNumber; // Sequence being used
- Point32 _position; // Current position
- Point32 _delta; // Momvement delta
- Common::Point _oldPosition; // Old position
- Common::Point _oldSize; // Image's old size
- Common::Point _goto; // Walk destination
- SpriteType _type; // Type of object
Common::Point _noShapeSize; // Size of a NO_SHAPE
int _status; // Status: open/closed, moved/not moved
int8 _misc; // Miscellaneous use
- int _numFrames; // How many frames the object has
// Rose Tattoo fields
- int _startSeq; // Frame sequence starts at
- int _flags; // Flags for the sprite
- int _aType; // Tells if this is an object, person, talk, etc.
- int _lookFrames; // How many frames to play of a canim before pausing
- int _seqCounter; // How many times the sequence has been run
- Common::Point _lookPosition; // Where to look when examining object
- int _lookFacing; // Direction to face when examining object
- int _lookCAnim;
- int _seqStack; // Allow gosubs to return to calling frame
- int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes
- uint _descOffset; // Tells where description starts in description text for scene
- int _seqCounter2; // Counter of calling frame sequence
- uint _seqSize; // Size of sequence
- UseType _use[6];
- int _quickDraw; // Flag telling whether to use quick draw routine or not
- int _scaleVal; // Tells how to scale the sprite
- int _requiredFlags1; // This flag must also be set, or the sprite is hidden
- int _gotoSeq; // Used by Talk to tell which sequence to goto when able
- int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen)
- int _restoreSlot; // Used when talk returns to the previous sequence
-
ImageFrame *_stopFrames[8]; // Stop/rest frame for each direction
ImageFile *_altImages; // Images used for alternate NPC sequences
- bool _altSequences; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq)
+ int _altSeq; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq)
int _centerWalk; // Flag telling the walk code to offset the walk destination
Common::Point _adjust; // Fine tuning adjustment to position when drawn
int _oldWalkSequence;
public:
- Sprite() { clear(); }
+ Sprite(): BaseObject() { clear(); }
+ virtual ~Sprite() {}
static void setVm(SherlockEngine *vm) { _vm = vm; }
@@ -224,16 +313,20 @@ public:
void setImageFrame();
/**
- * This adjusts the sprites position, as well as it's animation sequence:
- */
- void adjustSprite();
-
- /**
* Checks the sprite's position to see if it's collided with any special objects
*/
void checkSprite();
/**
+ * 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.
+ */
+ virtual void setObjTalkSequence(int seq) {}
+
+ /**
* Return frame width
*/
int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; }
@@ -242,71 +335,39 @@ public:
* Return frame height
*/
int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; }
-};
-enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 };
-#define USE_COUNT 4
+ /**
+ * Returns the old bounsd for the sprite from the previous frame
+ */
+ const Common::Rect getOldBounds() const;
-class Object {
-private:
- static SherlockEngine *_vm;
+ /**
+ * This adjusts the sprites position, as well as it's animation sequence:
+ */
+ virtual void adjustSprite() = 0;
/**
- * This will check to see if the object has reached the end of a sequence.
- * If it has, it switch to whichever next sequence should be started.
- * @returns true if the end of a sequence was reached
+ * Bring a moving character using the sprite to a standing position
*/
- bool checkEndOfSequence();
+ virtual void gotoStand() = 0;
/**
- * Scans through the sequences array and finds the designated sequence.
- * It then sets the frame number of the start of that sequence
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
*/
- void setObjSequence(int seq, bool wait);
-public:
- static bool _countCAnimFrames;
+ virtual void setWalking() = 0;
+};
- static void setVm(SherlockEngine *vm);
+enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 };
+#define USE_COUNT 4
+
+class Object: public BaseObject {
public:
Common::String _name; // Name
- Common::String _description; // Description lines
Common::String _examine; // Examine in-depth description
int _sequenceOffset;
- uint8 *_sequences; // Holds animation sequences
- ImageFile *_images; // Sprite images
- ImageFrame *_imageFrame; // Pointer to shape in the images
- int _walkCount; // Character walk counter
- int _allow; // Allowed menu commands - ObjectAllow
- int _frameNumber; // Frame number in rame sequence to draw
- int _sequenceNumber; // Sequence being used
- SpriteType _type; // Object type
- Common::Point _position; // Current position
- Common::Point _delta; // Momvement amount
- Common::Point _oldPosition; // Old position
- Common::Point _oldSize; // Image's old size
- Common::Point _goto; // Walk destination
-
int _pickup;
int _defaultCommand; // Default right-click command
- int _lookFlag; // Which flag LOOK will set (if any)
- int _requiredFlag; // Object will be hidden if not set
- Common::Point _noShapeSize; // Size of a NO_SHAPE
- int _status; // Status (open/closed, moved/not)
- int8 _misc; // Misc field -- use varies with type
- int _maxFrames; // Number of frames
- int _flags; // Tells if object can be walked behind
- AType _aType; // Tells if this is an object, person, talk, etc.
- int _lookFrames; // How many frames to play of the look anim before pausing
- int _seqCounter; // How many times this sequence has been executed
- Common::Point _lookPosition; // Where to walk when examining object
- int _lookFacing; // Direction to face when examining object
- int _lookcAnim;
- int _seqStack; // Allows gosubs to return to calling frame
- int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes
- uint _descOffset; // Tells where description starts in DescText
- int _seqCounter2; // Counter of calling frame sequence
- uint _seqSize; // Tells where description starts
- UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6
// Serrated Scalpel fields
int _pickupFlag; // Which flag PICKUP will set (if any)
@@ -314,20 +375,14 @@ public:
ActionType _aClose;
ActionType _aMove;
- // Rose Tattoo fields
- int _quickDraw;
- int _scaleVal;
- int _requiredFlag1;
- int _gotoSeq;
- int _talkSeq;
- int _restoreSlot;
-
Object();
+ virtual ~Object() {}
/**
* Load the data for the object
*/
void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+ void load3DO(Common::SeekableReadStream &s);
/**
* Toggle the type of an object between hidden and active
@@ -335,19 +390,6 @@ public:
void toggleHidden();
/**
- * Check the state of the object
- */
- void checkObject();
-
- /**
- * Checks for codes
- * @param name The name to check for codes
- * @param messages Provides a lookup list of messages that can be printed
- * @returns 0 if no codes are found, 1 if codes were found
- */
- int checkNameForCodes(const Common::String &name, const char *const messages[]);
-
- /**
* Handle setting any flags associated with the object
*/
void setFlagsAndToggles();
@@ -362,13 +404,13 @@ public:
* Handles trying to pick up an object. If allowed, plays an y necessary animation for picking
* up the item, and then adds it to the player's inventory
*/
- int pickUpObject(const char *const messages[]);
+ int pickUpObject(FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
/**
* Return the frame width
*/
int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; }
-
+
/**
* Return the frame height
*/
@@ -388,17 +430,25 @@ public:
* Returns the old bounsd for the sprite from the previous frame
*/
const Common::Rect getOldBounds() const;
+
+ /**
+ * 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.
+ */
+ virtual void setObjTalkSequence(int seq);
};
struct CAnim {
Common::String _name; // Name
Common::Point _position; // Position
- int _size; // Size of uncompressed animation
+ int _dataSize; // Size of uncompressed animation data
+ uint32 _dataOffset; // offset within room file of animation data
int _flags; // Tells if can be walked behind
- Common::Point _goto; // coords holmes should walk to before starting canim
- int _gotoDir;
- Common::Point _teleportPos; // Location Holmes shoul teleport to after
- int _teleportDir; // playing canim
+ PositionFacing _goto[2]; // Position Holmes (and NPC in Rose Tattoo) should walk to before anim starts
+ PositionFacing _teleport[2]; // Location Holmes (and NPC) shoul teleport to after playing canim
// Scalpel specific
byte _sequences[MAX_FRAME]; // Animation sequences
@@ -410,27 +460,8 @@ struct CAnim {
/**
* Load the data for the animation
*/
- void load(Common::SeekableReadStream &s, bool isRoseTattoo);
-};
-
-struct CAnimStream {
- Common::SeekableReadStream *_stream; // Stream to read frames from
- int _frameSize; // Temporary used to store the frame size
-
- void *_images; // TOOD: FIgure out hwo to hook up ImageFile with streaming support
- ImageFrame *_imageFrame;
-
- Common::Point _position; // Animation position
- Common::Rect _oldBounds; // Bounds of previous frame
- Common::Rect _removeBounds; // Remove area for just drawn frame
-
- int _flags; // Flags
- int _scaleVal; // Specifies the scale amount
- int _zPlacement; // Used by doBgAnim for determining Z order
-
- CAnimStream();
-
- void getNextFrame();
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset);
+ void load3DO(Common::SeekableReadStream &s, uint32 dataOffset);
};
struct SceneImage {
diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp
index 0ef49ffefb..b1f4abba47 100644
--- a/engines/sherlock/people.cpp
+++ b/engines/sherlock/people.cpp
@@ -22,14 +22,11 @@
#include "sherlock/people.h"
#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/tattoo/tattoo_people.h"
namespace Sherlock {
-// Walk speeds
-#define MWALK_SPEED 2
-#define XWALK_SPEED 4
-#define YWALK_SPEED 1
-
// Characer animation sequences
static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = {
{ 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Right
@@ -52,8 +49,7 @@ static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = {
// Rose Tattoo walk image libraries
// Walk resources within WALK.LIB
-#define NUM_IN_WALK_LIB 10
-const char *const WALK_LIB_NAMES[10] = {
+const char *const WALK_LIB_NAMES[NUM_IN_WALK_LIB] = {
"SVGAWALK.VGS",
"COATWALK.VGS",
"WATSON.VGS",
@@ -68,34 +64,97 @@ const char *const WALK_LIB_NAMES[10] = {
/*----------------------------------------------------------------*/
-Person::Person() : Sprite(), _walkLoaded(false), _npcIndex(0), _npcStack(0), _npcPause(false) {
- Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
- _tempX = _tempScaleVal = 0;
+Person::Person() : Sprite() {
+ _walkLoaded = false;
+ _oldWalkSequence = -1;
+ _srcZone = _destZone = 0;
}
-void Person::clearNPC() {
- Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
- _npcIndex = _npcStack = 0;
- _npcName = "";
+void Person::goAllTheWay() {
+ Scene &scene = *_vm->_scene;
+ Common::Point srcPt = getSourcePoint();
+
+ // Get the zone the player is currently in
+ _srcZone = scene.whichZone(srcPt);
+ if (_srcZone == -1)
+ _srcZone = scene.closestZone(srcPt);
+
+ // Get the zone of the destination
+ _destZone = scene.whichZone(_walkDest);
+ if (_destZone == -1) {
+ _destZone = scene.closestZone(_walkDest);
+
+ // Check for any restriction of final destination position
+ _walkDest = _vm->_people->restrictToZone(_destZone, _walkDest);
+ }
+
+ // Only do a walk if both zones are acceptable
+ if (_srcZone == -2 || _destZone == -2)
+ return;
+
+ // If the start and dest zones are the same, walk directly to the dest point
+ if (_srcZone == _destZone) {
+ setWalking();
+ } else {
+ // Otherwise a path needs to be formed from the path information
+ int i = scene._walkDirectory[_srcZone][_destZone];
+
+ // See if we need to use a reverse path
+ if (i == -1)
+ i = scene._walkDirectory[_destZone][_srcZone];
+
+ const WalkArray &points = scene._walkPoints[i];
+
+ // See how many points there are between the src and dest zones
+ if (!points._pointsCount || points._pointsCount == -1) {
+ // There are none, so just walk to the new zone
+ setWalking();
+ } else {
+ // There are points, so set up a multi-step path between points
+ // to reach the given destination
+ _walkTo.clear();
+
+ if (scene._walkDirectory[_srcZone][_destZone] != -1) {
+ for (int idx = (int)points.size() - 1; idx >= 0; --idx)
+ _walkTo.push(points[idx]);
+ } else {
+ for (int idx = 0; idx < (int)points.size(); ++idx) {
+ _walkTo.push(points[idx]);
+ }
+ }
+
+ // Final position
+ _walkTo.push(_walkDest);
+
+ // Start walking
+ _walkDest = _walkTo.pop();
+ setWalking();
+ }
+ }
}
/*----------------------------------------------------------------*/
-People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) {
+People *People::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelPeople(vm);
+ else
+ return new Tattoo::TattooPeople(vm);
+}
+
+People::People(SherlockEngine *vm) : _vm(vm) {
_holmesOn = true;
- _oldWalkSequence = -1;
- _allowWalkAbort = false;
+ _allowWalkAbort = true;
_portraitLoaded = false;
_portraitsOn = true;
_clearingThePortrait = false;
- _srcZone = _destZone = 0;
_talkPics = nullptr;
_portraitSide = 0;
_speakerFlip = false;
_holmesFlip = false;
_holmesQuotient = 0;
- _hSavedPos = Common::Point(-1, -1);
- _hSavedFacing = -1;
+ _savedPos = Point32(-1, -1);
+ _savedPos._facing = -1;
_forceWalkReload = false;
_useWalkLib = false;
_walkControl = 0;
@@ -104,9 +163,10 @@ People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) {
}
People::~People() {
- for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- if (_data[idx]._walkLoaded)
- delete _data[PLAYER]._images;
+ for (uint idx = 0; idx < _data.size(); ++idx) {
+ if (_data[idx]->_walkLoaded)
+ delete _data[idx]->_images;
+ delete _data[idx];
}
delete _talkPics;
@@ -114,27 +174,33 @@ People::~People() {
}
void People::reset() {
- _data[0]._description = "Sherlock Holmes!";
+ SaveManager &saves = *_vm->_saves;
+ Talk &talk = *_vm->_talk;
+ _data[HOLMES]->_description = "Sherlock Holmes!";
// Note: Serrated Scalpel only uses a single Person slot for Sherlock.. Watson is handled by scene sprites
int count = IS_SERRATED_SCALPEL ? 1 : MAX_CHARACTERS;
for (int idx = 0; idx < count; ++idx) {
- Sprite &p = _data[idx];
+ Person &p = *_data[idx];
- p._type = (idx == 0) ? CHARACTER : INVALID;
- if (IS_SERRATED_SCALPEL)
- p._position = Point32(10000, 11000);
- else
- p._position = Point32(36000, 29000);
+ if (IS_SERRATED_SCALPEL) {
+ p._type = CHARACTER;
+ p._position = Point32(100 * FIXED_INT_MULTIPLIER, 110 * FIXED_INT_MULTIPLIER);
+ } else if (!talk._scriptMoreFlag && !saves._justLoaded) {
+ p._type = (idx == 0) ? CHARACTER : INVALID;
+ p._position = Point32(36 * FIXED_INT_MULTIPLIER, 29 * FIXED_INT_MULTIPLIER);
+ p._use[0]._verb = "";
+ p._use[1]._verb = "";
+ }
- p._sequenceNumber = STOP_DOWNRIGHT;
+ p._sequenceNumber = IS_SERRATED_SCALPEL ? (int)Scalpel::STOP_DOWNRIGHT : (int)Tattoo::STOP_DOWNRIGHT;
p._imageFrame = nullptr;
p._frameNumber = 1;
- p._delta = Common::Point(0, 0);
+ p._startSeq = 0;
+ p._delta = Point32(0, 0);
p._oldPosition = Common::Point(0, 0);
p._oldSize = Common::Point(0, 0);
p._misc = 0;
- p._walkCount = 0;
p._pickUp = "";
p._allow = 0;
p._noShapeSize = Common::Point(0, 0);
@@ -147,11 +213,13 @@ void People::reset() {
p._restoreSlot = 0;
p._startSeq = 0;
p._altImages = nullptr;
- p._altSequences = 0;
+ p._altSeq = 0;
p._centerWalk = true;
p._adjust = Common::Point(0, 0);
// Load the default walk sequences
+ p._walkCount = 0;
+ p._walkTo.clear();
p._oldWalkSequence = -1;
p._walkSequences.clear();
if (IS_SERRATED_SCALPEL) {
@@ -166,93 +234,17 @@ void People::reset() {
}
}
}
-
- // Reset any walk path in progress when Sherlock leaves scenes
- _walkTo.clear();
-}
-
-bool People::loadWalk() {
- Resources &res = *_vm->_res;
- bool result = false;
-
- if (IS_SERRATED_SCALPEL) {
- if (_data[PLAYER]._walkLoaded) {
- return false;
- } else {
- _data[PLAYER]._images = new ImageFile("walk.vgs");
- _data[PLAYER].setImageFrame();
- _data[PLAYER]._walkLoaded = true;
-
- result = true;
- }
- } else {
- for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- if (!_data[idx]._walkLoaded && (_data[idx]._type == CHARACTER || _data[idx]._type == HIDDEN_CHARACTER)) {
- if (_data[idx]._type == HIDDEN_CHARACTER)
- _data[idx]._type = INVALID;
-
- // See if this is one of the more used Walk Graphics stored in WALK.LIB
- for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) {
- if (!_data[0]._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) {
- _useWalkLib = true;
- break;
- }
- }
-
- // Load the images for the character
- _data[idx]._images = new ImageFile(_data[idx]._walkVGSName, false);
- _data[idx]._numFrames = _data[idx]._images->size();
-
- // Load walk sequence data
- Common::String fname = Common::String(_data[idx]._walkVGSName.c_str(), strchr(_data[idx]._walkVGSName.c_str(), '.'));
- fname += ".SEQ";
-
- // Load the walk sequence data
- Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib");
-
- _data[idx]._walkSequences.resize(stream->readByte());
-
- for (uint seqNum = 0; seqNum < _data[idx]._walkSequences.size(); ++seqNum)
- _data[idx]._walkSequences[seqNum].load(*stream);
-
- // Close the sequences resource
- delete stream;
- _useWalkLib = false;
-
- _data[idx]._frameNumber = 0;
- _data[idx].setImageFrame();
-
- // Set the stop Frames pointers
- for (int dirNum = 0; dirNum < 8; ++dirNum) {
- int count = 0;
- while (_data[idx]._walkSequences[dirNum + 8][count] != 0)
- ++count;
- count += 2;
- count = _data[idx]._walkSequences[dirNum + 8][count] - 1;
- _data[idx]._stopFrames[dirNum] = &(*_data[idx]._images)[count];
- }
-
- result = true;
- _data[idx]._walkLoaded = true;
- } else if (_data[idx]._type != CHARACTER) {
- _data[idx]._walkLoaded = false;
- }
- }
- }
-
- _forceWalkReload = false;
- return result;
}
bool People::freeWalk() {
bool result = false;
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- if (_data[idx]._walkLoaded) {
- delete _data[idx]._images;
- _data[idx]._images = nullptr;
+ if (_data[idx]->_walkLoaded) {
+ delete _data[idx]->_images;
+ _data[idx]->_images = nullptr;
- _data[idx]._walkLoaded = false;
+ _data[idx]->_walkLoaded = false;
result = true;
}
}
@@ -260,322 +252,9 @@ bool People::freeWalk() {
return result;
}
-void People::setWalking() {
- Map &map = *_vm->_map;
- Scene &scene = *_vm->_scene;
- int oldDirection, oldFrame;
- Common::Point speed, delta;
-
- // Flag that player has now walked in the scene
- scene._walkedInScene = true;
-
- // Stop any previous walking, since a new dest is being set
- _player._walkCount = 0;
- oldDirection = _player._sequenceNumber;
- oldFrame = _player._frameNumber;
-
- // Set speed to use horizontal and vertical movement
- if (map._active) {
- speed = Common::Point(MWALK_SPEED, MWALK_SPEED);
- } else {
- speed = Common::Point(XWALK_SPEED, YWALK_SPEED);
- }
-
- // If the player is already close to the given destination that no
- // walking is needed, move to the next straight line segment in the
- // overall walking route, if there is one
- for (;;) {
- // Since we want the player to be centered on the destination they
- // clicked, but characters draw positions start at their left, move
- // the destination half the character width to draw him centered
- int temp;
- if (_walkDest.x >= (temp = _player._imageFrame->_frame.w / 2))
- _walkDest.x -= temp;
-
- delta = Common::Point(
- ABS(_player._position.x / 100 - _walkDest.x),
- ABS(_player._position.y / 100 - _walkDest.y)
- );
-
- // If we're ready to move a sufficient distance, that's it. Otherwise,
- // move onto the next portion of the walk path, if there is one
- if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
- break;
-
- // Pop next walk segment off the walk route stack
- _walkDest = _walkTo.pop();
- }
-
- // If a sufficient move is being done, then start the move
- if (delta.x > 3 || delta.y) {
- // See whether the major movement is horizontal or vertical
- if (delta.x >= delta.y) {
- // Set the initial frame sequence for the left and right, as well
- // as setting the delta x depending on direction
- if (_walkDest.x < (_player._position.x / 100)) {
- _player._sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT);
- _player._delta.x = speed.x * -100;
- } else {
- _player._sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT);
- _player._delta.x = speed.x * 100;
- }
-
- // See if the x delta is too small to be divided by the speed, since
- // this would cause a divide by zero error
- if (delta.x >= speed.x) {
- // Det the delta y
- _player._delta.y = (delta.y * 100) / (delta.x / speed.x);
- if (_walkDest.y < (_player._position.y / 100))
- _player._delta.y = -_player._delta.y;
-
- // Set how many times we should add the delta to the player's position
- _player._walkCount = delta.x / speed.x;
- } else {
- // The delta x was less than the speed (ie. we're really close to
- // the destination). So set delta to 0 so the player won't move
- _player._delta = Common::Point(0, 0);
- _player._position = Common::Point(_walkDest.x * 100, _walkDest.y * 100);
- _player._walkCount = 1;
- }
-
- // See if the sequence needs to be changed for diagonal walking
- if (_player._delta.y > 150) {
- if (!map._active) {
- switch (_player._sequenceNumber) {
- case WALK_LEFT:
- _player._sequenceNumber = WALK_DOWNLEFT;
- break;
- case WALK_RIGHT:
- _player._sequenceNumber = WALK_DOWNRIGHT;
- break;
- }
- }
- } else if (_player._delta.y < -150) {
- if (!map._active) {
- switch (_player._sequenceNumber) {
- case WALK_LEFT:
- _player._sequenceNumber = WALK_UPLEFT;
- break;
- case WALK_RIGHT:
- _player._sequenceNumber = WALK_UPRIGHT;
- break;
- }
- }
- }
- } else {
- // Major movement is vertical, so set the sequence for up and down,
- // and set the delta Y depending on the direction
- if (_walkDest.y < (_player._position.y / 100)) {
- _player._sequenceNumber = WALK_UP;
- _player._delta.y = speed.y * -100;
- } else {
- _player._sequenceNumber = WALK_DOWN;
- _player._delta.y = speed.y * 100;
- }
-
- // If we're on the overhead map, set the sequence so we keep moving
- // in the same direction
- if (map._active)
- _player._sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection;
-
- // Set the delta x
- _player._delta.x = (delta.x * 100) / (delta.y / speed.y);
- if (_walkDest.x < (_player._position.x / 100))
- _player._delta.x = -_player._delta.x;
-
- _player._walkCount = delta.y / speed.y;
- }
- }
-
- // See if the new walk sequence is the same as the old. If it's a new one,
- // we need to reset the frame number to zero so it's animation starts at
- // it's beginning. Otherwise, if it's the same sequence, we can leave it
- // as is, so it keeps the animation going at wherever it was up to
- if (_player._sequenceNumber != _oldWalkSequence)
- _player._frameNumber = 0;
- _oldWalkSequence = _player._sequenceNumber;
-
- if (!_player._walkCount)
- gotoStand(_player);
-
- // If the sequence is the same as when we started, then Holmes was
- // standing still and we're trying to re-stand him, so reset Holmes'
- // rame to the old frame number from before it was reset to 0
- if (_player._sequenceNumber == oldDirection)
- _player._frameNumber = oldFrame;
-}
-
-void People::gotoStand(Sprite &sprite) {
- Map &map = *_vm->_map;
- _walkTo.clear();
- sprite._walkCount = 0;
-
- switch (sprite._sequenceNumber) {
- case WALK_UP:
- sprite._sequenceNumber = STOP_UP;
- break;
- case WALK_DOWN:
- sprite._sequenceNumber = STOP_DOWN;
- break;
- case TALK_LEFT:
- case WALK_LEFT:
- sprite._sequenceNumber = STOP_LEFT;
- break;
- case TALK_RIGHT:
- case WALK_RIGHT:
- sprite._sequenceNumber = STOP_RIGHT;
- break;
- case WALK_UPRIGHT:
- sprite._sequenceNumber = STOP_UPRIGHT;
- break;
- case WALK_UPLEFT:
- sprite._sequenceNumber = STOP_UPLEFT;
- break;
- case WALK_DOWNRIGHT:
- sprite._sequenceNumber = STOP_DOWNRIGHT;
- break;
- case WALK_DOWNLEFT:
- sprite._sequenceNumber = STOP_DOWNLEFT;
- break;
- default:
- break;
- }
-
- // Only restart frame at 0 if the sequence number has changed
- if (_oldWalkSequence != -1 || sprite._sequenceNumber == STOP_UP)
- sprite._frameNumber = 0;
-
- if (map._active) {
- sprite._sequenceNumber = 0;
- _player._position.x = (map[map._charPoint].x - 6) * 100;
- _player._position.y = (map[map._charPoint].x + 10) * 100;
- }
-
- _oldWalkSequence = -1;
- _allowWalkAbort = true;
-}
-
-void People::walkToCoords(const Common::Point &destPos, int destDir) {
- Events &events = *_vm->_events;
- Scene &scene = *_vm->_scene;
- Talk &talk = *_vm->_talk;
-
- CursorId oldCursor = events.getCursor();
- events.setCursor(WAIT);
-
- _walkDest = Common::Point(destPos.x / 100 + 10, destPos.y / 100);
- _allowWalkAbort = true;
- goAllTheWay();
-
- // Keep calling doBgAnim until the walk is done
- do {
- events.pollEventsAndWait();
- scene.doBgAnim();
- } while (!_vm->shouldQuit() && _player._walkCount);
-
- if (!talk._talkToAbort) {
- // Put player exactly on destination position, and set direction
- _player._position = destPos;
- _player._sequenceNumber = destDir;
- gotoStand(_player);
-
- // Draw Holmes facing the new direction
- scene.doBgAnim();
-
- if (!talk._talkToAbort)
- events.setCursor(oldCursor);
- }
-}
-
-void People::goAllTheWay() {
- Scene &scene = *_vm->_scene;
- Common::Point srcPt(_player._position.x / 100 + _player.frameWidth() / 2,
- _player._position.y / 100);
-
- // Get the zone the player is currently in
- _srcZone = scene.whichZone(srcPt);
- if (_srcZone == -1)
- _srcZone = scene.closestZone(srcPt);
-
- // Get the zone of the destination
- _destZone = scene.whichZone(_walkDest);
- if (_destZone == -1) {
- _destZone = scene.closestZone(_walkDest);
-
- // The destination isn't in a zone
- if (_walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1))
- _walkDest.x = SHERLOCK_SCREEN_WIDTH - 2;
-
- // Trace a line between the centroid of the found closest zone to
- // the destination, to find the point at which the zone will be left
- const Common::Rect &destRect = scene._zones[_destZone];
- const Common::Point destCenter((destRect.left + destRect.right) / 2,
- (destRect.top + destRect.bottom) / 2);
- const Common::Point delta = _walkDest - destCenter;
- Common::Point pt(destCenter.x * 100, destCenter.y * 100);
-
- // Move along the line until the zone is left
- do {
- pt += delta;
- } while (destRect.contains(pt.x / 100, pt.y / 100));
-
- // Set the new walk destination to the last point that was in the
- // zone just before it was left
- _walkDest = Common::Point((pt.x - delta.x * 2) / 100,
- (pt.y - delta.y * 2) / 100);
- }
-
- // Only do a walk if both zones are acceptable
- if (_srcZone == -2 || _destZone == -2)
- return;
-
- // If the start and dest zones are the same, walk directly to the dest point
- if (_srcZone == _destZone) {
- setWalking();
- } else {
- // Otherwise a path needs to be formed from the path information
- int i = scene._walkDirectory[_srcZone][_destZone];
-
- // See if we need to use a reverse path
- if (i == -1)
- i = scene._walkDirectory[_destZone][_srcZone];
-
- int count = scene._walkData[i];
- ++i;
-
- // See how many points there are between the src and dest zones
- if (!count || count == -1) {
- // There are none, so just walk to the new zone
- setWalking();
- } else {
- // There are points, so set up a multi-step path between points
- // to reach the given destination
- _walkTo.clear();
-
- if (scene._walkDirectory[_srcZone][_destZone] != -1) {
- i += 3 * (count - 1);
- for (int idx = 0; idx < count; ++idx, i -= 3) {
- _walkTo.push(Common::Point(READ_LE_UINT16(&scene._walkData[i]),
- scene._walkData[i + 2]));
- }
- } else {
- for (int idx = 0; idx < count; ++idx, i += 3) {
- _walkTo.push(Common::Point(READ_LE_UINT16(&scene._walkData[i]), scene._walkData[i + 2]));
- }
- }
-
- // Final position
- _walkTo.push(_walkDest);
-
- // Start walking
- _walkDest = _walkTo.pop();
- setWalking();
- }
- }
-}
-
int People::findSpeaker(int speaker) {
Scene &scene = *_vm->_scene;
+ const char *portrait = _characters[speaker]._portrait;
for (int idx = 0; idx < (int)scene._bgShapes.size(); ++idx) {
Object &obj = scene._bgShapes[idx];
@@ -583,7 +262,7 @@ int People::findSpeaker(int speaker) {
if (obj._type == ACTIVE_BG_SHAPE) {
Common::String name(obj._name.c_str(), obj._name.c_str() + 4);
- if (name.equalsIgnoreCase(_characters[speaker]._portrait)
+ if (name.equalsIgnoreCase(portrait)
&& obj._name[4] >= '0' && obj._name[4] <= '9')
return idx;
}
@@ -622,91 +301,4 @@ void People::clearTalking() {
}
}
-void People::setTalking(int speaker) {
- Resources &res = *_vm->_res;
-
- // If no speaker is specified, then we can exit immediately
- if (speaker == -1)
- return;
-
- if (_portraitsOn) {
- delete _talkPics;
- Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait);
- _talkPics = new ImageFile(filename);
-
- // Load portrait sequences
- Common::SeekableReadStream *stream = res.load("sequence.txt");
- stream->seek(speaker * MAX_FRAME);
-
- int idx = 0;
- do {
- _portrait._sequences[idx] = stream->readByte();
- ++idx;
- } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]);
-
- delete stream;
-
- _portrait._maxFrames = idx;
- _portrait._frameNumber = 0;
- _portrait._sequenceNumber = 0;
- _portrait._images = _talkPics;
- _portrait._imageFrame = &(*_talkPics)[0];
- _portrait._position = Common::Point(_portraitSide, 10);
- _portrait._delta = Common::Point(0, 0);
- _portrait._oldPosition = Common::Point(0, 0);
- _portrait._goto = Common::Point(0, 0);
- _portrait._flags = 5;
- _portrait._status = 0;
- _portrait._misc = 0;
- _portrait._allow = 0;
- _portrait._type = ACTIVE_BG_SHAPE;
- _portrait._name = " ";
- _portrait._description = " ";
- _portrait._examine = " ";
- _portrait._walkCount = 0;
-
- if (_holmesFlip || _speakerFlip) {
- _portrait._flags |= 2;
-
- _holmesFlip = false;
- _speakerFlip = false;
- }
-
- if (_portraitSide == 20)
- _portraitSide = 220;
- else
- _portraitSide = 20;
-
- _portraitLoaded = true;
- }
-}
-
-void People::synchronize(Common::Serializer &s) {
- s.syncAsByte(_holmesOn);
-
- if (IS_SERRATED_SCALPEL) {
- s.syncAsSint16LE(_player._position.x);
- s.syncAsSint16LE(_player._position.y);
- s.syncAsSint16LE(_player._sequenceNumber);
- } else {
- for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- Person &p = _data[idx];
- s.syncAsSint16LE(p._position.x);
- s.syncAsSint16LE(p._position.y);
- s.syncAsSint16LE(p._sequenceNumber);
- s.syncAsSint16LE(p._type);
- s.syncString(p._walkVGSName);
- s.syncString(p._description);
- s.syncString(p._examine);
- }
- }
-
- s.syncAsSint16LE(_holmesQuotient);
-
- if (s.isLoading()) {
- _hSavedPos = _player._position;
- _hSavedFacing = _player._sequenceNumber;
- }
-}
-
} // End of namespace Sherlock
diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h
index 013727d8ba..c47d45145a 100644
--- a/engines/sherlock/people.h
+++ b/engines/sherlock/people.h
@@ -24,35 +24,28 @@
#define SHERLOCK_PEOPLE_H
#include "common/scummsys.h"
-#include "common/serializer.h"
#include "common/queue.h"
#include "sherlock/objects.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
enum PeopleId {
- PLAYER = 0,
- AL = 0,
- PEG = 1,
- MAX_CHARACTERS = 6,
- MAX_NPC = 5,
+ HOLMES = 0,
+ WATSON = 1,
MAX_NPC_PATH = 200
};
-// Animation sequence identifiers for characters
-enum {
- WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4,
- STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8,
- WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11,
- STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14,
- STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4
-};
-
enum {
MAP_UP = 1, MAP_UPRIGHT = 2, MAP_RIGHT = 1, MAP_DOWNRIGHT = 4,
MAP_DOWN = 5, MAP_DOWNLEFT = 6, MAP_LEFT = 2, MAP_UPLEFT = 8
};
+#define NUM_IN_WALK_LIB 10
+extern const char *const WALK_LIB_NAMES[10];
+
+#define MAX_CHARACTERS (IS_SERRATED_SCALPEL ? 1 : 6)
+
struct PersonData {
const char *_name;
const char *_portrait;
@@ -64,46 +57,55 @@ struct PersonData {
};
class Person : public Sprite {
+protected:
+ /**
+ * Get the source position for a character potentially affected by scaling
+ */
+ virtual Common::Point getSourcePoint() const = 0;
public:
+ Common::Queue<Common::Point> _walkTo;
+ int _srcZone, _destZone;
bool _walkLoaded;
Common::String _portrait;
-
- // NPC related fields
- int _npcIndex;
- int _npcStack;
- bool _npcPause;
- byte _npcPath[MAX_NPC_PATH];
+ Common::Point _walkDest;
Common::String _npcName;
- int _tempX;
- int _tempScaleVal;
// Rose Tattoo fields
Common::String _walkVGSName; // Name of walk library person is using
public:
Person();
+ virtual ~Person() {}
+
+ /**
+ * Called to set the character walking to the current cursor location.
+ * It uses the zones and the inter-zone points to determine a series
+ * of steps to walk to get to that position.
+ */
+ void goAllTheWay();
/**
- * Clear the NPC related data
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir) = 0;
+
+ /**
+ * Center the visible screen so that the person is in the center of the screen
*/
- void clearNPC();
+ virtual void centerScreenOnPerson() {}
};
class SherlockEngine;
class People {
-private:
+protected:
SherlockEngine *_vm;
- Person _data[MAX_CHARACTERS];
- int _oldWalkSequence;
- int _srcZone, _destZone;
+ Common::Array<Person *> _data;
+
+ People(SherlockEngine *vm);
public:
Common::Array<PersonData> _characters;
ImageFile *_talkPics;
- Common::Point _walkDest;
- Common::Point _hSavedPos;
- int _hSavedFacing;
- Common::Queue<Common::Point> _walkTo;
- Person &_player;
+ PositionFacing _savedPos;
bool _holmesOn;
bool _portraitLoaded;
bool _portraitsOn;
@@ -119,17 +121,11 @@ public:
int _walkControl;
public:
- People(SherlockEngine *vm);
- ~People();
+ static People *init(SherlockEngine *vm);
+ virtual ~People();
- Person &operator[](PeopleId id) {
- assert(id < MAX_CHARACTERS);
- return _data[id];
- }
- Person &operator[](int idx) {
- assert(idx < MAX_CHARACTERS);
- return _data[idx];
- }
+ Person &operator[](PeopleId id) { return *_data[id]; }
+ Person &operator[](int idx) { return *_data[idx]; }
/**
* Reset the player data
@@ -137,59 +133,50 @@ public:
void reset();
/**
- * Load the walking images for Sherlock
- */
- bool loadWalk();
-
- /**
* If the walk data has been loaded, then it will be freed
*/
bool freeWalk();
/**
- * Set the variables for moving a character from one poisition to another
- * in a straight line - goAllTheWay must have been previously called to
- * check for any obstacles in the path.
- */
- void setWalking();
-
- /**
- * Bring a moving character to a standing position. If the Scalpel chessboard
- * is being displayed, then the chraracter will always face down.
+ * Turn off any currently active portraits, and removes them from being drawn
*/
- void gotoStand(Sprite &sprite);
+ void clearTalking();
/**
- * Walk to the co-ordinates passed, and then face the given direction
- */
- void walkToCoords(const Common::Point &destPos, int destDir);
+ * Finds the scene background object corresponding to a specified speaker
+ */
+ virtual int findSpeaker(int speaker);
/**
- * Called to set the character walking to the current cursor location.
- * It uses the zones and the inter-zone points to determine a series
- * of steps to walk to get to that position.
+ * Synchronize the data for a savegame
*/
- void goAllTheWay();
+ virtual void synchronize(Serializer &s) = 0;
/**
- * Finds the scene background object corresponding to a specified speaker
+ * Change the sequence of the scene background object associated with the current speaker.
*/
- int findSpeaker(int speaker);
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1) = 0;
/**
- * Turn off any currently active portraits, and removes them from being drawn
+ * Load the walking images for Sherlock
*/
- void clearTalking();
+ virtual bool loadWalk() = 0;
/**
- * Setup the data for an animating speaker portrait at the top of the screen
+ * Restrict passed point to zone using Sherlock's positioning rules
*/
- void setTalking(int speaker);
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos) = 0;
/**
- * Synchronize the data for a savegame
+ * 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 synchronize(Common::Serializer &s);
+ virtual void setListenSequence(int speaker, int sequenceNum = 1) = 0;
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp
index 2e6a0c2d7c..c4093048bd 100644
--- a/engines/sherlock/resources.cpp
+++ b/engines/sherlock/resources.cpp
@@ -64,7 +64,7 @@ void Cache::load(const Common::String &name, Common::SeekableReadStream &stream)
// Check whether the file is compressed
if (signature == MKTAG('L', 'Z', 'V', 26)) {
- // It's compressed, so decompress the file and store it's data in the cache entry
+ // It's compressed, so decompress the file and store its data in the cache entry
Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream);
cacheEntry.resize(decompressed->size());
decompressed->read(&cacheEntry[0], decompressed->size());
@@ -89,18 +89,37 @@ Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) {
_resourceIndex = -1;
if (_vm->_interactiveFl) {
- addToCache("vgs.lib");
- addToCache("talk.lib");
- addToCache("journal.txt");
+ if (!IS_3DO) {
+ addToCache("vgs.lib");
+ addToCache("talk.lib");
+ addToCache("journal.txt");
+
+ if (IS_SERRATED_SCALPEL) {
+ addToCache("sequence.txt");
+ addToCache("portrait.lib");
+ } else {
+ addToCache("walk.lib");
+ }
+ } else {
+ // 3DO
- if (IS_SERRATED_SCALPEL) {
- addToCache("sequence.txt");
- addToCache("portrait.lib");
+ // ITEM data from VGS.LIB is in ITEM.LIB
+ addToCache("item.lib");
+
+ // talk.lib - resources themselves seem to be the same, although a few texts were slightly changed
+ addToCache("talk.lib");
+
+ // remaining files are missing
+ // portraits were replaced with FMV
}
}
}
void Resources::addToCache(const Common::String &filename) {
+ // Return immediately if the library has already been loaded
+ if (_indexes.contains(filename))
+ return;
+
_cache.load(filename);
// Check to see if the file is a library
@@ -176,16 +195,27 @@ void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) {
}
}
-Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile) {
+Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile,
+ bool suppressErrors) {
// Open up the library for access
Common::SeekableReadStream *libStream = load(libraryFile);
- // Check if the library has already had it's index read, and if not, load it
+ // Check if the library has already had its index read, and if not, load it
if (!_indexes.contains(libraryFile))
loadLibraryIndex(libraryFile, libStream, false);
+ LibraryIndex &libIndex = _indexes[libraryFile];
+
+ // Handle if resource is not present
+ if (!libIndex.contains(filename)) {
+ if (!suppressErrors)
+ error("Could not find resource - %s", filename.c_str());
+
+ delete libStream;
+ return nullptr;
+ }
// Extract the data for the specified resource and return it
- LibraryEntry &entry = _indexes[libraryFile][filename];
+ LibraryEntry &entry = libIndex[filename];
libStream->seek(entry._offset);
Common::SeekableReadStream *stream = libStream->readStream(entry._size);
decompressIfNecessary(stream);
@@ -203,38 +233,80 @@ void Resources::loadLibraryIndex(const Common::String &libFilename,
Common::SeekableReadStream *stream, bool isNewStyle) {
uint32 offset, nextOffset;
+ // Return immediately if the library has already been loaded
+ if (_indexes.contains(libFilename))
+ return;
+
// Create an index entry
_indexes[libFilename] = LibraryIndex();
LibraryIndex &index = _indexes[libFilename];
// Read in the number of resources
stream->seek(4);
- int count = stream->readUint16LE();
+ int count = 0;
- if (isNewStyle)
- stream->seek((count + 1) * 8, SEEK_CUR);
+ if (!IS_3DO) {
+ // PC
+ count = stream->readUint16LE();
- // Loop through reading in the entries
- for (int idx = 0; idx < count; ++idx) {
- // Read the name of the resource
- char resName[13];
- stream->read(resName, 13);
- resName[12] = '\0';
+ if (isNewStyle)
+ stream->seek((count + 1) * 8, SEEK_CUR);
- // Read the offset
- offset = stream->readUint32LE();
+ // Loop through reading in the entries
+ for (int idx = 0; idx < count; ++idx) {
+ // Read the name of the resource
+ char resName[13];
+ stream->read(resName, 13);
+ resName[12] = '\0';
- if (idx == (count - 1)) {
- nextOffset = stream->size();
- } else {
- // Read the size by jumping forward to read the next entry's offset
- stream->seek(13, SEEK_CUR);
- nextOffset = stream->readUint32LE();
- stream->seek(-17, SEEK_CUR);
+ // Read the offset
+ offset = stream->readUint32LE();
+
+ if (idx == (count - 1)) {
+ nextOffset = stream->size();
+ } else {
+ // Read the size by jumping forward to read the next entry's offset
+ stream->seek(13, SEEK_CUR);
+ nextOffset = stream->readUint32LE();
+ stream->seek(-17, SEEK_CUR);
+ }
+
+ // Add the entry to the index
+ index[resName] = LibraryEntry(idx, offset, nextOffset - offset);
}
- // Add the entry to the index
- index[resName] = LibraryEntry(idx, offset, nextOffset - offset);
+ } else {
+ // 3DO
+ count = stream->readUint16BE();
+
+ // 3DO header
+ // Loop through reading in the entries
+
+ // Read offset of first entry
+ offset = stream->readUint32BE();
+
+ for (int idx = 0; idx < count; ++idx) {
+
+ // Read the name of the resource
+ char resName[13];
+ stream->read(resName, 13);
+ resName[12] = '\0';
+
+ stream->skip(3); // filler
+
+ if (idx == (count - 1)) {
+ nextOffset = stream->size();
+ } else {
+ // Read the offset of the next entry
+ nextOffset = stream->readUint32BE();
+ }
+
+ // Add the entry to the index
+ index[resName] = LibraryEntry(idx, offset, nextOffset - offset);
+
+ // use next offset as current offset
+ offset = nextOffset;
+ }
}
}
@@ -242,10 +314,18 @@ int Resources::resourceIndex() const {
return _resourceIndex;
}
+void Resources::getResourceNames(const Common::String &libraryFile, Common::StringArray &names) {
+ addToCache(libraryFile);
+ LibraryIndex &libIndex = _indexes[libraryFile];
+ for (LibraryIndex::iterator i = libIndex.begin(); i != libIndex.end(); ++i) {
+ names.push_back(i->_key);
+ }
+}
+
Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source) {
// This variation can't be used by Rose Tattoo, since compressed resources include the input size,
// not the output size. Which means their decompression has to be done via passed buffers
- assert(_vm->getGameID() == GType_SerratedScalpel);
+ assert(IS_SERRATED_SCALPEL);
uint32 id = source.readUint32BE();
assert(id == MKTAG('L', 'Z', 'V', 0x1A));
@@ -255,7 +335,7 @@ Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &so
}
Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) {
- int inSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1;
+ int inSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1;
byte *outBuffer = (byte *)malloc(outSize);
Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES);
@@ -265,7 +345,7 @@ Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &so
}
void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) {
- int inputSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1;
+ int inputSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1;
decompressLZ(source, buffer, outSize, inputSize);
}
@@ -316,184 +396,4 @@ void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer
} while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos));
}
-/*----------------------------------------------------------------*/
-
-SherlockEngine *ImageFile::_vm;
-
-void ImageFile::setVm(SherlockEngine *vm) {
- _vm = vm;
-}
-
-ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) {
- Common::SeekableReadStream *stream = _vm->_res->load(name);
-
- Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
- load(*stream, skipPal, animImages);
-
- delete stream;
-}
-
-ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) {
- Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
- load(stream, skipPal, false);
-}
-
-ImageFile::~ImageFile() {
- for (uint idx = 0; idx < size(); ++idx)
- (*this)[idx]._frame.free();
-}
-
-void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) {
- loadPalette(stream);
-
- int streamSize = stream.size();
- while (stream.pos() < streamSize) {
- ImageFrame frame;
- frame._width = stream.readUint16LE() + 1;
- frame._height = stream.readUint16LE() + 1;
- frame._paletteBase = stream.readByte();
-
- if (animImages) {
- // Animation cutscene image files use a 16-bit x offset
- frame._offset.x = stream.readUint16LE();
- frame._rleEncoded = (frame._offset.x & 0xff) == 1;
- frame._offset.y = stream.readByte();
- } else {
- // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset
- frame._rleEncoded = stream.readByte() == 1;
- frame._offset.x = stream.readByte();
- frame._offset.y = stream.readByte();
- }
-
- frame._rleEncoded = !skipPalette && frame._rleEncoded;
-
- if (frame._paletteBase) {
- // Nibble packed frame data
- frame._size = (frame._width * frame._height) / 2;
- } else if (frame._rleEncoded) {
- // This size includes the header size, which we subtract
- frame._size = stream.readUint16LE() - 11;
- frame._rleMarker = stream.readByte();
- } else {
- // Uncompressed data
- frame._size = frame._width * frame._height;
- }
-
- // Load data for frame and decompress it
- byte *data = new byte[frame._size];
- stream.read(data, frame._size);
- decompressFrame(frame, data);
- delete[] data;
-
- push_back(frame);
- }
-}
-
-void ImageFile::loadPalette(Common::SeekableReadStream &stream) {
- // Check for palette
- int v1 = stream.readUint16LE() + 1;
- int v2 = stream.readUint16LE() + 1;
- stream.skip(1); // Skip paletteBase byte
- bool rleEncoded = stream.readByte() == 1;
- int palSize = v1 * v2;
-
- if ((palSize - 12) == PALETTE_SIZE && !rleEncoded) {
- // Found palette, so read it in
- stream.seek(2 + 12, SEEK_CUR);
- for (int idx = 0; idx < PALETTE_SIZE; ++idx)
- _palette[idx] = VGA_COLOR_TRANS(stream.readByte());
- } else {
- // Not a palette, so rewind to start of frame data for normal frame processing
- stream.seek(-6, SEEK_CUR);
- }
-}
-
-void ImageFile::decompressFrame(ImageFrame &frame, const byte *src) {
- frame._frame.create(frame._width, frame._height, Graphics::PixelFormat::createFormatCLUT8());
-
- if (frame._paletteBase) {
- // Nibble-packed
- byte *pDest = (byte *)frame._frame.getPixels();
- for (uint idx = 0; idx < frame._size; ++idx, ++src) {
- *pDest++ = *src & 0xF;
- *pDest++ = (*src >> 4);
- }
- } else if (frame._rleEncoded && _vm->getGameID() == GType_RoseTattoo) {
- // Rose Tattoo run length encoding doesn't use the RLE marker byte
- byte *dst = (byte *)frame._frame.getPixels();
-
- for (int yp = 0; yp < frame._height; ++yp) {
- int xSize = frame._width;
- while (xSize > 0) {
- // Skip a given number of pixels
- byte skip = *src++;
- dst += skip;
- xSize -= skip;
- if (!xSize)
- break;
-
- // Get a run length, and copy the following number of pixels
- int rleCount = *src++;
- xSize -= rleCount;
- while (rleCount-- > 0)
- *dst++ = *src++;
- }
- assert(xSize == 0);
- }
- } else if (frame._rleEncoded) {
- // RLE encoded
- byte *dst = (byte *)frame._frame.getPixels();
-
- int frameSize = frame._width * frame._height;
- while (frameSize > 0) {
- if (*src == frame._rleMarker) {
- byte rleColor = src[1];
- byte rleCount = src[2];
- src += 3;
- frameSize -= rleCount;
- while (rleCount--)
- *dst++ = rleColor;
- } else {
- *dst++ = *src++;
- --frameSize;
- }
- }
- assert(frameSize == 0);
- } else {
- // Uncompressed frame
- Common::copy(src, src + frame._width * frame._height,
- (byte *)frame._frame.getPixels());
- }
-}
-
-/*----------------------------------------------------------------*/
-
-int ImageFrame::sDrawXSize(int scaleVal) const {
- int width = _width;
- int scale = scaleVal == 0 ? 1 : scaleVal;
-
- if (scaleVal >= 256)
- --width;
-
- int result = width * 256 / scale;
- if (scaleVal >= 256)
- ++result;
-
- return result;
-}
-
-int ImageFrame::sDrawYSize(int scaleVal) const {
- int height = _height;
- int scale = scaleVal == 0 ? 1 : scaleVal;
-
- if (scaleVal >= 256)
- --height;
-
- int result = height * 256 / scale;
- if (scaleVal >= 256)
- ++result;
-
- return result;
-}
-
} // End of namespace Sherlock
diff --git a/engines/sherlock/resources.h b/engines/sherlock/resources.h
index 5c071e3922..99d58a51b1 100644
--- a/engines/sherlock/resources.h
+++ b/engines/sherlock/resources.h
@@ -29,6 +29,7 @@
#include "common/hash-str.h"
#include "common/rect.h"
#include "common/str.h"
+#include "common/str-array.h"
#include "common/stream.h"
#include "graphics/surface.h"
@@ -88,7 +89,7 @@ private:
int _resourceIndex;
/**
- * Reads in the index from a library file, and caches it's index for later use
+ * Reads in the index from a library file, and caches its index for later use
*/
void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle);
public:
@@ -96,7 +97,7 @@ public:
/**
* Adds the specified file to the cache. If it's a library file, takes care of
- * loading it's index for future use
+ * loading its index for future use
*/
void addToCache(const Common::String &filename);
@@ -113,7 +114,7 @@ public:
bool isInCache(const Common::String &filename) const { return _cache.isCached(filename); }
/**
- * Checks the passed stream, and if is compressed, deletes it and replaces it with it's uncompressed data
+ * Checks the passed stream, and if is compressed, deletes it and replaces it with its uncompressed data
*/
void decompressIfNecessary(Common::SeekableReadStream *&stream);
@@ -125,7 +126,7 @@ public:
/**
* Loads a specific resource from a given library file
*/
- Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile);
+ Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile, bool suppressErrors = false);
/**
* Returns true if the given file exists on disk or in the cache
@@ -133,13 +134,18 @@ public:
bool exists(const Common::String &filename) const;
/**
- * Returns the index of the last loaded resource in it's given library file.
+ * Returns the index of the last loaded resource in its given library file.
* This will be used primarily when loading talk files, so the engine can
* update the given conversation number in the journal
*/
int resourceIndex() const;
/**
+ * Produces a list of all resource names within a file. Used by the debugger.
+ */
+ void getResourceNames(const Common::String &libraryFile, Common::StringArray &names);
+
+ /**
* Decompresses LZW compressed data
*/
Common::SeekableReadStream *decompress(Common::SeekableReadStream &source);
@@ -165,53 +171,6 @@ public:
static void decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize);
};
-struct ImageFrame {
- uint32 _size;
- uint16 _width, _height;
- int _paletteBase;
- bool _rleEncoded;
- Common::Point _offset;
- byte _rleMarker;
- Graphics::Surface _frame;
-
- /**
- * Return the frame width adjusted by a specified scale amount
- */
- int sDrawXSize(int scaleVal) const;
-
- /**
- * Return the frame height adjusted by a specified scale amount
- */
- int sDrawYSize(int scaleVal) const;
-};
-
-class ImageFile : public Common::Array<ImageFrame> {
-private:
- static SherlockEngine *_vm;
-
- /**
- * Load the data of the sprite
- */
- void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages);
-
- /**
- * Gets the palette at the start of the sprite file
- */
- void loadPalette(Common::SeekableReadStream &stream);
-
- /**
- * Decompress a single frame for the sprite
- */
- void decompressFrame(ImageFrame &frame, const byte *src);
-public:
- byte _palette[256 * 3];
-public:
- ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false);
- ImageFile(Common::SeekableReadStream &stream, bool skipPal = false);
- ~ImageFile();
- static void setVm(SherlockEngine *vm);
-};
-
} // End of namespace Sherlock
#endif
diff --git a/engines/sherlock/saveload.cpp b/engines/sherlock/saveload.cpp
index ddf4917a34..f32552c7ea 100644
--- a/engines/sherlock/saveload.cpp
+++ b/engines/sherlock/saveload.cpp
@@ -23,31 +23,30 @@
#include "sherlock/saveload.h"
#include "sherlock/surface.h"
#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "sherlock/tattoo/widget_files.h"
#include "common/system.h"
#include "graphics/scaler.h"
#include "graphics/thumbnail.h"
namespace Sherlock {
-const int ENV_POINTS[6][3] = {
- { 41, 80, 61 }, // Exit
- { 81, 120, 101 }, // Load
- { 121, 160, 141 }, // Save
- { 161, 200, 181 }, // Up
- { 201, 240, 221 }, // Down
- { 241, 280, 261 } // Quit
-};
-
-static const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-";
+const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-";
static const char *const SAVEGAME_STR = "SHLK";
#define SAVEGAME_STR_SIZE 4
/*----------------------------------------------------------------*/
+SaveManager *SaveManager::init(SherlockEngine *vm, const Common::String &target) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelSaveManager(vm, target);
+ else
+ return new Tattoo::WidgetFiles(vm, target);
+}
+
SaveManager::SaveManager(SherlockEngine *vm, const Common::String &target) :
_vm(vm), _target(target) {
_saveThumb = nullptr;
- _envMode = SAVEMODE_NONE;
_justLoaded = false;
_savegameIndex = 0;
}
@@ -59,54 +58,6 @@ SaveManager::~SaveManager() {
}
}
-void SaveManager::drawInterface() {
- Screen &screen = *_vm->_screen;
- UserInterface &ui = *_vm->_ui;
-
- // Create a list of savegame slots
- createSavegameList();
-
- screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR);
- screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
-
- screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10),
- ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
- screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10),
- ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load");
- screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10),
- ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save");
- screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10),
- ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up");
- screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10),
- ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down");
- screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10),
- ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit");
-
- if (!_savegameIndex)
- screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up");
-
- if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)
- screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down");
-
- for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) {
- screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
- INV_FOREGROUND, "%d.", idx + 1);
- screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
- INV_FOREGROUND, "%s", _savegames[idx].c_str());
- }
-
- if (!ui._slideWindows) {
- screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
- } else {
- ui.summonWindow();
- }
-
- _envMode = SAVEMODE_NONE;
-}
-
void SaveManager::createSavegameList() {
Screen &screen = *_vm->_screen;
@@ -116,7 +67,7 @@ void SaveManager::createSavegameList() {
SaveStateList saveList = getSavegameList(_target);
for (uint idx = 0; idx < saveList.size(); ++idx) {
- int slot = saveList[idx].getSaveSlot() - 1;
+ int slot = saveList[idx].getSaveSlot();
if (slot >= 0 && slot < MAX_SAVEGAME_SLOTS)
_savegames[slot] = saveList[idx].getDescription();
}
@@ -153,7 +104,9 @@ SaveStateList SaveManager::getSavegameList(const Common::String &target) {
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
if (in) {
- readSavegameHeader(in, header);
+ if (!readSavegameHeader(in, header))
+ continue;
+
saveList.push_back(SaveStateDescriptor(slot, header._saveName));
header._thumbnail->free();
@@ -176,7 +129,7 @@ bool SaveManager::readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHea
return false;
header._version = in->readByte();
- if (header._version > SHERLOCK_SAVEGAME_VERSION)
+ if (header._version < MINIMUM_SAVEGAME_VERSION || header._version > CURRENT_SAVEGAME_VERSION)
return false;
// Read in the string
@@ -204,7 +157,7 @@ void SaveManager::writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegame
// Write out a savegame header
out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
- out->writeByte(SHERLOCK_SAVEGAME_VERSION);
+ out->writeByte(CURRENT_SAVEGAME_VERSION);
// Write savegame name
out->write(header._saveName.c_str(), header._saveName.size());
@@ -236,56 +189,19 @@ void SaveManager::createThumbnail() {
delete _saveThumb;
}
- uint8 thumbPalette[PALETTE_SIZE];
- _vm->_screen->getPalette(thumbPalette);
_saveThumb = new Graphics::Surface();
- ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette);
-}
-int SaveManager::getHighlightedButton() const {
- Common::Point pt = _vm->_events->mousePos();
-
- for (int idx = 0; idx < 6; ++idx) {
- if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y
- && pt.y < (CONTROLS_Y + 10))
- return idx;
+ if (!IS_3DO) {
+ uint8 thumbPalette[PALETTE_SIZE];
+ _vm->_screen->getPalette(thumbPalette);
+ ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette);
+ } else {
+ ::createThumbnailFromScreen(_saveThumb);
}
-
- return -1;
-}
-
-void SaveManager::highlightButtons(int btnIndex) {
- Screen &screen = *_vm->_screen;
- byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
-
- screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit");
-
- if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2)))
- screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load");
- else
- screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load");
-
- if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1)))
- screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save");
- else
- screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save");
-
- if (btnIndex == 3 && _savegameIndex)
- screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up");
- else
- if (_savegameIndex)
- screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up");
-
- if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5))
- screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down");
- else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5))
- screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down");
-
- color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
- screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit");
}
void SaveManager::loadGame(int slot) {
+ Events &events = *_vm->_events;
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
generateSaveName(slot));
if (!saveFile)
@@ -302,13 +218,16 @@ void SaveManager::loadGame(int slot) {
}
// Synchronize the savegame data
- Common::Serializer s(saveFile, nullptr);
+ Serializer s(saveFile, nullptr);
+ s.setSaveVersion(header._version);
synchronize(s);
delete saveFile;
+ events.clearEvents();
}
void SaveManager::saveGame(int slot, const Common::String &name) {
+ Events &events = *_vm->_events;
Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
generateSaveName(slot));
@@ -317,18 +236,20 @@ void SaveManager::saveGame(int slot, const Common::String &name) {
writeSavegameHeader(out, header);
// Synchronize the savegame data
- Common::Serializer s(nullptr, out);
+ Serializer s(nullptr, out);
+ s.setSaveVersion(CURRENT_SAVEGAME_VERSION);
synchronize(s);
out->finalize();
delete out;
+ events.clearEvents();
}
Common::String SaveManager::generateSaveName(int slot) {
return Common::String::format("%s.%03d", _target.c_str(), slot);
}
-void SaveManager::synchronize(Common::Serializer &s) {
+void SaveManager::synchronize(Serializer &s) {
Inventory &inv = *_vm->_inventory;
Journal &journal = *_vm->_journal;
Map &map = *_vm->_map;
@@ -351,130 +272,7 @@ void SaveManager::synchronize(Common::Serializer &s) {
if (screen.fontNumber() != oldFont)
journal.resetPosition();
- _justLoaded = true;
-}
-
-bool SaveManager::checkGameOnScreen(int slot) {
- Screen &screen = *_vm->_screen;
-
- // Check if it's already on-screen
- if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) {
- _savegameIndex = slot;
-
- screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
- SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
-
- for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) {
- screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
- INV_FOREGROUND, "%d.", idx + 1);
- screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
- INV_FOREGROUND, "%s", _savegames[idx].c_str());
- }
-
- screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT));
-
- byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND;
- screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up");
-
- color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND;
- screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down");
-
- return true;
- }
-
- return false;
-}
-
-bool SaveManager::promptForDescription(int slot) {
- Events &events = *_vm->_events;
- Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- int xp, yp;
- bool flag = false;
-
- screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit");
- screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load");
- screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save");
- screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up");
- screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down");
- screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit");
-
- Common::String saveName = _savegames[slot];
- if (isSlotEmpty(slot)) {
- // It's an empty slot, so start off with an empty save name
- saveName = "";
-
- yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
- screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND);
- }
-
- screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1);
- screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str());
- xp = 24 + screen.stringWidth(saveName);
- yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
-
- int done = 0;
- do {
- while (!_vm->shouldQuit() && !events.kbHit()) {
- scene.doBgAnim();
-
- if (talk._talkToAbort)
- return false;
-
- // Allow event processing
- events.pollEventsAndWait();
- events.setButtonState();
-
- flag = !flag;
- if (flag)
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
- else
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
- }
- if (_vm->shouldQuit())
- return false;
-
- // Get the next keypress
- Common::KeyState keyState = events.getKey();
-
- if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) {
- // Delete character of save name
- screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1,
- xp + 8, yp + 9), INV_BACKGROUND);
- xp -= screen.charWidth(saveName.lastChar());
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
- saveName.deleteLastChar();
-
- } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) {
- done = 1;
-
- } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
- done = -1;
-
- } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50
- && (xp + screen.charWidth(keyState.ascii)) < 308) {
- char c = (char)keyState.ascii;
-
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
- screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c);
- xp += screen.charWidth(c);
- screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
- saveName += c;
- }
- } while (!done);
-
- if (done == 1) {
- // Enter key perssed
- _savegames[slot] = saveName;
- } else {
- done = 0;
- _envMode = SAVEMODE_NONE;
- highlightButtons(-1);
- }
-
- return done == 1;
+ _justLoaded = s.isLoading();
}
bool SaveManager::isSlotEmpty(int slot) const {
diff --git a/engines/sherlock/saveload.h b/engines/sherlock/saveload.h
index a7ed852a5f..f4f3e7cfd9 100644
--- a/engines/sherlock/saveload.h
+++ b/engines/sherlock/saveload.h
@@ -34,11 +34,15 @@ namespace Sherlock {
#define MAX_SAVEGAME_SLOTS 99
#define ONSCREEN_FILES_COUNT 5
-#define SHERLOCK_SAVEGAME_VERSION 1
+
+enum {
+ CURRENT_SAVEGAME_VERSION = 4,
+ MINIMUM_SAVEGAME_VERSION = 4
+};
enum SaveMode { SAVEMODE_NONE = 0, SAVEMODE_LOAD = 1, SAVEMODE_SAVE = 2 };
-extern const int ENV_POINTS[6][3];
+extern const char *const EMPTY_SAVEGAME_SLOT;
struct SherlockSavegameHeader {
uint8 _version;
@@ -51,8 +55,22 @@ struct SherlockSavegameHeader {
class SherlockEngine;
+
+/**
+ * Derived serializer class with extra synchronization types
+ */
+class Serializer : public Common::Serializer {
+public:
+ Serializer(Common::SeekableReadStream *in, Common::WriteStream *out) : Common::Serializer(in, out) {}
+
+ /**
+ * New method to allow setting the version
+ */
+ void setSaveVersion(byte version) { _version = version; }
+};
+
class SaveManager {
-private:
+protected:
SherlockEngine *_vm;
Common::String _target;
Graphics::Surface *_saveThumb;
@@ -65,20 +83,15 @@ private:
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
public:
Common::StringArray _savegames;
int _savegameIndex;
- SaveMode _envMode;
bool _justLoaded;
public:
+ static SaveManager *init(SherlockEngine *vm, const Common::String &target);
SaveManager(SherlockEngine *vm, const Common::String &target);
- ~SaveManager();
-
- /**
- * Shows the in-game dialog interface for loading and saving games
- */
- void drawInterface();
+ virtual ~SaveManager();
/**
* Creates a thumbnail for the current on-screen contents
@@ -127,16 +140,6 @@ public:
void saveGame(int slot, const Common::String &name);
/**
- * Make sure that the selected savegame is on-screen
- */
- bool checkGameOnScreen(int slot);
-
- /**
- * Prompts the user to enter a description in a given slot
- */
- bool promptForDescription(int slot);
-
- /**
* Returns true if the given save slot is empty
*/
bool isSlotEmpty(int slot) const;
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp
new file mode 100644
index 0000000000..8e8f99bc19
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp
@@ -0,0 +1,510 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/3do.h"
+
+#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "image/codecs/cinepak.h"
+
+// for Test-Code
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "engines/engine.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder()
+ : _stream(0), _videoTrack(0), _audioTrack(0) {
+ _streamVideoOffset = 0;
+ _streamAudioOffset = 0;
+}
+
+Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() {
+ close();
+}
+
+bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
+ uint32 videoSubType = 0;
+ uint32 videoCodecTag = 0;
+ uint32 videoHeight = 0;
+ uint32 videoWidth = 0;
+ uint32 videoFrameCount = 0;
+ uint32 audioSubType = 0;
+ uint32 audioCodecTag = 0;
+ uint32 audioChannels = 0;
+ uint32 audioSampleRate = 0;
+
+ close();
+
+ _stream = stream;
+ _streamVideoOffset = 0;
+ _streamAudioOffset = 0;
+
+ // Look for packets that we care about
+ static const int maxPacketCheckCount = 20;
+ for (int i = 0; i < maxPacketCheckCount; i++) {
+ uint32 chunkTag = _stream->readUint32BE();
+ uint32 chunkSize = _stream->readUint32BE() - 8;
+
+ // Bail out if done
+ if (_stream->eos())
+ break;
+
+ uint32 dataStartOffset = _stream->pos();
+
+ switch (chunkTag) {
+ case MKTAG('F','I','L','M'): {
+ // See if this is a FILM header
+ _stream->skip(4); // time stamp (based on 240 per second)
+ _stream->skip(4); // Unknown 0x00000000
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // FILM header found
+ if (_videoTrack) {
+ warning("Sherlock 3DO movie: Multiple FILM headers found");
+ close();
+ return false;
+ }
+ _stream->readUint32BE();
+ videoCodecTag = _stream->readUint32BE();
+ videoHeight = _stream->readUint32BE();
+ videoWidth = _stream->readUint32BE();
+ _stream->skip(4); // time scale
+ videoFrameCount = _stream->readUint32BE();
+
+ _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount);
+ addTrack(_videoTrack);
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ break;
+
+ default:
+ warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ close();
+ return false;
+ }
+ break;
+ }
+
+ case MKTAG('S','N','D','S'): {
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Audio header
+
+ // Bail if we already have a track
+ if (_audioTrack) {
+ warning("Sherlock 3DO movie: Multiple SNDS headers found");
+ close();
+ return false;
+ }
+
+ // OK, this is the start of a audio stream
+ _stream->readUint32BE(); // Version, always 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000008 ?!
+ _stream->readUint32BE(); // Unknown 0x00007500
+ _stream->readUint32BE(); // Unknown 0x00004000
+ _stream->readUint32BE(); // Unknown 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000010
+ audioSampleRate = _stream->readUint32BE();
+ audioChannels = _stream->readUint32BE();
+ audioCodecTag = _stream->readUint32BE();
+ _stream->readUint32BE(); // Unknown 0x00000004 compression ratio?
+ _stream->readUint32BE(); // Unknown 0x00000A2C
+
+ _audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels);
+ addTrack(_audioTrack);
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Audio data
+ break;
+ default:
+ warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ close();
+ return false;
+ }
+ break;
+ }
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
+ case MKTAG('D','A','C','Q'):
+ case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
+ // Ignore these chunks
+ break;
+
+ case MKTAG('S','H','D','R'):
+ // Happens for EA logo, seems to be garbage data right at the start of the file
+ break;
+
+ default:
+ warning("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+ close();
+ return false;
+ }
+
+ if ((_videoTrack) && (_audioTrack))
+ break;
+
+ // Seek to next chunk
+ _stream->seek(dataStartOffset + chunkSize);
+ }
+
+ // Bail if we didn't find video + audio
+ if ((!_videoTrack) || (!_audioTrack)) {
+ close();
+ return false;
+ }
+
+ // Rewind back to the beginning
+ _stream->seek(0);
+
+ return true;
+}
+
+void Scalpel3DOMovieDecoder::close() {
+ Video::VideoDecoder::close();
+
+ delete _stream; _stream = 0;
+ _videoTrack = 0;
+}
+
+// We try to at least decode 1 frame
+// and also try to get at least 0.5 seconds of audio queued up
+void Scalpel3DOMovieDecoder::readNextPacket() {
+ uint32 currentMovieTime = getTime();
+ uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
+
+ int32 chunkOffset = 0;
+ int32 dataStartOffset = 0;
+ int32 nextChunkOffset = 0;
+ uint32 chunkTag = 0;
+ uint32 chunkSize = 0;
+
+ uint32 videoSubType = 0;
+ uint32 videoTimeStamp = 0;
+ uint32 videoFrameSize = 0;
+ uint32 audioSubType = 0;
+ uint32 audioBytes = 0;
+ bool videoGotFrame = false;
+ bool videoDone = false;
+ bool audioDone = false;
+
+ // Seek to smallest stream offset
+ if (_streamVideoOffset <= _streamAudioOffset) {
+ _stream->seek(_streamVideoOffset);
+ } else {
+ _stream->seek(_streamAudioOffset);
+ }
+
+ if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+ // already got enough audio queued up
+ audioDone = true;
+ }
+
+ while (1) {
+ chunkOffset = _stream->pos();
+ assert(chunkOffset >= 0);
+
+ // Read chunk header
+ chunkTag = _stream->readUint32BE();
+ chunkSize = _stream->readUint32BE() - 8;
+
+ // Calculate offsets
+ dataStartOffset = _stream->pos();
+ assert(dataStartOffset >= 0);
+ nextChunkOffset = dataStartOffset + chunkSize;
+
+ //warning("offset %lx - tag %lx", dataStartOffset, tag);
+
+ if (_stream->eos())
+ break;
+
+ switch (chunkTag) {
+ case MKTAG('F','I','L','M'):
+ videoTimeStamp = _stream->readUint32BE();
+ _stream->skip(4); // Unknown
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // Ignore video header
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ // Found frame data
+ if (_streamVideoOffset <= chunkOffset) {
+ // We are at an offset that is still relevant to video decoding
+ if (!videoDone) {
+ if (!videoGotFrame) {
+ // We haven't decoded any frame yet, so do so now
+ _stream->readUint32BE();
+ videoFrameSize = _stream->readUint32BE();
+ _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp);
+
+ _streamVideoOffset = nextChunkOffset;
+ videoGotFrame = true;
+
+ } else {
+ // Already decoded a frame, so get timestamp of follow-up frame
+ // and then we are done with video
+
+ // Calculate next frame time
+ // 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240.
+ uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime();
+ uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240;
+ assert(currentFrameStartTime <= nextFrameStartTime);
+ _videoTrack->setNextFrameStartTime(nextFrameStartTime);
+
+ // next time we want to start at the current chunk
+ _streamVideoOffset = chunkOffset;
+ videoDone = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ break;
+ }
+ break;
+
+ case MKTAG('S','N','D','S'):
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Ignore the audio header
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Got audio chunk
+ if (_streamAudioOffset <= chunkOffset) {
+ // We are at an offset that is still relevant to audio decoding
+ if (!audioDone) {
+ audioBytes = _stream->readUint32BE();
+ _audioTrack->queueAudio(_stream, audioBytes);
+
+ _streamAudioOffset = nextChunkOffset;
+ if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+ // Got enough audio
+ audioDone = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside SNDS packet");
+ break;
+ }
+ break;
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
+ case MKTAG('D','A','C','Q'):
+ case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
+ // Ignore these chunks
+ break;
+
+ case MKTAG('S','H','D','R'):
+ // Happens for EA logo, seems to be garbage data right at the start of the file
+ break;
+
+ default:
+ error("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+ }
+
+ // Always seek to end of chunk
+ // Sometimes not all of the chunk is filled with audio
+ _stream->seek(nextChunkOffset);
+
+ if ((videoDone) && (audioDone)) {
+ return;
+ }
+ }
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) {
+ _width = width;
+ _height = height;
+ _frameCount = frameCount;
+ _curFrame = -1;
+ _nextFrameStartTime = 0;
+
+ // Create the Cinepak decoder, if we're using it
+ if (codecTag == MKTAG('c', 'v', 'i', 'd'))
+ _codec = new Image::CinepakDecoder();
+ else
+ error("Unsupported Sherlock 3DO movie video codec tag '%s'", tag2str(codecTag));
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
+ delete _codec;
+}
+
+bool Scalpel3DOMovieDecoder::StreamVideoTrack::endOfTrack() const {
+ return getCurFrame() >= getFrameCount() - 1;
+}
+
+Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
+ return _codec->getPixelFormat();
+}
+
+void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) {
+ _surface = _codec->decodeFrame(*stream);
+ _curFrame++;
+}
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) {
+ switch (codecTag) {
+ case MKTAG('A','D','P','4'):
+ case MKTAG('S','D','X','2'):
+ // ADP4 + SDX2 are both allowed
+ break;
+
+ default:
+ error("Unsupported Sherlock 3DO movie audio codec tag '%s'", tag2str(codecTag));
+ }
+
+ _totalAudioQueued = 0; // currently 0 milliseconds queued
+
+ _codecTag = codecTag;
+ _sampleRate = sampleRate;
+ switch (channels) {
+ case 1:
+ _stereo = false;
+ break;
+ case 2:
+ _stereo = true;
+ break;
+ default:
+ error("Unsupported Sherlock 3DO movie audio channels %d", channels);
+ }
+
+ _audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
+
+ // reset audio decoder persistent spaces
+ memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace));
+ memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace));
+}
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
+ delete _audioStream;
+// free(_ADP4_PersistentSpace);
+// free(_SDX2_PersistentSpace);
+}
+
+void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
+ Common::SeekableReadStream *compressedAudioStream = 0;
+ Audio::RewindableAudioStream *audioStream = 0;
+ uint32 audioLengthMSecs = 0;
+
+ // Read the specified chunk into memory
+ compressedAudioStream = stream->readStream(size);
+
+ switch(_codecTag) {
+ case MKTAG('A','D','P','4'):
+ audioStream = Audio::make3DO_ADP4AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_ADP4_PersistentSpace);
+ break;
+ case MKTAG('S','D','X','2'):
+ audioStream = Audio::make3DO_SDX2AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_SDX2_PersistentSpace);
+ break;
+ default:
+ break;
+ }
+ if (audioStream) {
+ _totalAudioQueued += audioLengthMSecs;
+ _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
+ } else {
+ // in case there was an error
+ delete compressedAudioStream;
+ }
+}
+
+Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
+
+// Test-code
+
+// Code for showing a movie. Only meant for testing/debug purposes
+bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos) {
+ Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder();
+
+ if (!videoDecoder->loadFile(filename)) {
+ warning("Scalpel3DOMoviePlay: could not open '%s'", filename);
+ return false;
+ }
+
+ bool skipVideo = false;
+ //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel;
+ uint16 width = videoDecoder->getWidth();
+ uint16 height = videoDecoder->getHeight();
+ //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel;
+
+ videoDecoder->start();
+
+ while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && (!skipVideo)) {
+ if (videoDecoder->needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+ if (frame) {
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, pos.x, pos.y, width, height);
+ g_system->updateScreen();
+ }
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE))
+ skipVideo = true;
+ }
+
+ g_system->delayMillis(10);
+ }
+ videoDecoder->close();
+ delete videoDecoder;
+
+ return !skipVideo;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h
new file mode 100644
index 0000000000..9f1670fc6c
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.h
@@ -0,0 +1,127 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+
+#include "common/rect.h"
+#include "video/video_decoder.h"
+#include "audio/decoders/3do.h"
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+class Codec;
+}
+
+namespace Sherlock {
+
+class Scalpel3DOMovieDecoder : public Video::VideoDecoder {
+public:
+ Scalpel3DOMovieDecoder();
+ ~Scalpel3DOMovieDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+protected:
+ void readNextPacket();
+
+private:
+ int32 _streamVideoOffset; /* current stream offset for video decoding */
+ int32 _streamAudioOffset; /* current stream offset for audio decoding */
+
+private:
+ class StreamVideoTrack : public VideoTrack {
+ public:
+ StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount);
+ ~StreamVideoTrack();
+
+ bool endOfTrack() const;
+
+ uint16 getWidth() const { return _width; }
+ uint16 getHeight() const { return _height; }
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime; }
+ const Graphics::Surface *decodeNextFrame() { return _surface; }
+
+ void decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp);
+
+ private:
+ const Graphics::Surface *_surface;
+
+ int _curFrame;
+ uint32 _frameCount;
+ uint32 _nextFrameStartTime;
+
+ Image::Codec *_codec;
+ uint16 _width, _height;
+ };
+
+ class StreamAudioTrack : public AudioTrack {
+ public:
+ StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels);
+ ~StreamAudioTrack();
+
+ void queueAudio(Common::SeekableReadStream *stream, uint32 size);
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audioStream;
+ uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */
+
+ public:
+ uint32 getTotalAudioQueued() const { return _totalAudioQueued; }
+
+ private:
+ int16 decodeSample(uint8 dataNibble);
+
+ uint32 _codecTag;
+ uint16 _sampleRate;
+ bool _stereo;
+
+ Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace;
+ Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace;
+ };
+
+ Common::SeekableReadStream *_stream;
+ StreamVideoTrack *_videoTrack;
+ StreamAudioTrack *_audioTrack;
+};
+
+// Testing
+extern bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos);
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/drivers/adlib.cpp b/engines/sherlock/scalpel/drivers/adlib.cpp
index db1151c841..29a39f0c39 100644
--- a/engines/sherlock/scalpel/drivers/adlib.cpp
+++ b/engines/sherlock/scalpel/drivers/adlib.cpp
@@ -35,21 +35,21 @@ namespace Sherlock {
#define SHERLOCK_ADLIB_VOICES_COUNT 9
#define SHERLOCK_ADLIB_NOTES_COUNT 96
-byte adlib_Operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
+byte operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
};
-byte adlib_Operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
+byte operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15
};
-struct adlib_percussionChannelEntry {
+struct percussionChannelEntry {
byte requiredNote;
byte replacementNote;
};
// hardcoded, dumped from ADHOM.DRV
-const adlib_percussionChannelEntry adlib_percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = {
+const percussionChannelEntry percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = {
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x00, 0x00 },
@@ -61,7 +61,7 @@ const adlib_percussionChannelEntry adlib_percussionChannelTable[SHERLOCK_ADLIB_V
{ 0x26, 0x1E }
};
-struct adlib_InstrumentEntry {
+struct InstrumentEntry {
byte reg20op1;
byte reg40op1;
byte reg60op1;
@@ -77,7 +77,7 @@ struct adlib_InstrumentEntry {
};
// hardcoded, dumped from ADHOM.DRV
-const adlib_InstrumentEntry adlib_instrumentTable[] = {
+const InstrumentEntry instrumentTable[] = {
{ 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 },
{ 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 },
{ 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 },
@@ -203,7 +203,7 @@ const adlib_InstrumentEntry adlib_instrumentTable[] = {
};
// hardcoded, dumped from ADHOM.DRV
-uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = {
+uint16 frequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = {
0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242,
0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603,
0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB,
@@ -216,13 +216,14 @@ uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = {
0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89
};
-class MidiDriver_AdLib : public MidiDriver_Emulated {
+class MidiDriver_SH_AdLib : public MidiDriver {
public:
- MidiDriver_AdLib(Audio::Mixer *mixer)
- : MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) {
+ MidiDriver_SH_AdLib(Audio::Mixer *mixer)
+ : _masterVolume(15), _opl(0),
+ _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) {
memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping));
}
- virtual ~MidiDriver_AdLib() { }
+ virtual ~MidiDriver_SH_AdLib() { }
// MidiDriver
int open();
@@ -230,15 +231,13 @@ public:
void send(uint32 b);
MidiChannel *allocateChannel() { return NULL; }
MidiChannel *getPercussionChannel() { return NULL; }
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
- // AudioStream
- bool isStereo() const { return false; }
- int getRate() const { return _mixer->getOutputRate(); }
int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; }
bool hasRhythmChannel() const { return false; }
- // MidiDriver_Emulated
- void generateSamples(int16 *buf, int len);
+ virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
void setVolume(byte volume);
virtual uint32 property(int prop, uint32 param);
@@ -249,7 +248,7 @@ private:
struct adlib_ChannelEntry {
bool inUse;
uint16 inUseTimer;
- const adlib_InstrumentEntry *currentInstrumentPtr;
+ const InstrumentEntry *currentInstrumentPtr;
byte currentNote;
byte currentA0hReg;
byte currentB0hReg;
@@ -261,19 +260,22 @@ private:
OPL::OPL *_opl;
int _masterVolume;
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+
+ bool _isOpen;
+
// points to a MIDI channel for each of the new voice channels
byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT];
// stores information about all FM voice channels
adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT];
-protected:
void onTimer();
-private:
void resetAdLib();
- void resetAdLib_OperatorRegisters(byte baseRegister, byte value);
- void resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value);
+ void resetAdLibOperatorRegisters(byte baseRegister, byte value);
+ void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
void programChange(byte MIDIchannel, byte parameter);
void setRegister(int reg, int value);
@@ -284,32 +286,31 @@ private:
void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
};
-int MidiDriver_AdLib::open() {
- int rate = _mixer->getOutputRate();
-
- debug(3, "ADLIB: Starting driver");
+int MidiDriver_SH_AdLib::open() {
+ debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
_opl = OPL::Config::create(OPL::Config::kOpl2);
if (!_opl)
return -1;
- _opl->init(rate);
+ _opl->init();
- MidiDriver_Emulated::open();
+ _isOpen = true;
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
+ _opl->start(new Common::Functor0Mem<void, MidiDriver_SH_AdLib>(this, &MidiDriver_SH_AdLib::onTimer));
return 0;
}
-void MidiDriver_AdLib::close() {
- _mixer->stopHandle(_mixerSoundHandle);
+void MidiDriver_SH_AdLib::close() {
+ // Stop the OPL timer
+ _opl->stop();
delete _opl;
}
-void MidiDriver_AdLib::setVolume(byte volume) {
+void MidiDriver_SH_AdLib::setVolume(byte volume) {
_masterVolume = volume;
//renewNotes(-1, true);
}
@@ -317,7 +318,13 @@ void MidiDriver_AdLib::setVolume(byte volume) {
// this should/must get called per tick
// original driver did this before MIDI data processing on each tick
// we do it atm after MIDI data processing
-void MidiDriver_AdLib::onTimer() {
+void MidiDriver_SH_AdLib::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
+
+ // this should/must get called per tick
+ // original driver did this before MIDI data processing on each tick
+ // we do it atm after MIDI data processing
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_channels[FMvoiceChannel].inUse) {
_channels[FMvoiceChannel].inUseTimer++;
@@ -326,7 +333,7 @@ void MidiDriver_AdLib::onTimer() {
}
// Called when a music track got loaded into memory
-void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) {
+void MidiDriver_SH_AdLib::newMusicData(byte *musicData, int32 musicDataSize) {
assert(musicDataSize >= 0x7F);
// MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data
memcpy(&_voiceChannelMapping, musicData + 0x22, 9);
@@ -338,7 +345,7 @@ void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) {
memset(&_channels, 0, sizeof(_channels));
}
-void MidiDriver_AdLib::resetAdLib() {
+void MidiDriver_SH_AdLib::resetAdLib() {
setRegister(0x01, 0x20); // enable waveform control on both operators
setRegister(0x04, 0xE0); // Timer control
@@ -347,17 +354,17 @@ void MidiDriver_AdLib::resetAdLib() {
setRegister(0xBD, 0); // disable Rhythm
// reset FM voice instrument data
- resetAdLib_OperatorRegisters(0x20, 0);
- resetAdLib_OperatorRegisters(0x60, 0);
- resetAdLib_OperatorRegisters(0x80, 0);
- resetAdLib_FMVoiceChannelRegisters(0xA0, 0);
- resetAdLib_FMVoiceChannelRegisters(0xB0, 0);
- resetAdLib_FMVoiceChannelRegisters(0xC0, 0);
- resetAdLib_OperatorRegisters(0xE0, 0);
- resetAdLib_OperatorRegisters(0x40, 0x3F);
+ resetAdLibOperatorRegisters(0x20, 0);
+ resetAdLibOperatorRegisters(0x60, 0);
+ resetAdLibOperatorRegisters(0x80, 0);
+ resetAdLibFMVoiceChannelRegisters(0xA0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xB0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xC0, 0);
+ resetAdLibOperatorRegisters(0xE0, 0);
+ resetAdLibOperatorRegisters(0x40, 0x3F);
}
-void MidiDriver_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte value) {
+void MidiDriver_SH_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
byte operatorIndex;
for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
@@ -373,7 +380,7 @@ void MidiDriver_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte valu
}
}
-void MidiDriver_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value) {
+void MidiDriver_SH_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
byte FMvoiceChannel;
for (FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
@@ -382,7 +389,7 @@ void MidiDriver_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byt
}
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
-void MidiDriver_AdLib::send(uint32 b) {
+void MidiDriver_SH_AdLib::send(uint32 b) {
byte command = b & 0xf0;
byte channel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
@@ -406,22 +413,18 @@ void MidiDriver_AdLib::send(uint32 b) {
// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
break;
case 0xe0:
- warning("pitch bend change");
+ debugC(kDebugLevelAdLibDriver, "AdLib: pitch bend change");
pitchBendChange(channel, op1, op2);
break;
case 0xf0: // SysEx
- warning("SysEx: %x", b);
+ warning("ADLIB: SysEx: %x", b);
break;
default:
warning("ADLIB: Unknown event %02x", command);
}
}
-void MidiDriver_AdLib::generateSamples(int16 *data, int len) {
- _opl->readBuffer(data, len);
-}
-
-void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
+void MidiDriver_SH_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
int16 oldestInUseChannel = -1;
uint16 oldestInUseTimer = 0;
@@ -453,7 +456,7 @@ void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
}
if (oldestInUseChannel >= 0) {
// channel found
- warning("used In-Use channel");
+ debugC(kDebugLevelAdLibDriver, "AdLib: used In-Use channel");
// original driver used note 0, we use the current note
// because using note 0 could create a bad note (out of index) and we check that. Original driver didn't.
voiceOnOff(oldestInUseChannel, false, _channels[oldestInUseChannel].currentNote, 0);
@@ -464,27 +467,26 @@ void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
voiceOnOff(oldestInUseChannel, true, note, velocity);
return;
}
- warning("MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel);
+ debugC(kDebugLevelAdLibDriver, "AdLib: MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel);
} else {
// Percussion channel
- warning("percussion!");
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
- if (note == adlib_percussionChannelTable[FMvoiceChannel].requiredNote) {
+ if (note == percussionChannelTable[FMvoiceChannel].requiredNote) {
_channels[FMvoiceChannel].inUse = true;
_channels[FMvoiceChannel].currentNote = note;
- voiceOnOff(FMvoiceChannel, true, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, velocity);
+ voiceOnOff(FMvoiceChannel, true, percussionChannelTable[FMvoiceChannel].replacementNote, velocity);
return;
}
}
}
- warning("percussion MIDI channel not mapped/all FM voice channels busy");
+ debugC(kDebugLevelAdLibDriver, "AdLib: percussion MIDI channel not mapped/all FM voice channels busy");
}
}
-void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) {
+void MidiDriver_SH_AdLib::noteOff(byte MIDIchannel, byte note) {
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (_channels[FMvoiceChannel].currentNote == note) {
@@ -496,7 +498,7 @@ void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) {
// not-percussion
voiceOnOff(FMvoiceChannel, false, note, 0);
} else {
- voiceOnOff(FMvoiceChannel, false, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, 0);
+ voiceOnOff(FMvoiceChannel, false, percussionChannelTable[FMvoiceChannel].replacementNote, 0);
}
return;
}
@@ -504,7 +506,7 @@ void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) {
}
}
-void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) {
+void MidiDriver_SH_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) {
byte frequencyOffset = 0;
uint16 frequency = 0;
byte op2RegAdjust = 0;
@@ -519,10 +521,10 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by
frequencyOffset = note;
}
if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) {
- warning("CRITICAL - bad note!!!");
+ warning("CRITICAL - AdLib driver: bad note!!!");
return;
}
- frequency = adlib_FrequencyLookUpTable[frequencyOffset];
+ frequency = frequencyLookUpTable[frequencyOffset];
if (keyOn) {
// adjust register 40h
@@ -530,7 +532,7 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by
regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2;
}
regValue40h = regValue40h - (velocity >> 3);
- op2RegAdjust = adlib_Operator2Register[FMvoiceChannel];
+ op2RegAdjust = operator2Register[FMvoiceChannel];
setRegister(0x40 + op2RegAdjust, regValue40h);
}
@@ -546,7 +548,7 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by
_channels[FMvoiceChannel].currentB0hReg = regValueB0h;
}
-void MidiDriver_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) {
+void MidiDriver_SH_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) {
uint16 channelFrequency = 0;
byte channelRegB0hWithoutFrequency = 0;
uint16 parameter = 0;
@@ -585,20 +587,20 @@ void MidiDriver_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte p
}
}
-void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) {
- const adlib_InstrumentEntry *instrumentPtr;
+void MidiDriver_SH_AdLib::programChange(byte MIDIchannel, byte op1) {
+ const InstrumentEntry *instrumentPtr;
byte op1Reg = 0;
byte op2Reg = 0;
// setup instrument
- instrumentPtr = &adlib_instrumentTable[op1];
+ instrumentPtr = &instrumentTable[op1];
//warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1);
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
- op1Reg = adlib_Operator1Register[FMvoiceChannel];
- op2Reg = adlib_Operator2Register[FMvoiceChannel];
+ op1Reg = operator1Register[FMvoiceChannel];
+ op2Reg = operator2Register[FMvoiceChannel];
setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
@@ -619,21 +621,26 @@ void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) {
}
}
}
-void MidiDriver_AdLib::setRegister(int reg, int value) {
+void MidiDriver_SH_AdLib::setRegister(int reg, int value) {
_opl->write(0x220, reg);
_opl->write(0x221, value);
}
-uint32 MidiDriver_AdLib::property(int prop, uint32 param) {
+uint32 MidiDriver_SH_AdLib::property(int prop, uint32 param) {
return 0;
}
-MidiDriver *MidiDriver_AdLib_create() {
- return new MidiDriver_AdLib(g_system->getMixer());
+void MidiDriver_SH_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+}
+
+MidiDriver *MidiDriver_SH_AdLib_create() {
+ return new MidiDriver_SH_AdLib(g_system->getMixer());
}
-void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
- static_cast<MidiDriver_AdLib *>(driver)->newMusicData(musicData, musicDataSize);
+void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
+ static_cast<MidiDriver_SH_AdLib *>(driver)->newMusicData(musicData, musicDataSize);
}
-} // End of namespace Sci
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/drivers/mididriver.h b/engines/sherlock/scalpel/drivers/mididriver.h
index 64213315e8..1b8ceeda3d 100644
--- a/engines/sherlock/scalpel/drivers/mididriver.h
+++ b/engines/sherlock/scalpel/drivers/mididriver.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef SHERLOCK_SOFTSEQ_MIDIDRIVER_H
-#define SHERLOCK_SOFTSEQ_MIDIDRIVER_H
+#ifndef SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
+#define SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
#include "sherlock/sherlock.h"
#include "audio/mididrv.h"
@@ -29,9 +29,13 @@
namespace Sherlock {
-extern MidiDriver *MidiDriver_AdLib_create();
-extern void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
+extern MidiDriver *MidiDriver_SH_AdLib_create();
+extern void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
-} // End of namespace Sci
+extern MidiDriver *MidiDriver_MT32_create();
+extern void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize);
+extern void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
-#endif // SHERLOCK_SOFTSEQ_MIDIDRIVER_H
+} // End of namespace Sherlock
+
+#endif // SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
diff --git a/engines/sherlock/scalpel/drivers/mt32.cpp b/engines/sherlock/scalpel/drivers/mt32.cpp
new file mode 100644
index 0000000000..33e7671719
--- /dev/null
+++ b/engines/sherlock/scalpel/drivers/mt32.cpp
@@ -0,0 +1,282 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/drivers/mididriver.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+//#include "audio/mididrv.h"
+
+namespace Sherlock {
+
+#define SHERLOCK_MT32_CHANNEL_COUNT 16
+
+const byte mt32ReverbDataSysEx[] = {
+ 0x10, 0x00, 0x01, 0x01, 0x05, 0x05, 0xFF
+};
+
+class MidiDriver_MT32 : public MidiDriver {
+public:
+ MidiDriver_MT32() {
+ _driver = NULL;
+ _isOpen = false;
+ _nativeMT32 = false;
+ _baseFreq = 250;
+
+ memset(_MIDIchannelActive, 1, sizeof(_MIDIchannelActive));
+ }
+ virtual ~MidiDriver_MT32();
+
+ // MidiDriver
+ int open();
+ void close();
+ bool isOpen() const { return _isOpen; }
+
+ void send(uint32 b);
+
+ void newMusicData(byte *musicData, int32 musicDataSize);
+
+ MidiChannel *allocateChannel() {
+ if (_driver)
+ return _driver->allocateChannel();
+ return NULL;
+ }
+ MidiChannel *getPercussionChannel() {
+ if (_driver)
+ return _driver->getPercussionChannel();
+ return NULL;
+ }
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ if (_driver)
+ _driver->setTimerCallback(timer_param, timer_proc);
+ }
+
+ uint32 getBaseTempo() {
+ if (_driver) {
+ return _driver->getBaseTempo();
+ }
+ return 1000000 / _baseFreq;
+ }
+
+protected:
+ Common::Mutex _mutex;
+ MidiDriver *_driver;
+ bool _nativeMT32;
+
+ bool _isOpen;
+ int _baseFreq;
+
+private:
+ // points to a MIDI channel for each of the new voice channels
+ byte _MIDIchannelActive[SHERLOCK_MT32_CHANNEL_COUNT];
+
+public:
+ void uploadMT32Patches(byte *driverData, int32 driverSize);
+
+ void mt32SysEx(const byte *&dataPtr, int32 &bytesLeft);
+};
+
+MidiDriver_MT32::~MidiDriver_MT32() {
+ Common::StackLock lock(_mutex);
+ if (_driver) {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ }
+ _driver = NULL;
+}
+
+int MidiDriver_MT32::open() {
+ assert(!_driver);
+
+ debugC(kDebugLevelMT32Driver, "MT32: starting driver");
+
+ // Setup midi driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ switch (musicType) {
+ case MT_MT32:
+ _nativeMT32 = true;
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _nativeMT32 = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ _driver = MidiDriver::createMidi(dev);
+ if (!_driver)
+ return 255;
+
+ if (_nativeMT32)
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ if (_nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+
+ return 0;
+}
+
+void MidiDriver_MT32::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+// Called when a music track got loaded into memory
+void MidiDriver_MT32::newMusicData(byte *musicData, int32 musicDataSize) {
+ assert(musicDataSize >= 0x7F); // Security check
+
+ // MIDI Channel Enable/Disable bytes at offset 0x2 of music data
+ memcpy(&_MIDIchannelActive, musicData + 0x2, SHERLOCK_MT32_CHANNEL_COUNT);
+
+ // Send 16 bytes from offset 0x12 to MT32
+ // All the music tracks of Sherlock seem to contain dummy data
+ // probably a feature, that was used in the game "Ski or Die"
+ // that's why we don't implement this
+
+ // Also send these bytes to MT32 (SysEx) - seems to be reverb configuration
+ if (_nativeMT32) {
+ const byte *reverbData = mt32ReverbDataSysEx;
+ int32 reverbDataSize = sizeof(mt32ReverbDataSysEx);
+ mt32SysEx(reverbData, reverbDataSize);
+ }
+}
+
+void MidiDriver_MT32::uploadMT32Patches(byte *driverData, int32 driverSize) {
+ if (!_driver)
+ return;
+
+ if (!_nativeMT32)
+ return;
+
+ // patch data starts at offset 0x863
+ assert(driverSize == 0x13B9); // Security check
+ assert(driverData[0x863] == 0x7F); // another security check
+
+ const byte *patchPtr = driverData + 0x863;
+ int32 bytesLeft = driverSize - 0x863;
+
+ while(1) {
+ mt32SysEx(patchPtr, bytesLeft);
+
+ assert(bytesLeft);
+ if (*patchPtr == 0x80) // List terminator
+ break;
+ }
+}
+
+void MidiDriver_MT32::mt32SysEx(const byte *&dataPtr, int32 &bytesLeft) {
+ byte sysExMessage[270];
+ uint16 sysExPos = 0;
+ byte sysExByte = 0;
+ uint16 sysExChecksum = 0;
+
+ memset(&sysExMessage, 0, sizeof(sysExMessage));
+
+ sysExMessage[0] = 0x41; // Roland
+ sysExMessage[1] = 0x10;
+ sysExMessage[2] = 0x16; // Model MT32
+ sysExMessage[3] = 0x12; // Command DT1
+
+ sysExPos = 4;
+ sysExChecksum = 0;
+ while (1) {
+ assert(bytesLeft);
+
+ sysExByte = *dataPtr++;
+ bytesLeft--;
+ if (sysExByte == 0xff)
+ break; // Message done
+
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExByte;
+ sysExChecksum -= sysExByte;
+ }
+
+ // Calculate checksum
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
+
+ debugC(kDebugLevelMT32Driver, "MT32: uploading patch data, size %d", sysExPos);
+
+ // Send SysEx
+ _driver->sysEx(sysExMessage, sysExPos);
+
+ // Wait the time it takes to send the SysEx data
+ uint32 delay = (sysExPos + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_nativeMT32)
+ delay += 40;
+
+ g_system->delayMillis(delay);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_MT32::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+
+ if (command == 0xF0) {
+ if (_driver) {
+ _driver->send(b);
+ }
+ return;
+ }
+
+ if (_MIDIchannelActive[channel]) {
+ // Only forward MIDI-data in case the channel is currently enabled via music-data
+ if (_driver) {
+ _driver->send(b);
+ }
+ }
+}
+
+MidiDriver *MidiDriver_MT32_create() {
+ return new MidiDriver_MT32();
+}
+
+void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
+ static_cast<MidiDriver_MT32 *>(driver)->newMusicData(musicData, musicDataSize);
+}
+
+void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize) {
+ static_cast<MidiDriver_MT32 *>(driver)->uploadMT32Patches(driverData, driverSize);
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp
index 43b69068ab..c3915a1cf2 100644
--- a/engines/sherlock/scalpel/scalpel.cpp
+++ b/engines/sherlock/scalpel/scalpel.cpp
@@ -22,10 +22,16 @@
#include "engines/util.h"
#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_scene.h"
#include "sherlock/scalpel/tsage/logo.h"
#include "sherlock/sherlock.h"
#include "sherlock/music.h"
#include "sherlock/animation.h"
+// for 3DO
+#include "sherlock/scalpel/3do/movie_decoder.h"
namespace Sherlock {
@@ -92,280 +98,80 @@ static const byte MAP_SEQUENCES[3][MAX_FRAME] = {
#define MAX_PEOPLE 66
-const char PEOPLE_PORTRAITS[MAX_PEOPLE][5] = {
- { "HOLM" }, // Sherlock Holmes
- { "WATS" }, // Dr. Watson
- { "LEST" }, // Inspector Lestrade
- { "CON1" }, // Constable O'Brien
- { "CON2" }, // Constable Lewis
- { "SHEI" }, // Sheila Parker
- { "HENR" }, // Henry Carruthers
- { "LESL" }, // Lesley (flower girl)
- { "USH1" }, // Usher #1
- { "USH2" }, // Usher #2
- { "FRED" }, // Fredrick Epstein
- { "WORT" }, // Mrs. Worthington
- { "COAC" }, // Coach
- { "PLAY" }, // Player
- { "WBOY" }, // Tim (Waterboy)
- { "JAME" }, // James Sanders
- { "BELL" }, // Belle (perfumerie)
- { "GIRL" }, // Cleaning Girl (perfumerie)
- { "EPST" }, // Epstien in the Opera Balcony
- { "WIGG" }, // Wiggins
- { "PAUL" }, // Paul (Brumwell / Carroway)
- { "BART" }, // Bartender
- { "DIRT" }, // Dirty Drunk
- { "SHOU" }, // Shouting Drunk
- { "STAG" }, // Staggering Drunk
- { "BOUN" }, // Bouncer
- { "SAND" }, // James Sanders - At Home
- { "CORO" }, // The Coroner
- { "EQUE" }, // The Equestrian Shop Keeper
- { "GEOR" }, // George Blackwood
- { "LARS" }, // Lars
- { "PARK" }, // Sheila Parker (happy)
- { "CHEM" }, // Chemist
- { "GREG" }, // Inspector Gregson
- { "LAWY" }, // Jacob Farthington Lawyer
- { "MYCR" }, // Mycroft
- { "SHER" }, // Old Sherman
- { "CHMB" }, // Richard Chemist Stock boy
- { "BARM" }, // Barman
- { "DAND" }, // Dandy Player
- { "ROUG" }, // Rough-looking Player
- { "SPEC" }, // Spectator
- { "HUNT" }, // Robert Hunt
- { "VIOL" }, // Violet Secretary
- { "PETT" }, // Pettigrew
- { "APPL" }, // Augie (apple seller)
- { "ANNA" }, // Anna Carroway
- { "GUAR" }, // Guard
- { "ANTO" }, // Antonio Caruso
- { "TOBY" }, // Toby the Dog
- { "KING" }, // Simon Kingsley
- { "ALFR" }, // Alfred Tobacco Clerk
- { "LADY" }, // Lady Brumwell
- { "ROSA" }, // Madame Rosa
- { "LADB" }, // Lady Brumwell
- { "MOOR" }, // Joseph Moorehead
- { "BEAL" }, // Mrs. Beale
- { "LION" }, // Felix the Lion
- { "HOLL" }, // Hollingston
- { "CALL" }, // Constable Callaghan
- { "JERE" }, // Sergeant Jeremy Duncan
- { "LORD" }, // Lord Brumwell
- { "NIGE" }, // Nigel Jameson
- { "JONA" }, // Jonas (newspaper seller)
- { "DUGA" }, // Constable Dugan
- { "INSP" } // Inspector Lestrade (Scotland Yard)
+struct PeopleData {
+ const char *portrait;
+ const char *name;
+ byte stillSequences[MAX_TALK_SEQUENCES];
+ byte talkSequences[MAX_TALK_SEQUENCES];
};
-const char *const PEOPLE_NAMES[MAX_PEOPLE] = {
- "Sherlock Holmes",
- "Dr. Watson",
- "Inspector Lestrade",
- "Constable O'Brien",
- "Constable Lewis",
- "Sheila Parker",
- "Henry Carruthers",
- "Lesley",
- "An Usher",
- "An Usher",
- "Fredrick Epstein",
- "Mrs. Worthington",
- "The Coach",
- "A Player",
- "Tim",
- "James Sanders",
- "Belle",
- "Cleaning Girl",
- "Fredrick Epstein",
- "Wiggins",
- "Paul",
- "The Bartender",
- "A Dirty Drunk",
- "A Shouting Drunk",
- "A Staggering Drunk",
- "The Bouncer",
- "James Sanders",
- "The Coroner",
- "Reginald Snipes",
- "George Blackwood",
- "Lars",
- "Sheila Parker",
- "The Chemist",
- "Inspector Gregson",
- "Jacob Farthington",
- "Mycroft",
- "Old Sherman",
- "Richard",
- "The Barman",
- "A Dandy Player",
- "A Rough-looking Player",
- "A Spectator",
- "Robert Hunt",
- "Violet",
- "Pettigrew",
- "Augie",
- "Anna Carroway",
- "A Guard",
- "Antonio Caruso",
- "Toby the Dog",
- "Simon Kingsley",
- "Alfred",
- "Lady Brumwell",
- "Madame Rosa",
- "Lady Brumwell",
- "Joseph Moorehead",
- "Mrs. Beale",
- "Felix",
- "Hollingston",
- "Constable Callaghan",
- "Sergeant Duncan",
- "Lord Brumwell",
- "Nigel Jaimeson",
- "Jonas",
- "Constable Dugan",
- "Inspector Lestrade"
-};
-
-static const byte PEOPLE_STILL_SEQUENCES[MAX_PEOPLE][MAX_TALK_SEQUENCES] = {
- { 1, 0, 0 }, // Sherlock Holmes
- { 6, 0, 0 }, // Dr. Watson
- { 4, 0, 0 }, // Inspector Lestrade
- { 2, 0, 0 }, // Constable #1
- { 2, 0, 0 }, // Constable #2
- { 2, 0, 0 }, // Sheila Parker
- { 3, 0, 0 }, // Henry Carruthers
- { 9, 0, 0 }, // Lesly (flower girl)
- { 13, 0, 0 }, // Usher #1
- { 2, 0, 0 }, // Usher #2
- { 4, 0, 0 }, // Fredrick Epstein
- { 9, 0, 0 }, // Mrs.Worthington
- { 2, 0, 0 }, // Coach
- { 8, 0, 0 }, // Player
- { 13, 0, 0 }, // Waterboy
- { 6, 0, 0 }, // James Sanders
- { 1, 0, 0 }, // Belle (perfumerie)
- { 20, 0, 0 }, // Cleaning Girl (perfumerie)
- { 17, 0, 0 }, // Epstien in the Opera Balcony
- { 3, 0, 0 }, // Wiggins
- { 2, 0, 0 }, // Paul (Brumwell/Carroway)
- { 1, 0, 0 }, // Bartender
- { 1, 0, 0 }, // Dirty Drunk
- { 1, 0, 0 }, // Shouting Drunk
- { 1, 0, 0 }, // Staggering Drunk
- { 1, 0, 0 }, // Bouncer
- { 6, 0, 0 }, // James Sanders - At Home
- { 6, 0, 0 }, // The Coroner
- { 1, 0, 0 }, // The Equestrian Shop Keeper
- { 1, 0, 0 }, // George Blackwood
- { 7, 0, 0 }, // Lars
- { 1, 0, 0 }, // Sheila Parker
- { 8, 0, 0 }, // Chemist
- { 6, 0, 0 }, // Inspector Gregson
- { 1, 0, 0 }, // Lawyer
- { 1, 0, 0 }, // Mycroft
- { 7, 0, 0 }, // Old Sherman
- { 1, 0, 0 }, // Stock Boy in Chemist Shop
- { 1, 0, 0 }, // Barman
- { 1, 0, 0 }, // Dandy Player
- { 1, 0, 0 }, // Rough-looking Player
- { 1, 0, 0 }, // Spectator
- { 1, 0, 0 }, // Robert Hunt
- { 3, 0, 0 }, // Violet Secretary
- { 1, 0, 0 }, // Pettigrew
- { 8, 0, 0 }, // Augie (apple seller)
- { 16, 0, 0 }, // Anna Carroway
- { 1, 0, 0 }, // Guard
- { 8, 0, 0 }, // Antonio Caruso
- { 1, 0, 0 }, // Toby the Dog
- { 13, 0, 0 }, // Simon Kingsley
- { 2, 0, 0 }, // Alfred Tobacco Clerk
- { 1, 0, 0 }, // Lady Brumwell
- { 1, 0, 0 }, // Madame Rosa
- { 1, 0, 0 }, // Lady Brumwell
- { 1, 0, 0 }, // Joseph Moorehead
- { 5, 0, 0 }, // Mrs. Beale
- { 1, 0, 0 }, // Felix the Lion
- { 1, 0, 0 }, // Hollingston
- { 1, 0, 0 }, // Constable Callaghan
- { 2, 0, 0 }, // Sergeant Jeremy Duncan
- { 1, 0, 0 }, // Lord Brumwell
- { 1, 0, 0 }, // Nigel Jameson
- { 1, 0, 0 }, // Jonas (newspaper seller)
- { 1, 0, 0 }, // Constable Dugan
- { 4, 0, 0 } // Inspector Lestrade (Yard)
-};
-
-static const byte PEOPLE_TALK_SEQUENCES[MAX_PEOPLE][MAX_TALK_SEQUENCES] = {
- { 1, 0, 0 }, // Sherlock Holmes
- { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 }, // Dr. Watson
- { 2, 0, 0 }, // Inspector Lestrade
- { 1, 0, 0 }, // Constable #1
- { 1, 0, 0 }, // Constable #2
- { 2, 3, 0, 0 }, // Sheila Parker
- { 3, 0, 0 }, // Henry Carruthers
- { 1, 2, 3, 2, 1, 2, 3, 0, 0 }, // Lesly (flower girl)
- { 13, 14, 0, 0 }, // Usher #1
- { 2, 0, 0 }, // Usher #2
- { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 }, // Fredrick Epstein
- { 8, 0, 0 }, // Mrs.Worthington
- { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 }, // Coach
- { 7, 8, 0, 0 }, // Player
- { 12, 13, 0, 0 }, // Waterboy
- { 3, 4, 0, 0 }, // James Sanders
- { 4, 5, 0, 0 }, // Belle (perfumerie)
- { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 }, // Cleaning Girl (perfumerie)
- { 16, 17, 18, 18, 18, 17, 17, 0, 0 }, // Epstien in the Opera Balcony
- { 2, 3, 0, 0 }, // Wiggins
- { 1, 2, 0, 0 }, // Paul (Brumwell/Carroway)
- { 1, 0, 0 }, // Bartender
- { 1, 0, 0 }, // Dirty Drunk
- { 1, 0, 0 }, // Shouting Drunk
- { 1, 0, 0 }, // Staggering Drunk
- { 1, 0, 0 }, // Bouncer
- { 5, 6, 0, 0 }, // James Sanders - At Home
- { 4, 5, 0, 0 }, // The Coroner
- { 1, 0, 0 }, // The Equestrian Shop Keeper
- { 1, 0, 0 }, // George Blackwood
- { 5, 6, 0, 0 }, // Lars
- { 1, 0, 0 }, // Sheila Parker
- { 8, 9, 0, 0 }, // Chemist
- { 5, 6, 0, 0 }, // Inspector Gregson
- { 1, 0, 0 }, // Lawyer
- { 1, 0, 0 }, // Mycroft
- { 7, 8, 0, 0 }, // Old Sherman
- { 1, 0, 0 }, // Stock Boy in Chemist Shop
- { 1, 0, 0 }, // Barman
- { 1, 0, 0 }, // Dandy Player
- { 1, 0, 0 }, // Rough-looking Player
- { 1, 0, 0 }, // Spectator
- { 1, 0, 0 }, // Robert Hunt
- { 3, 4, 0, 0 }, // Violet Secretary
- { 1, 0, 0 }, // Pettigrew
- { 14, 15, 0, 0 }, // Augie (apple seller)
- { 3, 4, 5, 6, 0, 0 }, // Anna Carroway
- { 4, 5, 6, 0, 0 }, // Guard
- { 7, 8, 0, 0 }, // Antonio Caruso
- { 1, 0, 0 }, // Toby the Dog
- { 13, 14, 0, 0 }, // Simon Kingsley
- { 2, 3, 0, 0 }, // Alfred Tobacco Clerk
- { 3, 4, 0, 0 }, // Lady Brumwell
- { 1, 30, 0, 0 }, // Madame Rosa
- { 3, 4, 0, 0 }, // Lady Brumwell
- { 1, 0, 0 }, // Joseph Moorehead
- { 14, 15, 16, 17, 18, 19, 20, 0, 0 }, // Mrs. Beale
- { 1, 0, 0 }, // Felix the Lion
- { 1, 0, 0 }, // Hollingston
- { 1, 0, 0 }, // Constable Callaghan
- { 1, 1, 2, 2, 0, 0 }, // Sergeant Jeremy Duncan
- { 9, 10, 0, 0 }, // Lord Brumwell
- { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 }, // Nigel Jameson
- { 1, 8, 0, 0 }, // Jonas (newspaper seller)
- { 1, 0, 0 }, // Constable Dugan
- { 2, 0, 0 } // Inspector Lestrade (Yard)
+const PeopleData PEOPLE_DATA[MAX_PEOPLE] = {
+ { "HOLM", "Sherlock Holmes", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "WATS", "Dr. Watson", { 6, 0, 0 }, { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 } },
+ { "LEST", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } },
+ { "CON1", "Constable O'Brien", { 2, 0, 0 }, { 1, 0, 0 } },
+ { "CON2", "Constable Lewis", { 2, 0, 0 }, { 1, 0, 0 } },
+ { "SHEI", "Sheila Parker", { 2, 0, 0 }, { 2, 3, 0, 0 } },
+ { "HENR", "Henry Carruthers", { 3, 0, 0 }, { 3, 0, 0 } },
+ { "LESL", "Lesley", { 9, 0, 0 }, { 1, 2, 3, 2, 1, 2, 3, 0, 0 } },
+ { "USH1", "An Usher", { 13, 0, 0 }, { 13, 14, 0, 0 } },
+ { "USH2", "An Usher", { 2, 0, 0 }, { 2, 0, 0 } },
+ { "FRED", "Fredrick Epstein", { 4, 0, 0 }, { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 } },
+ { "WORT", "Mrs. Worthington", { 9, 0, 0 }, { 8, 0, 0 } },
+ { "COAC", "The Coach", { 2, 0, 0 }, { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 } },
+ { "PLAY", "A Player", { 8, 0, 0 }, { 7, 8, 0, 0 } },
+ { "WBOY", "Tim", { 13, 0, 0 }, { 12, 13, 0, 0 } },
+ { "JAME", "James Sanders", { 6, 0, 0 }, { 3, 4, 0, 0 } },
+ { "BELL", "Belle", { 1, 0, 0 }, { 4, 5, 0, 0 } },
+ { "GIRL", "Cleaning Girl", { 20, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 } },
+ { "EPST", "Fredrick Epstein", { 17, 0, 0 }, { 16, 17, 18, 18, 18, 17, 17, 0, 0 } },
+ { "WIGG", "Wiggins", { 3, 0, 0 }, { 2, 3, 0, 0 } },
+ { "PAUL", "Paul", { 2, 0, 0 }, { 1, 2, 0, 0 } },
+ { "BART", "The Bartender", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "DIRT", "A Dirty Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SHOU", "A Shouting Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "STAG", "A Staggering Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BOUN", "The Bouncer", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SAND", "James Sanders", { 6, 0, 0 }, { 5, 6, 0, 0 } },
+ { "CORO", "The Coroner", { 6, 0, 0 }, { 4, 5, 0, 0 } },
+ { "EQUE", "Reginald Snipes", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "GEOR", "George Blackwood", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "LARS", "Lars", { 7, 0, 0 }, { 5, 6, 0, 0 } },
+ { "PARK", "Sheila Parker", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "CHEM", "The Chemist", { 8, 0, 0 }, { 8, 9, 0, 0 } },
+ { "GREG", "Inspector Gregson", { 6, 0, 0 }, { 5, 6, 0, 0 } },
+ { "LAWY", "Jacob Farthington", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "MYCR", "Mycroft", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SHER", "Old Sherman", { 7, 0, 0 }, { 7, 8, 0, 0 } },
+ { "CHMB", "Richard", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BARM", "The Barman", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "DAND", "A Dandy Player", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "ROUG", "A Rough-looking Player", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SPEC", "A Spectator", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "HUNT", "Robert Hunt", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "VIOL", "Violet", { 3, 0, 0 }, { 3, 4, 0, 0 } },
+ { "PETT", "Pettigrew", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "APPL", "Augie", { 8, 0, 0 }, { 14, 15, 0, 0 } },
+ { "ANNA", "Anna Carroway", { 16, 0, 0 }, { 3, 4, 5, 6, 0, 0 } },
+ { "GUAR", "A Guard", { 1, 0, 0 }, { 4, 5, 6, 0, 0 } },
+ { "ANTO", "Antonio Caruso", { 8, 0, 0 }, { 7, 8, 0, 0 } },
+ { "TOBY", "Toby the Dog", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "KING", "Simon Kingsley", { 13, 0, 0 }, { 13, 14, 0, 0 } },
+ { "ALFR", "Alfred", { 2, 0, 0 }, { 2, 3, 0, 0 } },
+ { "LADY", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } },
+ { "ROSA", "Madame Rosa", { 1, 0, 0 }, { 1, 30, 0, 0 } },
+ { "LADB", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } },
+ { "MOOR", "Joseph Moorehead", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BEAL", "Mrs. Beale", { 5, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 0, 0 } },
+ { "LION", "Felix", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "HOLL", "Hollingston", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "CALL", "Constable Callaghan", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "JERE", "Sergeant Duncan", { 2, 0, 0 }, { 1, 1, 2, 2, 0, 0 } },
+ { "LORD", "Lord Brumwell", { 1, 0, 0 }, { 9, 10, 0, 0 } },
+ { "NIGE", "Nigel Jaimeson", { 1, 0, 0 }, { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 } },
+ { "JONA", "Jonas", { 1, 0, 0 }, { 1, 8, 0, 0 } },
+ { "DUGA", "Constable Dugan", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "INSP", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } }
};
/*----------------------------------------------------------------*/
@@ -381,7 +187,16 @@ ScalpelEngine::~ScalpelEngine() {
}
void ScalpelEngine::initialize() {
- initGraphics(320, 200, false);
+ // 3DO actually uses RGB555, but some platforms of ours only support RGB565, so we use that
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
+ // 320x200 16-bit RGB565 for 3DO support
+ initGraphics(320, 200, false, &pixelFormatRGB565);
+ } else {
+ // 320x200 palettized
+ initGraphics(320, 200, false);
+ }
// Let the base engine intialize
SherlockEngine::initialize();
@@ -394,9 +209,10 @@ void ScalpelEngine::initialize() {
if (!isDemo()) {
// Load the map co-ordinates for each scene and sequence data
- _map->loadPoints(NUM_PLACES, &MAP_X[0], &MAP_Y[0], &MAP_TRANSLATE[0]);
- _map->loadSequences(3, &MAP_SEQUENCES[0][0]);
- _map->_oldCharPoint = BAKER_ST_EXTERIOR;
+ ScalpelMap &map = *(ScalpelMap *)_map;
+ map.loadPoints(NUM_PLACES, &MAP_X[0], &MAP_Y[0], &MAP_TRANSLATE[0]);
+ map.loadSequences(3, &MAP_SEQUENCES[0][0]);
+ map._oldCharPoint = BAKER_ST_EXTERIOR;
}
// Load the inventory
@@ -404,8 +220,8 @@ void ScalpelEngine::initialize() {
// Set up list of people
for (int idx = 0; idx < MAX_PEOPLE; ++idx)
- _people->_characters.push_back(PersonData(PEOPLE_NAMES[idx], PEOPLE_PORTRAITS[idx],
- PEOPLE_STILL_SEQUENCES[idx], PEOPLE_TALK_SEQUENCES[idx]));
+ _people->_characters.push_back(PersonData(PEOPLE_DATA[idx].name, PEOPLE_DATA[idx].portrait,
+ PEOPLE_DATA[idx].stillSequences, PEOPLE_DATA[idx].talkSequences));
_animation->setPrologueNames(&PROLOGUE_NAMES[0], PROLOGUE_NAMES_COUNT);
_animation->setPrologueFrames(&PROLOGUE_FRAMES[0][0], 6, 9);
@@ -421,45 +237,81 @@ void ScalpelEngine::initialize() {
}
void ScalpelEngine::showOpening() {
+ bool finished = true;
+
if (isDemo() && _interactiveFl)
return;
- if (!TsAGE::Logo::show(this))
- return;
- if (!showCityCutscene())
- return;
- if (!showAlleyCutscene())
- return;
- if (!showStreetCutscene())
- return;
- if (!showOfficeCutscene())
- return;
+ _events->setFrameRate(60);
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ show3DOSplash();
+
+ finished = showCityCutscene3DO();
+ if (finished)
+ finished = showAlleyCutscene3DO();
+ if (finished)
+ finished = showStreetCutscene3DO();
+ if (finished)
+ showOfficeCutscene3DO();
+
+ _events->clearEvents();
+ _music->stopMusic();
+ } else {
+ TsAGE::Logo::show(this);
+
+ finished = showCityCutscene();
+ if (finished)
+ finished = showAlleyCutscene();
+ if (finished)
+ finished = showStreetCutscene();
+ if (finished)
+ showOfficeCutscene();
+
+ _events->clearEvents();
+ _music->stopMusic();
+ }
- _events->clearEvents();
- _music->stopMusic();
+ _events->setFrameRate(GAME_FRAME_RATE);
}
bool ScalpelEngine::showCityCutscene() {
+ byte greyPalette[PALETTE_SIZE];
byte palette[PALETTE_SIZE];
- _music->playMusic("prolog1.mus");
+ // Demo fades from black into grey and then fades from grey into the scene
+ Common::fill(&greyPalette[0], &greyPalette[PALETTE_SIZE], 142);
+ _screen->fadeIn((const byte *)greyPalette, 3);
+
+ _music->loadSong("prolog1");
_animation->_gfxLibraryFilename = "title.lib";
_animation->_soundLibraryFilename = "title.snd";
- bool finished = _animation->play("26open1", 1, 255, true, 2);
+ bool finished = _animation->play("26open1", true, 1, 255, true, 2);
if (finished) {
- ImageFile titleImages("title2.vgs", true);
+ ImageFile titleImages_LondonNovember("title2.vgs", true);
_screen->_backBuffer1.blitFrom(*_screen);
_screen->_backBuffer2.blitFrom(*_screen);
+ Common::Point londonPosition;
+
+ if ((titleImages_LondonNovember[0]._width == 302) && (titleImages_LondonNovember[0]._height == 39)) {
+ // Spanish
+ londonPosition = Common::Point(9, 8);
+ } else {
+ // English (German uses the same English graphics), width 272, height 37
+ // In the German version this is placed differently, check against German floppy version TODO
+ londonPosition = Common::Point(30, 50);
+ }
+
// London, England
- _screen->_backBuffer1.transBlitFrom(titleImages[0], Common::Point(10, 11));
+ _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[0], londonPosition);
_screen->randomTransition();
finished = _events->delay(1000, true);
// November, 1888
if (finished) {
- _screen->_backBuffer1.transBlitFrom(titleImages[1], Common::Point(101, 102));
+ _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100));
_screen->randomTransition();
finished = _events->delay(5000, true);
}
@@ -470,19 +322,35 @@ bool ScalpelEngine::showCityCutscene() {
}
if (finished)
- finished = _animation->play("26open2", 1, 0, false, 2);
+ finished = _animation->play("26open2", true, 1, 0, false, 2);
if (finished) {
- ImageFile titleImages("title.vgs", true);
+ ImageFile titleImages_SherlockHolmesTitle("title.vgs", true);
_screen->_backBuffer1.blitFrom(*_screen);
_screen->_backBuffer2.blitFrom(*_screen);
+ Common::Point lostFilesPosition;
+ Common::Point sherlockHolmesPosition;
+ Common::Point copyrightPosition;
+
+ if ((titleImages_SherlockHolmesTitle[0]._width == 306) && (titleImages_SherlockHolmesTitle[0]._height == 39)) {
+ // Spanish
+ lostFilesPosition = Common::Point(5, 5);
+ sherlockHolmesPosition = Common::Point(24, 40);
+ copyrightPosition = Common::Point(3, 190);
+ } else {
+ // English (German uses the same English graphics), width 208, height 39
+ lostFilesPosition = Common::Point(75, 6);
+ sherlockHolmesPosition = Common::Point(34, 21);
+ copyrightPosition = Common::Point(4, 190);
+ }
+
// The Lost Files of
- _screen->_backBuffer1.transBlitFrom(titleImages[0], Common::Point(75, 6));
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition);
// Sherlock Holmes
- _screen->_backBuffer1.transBlitFrom(titleImages[1], Common::Point(34, 21));
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition);
// copyright
- _screen->_backBuffer1.transBlitFrom(titleImages[2], Common::Point(4, 190));
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition);
_screen->verticalTransition();
finished = _events->delay(4000, true);
@@ -500,9 +368,23 @@ bool ScalpelEngine::showCityCutscene() {
if (finished) {
// In the alley...
- _screen->transBlitFrom(titleImages[3], Common::Point(72, 51));
+ Common::Point alleyPosition;
+
+ if ((titleImages_SherlockHolmesTitle[3]._width == 105) && (titleImages_SherlockHolmesTitle[3]._height == 16)) {
+ // German
+ alleyPosition = Common::Point(72, 50);
+ } else if ((titleImages_SherlockHolmesTitle[3]._width == 166) && (titleImages_SherlockHolmesTitle[3]._height == 36)) {
+ // Spanish
+ alleyPosition = Common::Point(71, 50);
+ } else {
+ // English, width 175, height 38
+ alleyPosition = Common::Point(72, 51);
+ }
+ _screen->transBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition);
_screen->fadeIn(palette, 3);
- finished = _events->delay(3000, true);
+
+ // Wait until the track got looped and the first few notes were played
+ finished = _music->waitUntilMSec(4300, 21300, 0, 2500); // ticks 0x104 / ticks 0x500
}
}
@@ -513,22 +395,45 @@ bool ScalpelEngine::showCityCutscene() {
bool ScalpelEngine::showAlleyCutscene() {
byte palette[PALETTE_SIZE];
- _music->playMusic("prolog2.mus");
+ _music->loadSong("prolog2");
_animation->_gfxLibraryFilename = "TITLE.LIB";
_animation->_soundLibraryFilename = "TITLE.SND";
- bool finished = _animation->play("27PRO1", 1, 3, true, 2);
- if (finished)
- finished = _animation->play("27PRO2", 1, 0, false, 2);
+ // Fade "In The Alley..." text to black
+ _screen->fadeToBlack(2);
+
+ bool finished = _animation->play("27PRO1", true, 1, 3, true, 2);
+ if (finished) {
+ _screen->getPalette(palette);
+ _screen->fadeToBlack(2);
+
+ // wait until second lower main note
+ finished = _music->waitUntilMSec(26800, 0xFFFFFFFF, 0, 1000); // ticks 0x64A
+ }
+
+ if (finished) {
+ _screen->setPalette(palette);
+ finished = _animation->play("27PRO2", true, 1, 0, false, 2);
+ }
if (finished) {
showLBV("scream.lbv");
- finished = _events->delay(6000);
+
+ // wait until first "scream" in music happened
+ finished = _music->waitUntilMSec(45800, 0xFFFFFFFF, 0, 6000); // ticks 0xABE
+ }
+
+ if (finished) {
+ // quick fade out
+ _screen->fadeToBlack(1);
+
+ // wait until after third "scream" in music happened
+ finished = _music->waitUntilMSec(49000, 0xFFFFFFFF, 0, 2000); // ticks 0xB80
}
if (finished)
- finished = _animation->play("27PRO3", 1, 0, true, 2);
+ finished = _animation->play("27PRO3", true, 1, 0, true, 2);
if (finished) {
_screen->getPalette(palette);
@@ -536,11 +441,28 @@ bool ScalpelEngine::showAlleyCutscene() {
}
if (finished) {
- ImageFile titleImages("title3.vgs", true);
+ ImageFile titleImages_EarlyTheFollowingMorning("title3.vgs", true);
// "Early the following morning on Baker Street..."
- _screen->_backBuffer1.transBlitFrom(titleImages[0], Common::Point(35, 51), false, 0);
- _screen->fadeIn(palette, 3);
- finished = _events->delay(1000);
+ Common::Point earlyTheFollowingMorningPosition;
+
+ if ((titleImages_EarlyTheFollowingMorning[0]._width == 164) && (titleImages_EarlyTheFollowingMorning[0]._height == 19)) {
+ // German
+ earlyTheFollowingMorningPosition = Common::Point(35, 50);
+ } else if ((titleImages_EarlyTheFollowingMorning[0]._width == 171) && (titleImages_EarlyTheFollowingMorning[0]._height == 32)) {
+ // Spanish
+ earlyTheFollowingMorningPosition = Common::Point(35, 50);
+ } else {
+ // English, width 218, height 31
+ earlyTheFollowingMorningPosition = Common::Point(35, 52);
+ }
+
+ _screen->transBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition);
+
+ // fast fade-in
+ _screen->fadeIn(palette, 1);
+
+ // wait for music to end and wait an additional 2.5 seconds
+ finished = _music->waitUntilMSec(0xFFFFFFFF, 0xFFFFFFFF, 2500, 3000);
}
_animation->_gfxLibraryFilename = "";
@@ -552,24 +474,89 @@ bool ScalpelEngine::showStreetCutscene() {
_animation->_gfxLibraryFilename = "TITLE.LIB";
_animation->_soundLibraryFilename = "TITLE.SND";
- _music->playMusic("PROLOG3.MUS");
+ _music->loadSong("prolog3");
+
+ // wait a bit
+ bool finished = _events->delay(500);
- bool finished = _animation->play("14KICK", 1, 3, true, 2);
+ if (finished) {
+ // fade out "Early the following morning..."
+ _screen->fadeToBlack(2);
+
+ // wait for music a bit
+ finished = _music->waitUntilMSec(3800, 0xFFFFFFFF, 0, 1000); // ticks 0xE4
+ }
if (finished)
- finished = _animation->play("14NOTE", 1, 0, false, 2);
+ finished = _animation->play("14KICK", true, 1, 3, true, 2);
+
+ // Constable animation plays slower than speed 2
+ // If we play it with speed 2, music gets obviously out of sync
+ if (finished)
+ finished = _animation->play("14NOTE", true, 1, 0, false, 3);
+
+ // Fade to black
+ if (finished)
+ _screen->fadeToBlack(1);
_animation->_gfxLibraryFilename = "";
_animation->_soundLibraryFilename = "";
return finished;
}
+bool ScalpelEngine::showOfficeCutscene() {
+ _music->loadSong("prolog4");
+ _animation->_gfxLibraryFilename = "TITLE2.LIB";
+ _animation->_soundLibraryFilename = "TITLE.SND";
+
+ bool finished = _animation->play("COFF1", true, 1, 3, true, 3);
+ if (finished)
+ finished = _animation->play("COFF2", true, 1, 0, false, 3);
+ if (finished) {
+ showLBV("note.lbv");
+
+ if (_sound->_voices) {
+ finished = _sound->playSound("NOTE1", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE2", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE3", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE4", WAIT_KBD_OR_FINISH);
+ } else
+ finished = _events->delay(19000);
+
+ if (finished) {
+ _events->clearEvents();
+ finished = _events->delay(500);
+ }
+ }
+
+ if (finished)
+ finished = _animation->play("COFF3", true, 1, 0, true, 3);
+
+ if (finished)
+ finished = _animation->play("COFF4", true, 1, 0, false, 3);
+
+ if (finished)
+ finished = scrollCredits();
+
+ if (finished)
+ _screen->fadeToBlack(3);
+
+ _animation->_gfxLibraryFilename = "";
+ _animation->_soundLibraryFilename = "";
+ return finished;
+}
bool ScalpelEngine::scrollCredits() {
// Load the images for displaying credit text
Common::SeekableReadStream *stream = _res->load("credits.vgs", "title.lib");
ImageFile creditsImages(*stream);
- _screen->setPalette(creditsImages._palette);
+
+ // Demo fades slowly from the scene into credits palette
+ _screen->fadeIn(creditsImages._palette, 3);
+
delete stream;
// Save a copy of the screen background for use in drawing each credit frame
@@ -597,70 +584,327 @@ bool ScalpelEngine::scrollCredits() {
return true;
}
-bool ScalpelEngine::showOfficeCutscene() {
- _music->playMusic("PROLOG4.MUS");
- _animation->_gfxLibraryFilename = "TITLE2.LIB";
+// 3DO variant
+bool ScalpelEngine::show3DOSplash() {
+ // 3DO EA Splash screen
+ ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel);
+
+ _screen->transBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20));
+ bool finished = _events->delay(3000, true);
+
+ if (finished) {
+ _screen->clear();
+ finished = _events->delay(500, true);
+ }
+
+ if (finished) {
+ // EA logo movie
+ Scalpel3DOMoviePlay("EAlogo.stream", Common::Point(20, 0));
+ }
+
+ // Always clear screen
+ _screen->clear();
+ return finished;
+}
+
+bool ScalpelEngine::showCityCutscene3DO() {
_animation->_soundLibraryFilename = "TITLE.SND";
- bool finished = _animation->play("COFF1", 1, 3, true, 3);
+ _screen->clear();
+ bool finished = _events->delay(2500, true);
+
+ // rain.aiff seems to be playing in an endless loop until
+ // sherlock logo fades away TODO
+
+ if (finished) {
+ finished = _events->delay(2500, true);
+
+ // Play intro music
+ _music->loadSong("prolog");
+
+ // Fade screen to grey
+ _screen->_backBuffer1.fill(0xCE59); // RGB565: 25, 50, 25 (grey)
+ _screen->fadeIntoScreen3DO(2);
+ }
+
+ if (finished) {
+ finished = _music->waitUntilMSec(3400, 0, 0, 3400);
+ }
+
+ if (finished) {
+ _screen->_backBuffer1.fill(0); // fill backbuffer with black to avoid issues during fade from white
+ finished = _animation->play3DO("26open1", true, 1, true, 2);
+ }
+
+ if (finished) {
+ _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade
+ _screen->_backBuffer2.blitFrom(*_screen); // save into backbuffer 2, for restoring later
+
+ // "London, England"
+ ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50));
+
+ _screen->fadeIntoScreen3DO(1);
+ finished = _events->delay(1500, true);
+
+ if (finished) {
+ // "November, 1888"
+ ImageFile3DO titleImage_November("title2b.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100));
+
+ _screen->fadeIntoScreen3DO(1);
+ finished = _music->waitUntilMSec(14700, 0, 0, 5000);
+ }
+
+ if (finished) {
+ // Restore screen
+ _screen->blitFrom(_screen->_backBuffer2);
+ }
+ }
+
if (finished)
- finished = _animation->play("COFF2", 1, 0, false, 3);
+ finished = _animation->play3DO("26open2", true, 1, false, 2);
+
if (finished) {
- showLBV("note.lbv");
+ _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade
- if (_sound->_voices) {
- finished = _sound->playSound("NOTE1", WAIT_KBD_OR_FINISH);
- if (finished)
- finished = _sound->playSound("NOTE2", WAIT_KBD_OR_FINISH);
- if (finished)
- finished = _sound->playSound("NOTE3", WAIT_KBD_OR_FINISH);
- if (finished)
- finished = _sound->playSound("NOTE4", WAIT_KBD_OR_FINISH);
- } else
- finished = _events->delay(19000);
+ // "Sherlock Holmes" (title)
+ ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5));
+
+ // Blend in
+ _screen->fadeIntoScreen3DO(2);
+ finished = _events->delay(500, true);
+ // Title should fade in, Copyright should be displayed a bit after that
if (finished) {
- _events->clearEvents();
- finished = _events->delay(500);
+ ImageFile3DO titleImage_Copyright("title1c.cel", kImageFile3DOType_Cel);
+
+ _screen->transBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190));
+ finished = _events->delay(3500, true);
}
}
if (finished)
- finished = _animation->play("COFF3", 1, 0, true, 3);
+ finished = _music->waitUntilMSec(33600, 0, 0, 2000);
+
+ if (finished) {
+ // Fade to black
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(3);
+ }
+
+ if (finished) {
+ // "In the alley behind the Regency Theatre..."
+ ImageFile3DO titleImage_InTheAlley("title1d.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51));
+
+ // Fade in
+ _screen->fadeIntoScreen3DO(4);
+ finished = _music->waitUntilMSec(39900, 0, 0, 2500);
+
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+ }
+ return finished;
+}
+
+bool ScalpelEngine::showAlleyCutscene3DO() {
+ bool finished = _music->waitUntilMSec(43500, 0, 0, 1000);
if (finished)
- finished = _animation->play("COFF4", 1, 0, false, 3);
+ finished = _animation->play3DO("27PRO1", true, 1, false, 2);
+
+ if (finished) {
+ // Fade out...
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(3);
+
+ finished = _music->waitUntilMSec(67100, 0, 0, 1000); // 66700
+ }
if (finished)
- finished = scrollCredits();
+ finished = _animation->play3DO("27PRO2", true, 1, false, 2);
if (finished)
- _screen->fadeToBlack(3);
+ finished = _music->waitUntilMSec(76000, 0, 0, 1000);
+
+ if (finished) {
+ // Show screaming victim
+ ImageFile3DO titleImage_ScreamingVictim("scream.cel", kImageFile3DOType_Cel);
+
+ _screen->clear();
+ _screen->transBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0));
+
+ // Play "scream.aiff"
+ if (_sound->_voices)
+ _sound->playSound("prologue/sounds/scream.aiff", WAIT_RETURN_IMMEDIATELY, 100);
+
+ finished = _music->waitUntilMSec(81600, 0, 0, 6000);
+ }
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(5);
+
+ finished = _music->waitUntilMSec(84400, 0, 0, 2000);
+ }
+
+ if (finished)
+ finished = _animation->play3DO("27PRO3", true, 1, false, 2);
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(5);
+ }
+
+ if (finished) {
+ // "Early the following morning on Baker Street..."
+ ImageFile3DO titleImage_EarlyTheFollowingMorning("title3.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51));
+
+ // Fade in
+ _screen->fadeIntoScreen3DO(4);
+ finished = _music->waitUntilMSec(96700, 0, 0, 3000);
+ }
+
+ return finished;
+}
+
+bool ScalpelEngine::showStreetCutscene3DO() {
+ bool finished = true;
+
+ if (finished) {
+ // fade out "Early the following morning..."
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+
+ // wait for music a bit
+ finished = _music->waitUntilMSec(100300, 0, 0, 1000);
+ }
+
+ if (finished)
+ finished = _animation->play3DO("14KICK", true, 1, false, 2);
+
+ // note: part of the constable is sticking to the door during the following
+ // animation, when he walks away. This is a bug of course, but it actually happened on 3DO!
+ // I'm not sure if it happens because the door is pure black (0, 0, 0) and it's because
+ // of transparency - or if the animation itself is bad. We will definitely have to adjust
+ // the animation data to fix it.
+ if (finished)
+ finished = _animation->play3DO("14NOTE", true, 1, false, 3);
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+ }
+
+ return finished;
+}
+
+bool ScalpelEngine::showOfficeCutscene3DO() {
+ bool finished = _music->waitUntilMSec(151000, 0, 0, 1000);
+
+ if (finished)
+ finished = _animation->play3DO("COFF1", true, 1, false, 3);
+
+ if (finished)
+ finished = _animation->play3DO("COFF2", true, 1, false, 3);
+
+ if (finished)
+ finished = _music->waitUntilMSec(182400, 0, 0, 1000);
+
+ if (finished) {
+ // Show the note
+ ImageFile3DO titleImage_CoffeeNote("note.cel", kImageFile3DOType_Cel);
+
+ _screen->clear();
+ _screen->transBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0));
+
+ if (_sound->_voices) {
+ finished = _sound->playSound("prologue/sounds/note.aiff", WAIT_KBD_OR_FINISH);
+ } else
+ finished = _events->delay(19000);
+
+ if (finished)
+ finished = _music->waitUntilMSec(218800, 0, 0, 1000);
+
+ // Fade out
+ _screen->clear();
+ }
+
+ if (finished)
+ finished = _music->waitUntilMSec(222200, 0, 0, 1000);
+
+ if (finished)
+ finished = _animation->play3DO("COFF3", true, 1, false, 3);
+
+ if (finished)
+ finished = _animation->play3DO("COFF4", true, 1, false, 3);
+
+ if (finished) {
+ finished = _music->waitUntilMSec(244500, 0, 0, 2000);
+
+ // TODO: Brighten the image, possibly by doing a partial fade
+ // to white.
+
+ _screen->_backBuffer1.blitFrom(*_screen);
+
+ for (int nr = 1; finished && nr <= 4; nr++) {
+ char filename[15];
+ sprintf(filename, "credits%d.cel", nr);
+ ImageFile3DO *creditsImage = new ImageFile3DO(filename, kImageFile3DOType_Cel);
+ ImageFrame *creditsFrame = &(*creditsImage)[0];
+ for (int i = 0; finished && i < 200 + creditsFrame->_height; i++) {
+ _screen->blitFrom(_screen->_backBuffer1);
+ _screen->transBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i));
+ if (!_events->delay(70, true))
+ finished = false;
+ }
+ delete creditsImage;
+ }
+ }
- _animation->_gfxLibraryFilename = "";
- _animation->_soundLibraryFilename = "";
return finished;
}
void ScalpelEngine::loadInventory() {
+ ScalpelFixedText &fixedText = *(ScalpelFixedText *)_fixedText;
Inventory &inv = *_inventory;
+ Common::String fixedText_Message = fixedText.getText(kFixedText_InitInventory_Message);
+ Common::String fixedText_HolmesCard = fixedText.getText(kFixedText_InitInventory_HolmesCard);
+ Common::String fixedText_Tickets = fixedText.getText(kFixedText_InitInventory_Tickets);
+ Common::String fixedText_CuffLink = fixedText.getText(kFixedText_InitInventory_CuffLink);
+ Common::String fixedText_WireHook = fixedText.getText(kFixedText_InitInventory_WireHook);
+ Common::String fixedText_Note = fixedText.getText(kFixedText_InitInventory_Note);
+ Common::String fixedText_OpenWatch = fixedText.getText(kFixedText_InitInventory_OpenWatch);
+ Common::String fixedText_Paper = fixedText.getText(kFixedText_InitInventory_Paper);
+ Common::String fixedText_Letter = fixedText.getText(kFixedText_InitInventory_Letter);
+ Common::String fixedText_Tarot = fixedText.getText(kFixedText_InitInventory_Tarot);
+ Common::String fixedText_OrnateKey = fixedText.getText(kFixedText_InitInventory_OrnateKey);
+ Common::String fixedText_PawnTicket = fixedText.getText(kFixedText_InitInventory_PawnTicket);
+
// Initial inventory
inv._holdings = 2;
- inv.push_back(InventoryItem(0, "Message", "A message requesting help", "_ITEM03A"));
- inv.push_back(InventoryItem(0, "Holmes Card", "A number of business cards", "_ITEM07A"));
+ inv.push_back(InventoryItem(0, "Message", fixedText_Message, "_ITEM03A"));
+ inv.push_back(InventoryItem(0, "Holmes Card", fixedText_HolmesCard, "_ITEM07A"));
// Hidden items
- inv.push_back(InventoryItem(95, "Tickets", "Opera Tickets", "_ITEM10A"));
- inv.push_back(InventoryItem(138, "Cuff Link", "Cuff Link", "_ITEM04A"));
- inv.push_back(InventoryItem(138, "Wire Hook", "Wire Hook", "_ITEM06A"));
- inv.push_back(InventoryItem(150, "Note", "Note", "_ITEM13A"));
- inv.push_back(InventoryItem(481, "Open Watch", "An open pocket watch", "_ITEM62A"));
- inv.push_back(InventoryItem(481, "Paper", "A piece of paper with numbers on it", "_ITEM44A"));
- inv.push_back(InventoryItem(532, "Letter", "A letter folded many times", "_ITEM68A"));
- inv.push_back(InventoryItem(544, "Tarot", "Tarot Cards", "_ITEM71A"));
- inv.push_back(InventoryItem(544, "Ornate Key", "An ornate key", "_ITEM70A"));
- inv.push_back(InventoryItem(586, "Pawn ticket", "A pawn ticket", "_ITEM16A"));
+ inv.push_back(InventoryItem(95, "Tickets", fixedText_Tickets, "_ITEM10A"));
+ inv.push_back(InventoryItem(138, "Cuff Link", fixedText_CuffLink, "_ITEM04A"));
+ inv.push_back(InventoryItem(138, "Wire Hook", fixedText_WireHook, "_ITEM06A"));
+ inv.push_back(InventoryItem(150, "Note", fixedText_Note, "_ITEM13A"));
+ inv.push_back(InventoryItem(481, "Open Watch", fixedText_OpenWatch, "_ITEM62A"));
+ inv.push_back(InventoryItem(481, "Paper", fixedText_Paper, "_ITEM44A"));
+ inv.push_back(InventoryItem(532, "Letter", fixedText_Letter, "_ITEM68A"));
+ inv.push_back(InventoryItem(544, "Tarot", fixedText_Tarot, "_ITEM71A"));
+ inv.push_back(InventoryItem(544, "Ornate Key", fixedText_OrnateKey, "_ITEM70A"));
+ inv.push_back(InventoryItem(586, "Pawn ticket", fixedText_PawnTicket, "_ITEM16A"));
}
void ScalpelEngine::showLBV(const Common::String &filename) {
@@ -682,8 +926,8 @@ void ScalpelEngine::startScene() {
_scene->_goToScene = _map->show();
_music->freeSong();
- _people->_hSavedPos = Common::Point(-1, -1);
- _people->_hSavedFacing = -1;
+ _people->_savedPos = Common::Point(-1, -1);
+ _people->_savedPos._facing = -1;
}
// Some rooms are prologue cutscenes, rather than normal game scenes. These are:
@@ -705,8 +949,8 @@ void ScalpelEngine::startScene() {
// Blackwood's capture
_res->addToCache("final2.vda", "epilogue.lib");
_res->addToCache("final2.vdx", "epilogue.lib");
- _animation->play("final1", 1, 3, true, 4);
- _animation->play("final2", 1, 0, false, 4);
+ _animation->play("final1", false, 1, 3, true, 4);
+ _animation->play("final2", false, 1, 0, false, 4);
break;
case RESCUE_ANNA:
@@ -722,8 +966,8 @@ void ScalpelEngine::startScene() {
_res->addToCache("finale4.vda", "EPILOG2.lib");
_res->addToCache("finale4.vdx", "EPILOG2.lib");
- _animation->play("finalr1", 1, 3, true, 4);
- _animation->play("finalr2", 1, 0, false, 4);
+ _animation->play("finalr1", false, 1, 3, true, 4);
+ _animation->play("finalr2", false, 1, 0, false, 4);
if (!_res->isInCache("finale2.vda")) {
// Finale file isn't cached
@@ -735,12 +979,12 @@ void ScalpelEngine::startScene() {
_res->addToCache("finale4.vdx", "EPILOG2.lib");
}
- _animation->play("finale1", 1, 0, false, 4);
- _animation->play("finale2", 1, 0, false, 4);
- _animation->play("finale3", 1, 0, false, 4);
+ _animation->play("finale1", false, 1, 0, false, 4);
+ _animation->play("finale2", false, 1, 0, false, 4);
+ _animation->play("finale3", false, 1, 0, false, 4);
_useEpilogue2 = true;
- _animation->play("finale4", 1, 0, false, 4);
+ _animation->play("finale4", false, 1, 0, false, 4);
_useEpilogue2 = false;
break;
@@ -751,9 +995,9 @@ void ScalpelEngine::startScene() {
_res->addToCache("SUBWAY3.vda", "epilogue.lib");
_res->addToCache("SUBWAY3.vdx", "epilogue.lib");
- _animation->play("SUBWAY1", 1, 3, true, 4);
- _animation->play("SUBWAY2", 1, 0, false, 4);
- _animation->play("SUBWAY3", 1, 0, false, 4);
+ _animation->play("SUBWAY1", false, 1, 3, true, 4);
+ _animation->play("SUBWAY2", false, 1, 0, false, 4);
+ _animation->play("SUBWAY3", false, 1, 0, false, 4);
// Set fading to direct fade temporary so the transition goes quickly.
_scene->_tempFadeStyle = _screen->_fadeStyle ? 257 : 256;
@@ -762,7 +1006,7 @@ void ScalpelEngine::startScene() {
case BRUMWELL_SUICIDE:
// Brumwell suicide
- _animation->play("suicid", 1, 3, true, 4);
+ _animation->play("suicid", false, 1, 3, true, 4);
break;
default:
break;
@@ -822,8 +1066,8 @@ void ScalpelEngine::startScene() {
_mapResult = _scene->_goToScene;
}
-void ScalpelEngine::eraseMirror12() {
- Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100);
+void ScalpelEngine::eraseBrumwellMirror() {
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
// If player is in range of the mirror, then restore background from the secondary back buffer
if (Common::Rect(70, 100, 200, 200).contains(pt)) {
@@ -832,15 +1076,15 @@ void ScalpelEngine::eraseMirror12() {
}
}
-void ScalpelEngine::doMirror12() {
+void ScalpelEngine::doBrumwellMirror() {
People &people = *_people;
- Person &player = people._player;
+ Person &player = people[HOLMES];
- Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100);
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
int frameNum = player._walkSequences[player._sequenceNumber][player._frameNumber] +
player._walkSequences[player._sequenceNumber][0] - 2;
- switch ((*_people)[AL]._sequenceNumber) {
+ switch ((*_people)[HOLMES]._sequenceNumber) {
case WALK_DOWN:
frameNum -= 7;
break;
@@ -883,12 +1127,12 @@ void ScalpelEngine::doMirror12() {
if (Common::Rect(80, 100, 145, 138).contains(pt)) {
// Get the frame of Sherlock to draw
- ImageFrame &imageFrame = (*people[AL]._images)[frameNum];
+ ImageFrame &imageFrame = (*people[HOLMES]._images)[frameNum];
// Draw the mirror image of Holmes
- bool flipped = people[AL]._sequenceNumber == WALK_LEFT || people[AL]._sequenceNumber == STOP_LEFT
- || people[AL]._sequenceNumber == WALK_UPRIGHT || people[AL]._sequenceNumber == STOP_UPRIGHT
- || people[AL]._sequenceNumber == WALK_DOWNLEFT || people[AL]._sequenceNumber == STOP_DOWNLEFT;
+ bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT
+ || people[HOLMES]._sequenceNumber == WALK_UPRIGHT || people[HOLMES]._sequenceNumber == STOP_UPRIGHT
+ || people[HOLMES]._sequenceNumber == WALK_DOWNLEFT || people[HOLMES]._sequenceNumber == STOP_DOWNLEFT;
_screen->_backBuffer1.transBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped);
// Redraw the mirror borders to prevent the drawn image of Holmes from appearing outside of the mirror
@@ -907,8 +1151,8 @@ void ScalpelEngine::doMirror12() {
}
}
-void ScalpelEngine::flushMirror12() {
- Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100);
+void ScalpelEngine::flushBrumwellMirror() {
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
// If player is in range of the mirror, then draw the entire mirror area to the screen
if (Common::Rect(70, 100, 200, 200).contains(pt))
@@ -917,4 +1161,4 @@ void ScalpelEngine::flushMirror12() {
} // End of namespace Scalpel
-} // End of namespace Scalpel
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel.h b/engines/sherlock/scalpel/scalpel.h
index 8743bfb7a9..cb1cb20492 100644
--- a/engines/sherlock/scalpel/scalpel.h
+++ b/engines/sherlock/scalpel/scalpel.h
@@ -24,40 +24,56 @@
#define SHERLOCK_SCALPEL_H
#include "sherlock/sherlock.h"
-#include "sherlock/scalpel/darts.h"
+#include "sherlock/scalpel/scalpel_darts.h"
namespace Sherlock {
namespace Scalpel {
-enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19,
- LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55,
- BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 };
+enum {
+ BUTTON_TOP = 233,
+ BUTTON_MIDDLE = 244,
+ BUTTON_BOTTOM = 248,
+ COMMAND_FOREGROUND = 15,
+ COMMAND_HIGHLIGHTED = 10,
+ COMMAND_NULL = 248,
+ INFO_FOREGROUND = 11,
+ INFO_BACKGROUND = 1,
+ INV_FOREGROUND = 14,
+ INV_BACKGROUND = 1,
+ PEN_COLOR = 250
+};
class ScalpelEngine : public SherlockEngine {
private:
Darts *_darts;
int _mapResult;
+ bool show3DOSplash();
+
/**
* Show the starting city cutscene which shows the game title
*/
bool showCityCutscene();
+ bool showCityCutscene3DO();
/**
* Show the back alley where the initial murder takes place
*/
bool showAlleyCutscene();
+ bool showAlleyCutscene3DO();
/**
* Show the Baker Street outside cutscene
*/
bool showStreetCutscene();
+ bool showStreetCutscene3DO();
/**
* Show Holmes and Watson at the breakfast table, lestrade's note, and then the scrolling credits
*/
bool showOfficeCutscene();
+ bool showOfficeCutscene3DO();
/**
* Show the game credits
@@ -97,17 +113,17 @@ public:
/**
* Takes care of clearing the mirror in scene 12 (mansion drawing room), in case anything drew over it
*/
- void eraseMirror12();
+ void eraseBrumwellMirror();
/**
* Takes care of drawing Holme's reflection onto the mirror in scene 12 (mansion drawing room)
*/
- void doMirror12();
+ void doBrumwellMirror();
/**
* This clears the mirror in scene 12 (mansion drawing room) in case anything messed draw over it
*/
- void flushMirror12();
+ void flushBrumwellMirror();
};
} // End of namespace Scalpel
diff --git a/engines/sherlock/scalpel/darts.cpp b/engines/sherlock/scalpel/scalpel_darts.cpp
index 8d78335a55..87f4566837 100644
--- a/engines/sherlock/scalpel/darts.cpp
+++ b/engines/sherlock/scalpel/scalpel_darts.cpp
@@ -20,7 +20,7 @@
*
*/
-#include "sherlock/scalpel/darts.h"
+#include "sherlock/scalpel/scalpel_darts.h"
#include "sherlock/scalpel/scalpel.h"
namespace Sherlock {
@@ -117,7 +117,7 @@ void Darts::playDarts() {
if (playerNumber == 0) {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "Holmes Wins!");
if (_level < OPPONENTS_COUNT)
- setFlagsForDarts(318 + _level);
+ _vm->setFlagsDirect(318 + _level);
} else {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "%s Wins!", _opponent.c_str());
}
@@ -366,8 +366,8 @@ void Darts::drawDartThrow(const Common::Point &pt) {
void Darts::erasePowerBars() {
Screen &screen = *_vm->_screen;
- screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), 0);
- screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), 0);
+ screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK);
+ screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK);
screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1));
screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1));
screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11);
@@ -377,15 +377,11 @@ void Darts::erasePowerBars() {
int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
- Music &music = *_vm->_music;
bool done;
int idx = 0;
events.clearEvents();
- if (music._musicOn)
- music.waitTimerRoland(10);
- else
- events.delay(100);
+ events.delay(100);
// Display loop
do {
@@ -395,7 +391,7 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i
// Reached target power for a computer player
done = true;
else if (goToPower == 0) {
- // Check for pres
+ // Check for press
if (dartHit())
done = true;
}
@@ -410,10 +406,7 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i
screen.slamArea(pt.x + idx, pt.y, 1, 8);
}
- if (music._musicOn) {
- if (!(idx % 3))
- music.waitTimerRoland(1);
- } else if (!(idx % 8))
+ if (!(idx % 8))
events.wait(1);
++idx;
@@ -422,16 +415,16 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i
return MIN(idx * 100 / DARTBARSIZE, 100);
}
-bool Darts::dartHit() {
+int Darts::dartHit() {
Events &events = *_vm->_events;
// Process pending events
events.pollEventsAndWait();
if (events.kbHit()) {
- // Key was pressed, so discard it and return true
- events.clearKeyboard();
- return true;
+ // Key was pressed, so return it
+ Common::KeyState keyState = events.getKey();
+ return keyState.keycode;
}
_oldDartButtons = events._pressed;
@@ -548,10 +541,6 @@ bool Darts::findNumberOnBoard(int aim, Common::Point &pt) {
return done;
}
-void Darts::setFlagsForDarts(int flagNum) {
- _vm->_flags[ABS(flagNum)] = flagNum >= 0;
-}
-
} // End of namespace Scalpel
-} // End of namespace Scalpel
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/darts.h b/engines/sherlock/scalpel/scalpel_darts.h
index 42990f8056..483a163510 100644
--- a/engines/sherlock/scalpel/darts.h
+++ b/engines/sherlock/scalpel/scalpel_darts.h
@@ -20,10 +20,10 @@
*
*/
-#ifndef SHERLOCK_DARTS_H
-#define SHERLOCK_DARTS_H
+#ifndef SHERLOCK_SCALPEL_DARTS_H
+#define SHERLOCK_SCALPEL_DARTS_H
-#include "sherlock/resources.h"
+#include "sherlock/image_file.h"
namespace Sherlock {
@@ -97,7 +97,7 @@ private:
/**
* Returns true if a mouse button or key is pressed.
*/
- bool dartHit();
+ int dartHit();
/**
* Return the score of the given location on the dart-board
@@ -114,12 +114,6 @@ private:
* Returns the center position for the area of the dartboard with a given number
*/
bool findNumberOnBoard(int aim, Common::Point &pt);
-
- /**
- * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive.
- * @remarks We don't use the global setFlags method because we don't want to check scene flags
- */
- void setFlagsForDarts(int flagNum);
public:
Darts(ScalpelEngine *vm);
diff --git a/engines/sherlock/scalpel/scalpel_debugger.cpp b/engines/sherlock/scalpel/scalpel_debugger.cpp
new file mode 100644
index 0000000000..7f5e1efa69
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_debugger.cpp
@@ -0,0 +1,91 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_debugger.h"
+#include "sherlock/sherlock.h"
+#include "audio/mixer.h"
+#include "audio/decoders/3do.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelDebugger::ScalpelDebugger(SherlockEngine *vm) : Debugger(vm) {
+ registerCmd("3do_playmovie", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayMovie));
+ registerCmd("3do_playaudio", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayAudio));
+}
+
+bool ScalpelDebugger::cmd3DO_PlayMovie(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: 3do_playmovie <3do-movie-file>\n");
+ return true;
+ }
+
+ // play gets postboned until debugger is closed
+ Common::String filename = argv[1];
+ _3doPlayMovieFile = filename;
+
+ return cmdExit(0, 0);
+}
+
+bool ScalpelDebugger::cmd3DO_PlayAudio(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: 3do_playaudio <3do-audio-file>\n");
+ return true;
+ }
+
+ Common::File *file = new Common::File();
+ if (!file->open(argv[1])) {
+ debugPrintf("can not open specified audio file\n");
+ return true;
+ }
+
+ Audio::AudioStream *testStream;
+ Audio::SoundHandle testHandle;
+
+ // Try to load the given file as AIFF/AIFC
+ testStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
+
+ if (testStream) {
+ g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &testHandle, testStream);
+ _vm->_events->clearEvents();
+
+ while ((!_vm->shouldQuit()) && g_system->getMixer()->isSoundHandleActive(testHandle)) {
+ _vm->_events->pollEvents();
+ g_system->delayMillis(10);
+ if (_vm->_events->kbHit()) {
+ break;
+ }
+ }
+
+ debugPrintf("playing completed\n");
+ g_system->getMixer()->stopHandle(testHandle);
+ }
+
+ return true;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_debugger.h b/engines/sherlock/scalpel/scalpel_debugger.h
new file mode 100644
index 0000000000..17a84779f0
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_debugger.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_DEBUGGER_H
+#define SHERLOCK_SCALPEL_DEBUGGER_H
+
+#include "sherlock/debugger.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+class ScalpelDebugger : public Debugger {
+private:
+ /**
+ * Plays a 3DO movie
+ */
+ bool cmd3DO_PlayMovie(int argc, const char **argv);
+
+ /**
+ * Plays a 3DO audio
+ */
+ bool cmd3DO_PlayAudio(int argc, const char **argv);
+public:
+ ScalpelDebugger(SherlockEngine *vm);
+ virtual ~ScalpelDebugger() {}
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_DEBUGGER_H */
diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.cpp b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
new file mode 100644
index 0000000000..63f84d68c6
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
@@ -0,0 +1,377 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+static const char *const fixedTextEN[] = {
+ // SH1: Window buttons
+ "Exit",
+ "Up",
+ "Down",
+ // SH1: Inventory buttons
+ "Exit",
+ "Look",
+ "Use",
+ "Give",
+ // SH1: Journal text
+ "Watson's Journal",
+ "Page %d",
+ // SH1: Journal buttons
+ "Exit",
+ "Back 10",
+ "Up",
+ "Down",
+ "Ahead 10",
+ "Search",
+ "First Page",
+ "Last Page",
+ "Print Text",
+ // SH1: Journal search
+ "Exit",
+ "Backward",
+ "Forward",
+ "Text Not Found !",
+ // SH1: Initial Inventory
+ "A message requesting help",
+ "A number of business cards",
+ "Opera Tickets",
+ "Cuff Link",
+ "Wire Hook",
+ "Note",
+ "An open pocket watch",
+ "A piece of paper with numbers on it",
+ "A letter folded many times",
+ "Tarot Cards",
+ "An ornate key",
+ "A pawn ticket",
+ // SH2: Verbs
+ "Open",
+ "Look",
+ "Talk",
+ "Journal"
+};
+
+// sharp-s : 0xE1 / octal 341
+// small a-umlaut: 0x84 / octal 204
+// small o-umlaut: 0x94 / octal 224
+// small u-umlaut: 0x81 / octal 201
+static const char *const fixedTextDE[] = {
+ // SH1: Window buttons
+ "Zur\201ck",
+ "Hoch",
+ "Runter",
+ // SH1: Inventory buttons
+ "Zur\201ck",
+ "Schau",
+ "Benutze",
+ "Gib",
+ // SH1: Journal text
+ "Watsons Tagebuch",
+ "Seite %d",
+ // SH1: Journal buttons
+ "Zur\201ck",
+ "10 hoch",
+ "Hoch",
+ "Runter",
+ "10 runter",
+ "Suche",
+ "Erste Seite",
+ "Letzte Seite",
+ "Drucke Text",
+ // SH1: Journal search
+ "Zur\201ck",
+ "R\201ckw\204rts", // original: "Backward"
+ "Vorw\204rts", // original: "Forward"
+ "Text nicht gefunden!",
+ // SH1: Initial Inventory
+ "Ein Hilferuf von Lestrade",
+ "Holmes' Visitenkarten",
+ "Karten f\201rs Opernhaus",
+ "Manschettenkn\224pfe",
+ "Zum Haken verbogener Drahtkorb",
+ "Mitteilung am Epstein",
+ "Eine offene Taschenuhr",
+ "Ein Zettel mit Zahlen drauf",
+ "Ein mehrfach gefalteter Briefbogen",
+ "Ein Tarock-Kartenspiel", // [sic]
+ "Ein verzierter Schl\201ssel",
+ "Ein Pfandschein",
+ // SH2: Verbs
+ "\231ffne",
+ "Schau",
+ "Rede",
+ "Tagebuch"
+};
+
+// up-side down exclamation mark - 0xAD / octal 255
+// up-side down question mark - 0xA8 / octal 250
+// n with a wave on top - 0xA4 / octal 244
+static const char *const fixedTextES[] = {
+ // SH1: Window buttons
+ "Exit",
+ "Subir",
+ "Bajar",
+ // SH1: Inventory buttons
+ "Exit",
+ "Mirar",
+ "Usar",
+ "Dar",
+ // SH1: Journal text
+ "Diario de Watson",
+ "Pagina %d",
+ // SH1: Journal buttons
+ "Exit",
+ "Retroceder",
+ "Subir",
+ "baJar",
+ "Adelante",
+ "Buscar",
+ "1a pagina",
+ "Ult pagina",
+ "Imprimir",
+ // SH1: Journal search
+ "Exit",
+ "Retroceder",
+ "Avanzar",
+ "Texto no encontrado!",
+ // SH1: Initial Inventory
+ "Un mensaje solicitando ayuda",
+ "Unas cuantas tarjetas de visita",
+ "Entradas para la opera",
+ "Unos gemelos",
+ "Un gancho de alambre",
+ "Una nota",
+ "Un reloj de bolsillo abierto",
+ "Un trozo de papel con unos numeros",
+ "Un carta muy plegada",
+ "Unas cartas de Tarot",
+ "Una llave muy vistosa",
+ "Una papeleta de empe\244o",
+};
+
+// =========================================
+
+// === Sherlock Holmes 1: Serrated Scalpel ===
+static const char *const fixedTextEN_ActionOpen[] = {
+ "This cannot be opened",
+ "It is already open",
+ "It is locked",
+ "Wait for Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextDE_ActionOpen[] = {
+ "Das kann man nicht \224ffnen",
+ "Ist doch schon offen!",
+ "Leider verschlossen",
+ "Warte auf Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextES_ActionOpen[] = {
+ "No puede ser abierto",
+ "Ya esta abierto",
+ "Esta cerrado",
+ "Espera a Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextEN_ActionClose[] = {
+ "This cannot be closed",
+ "It is already closed",
+ "The safe door is in the way"
+};
+
+static const char *const fixedTextDE_ActionClose[] = {
+ "Das kann man nicht schlie\341en",
+ "Ist doch schon zu!",
+ "Die safet\201r ist Weg"
+};
+
+static const char *const fixedTextES_ActionClose[] = {
+ "No puede ser cerrado",
+ "Ya esta cerrado",
+ "La puerta de seguridad esta entre medias"
+};
+
+static const char *const fixedTextEN_ActionMove[] = {
+ "This cannot be moved",
+ "It is bolted to the floor",
+ "It is too heavy",
+ "The other crate is in the way"
+};
+
+
+static const char *const fixedTextDE_ActionMove[] = {
+ "L\204\341t sich nicht bewegen",
+ "Festged\201belt in der Erde...",
+ "Oha, VIEL zu schwer",
+ "Der andere Kiste ist im Weg" // [sic]
+};
+
+static const char *const fixedTextES_ActionMove[] = {
+ "No puede moverse",
+ "Esta sujeto a la pared",
+ "Es demasiado pesado",
+ "El otro cajon esta en mitad"
+};
+
+static const char *const fixedTextEN_ActionPick[] = {
+ "Nothing of interest here",
+ "It is bolted down",
+ "It is too big to carry",
+ "It is too heavy",
+ "I think a girl would be more your type",
+ "Those flowers belong to Penny",
+ "She's far too young for you!",
+ "I think a girl would be more your type!",
+ "Government property for official use only"
+};
+
+static const char *const fixedTextDE_ActionPick[] = {
+ "Nichts Interessantes da",
+ "Zu gut befestigt",
+ "Ist ja wohl ein bi\341chen zu gro\341, oder ?",
+ "Oha, VIEL zu schwer",
+ "Ich denke, Du stehst mehr auf M\204dchen ?",
+ "Diese Blumen geh\224ren Penny",
+ "Sie ist doch viel zu jung f\201r Dich!",
+ "Ich denke, Du stehst mehr auf M\204dchen ?",
+ "Staatseigentum - Nur f\201r den Dienstgebrauch !"
+};
+
+static const char *const fixedTextES_ActionPick[] = {
+ "No hay nada interesante",
+ "Esta anclado al suelo",
+ "Es muy grande para llevarlo",
+ "Pesa demasiado",
+ "Creo que una chica sera mas tu tipo",
+ "Esas flores pertenecen a Penny",
+ "\255Es demasiado joven para ti!",
+ "\255Creo que una chica sera mas tu tipo!",
+ "Propiedad del gobierno para uso oficial"
+};
+
+static const char *const fixedTextEN_ActionUse[] = {
+ "You can't do that",
+ "It had no effect",
+ "You can't reach it",
+ "OK, the door looks bigger! Happy?",
+ "Doors don't smoke"
+};
+
+static const char *const fixedTextDE_ActionUse[] = {
+ "Nein, das geht wirklich nicht",
+ "Tja keinerlei Wirkung",
+ "Da kommst du nicht dran",
+ "Na gut, die T\201r sieht jetzt gr\224\341er aus. Zufrieden?",
+ "T\201ren sind Nichtraucher!"
+};
+
+static const char *const fixedTextES_ActionUse[] = {
+ "No puedes hacerlo",
+ "No tuvo ningun efecto",
+ "No puedes alcanzarlo",
+ "Bien, \255es enorme! \250Feliz?",
+ "Las puertas no fuman"
+};
+
+#define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *)
+#define FIXEDTEXT_ENTRY(_name_) _name_, FIXEDTEXT_GETCOUNT(_name_)
+
+static const FixedTextActionEntry fixedTextEN_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionUse) }
+};
+
+static const FixedTextActionEntry fixedTextDE_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionUse) }
+};
+
+static const FixedTextActionEntry fixedTextES_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionUse) }
+};
+
+// =========================================
+
+// TODO:
+// It seems there was a French version of Sherlock Holmes 2
+static const FixedTextLanguageEntry fixedTextLanguages[] = {
+ { Common::DE_DEU, fixedTextDE, fixedTextDE_Actions },
+ { Common::ES_ESP, fixedTextES, fixedTextES_Actions },
+ { Common::EN_ANY, fixedTextEN, fixedTextEN_Actions },
+ { Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions }
+};
+
+// =========================================
+
+// =========================================
+
+ScalpelFixedText::ScalpelFixedText(SherlockEngine *vm) : FixedText(vm) {
+ // Figure out which fixed texts to use
+ Common::Language curLanguage = _vm->getLanguage();
+
+ const FixedTextLanguageEntry *curLanguageEntry = fixedTextLanguages;
+
+ while (curLanguageEntry->language != Common::UNK_LANG) {
+ if (curLanguageEntry->language == curLanguage)
+ break; // found current language
+ curLanguageEntry++;
+ }
+ _curLanguageEntry = curLanguageEntry;
+}
+
+const char *ScalpelFixedText::getText(int fixedTextId) {
+ return _curLanguageEntry->fixedTextArray[fixedTextId];
+}
+
+const Common::String ScalpelFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) {
+ assert(actionId >= 0);
+ assert(messageIndex >= 0);
+ const FixedTextActionEntry *curActionEntry = &_curLanguageEntry->actionArray[actionId];
+
+ assert(messageIndex < curActionEntry->fixedTextArrayCount);
+ return Common::String(curActionEntry->fixedTextArray[messageIndex]);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.h b/engines/sherlock/scalpel/scalpel_fixed_text.h
new file mode 100644
index 0000000000..eae86b8f27
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_fixed_text.h
@@ -0,0 +1,108 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_FIXED_TEXT_H
+#define SHERLOCK_SCALPEL_FIXED_TEXT_H
+
+#include "sherlock/fixed_text.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum FixedTextId {
+ // Window buttons
+ kFixedText_Window_Exit = 0,
+ kFixedText_Window_Up,
+ kFixedText_Window_Down,
+ // Inventory buttons
+ kFixedText_Inventory_Exit,
+ kFixedText_Inventory_Look,
+ kFixedText_Inventory_Use,
+ kFixedText_Inventory_Give,
+ // Journal text
+ kFixedText_Journal_WatsonsJournal,
+ kFixedText_Journal_Page,
+ // Journal buttons
+ kFixedText_Journal_Exit,
+ kFixedText_Journal_Back10,
+ kFixedText_Journal_Up,
+ kFixedText_Journal_Down,
+ kFixedText_Journal_Ahead10,
+ kFixedText_Journal_Search,
+ kFixedText_Journal_FirstPage,
+ kFixedText_Journal_LastPage,
+ kFixedText_Journal_PrintText,
+ // Journal search
+ kFixedText_JournalSearch_Exit,
+ kFixedText_JournalSearch_Backward,
+ kFixedText_JournalSearch_Forward,
+ kFixedText_JournalSearch_NotFound,
+ // Initial inventory
+ kFixedText_InitInventory_Message,
+ kFixedText_InitInventory_HolmesCard,
+ kFixedText_InitInventory_Tickets,
+ kFixedText_InitInventory_CuffLink,
+ kFixedText_InitInventory_WireHook,
+ kFixedText_InitInventory_Note,
+ kFixedText_InitInventory_OpenWatch,
+ kFixedText_InitInventory_Paper,
+ kFixedText_InitInventory_Letter,
+ kFixedText_InitInventory_Tarot,
+ kFixedText_InitInventory_OrnateKey,
+ kFixedText_InitInventory_PawnTicket
+};
+
+struct FixedTextActionEntry {
+ const char *const *fixedTextArray;
+ int fixedTextArrayCount;
+};
+
+struct FixedTextLanguageEntry {
+ Common::Language language;
+ const char *const *fixedTextArray;
+ const FixedTextActionEntry *actionArray;
+};
+
+class ScalpelFixedText: public FixedText {
+private:
+ const FixedTextLanguageEntry *_curLanguageEntry;
+public:
+ ScalpelFixedText(SherlockEngine *vm);
+ virtual ~ScalpelFixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId);
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_inventory.cpp b/engines/sherlock/scalpel/scalpel_inventory.cpp
new file mode 100644
index 0000000000..e19a43238c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_inventory.cpp
@@ -0,0 +1,296 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_inventory.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelInventory::ScalpelInventory(SherlockEngine *vm) : Inventory(vm) {
+ _invShapes.resize(6);
+}
+
+ScalpelInventory::~ScalpelInventory() {
+}
+
+void ScalpelInventory::drawInventory(InvNewMode mode) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+ InvNewMode tempMode = mode;
+
+ loadInv();
+
+ if (mode == INVENTORY_DONT_DISPLAY) {
+ screen._backBuffer = &screen._backBuffer2;
+ }
+
+ // Draw the window background
+ Surface &bb = *screen._backBuffer;
+ bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2),
+ INV_BACKGROUND);
+
+ // Draw the buttons
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[0][0], CONTROLS_Y1, INVENTORY_POINTS[0][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[1][0], CONTROLS_Y1, INVENTORY_POINTS[1][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[1][2] - screen.stringWidth(fixedText_Look) / 2, fixedText_Look);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[2][0], CONTROLS_Y1, INVENTORY_POINTS[2][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[2][2] - screen.stringWidth(fixedText_Use) / 2, fixedText_Use);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[3][0], CONTROLS_Y1, INVENTORY_POINTS[3][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[3][2] - screen.stringWidth(fixedText_Give) / 2, fixedText_Give);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[4][0], CONTROLS_Y1, INVENTORY_POINTS[4][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[4][2], "^^"); // 2 arrows pointing to the left
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[5][0], CONTROLS_Y1, INVENTORY_POINTS[5][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[5][2], "^"); // 1 arrow pointing to the left
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[6][0], CONTROLS_Y1, INVENTORY_POINTS[6][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[6][2], "_"); // 1 arrow pointing to the right
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[7][0], CONTROLS_Y1, INVENTORY_POINTS[7][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[7][2], "__"); // 2 arrows pointing to the right
+
+ if (tempMode == INVENTORY_DONT_DISPLAY)
+ mode = LOOK_INVENTORY_MODE;
+ _invMode = (InvMode)((int)mode);
+
+ if (mode != PLAIN_INVENTORY) {
+ ui._oldKey = INVENTORY_COMMANDS[(int)mode];
+ } else {
+ ui._oldKey = -1;
+ }
+
+ invCommands(0);
+ putInv(SLAM_DONT_DISPLAY);
+
+ if (tempMode != INVENTORY_DONT_DISPLAY) {
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow(false, CONTROLS_Y1);
+ }
+
+ ui._windowOpen = true;
+ } else {
+ // Reset the screen back buffer to the first buffer now that drawing is done
+ screen._backBuffer = &screen._backBuffer1;
+ }
+
+ assert(IS_SERRATED_SCALPEL);
+ ((ScalpelUserInterface *)_vm->_ui)->_oldUse = -1;
+}
+
+void ScalpelInventory::invCommands(bool slamIt) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
+ _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
+ true, fixedText_Exit);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
+ _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
+ true, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
+ _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ true, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
+ _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ true, fixedText_Give);
+ screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^^"); // 2 arrows pointing to the left
+ screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^"); // 2 arrows pointing to the left
+ screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1),
+ (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "_"); // 1 arrow pointing to the right
+ screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1),
+ (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "__"); // 2 arrows pointing to the right
+ if (_invMode != INVMODE_LOOK)
+ ui.clearInfo();
+ } else {
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
+ _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
+ _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
+ _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
+ _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Give);
+ screen.gPrint(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^^"); // 2 arrows pointing to the left
+ screen.gPrint(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^"); // 1 arrow pointing to the left
+ screen.gPrint(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1),
+ (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "_"); // 1 arrow pointing to the right
+ screen.gPrint(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1),
+ (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "__"); // 2 arrows pointing to the right
+ }
+}
+
+void ScalpelInventory::highlight(int index, byte color) {
+ Screen &screen = *_vm->_screen;
+ Surface &bb = *screen._backBuffer;
+ int slot = index - _invIndex;
+ ImageFrame &frame = (*_invShapes[slot])[0];
+
+ bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color);
+ bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2),
+ 163 + ((33 - frame._height) / 2)));
+ screen.slamArea(8 + slot * 52, 165, 44, 30);
+}
+
+void ScalpelInventory::refreshInv() {
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+
+ ui._invLookFlag = true;
+ freeInv();
+
+ ui._infoFlag = true;
+ ui.clearInfo();
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y),
+ Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ ui.examine();
+
+ if (!talk._talkToAbort) {
+ screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y));
+ loadInv();
+ }
+}
+
+void ScalpelInventory::putInv(InvSlamMode slamIt) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // If an inventory item has disappeared (due to using it or giving it),
+ // a blank space slot may have appeared. If so, adjust the inventory
+ if (_invIndex > 0 && _invIndex > (_holdings - (int)_invShapes.size())) {
+ --_invIndex;
+ freeGraphics();
+ loadGraphics();
+ }
+
+ if (slamIt != SLAM_SECONDARY_BUFFER) {
+ screen.makePanel(Common::Rect(6, 163, 54, 197));
+ screen.makePanel(Common::Rect(58, 163, 106, 197));
+ screen.makePanel(Common::Rect(110, 163, 158, 197));
+ screen.makePanel(Common::Rect(162, 163, 210, 197));
+ screen.makePanel(Common::Rect(214, 163, 262, 197));
+ screen.makePanel(Common::Rect(266, 163, 314, 197));
+ }
+
+ // Iterate through displaying up to 6 objects at a time
+ for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < (int)_invShapes.size(); ++idx) {
+ int itemNum = idx - _invIndex;
+ Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1;
+ Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194);
+
+ // Draw the background
+ if (idx == ui._selector) {
+ bb.fillRect(r, BUTTON_BACKGROUND);
+ }
+ else if (slamIt == SLAM_SECONDARY_BUFFER) {
+ bb.fillRect(r, BUTTON_MIDDLE);
+ }
+
+ // Draw the item image
+ ImageFrame &frame = (*_invShapes[itemNum])[0];
+ bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2),
+ 163 + ((33 - frame._height) / 2)));
+ }
+
+ if (slamIt == SLAM_DISPLAY)
+ screen.slamArea(6, 163, 308, 34);
+
+ if (slamIt != SLAM_SECONDARY_BUFFER)
+ ui.clearInfo();
+
+ if (slamIt == 0) {
+ invCommands(0);
+ }
+ else if (slamIt == SLAM_SECONDARY_BUFFER) {
+ screen._backBuffer = &screen._backBuffer2;
+ invCommands(0);
+ screen._backBuffer = &screen._backBuffer1;
+ }
+}
+
+void ScalpelInventory::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 streamSize = stream->size();
+ while (stream->pos() < streamSize) {
+ Common::String name;
+ char c;
+ while ((c = stream->readByte()) != 0)
+ name += c;
+
+ _names.push_back(name);
+ }
+
+ delete stream;
+
+ loadGraphics();
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_inventory.h b/engines/sherlock/scalpel/scalpel_inventory.h
new file mode 100644
index 0000000000..afafb0b94a
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_inventory.h
@@ -0,0 +1,74 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_INVENTORY_H
+#define SHERLOCK_SCALPEL_INVENTORY_H
+
+#include "sherlock/inventory.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+class ScalpelInventory : public Inventory {
+public:
+ ScalpelInventory(SherlockEngine *vm);
+ ~ScalpelInventory();
+
+ /**
+ * Put the game into inventory mode and open the interface window.
+ */
+ void drawInventory(InvNewMode flag);
+
+ /**
+ * Prints the line of inventory commands at the top of an inventory window with
+ * the correct highlighting
+ */
+ void invCommands(bool slamIt);
+
+ /**
+ * Set the highlighting color of a given inventory item
+ */
+ void highlight(int index, byte color);
+
+ /**
+ * Support method for refreshing the display of the inventory
+ */
+ void refreshInv();
+
+ /**
+ * Display the character's inventory. The slamIt parameter specifies:
+ */
+ void putInv(InvSlamMode slamIt);
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp
new file mode 100644
index 0000000000..787d899aee
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_journal.cpp
@@ -0,0 +1,636 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/journal.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/tattoo/tattoo_journal.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+#define JOURNAL_BUTTONS_Y 178
+#define JOURNAL_SEARCH_LEFT 15
+#define JOURNAL_SEARCH_TOP 186
+#define JOURNAL_SEARCH_RIGHT 296
+#define JOURNAL_SEACRH_MAX_CHARS 50
+
+// Positioning of buttons in the journal view
+static const int JOURNAL_POINTS[9][3] = {
+ { 6, 68, 37 },
+ { 69, 131, 100 },
+ { 132, 192, 162 },
+ { 193, 250, 221 },
+ { 251, 313, 281 },
+ { 6, 82, 44 },
+ { 83, 159, 121 },
+ { 160, 236, 198 },
+ { 237, 313, 275 }
+};
+
+static const int SEARCH_POINTS[3][3] = {
+ { 51, 123, 86 },
+ { 124, 196, 159 },
+ { 197, 269, 232 }
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) {
+ if (_vm->_interactiveFl) {
+ // Load the journal directory and location names
+ loadLocations();
+ }
+}
+
+void ScalpelJournal::loadLocations() {
+ Resources &res = *_vm->_res;
+
+ _directory.clear();
+ _locations.clear();
+
+
+ Common::SeekableReadStream *dir = res.load("talk.lib");
+ dir->skip(4); // Skip header
+
+ // Get the numer of entries
+ _directory.resize(dir->readUint16LE());
+
+ // Read in each entry
+ char buffer[17];
+ for (uint idx = 0; idx < _directory.size(); ++idx) {
+ dir->read(buffer, 17);
+ buffer[16] = '\0';
+
+ _directory[idx] = Common::String(buffer);
+ }
+
+ delete dir;
+
+ if (IS_3DO) {
+ // 3DO: storage of locations is currently unknown TODO
+ return;
+ }
+
+ // Load in the locations stored in journal.txt
+ Common::SeekableReadStream *loc = res.load("journal.txt");
+
+ while (loc->pos() < loc->size()) {
+ Common::String line;
+ char c;
+ while ((c = loc->readByte()) != 0)
+ line += c;
+
+ _locations.push_back(line);
+ }
+
+ delete loc;
+}
+
+void ScalpelJournal::drawFrame() {
+ FixedText &fixedText = *_vm->_fixedText;
+ Resources &res = *_vm->_res;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte palette[PALETTE_SIZE];
+
+ // Load in the journal background
+ Common::SeekableReadStream *bg = res.load("journal.lbv");
+ bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
+ bg->read(palette, PALETTE_SIZE);
+ delete bg;
+
+ // Translate the palette for display
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx)
+ palette[idx] = VGA_COLOR_TRANS(palette[idx]);
+
+ Common::String fixedText_WatsonsJournal = fixedText.getText(kFixedText_Journal_WatsonsJournal);
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit);
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ // Set the palette and print the title
+ screen.setPalette(palette);
+ screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", fixedText_WatsonsJournal.c_str());
+ screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", fixedText_WatsonsJournal.c_str());
+
+ // Draw the buttons
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[1][2] - screen.stringWidth(fixedText_Back10) / 2, fixedText_Back10);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[2][2] - screen.stringWidth(fixedText_Up) / 2, fixedText_Up);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[3][2] - screen.stringWidth(fixedText_Down) / 2, fixedText_Down);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[4][2] - screen.stringWidth(fixedText_Ahead10) / 2, fixedText_Ahead10);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[5][2] - screen.stringWidth(fixedText_Search) / 2, fixedText_Search);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[6][2] - screen.stringWidth(fixedText_FirstPage) / 2, fixedText_FirstPage);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[7][2] - screen.stringWidth(fixedText_LastPage) / 2, fixedText_LastPage);
+
+ // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[8][2] - screen.stringWidth(fixedText_PrintText) / 2, fixedText_PrintText);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11),
+ COMMAND_NULL, false, fixedText_PrintText);
+}
+
+void ScalpelJournal::drawInterface() {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ drawFrame();
+
+ if (_journal.empty()) {
+ _up = _down = 0;
+ } else {
+ drawJournal(0, 0);
+ }
+
+ doArrows();
+
+ // Show the entire screen
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+}
+
+void ScalpelJournal::doArrows() {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte color;
+
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Back10);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Up);
+
+ color = _down ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Down);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Ahead10);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_LastPage);
+
+ color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_Search);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, fixedText_PrintText);
+
+ color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_FirstPage);
+}
+
+JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) {
+ if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10))
+ return BTN_EXIT;
+
+ if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1)
+ return BTN_BACK10;
+
+ if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _up)
+ return BTN_UP;
+
+ if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+ return BTN_DOWN;
+
+ if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+ return BTN_AHEAD110;
+
+ if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+ return BTN_SEARCH;
+
+ if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && _up)
+ return BTN_FIRST_PAGE;
+
+ if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && _down)
+ return BTN_LAST_PAGE;
+
+ if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+ return BTN_PRINT_TEXT;
+
+ return BTN_NONE;
+}
+
+bool ScalpelJournal::handleEvents(int key) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ bool doneFlag = false;
+
+ Common::Point pt = events.mousePos();
+ JournalButton btn = getHighlightedButton(pt);
+ byte color;
+
+ if (events._pressed || events._released) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit);
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ // Exit button
+ color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, fixedText_Exit);
+
+ // Back 10 button
+ if (btn == BTN_BACK10) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Back10);
+ } else if (_page > 1) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Back10);
+ }
+
+ // Up button
+ if (btn == BTN_UP) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up);
+ } else if (_up) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
+ }
+
+ // Down button
+ if (btn == BTN_DOWN) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down);
+ } else if (_down) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
+ }
+
+ // Ahead 10 button
+ if (btn == BTN_AHEAD110) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Ahead10);
+ } else if (_down) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Ahead10);
+ }
+
+ // Search button
+ if (btn == BTN_SEARCH) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_journal.empty()) {
+ color = COMMAND_NULL;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_Search);
+
+ // First Page button
+ if (btn == BTN_FIRST_PAGE) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_up) {
+ color = COMMAND_FOREGROUND;
+ } else {
+ color = COMMAND_NULL;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_FirstPage);
+
+ // Last Page button
+ if (btn == BTN_LAST_PAGE) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_down) {
+ color = COMMAND_FOREGROUND;
+ } else {
+ color = COMMAND_NULL;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_LastPage);
+
+ // Print Text button
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, fixedText_PrintText);
+ }
+
+ if (btn == BTN_EXIT && events._released) {
+ // Exit button pressed
+ doneFlag = true;
+
+ } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) {
+ // Scrolll up 10 pages
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) {
+ // Scroll up
+ drawJournal(1, LINES_PER_PAGE);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) {
+ // Scroll down
+ drawJournal(2, LINES_PER_PAGE);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) {
+ // Scroll down 10 pages
+ if ((_page + 10) > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search");
+ bool notFound = false;
+
+ do {
+ int dir;
+ if ((dir = getSearchString(notFound)) != 0) {
+ int savedIndex = _index;
+ int savedSub = _sub;
+ int savedPage = _page;
+
+ if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) {
+ _index = savedIndex;
+ _sub = savedSub;
+ _page = savedPage;
+
+ drawFrame();
+ drawJournal(0, 0);
+ notFound = true;
+ } else {
+ doneFlag = true;
+ }
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ doneFlag = true;
+ }
+ } while (!doneFlag);
+ doneFlag = false;
+
+ } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) {
+ // First page
+ _index = _sub = 0;
+ _up = _down = false;
+ _page = 1;
+
+ drawFrame();
+ drawJournal(0, 0);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) {
+ // Last page
+ if ((_page + 10) > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 1000 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ events.wait(2);
+
+ return doneFlag;
+}
+
+int ScalpelJournal::getSearchString(bool printError) {
+ enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD };
+
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int xp;
+ int yp = 174;
+ bool flag = false;
+ Common::String name;
+ int done = 0;
+ byte color;
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_JournalSearch_Exit);
+ Common::String fixedText_Backward = fixedText.getText(kFixedText_JournalSearch_Backward);
+ Common::String fixedText_Forward = fixedText.getText(kFixedText_JournalSearch_Forward);
+ Common::String fixedText_NotFound = fixedText.getText(kFixedText_JournalSearch_NotFound);
+
+ // Draw search panel
+ screen.makePanel(Common::Rect(6, 171, 313, 199));
+ screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10),
+ SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10),
+ SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, fixedText_Backward);
+ screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10),
+ SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, fixedText_Forward);
+
+ screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Exit[0]);
+ screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Backward[0]);
+ screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Forward[0]);
+
+ screen.makeField(Common::Rect(12, 185, 307, 196));
+
+ screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM);
+ screen.vLine(12, 185, 195, BUTTON_BOTTOM);
+ screen.hLine(13, 195, 306, BUTTON_TOP);
+ screen.hLine(306, 186, 195, BUTTON_TOP);
+
+ if (printError) {
+ screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(fixedText_NotFound)) / 2, 185),
+ INV_FOREGROUND, "%s", fixedText_NotFound.c_str());
+ } else if (!_find.empty()) {
+ // There's already a search term, display it already
+ screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(6, 171, 307, 28);
+
+ if (printError) {
+ // Give time for user to see the message
+ events.setButtonState();
+ for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) {
+ events.pollEvents();
+ events.setButtonState();
+ events.wait(2);
+ }
+
+ events.clearKeyboard();
+ screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE);
+
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name);
+ yp = JOURNAL_SEARCH_TOP;
+
+ do {
+ events._released = false;
+ Button found = BTN_NONE;
+
+ while (!_vm->shouldQuit() && !events.kbHit() && !events._released) {
+ found = BTN_NONE;
+ if (talk._talkToAbort)
+ return 0;
+
+ // Check if key or mouse button press has occurred
+ events.setButtonState();
+ Common::Point pt = events.mousePos();
+
+ flag = !flag;
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE);
+
+ if (events._pressed || events._released) {
+ if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_EXIT;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, 175), color, "%s", fixedText_Exit.c_str());
+
+ if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_BACKWARD;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, 175), color, "%s", fixedText_Backward.c_str());
+
+ if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_FORWARD;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, 175), color, "%s", fixedText_Forward.c_str());
+ }
+
+ events.wait(2);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) {
+ screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ xp -= screen.charWidth(name.lastChar());
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND);
+ name.deleteLastChar();
+
+ } else if (keyState.keycode == Common::KEYCODE_RETURN) {
+ done = 1;
+
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ done = -1;
+
+ } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT &&
+ name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) {
+ char ch = toupper(keyState.ascii);
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch);
+ xp += screen.charWidth(ch);
+ name += ch;
+ }
+ }
+
+ if (events._released) {
+ switch (found) {
+ case BTN_EXIT:
+ done = -1; break;
+ case BTN_BACKWARD:
+ done = 2; break;
+ case BTN_FORWARD:
+ done = 1; break;
+ default:
+ break;
+ }
+ }
+ } while (!done && !_vm->shouldQuit());
+
+ if (done != -1) {
+ _find = name;
+ } else {
+ done = 0;
+ }
+
+ // Redisplay the journal screen
+ drawFrame();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ return done;
+}
+
+void ScalpelJournal::resetPosition() {
+ _index = _sub = _up = _down = 0;
+ _page = 1;
+}
+
+void ScalpelJournal::record(int converseNum, int statementNum, bool replyOnly) {
+ // there seems to be no journal in the 3DO version
+ if (!IS_3DO)
+ Journal::record(converseNum, statementNum, replyOnly);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h
new file mode 100644
index 0000000000..c8e9c01739
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_journal.h
@@ -0,0 +1,99 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_JOURNAL_H
+#define SHERLOCK_SCALPEL_JOURNAL_H
+
+#include "sherlock/journal.h"
+#include "sherlock/saveload.h"
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "common/stream.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum JournalButton {
+ BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH,
+ BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT
+};
+
+class ScalpelJournal: public Journal {
+private:
+ /**
+ * Load the list of journal locations
+ */
+ void loadLocations();
+
+ /**
+ * Display the arrows that can be used to scroll up and down pages
+ */
+ void doArrows();
+
+ /**
+ * Show the search submenu and allow the player to enter a search string
+ */
+ int getSearchString(bool printError);
+
+ /**
+ * Returns the button, if any, that is under the specified position
+ */
+ JournalButton getHighlightedButton(const Common::Point &pt);
+public:
+ ScalpelJournal(SherlockEngine *vm);
+ virtual ~ScalpelJournal() {}
+
+ /**
+ * Display the journal
+ */
+ void drawInterface();
+
+ /**
+ * Handle events whilst the journal is being displayed
+ */
+ bool handleEvents(int key);
+public:
+ /**
+ * Draw the journal background, frame, and interface buttons
+ */
+ virtual void drawFrame();
+
+ /**
+ * Reset viewing position to the start of the journal
+ */
+ virtual void resetPosition();
+
+ /**
+ * Records statements that are said, in the order which they are said. The player
+ * can then read the journal to review them
+ */
+ virtual void record(int converseNum, int statementNum, bool replyOnly = false);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_map.cpp b/engines/sherlock/scalpel/scalpel_map.cpp
new file mode 100644
index 0000000000..369822ba02
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_map.cpp
@@ -0,0 +1,596 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/events.h"
+#include "sherlock/people.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+MapPaths::MapPaths() {
+ _numLocations = 0;
+}
+
+void MapPaths::load(int numLocations, Common::SeekableReadStream &s) {
+ _numLocations = numLocations;
+ _paths.resize(_numLocations * _numLocations);
+
+ for (int idx = 0; idx < (numLocations * numLocations); ++idx) {
+ Common::Array<byte> &path = _paths[idx];
+ int v;
+
+ do {
+ v = s.readByte();
+ path.push_back(v);
+ } while (v && v < 254);
+ }
+}
+
+const byte *MapPaths::getPath(int srcLocation, int destLocation) {
+ return &_paths[srcLocation * _numLocations + destLocation][0];
+}
+
+/*----------------------------------------------------------------*/
+
+ScalpelMap::ScalpelMap(SherlockEngine *vm): Map(vm), _topLine(g_system->getWidth(), 12) {
+ _mapCursors = nullptr;
+ _shapes = nullptr;
+ _iconShapes = nullptr;
+ _point = 0;
+ _placesShown = false;
+ _cursorIndex = -1;
+ _drawMap = false;
+ _overPos = Point32(130 * FIXED_INT_MULTIPLIER, 126 * FIXED_INT_MULTIPLIER);
+ _frameChangeFlag = false;
+
+ // Initialise the initial walk sequence set
+ _walkSequences.resize(MAX_HOLMES_SEQUENCE);
+ for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) {
+ _walkSequences[idx]._sequences.resize(MAX_FRAME);
+ Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0);
+ }
+
+ if (!_vm->isDemo())
+ loadData();
+}
+
+void ScalpelMap::loadPoints(int count, const int *xList, const int *yList, const int *transList) {
+ for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) {
+ _points.push_back(MapEntry(*xList, *yList, *transList));
+ }
+}
+
+void ScalpelMap::loadSequences(int count, const byte *seq) {
+ for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME)
+ Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]);
+}
+
+void ScalpelMap::loadData() {
+ // Load the list of location names
+ Common::SeekableReadStream *txtStream = _vm->_res->load("chess.txt");
+
+ int streamSize = txtStream->size();
+ while (txtStream->pos() < streamSize) {
+ Common::String line;
+ char c;
+ while ((c = txtStream->readByte()) != '\0')
+ line += c;
+
+ _locationNames.push_back(line);
+ }
+
+ delete txtStream;
+
+ // Load the path data
+ Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth");
+
+ // Get routes between different locations on the map
+ _paths.load(31, *pathStream);
+
+ // Load in the co-ordinates that the paths refer to
+ _pathPoints.resize(208);
+ for (uint idx = 0; idx < _pathPoints.size(); ++idx) {
+ _pathPoints[idx].x = pathStream->readSint16LE();
+ _pathPoints[idx].y = pathStream->readSint16LE();
+ }
+
+ delete pathStream;
+}
+
+int ScalpelMap::show() {
+ Debugger &debugger = *_vm->_debugger;
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ bool changed = false, exitFlag = false;
+ _active = true;
+
+ // Set font and custom cursor for the map
+ int oldFont = screen.fontNumber();
+ screen.setFont(0);
+
+ // Initial screen clear
+ screen._backBuffer1.clear();
+ screen.clear();
+
+ // Load the entire map
+ ImageFile *bigMap = NULL;
+ if (!IS_3DO) {
+ // PC
+ bigMap = new ImageFile("bigmap.vgs");
+ screen.setPalette(bigMap->_palette);
+ } else {
+ // 3DO
+ bigMap = new ImageFile3DO("overland.cel", kImageFile3DOType_Cel);
+ }
+
+ // Load need sprites
+ setupSprites();
+
+ if (!IS_3DO) {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ } else {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ }
+
+ _drawMap = true;
+ _charPoint = -1;
+ _point = -1;
+ people[HOLMES]._position = _lDrawnPos = _overPos;
+
+ // Show place icons
+ showPlaces();
+ saveTopLine();
+ _placesShown = true;
+
+ // Keep looping until either a location is picked, or the game is ended
+ while (!_vm->shouldQuit() && !exitFlag) {
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ if (debugger._showAllLocations == LOC_REFRESH) {
+ showPlaces();
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
+ }
+
+ // Keyboard handling
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) {
+ // Both space and enter simulate a mouse release
+ events._pressed = false;
+ events._released = true;
+ events._oldButtons = 0;
+ }
+ }
+
+ // Ignore scrolling attempts until the screen is drawn
+ if (!_drawMap) {
+ Common::Point pt = events.mousePos();
+
+ // Check for vertical map scrolling
+ if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) {
+ if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10))
+ _bigPos.y += 10;
+ else
+ _bigPos.y -= 10;
+
+ changed = true;
+ }
+
+ // Check for horizontal map scrolling
+ if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) {
+ if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10))
+ _bigPos.x += 15;
+ else
+ _bigPos.x -= 15;
+
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ // Map has scrolled, so redraw new map view
+ changed = false;
+
+ if (!IS_3DO) {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ } else {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ }
+
+ showPlaces();
+ _placesShown = false;
+
+ saveTopLine();
+ _savedPos.x = -1;
+ updateMap(true);
+ } else if (!_drawMap) {
+ if (!_placesShown) {
+ showPlaces();
+ _placesShown = true;
+ }
+
+ if (_cursorIndex == 0) {
+ Common::Point pt = events.mousePos();
+ highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y));
+ }
+ updateMap(false);
+ }
+
+ if ((events._released || events._rightReleased) && _point != -1) {
+ if (people[HOLMES]._walkCount == 0) {
+ people[HOLMES]._walkDest = _points[_point] + Common::Point(4, 9);
+ _charPoint = _point;
+
+ // Start walking to selected location
+ walkTheStreets();
+
+ // Show wait cursor
+ _cursorIndex = 1;
+ events.setCursor((*_mapCursors)[_cursorIndex]._frame);
+ }
+ }
+
+ // Check if a scene has beeen selected and we've finished "moving" to it
+ if (people[HOLMES]._walkCount == 0) {
+ if (_charPoint >= 1 && _charPoint < (int)_points.size())
+ exitFlag = true;
+ }
+
+ if (_drawMap) {
+ _drawMap = false;
+
+ if (screen._fadeStyle)
+ screen.randomTransition();
+ else
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ // Wait for a frame
+ events.wait(1);
+ }
+
+ freeSprites();
+ _overPos = people[HOLMES]._position;
+
+ // Reset font
+ screen.setFont(oldFont);
+
+ // Free map graphic
+ delete bigMap;
+
+ _active = false;
+ return _charPoint;
+}
+
+void ScalpelMap::setupSprites() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ _savedPos.x = -1;
+
+ if (!IS_3DO) {
+ // PC
+ _mapCursors = new ImageFile("omouse.vgs");
+ _shapes = new ImageFile("mapicon.vgs");
+ _iconShapes = new ImageFile("overicon.vgs");
+ } else {
+ // 3DO
+ _mapCursors = new ImageFile3DO("omouse.vgs", kImageFile3DOType_RoomFormat);
+ _shapes = new ImageFile3DO("mapicon.vgs", kImageFile3DOType_RoomFormat);
+ _iconShapes = new ImageFile3DO("overicon.vgs", kImageFile3DOType_RoomFormat);
+ }
+
+ _cursorIndex = 0;
+ events.setCursor((*_mapCursors)[_cursorIndex]._frame);
+
+ _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height);
+ Person &p = people[HOLMES];
+ p._description = " ";
+ p._type = CHARACTER;
+ p._position = Common::Point(12400, 5000);
+ p._sequenceNumber = 0;
+ p._images = _shapes;
+ p._imageFrame = nullptr;
+ p._frameNumber = 0;
+ p._delta = Common::Point(0, 0);
+ p._oldSize = Common::Point(0, 0);
+ p._oldSize = Common::Point(0, 0);
+ p._misc = 0;
+ p._walkCount = 0;
+ p._allow = 0;
+ p._noShapeSize = Common::Point(0, 0);
+ p._goto = Common::Point(28000, 15000);
+ p._status = 0;
+ p._walkSequences = _walkSequences;
+ p.setImageFrame();
+ scene._bgShapes.clear();
+}
+
+void ScalpelMap::freeSprites() {
+ delete _mapCursors;
+ delete _shapes;
+ delete _iconShapes;
+ _iconSave.free();
+}
+
+void ScalpelMap::showPlaces() {
+ Debugger &debugger = *_vm->_debugger;
+ Screen &screen = *_vm->_screen;
+
+ for (uint idx = 0; idx < _points.size(); ++idx) {
+ const MapEntry &pt = _points[idx];
+
+ if (pt.x != 0 && pt.y != 0) {
+ if (debugger._showAllLocations != LOC_DISABLED)
+ _vm->setFlagsDirect(idx);
+
+ if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH
+ && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) {
+ if (_vm->readFlags(idx)) {
+ screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate],
+ Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12));
+ }
+ }
+ }
+ }
+
+ if (debugger._showAllLocations == LOC_REFRESH)
+ debugger._showAllLocations = LOC_ALL;
+}
+
+void ScalpelMap::saveTopLine() {
+ _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12));
+}
+
+void ScalpelMap::eraseTopLine() {
+ Screen &screen = *_vm->_screen;
+ screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0));
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h());
+}
+
+void ScalpelMap::showPlaceName(int idx, bool highlighted) {
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+
+ Common::String name = _locationNames[idx];
+ int width = screen.stringWidth(name);
+
+ if (!_cursorIndex) {
+ saveIcon(people[HOLMES]._imageFrame, _lDrawnPos);
+
+ bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
+ || people[HOLMES]._sequenceNumber == MAP_UPLEFT;
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped);
+ }
+
+ if (highlighted) {
+ int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2;
+ screen.gPrint(Common::Point(xp + 2, 2), BLACK, "%s", name.c_str());
+ screen.gPrint(Common::Point(xp + 1, 1), BLACK, "%s", name.c_str());
+ screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str());
+
+ screen.slamArea(xp, 0, width + 2, 15);
+ }
+}
+
+void ScalpelMap::updateMap(bool flushScreen) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Common::Point osPos = _savedPos;
+ Common::Point osSize = _savedSize;
+ Common::Point hPos;
+
+ if (_cursorIndex >= 1) {
+ if (++_cursorIndex > (1 + 8))
+ _cursorIndex = 1;
+
+ events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame);
+ }
+
+ if (!_drawMap && !flushScreen)
+ restoreIcon();
+ else
+ _savedPos.x = -1;
+
+ people[HOLMES].adjustSprite();
+
+ _lDrawnPos.x = hPos.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x;
+ _lDrawnPos.y = hPos.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y;
+
+ // Draw the person icon
+ saveIcon(people[HOLMES]._imageFrame, hPos);
+ if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
+ || people[HOLMES]._sequenceNumber == MAP_UPLEFT)
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, true);
+ else
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, false);
+
+ if (flushScreen) {
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else if (!_drawMap) {
+ if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT)
+ screen.flushImage(people[HOLMES]._imageFrame, Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y),
+ &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
+
+ if (osPos.x != -1)
+ screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y);
+ }
+}
+
+void ScalpelMap::walkTheStreets() {
+ People &people = *_vm->_people;
+ Common::Array<Common::Point> tempPath;
+
+ // Get indexes into the path lists for the start and destination scenes
+ int start = _points[_oldCharPoint]._translate;
+ int dest = _points[_charPoint]._translate;
+
+ // Get pointer to start of path
+ const byte *path = _paths.getPath(start, dest);
+
+ // Add in destination position
+ people[HOLMES]._walkTo.clear();
+ Common::Point destPos = people[HOLMES]._walkDest;
+
+ // Check for any intermediate points between the two locations
+ if (path[0] || _charPoint > 50 || _oldCharPoint > 50) {
+ people[HOLMES]._sequenceNumber = -1;
+
+ if (_charPoint == 51 || _oldCharPoint == 51) {
+ people[HOLMES].setWalking();
+ } else {
+ bool reversePath = false;
+
+ // Check for moving the path backwards or forwards
+ if (path[0] == 255) {
+ reversePath = true;
+ SWAP(start, dest);
+ path = _paths.getPath(start, dest);
+ }
+
+ do {
+ int idx = *path++;
+ tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4));
+ } while (*path != 254);
+
+ // Load up the path to use
+ people[HOLMES]._walkTo.clear();
+
+ if (reversePath) {
+ for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx)
+ people[HOLMES]._walkTo.push(tempPath[idx]);
+ } else {
+ for (int idx = 0; idx < (int)tempPath.size(); ++idx)
+ people[HOLMES]._walkTo.push(tempPath[idx]);
+ }
+
+ people[HOLMES]._walkDest = people[HOLMES]._walkTo.pop() + Common::Point(12, 6);
+ people[HOLMES].setWalking();
+ }
+ } else {
+ people[HOLMES]._walkCount = 0;
+ }
+
+ // Store the final destination icon position
+ people[HOLMES]._walkTo.push(destPos);
+}
+
+void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) {
+ Screen &screen = *_vm->_screen;
+ Common::Point size(src->_width, src->_height);
+ Common::Point pos = pt;
+
+ if (pos.x < 0) {
+ size.x += pos.x;
+ pos.x = 0;
+ }
+
+ if (pos.y < 0) {
+ size.y += pos.y;
+ pos.y = 0;
+ }
+
+ if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH)
+ size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH;
+
+ if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT)
+ size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT;
+
+ if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) {
+ // Flag as the area not needing to be saved
+ _savedPos.x = -1;
+ return;
+ }
+
+ assert(size.x <= _iconSave.w() && size.y <= _iconSave.h());
+ _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0),
+ Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y));
+ _savedPos = pos;
+ _savedSize = size;
+}
+
+void ScalpelMap::restoreIcon() {
+ Screen &screen = *_vm->_screen;
+
+ if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH
+ && _savedPos.y < SHERLOCK_SCREEN_HEIGHT)
+ screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y));
+}
+
+void ScalpelMap::highlightIcon(const Common::Point &pt) {
+ int oldPoint = _point;
+
+ // Iterate through the icon list
+ bool done = false;
+ for (int idx = 0; idx < (int)_points.size(); ++idx) {
+ const MapEntry &entry = _points[idx];
+
+ // Check whether the mouse is over a given icon
+ if (entry.x != 0 && entry.y != 0) {
+ if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) {
+ done = true;
+
+ if (_point != idx && _vm->readFlags(idx)) {
+ // Changed to a new valid (visible) location
+ eraseTopLine();
+ showPlaceName(idx, true);
+ _point = idx;
+ }
+ }
+ }
+ }
+
+ if (!done) {
+ // No icon was highlighted
+ if (_point != -1) {
+ // No longer highlighting previously highlighted icon, so erase it
+ showPlaceName(_point, false);
+ eraseTopLine();
+ }
+
+ _point = -1;
+ } else if (oldPoint != -1 && oldPoint != _point) {
+ showPlaceName(oldPoint, false);
+ eraseTopLine();
+ }
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_map.h b/engines/sherlock/scalpel/scalpel_map.h
new file mode 100644
index 0000000000..b17677725c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_map.h
@@ -0,0 +1,173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_MAP_H
+#define SHERLOCK_SCALPEL_MAP_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "sherlock/surface.h"
+#include "sherlock/map.h"
+#include "sherlock/resources.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+
+struct MapEntry : Common::Point {
+ int _translate;
+
+ MapEntry() : Common::Point(), _translate(-1) {}
+
+ MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {}
+};
+
+class MapPaths {
+private:
+ int _numLocations;
+ Common::Array< Common::Array<byte> > _paths;
+
+public:
+ MapPaths();
+
+ /**
+ * Load the data for the paths between locations on the map
+ */
+ void load(int numLocations, Common::SeekableReadStream &s);
+
+ /**
+ * Get the path between two locations on the map
+ */
+ const byte *getPath(int srcLocation, int destLocation);
+};
+
+class ScalpelMap: public Map {
+private:
+ Common::Array<MapEntry> _points; // Map locations for each scene
+ Common::StringArray _locationNames;
+ MapPaths _paths;
+ Common::Array<Common::Point> _pathPoints;
+ Common::Point _savedPos;
+ Common::Point _savedSize;
+ Surface _topLine;
+ ImageFile *_mapCursors;
+ ImageFile *_shapes;
+ ImageFile *_iconShapes;
+ WalkSequences _walkSequences;
+ Point32 _lDrawnPos;
+ int _point;
+ bool _placesShown;
+ int _cursorIndex;
+ bool _drawMap;
+ Surface _iconSave;
+protected:
+ /**
+ * Load data needed for the map
+ */
+ void loadData();
+
+ /**
+ * Load and initialize all the sprites that are needed for the map display
+ */
+ void setupSprites();
+
+ /**
+ * Free the sprites and data used by the map
+ */
+ void freeSprites();
+
+ /**
+ * Draws an icon for every place that's currently known
+ */
+ void showPlaces();
+
+ /**
+ * Makes a copy of the top rows of the screen that are used to display location names
+ */
+ void saveTopLine();
+
+ /**
+ * Erases anything shown in the top line by restoring the previously saved original map background
+ */
+ void eraseTopLine();
+
+ /**
+ * Prints the name of the specified icon
+ */
+ void showPlaceName(int idx, bool highlighted);
+
+ /**
+ * Update all on-screen sprites to account for any scrolling of the map
+ */
+ void updateMap(bool flushScreen);
+
+ /**
+ * Handle moving icon for player from their previous location on the map to a destination location
+ */
+ void walkTheStreets();
+
+ /**
+ * Save the area under the player's icon
+ */
+ void saveIcon(ImageFrame *src, const Common::Point &pt);
+
+ /**
+ * Restore the area under the player's icon
+ */
+ void restoreIcon();
+
+ /**
+ * Handles highlighting map icons, showing their names
+ */
+ void highlightIcon(const Common::Point &pt);
+public:
+ ScalpelMap(SherlockEngine *vm);
+ virtual ~ScalpelMap() {}
+
+ const MapEntry &operator[](int idx) { return _points[idx]; }
+
+ /**
+ * Loads the list of points for locations on the map for each scene
+ */
+ void loadPoints(int count, const int *xList, const int *yList, const int *transList);
+
+ /**
+ * Load the sequence data for player icon animations
+ */
+ void loadSequences(int count, const byte *seq);
+
+ /**
+ * Show the map
+ */
+ virtual int show();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_people.cpp b/engines/sherlock/scalpel/scalpel_people.cpp
new file mode 100644
index 0000000000..924095cd50
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_people.cpp
@@ -0,0 +1,567 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+// Walk speeds
+#define MWALK_SPEED 2
+#define XWALK_SPEED 4
+#define YWALK_SPEED 1
+
+/*----------------------------------------------------------------*/
+
+void ScalpelPerson::adjustSprite() {
+ Map &map = *_vm->_map;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ if (_type == INVALID || (_type == CHARACTER && scene._animating))
+ return;
+
+ if (!talk._talkCounter && _type == CHARACTER && _walkCount) {
+ // Handle active movement for the sprite
+ _position += _delta;
+ --_walkCount;
+
+ if (!_walkCount) {
+ // If there any points left for the character to walk to along the
+ // route to a destination, then move to the next point
+ if (!people[HOLMES]._walkTo.empty()) {
+ _walkDest = people[HOLMES]._walkTo.pop();
+ setWalking();
+ } else {
+ gotoStand();
+ }
+ }
+ }
+
+ if (_type == CHARACTER && !map._active) {
+ if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) {
+ _position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) {
+ _position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) {
+ _position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) {
+ _position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+ } else if (!map._active) {
+ _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT);
+ _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT);
+ }
+
+ if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag))
+ ++_frameNumber;
+
+ if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() ||
+ _walkSequences[_sequenceNumber][_frameNumber] == 0) {
+ switch (_sequenceNumber) {
+ case STOP_UP:
+ case STOP_DOWN:
+ case STOP_LEFT:
+ case STOP_RIGHT:
+ case STOP_UPRIGHT:
+ case STOP_UPLEFT:
+ case STOP_DOWNRIGHT:
+ case STOP_DOWNLEFT:
+ // We're in a stop sequence, so reset back to the last frame, so
+ // the character is shown as standing still
+ --_frameNumber;
+ break;
+
+ default:
+ // Move 1 past the first frame - we need to compensate, since we
+ // already passed the frame increment
+ _frameNumber = 1;
+ break;
+ }
+ }
+
+ // Update the _imageFrame to point to the new frame's image
+ setImageFrame();
+
+ // Check to see if character has entered an exit zone
+ if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) {
+ Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2,
+ _position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2);
+ Exit *exit = scene.checkForExit(charRect);
+
+ if (exit) {
+ scene._goToScene = exit->_scene;
+
+ if (exit->_newPosition.x != 0) {
+ people._savedPos = exit->_newPosition;
+
+ if (people._savedPos._facing > 100 && people._savedPos.x < 1)
+ people._savedPos.x = 100;
+ }
+ }
+ }
+}
+
+void ScalpelPerson::gotoStand() {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ _walkTo.clear();
+ _walkCount = 0;
+
+ switch (_sequenceNumber) {
+ case Scalpel::WALK_UP:
+ _sequenceNumber = STOP_UP;
+ break;
+ case WALK_DOWN:
+ _sequenceNumber = STOP_DOWN;
+ break;
+ case TALK_LEFT:
+ case WALK_LEFT:
+ _sequenceNumber = STOP_LEFT;
+ break;
+ case TALK_RIGHT:
+ case WALK_RIGHT:
+ _sequenceNumber = STOP_RIGHT;
+ break;
+ case WALK_UPRIGHT:
+ _sequenceNumber = STOP_UPRIGHT;
+ break;
+ case WALK_UPLEFT:
+ _sequenceNumber = STOP_UPLEFT;
+ break;
+ case WALK_DOWNRIGHT:
+ _sequenceNumber = STOP_DOWNRIGHT;
+ break;
+ case WALK_DOWNLEFT:
+ _sequenceNumber = STOP_DOWNLEFT;
+ break;
+ default:
+ break;
+ }
+
+ // Only restart frame at 0 if the sequence number has changed
+ if (_oldWalkSequence != -1 || _sequenceNumber == Scalpel::STOP_UP)
+ _frameNumber = 0;
+
+ if (map._active) {
+ _sequenceNumber = 0;
+ people[HOLMES]._position.x = (map[map._charPoint].x - 6) * FIXED_INT_MULTIPLIER;
+ people[HOLMES]._position.y = (map[map._charPoint].y + 10) * FIXED_INT_MULTIPLIER;
+ }
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+}
+
+void ScalpelPerson::setWalking() {
+ Map &map = *_vm->_map;
+ Scene &scene = *_vm->_scene;
+ int oldDirection, oldFrame;
+ Common::Point speed, delta;
+
+ // Flag that player has now walked in the scene
+ scene._walkedInScene = true;
+
+ // Stop any previous walking, since a new dest is being set
+ _walkCount = 0;
+ oldDirection = _sequenceNumber;
+ oldFrame = _frameNumber;
+
+ // Set speed to use horizontal and vertical movement
+ if (map._active) {
+ speed = Common::Point(MWALK_SPEED, MWALK_SPEED);
+ } else {
+ speed = Common::Point(XWALK_SPEED, YWALK_SPEED);
+ }
+
+ // If the player is already close to the given destination that no
+ // walking is needed, move to the next straight line segment in the
+ // overall walking route, if there is one
+ for (;;) {
+ // Since we want the player to be centered on the destination they
+ // clicked, but characters draw positions start at their left, move
+ // the destination half the character width to draw him centered
+ int temp;
+ if (_walkDest.x >= (temp = _imageFrame->_frame.w / 2))
+ _walkDest.x -= temp;
+
+ delta = Common::Point(
+ ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
+ ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
+ );
+
+ // If we're ready to move a sufficient distance, that's it. Otherwise,
+ // move onto the next portion of the walk path, if there is one
+ if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
+ break;
+
+ // Pop next walk segment off the walk route stack
+ _walkDest = _walkTo.pop();
+ }
+
+ // If a sufficient move is being done, then start the move
+ if (delta.x > 3 || delta.y) {
+ // See whether the major movement is horizontal or vertical
+ if (delta.x >= delta.y) {
+ // Set the initial frame sequence for the left and right, as well
+ // as setting the delta x depending on direction
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT);
+ _delta.x = speed.x * -FIXED_INT_MULTIPLIER;
+ } else {
+ _sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT);
+ _delta.x = speed.x * FIXED_INT_MULTIPLIER;
+ }
+
+ // See if the x delta is too small to be divided by the speed, since
+ // this would cause a divide by zero error
+ if (delta.x >= speed.x) {
+ // Det the delta y
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x / speed.x);
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
+ _delta.y = -_delta.y;
+
+ // Set how many times we should add the delta to the player's position
+ _walkCount = delta.x / speed.x;
+ } else {
+ // The delta x was less than the speed (ie. we're really close to
+ // the destination). So set delta to 0 so the player won't move
+ _delta = Point32(0, 0);
+ _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);
+
+ _walkCount = 1;
+ }
+
+ // See if the sequence needs to be changed for diagonal walking
+ if (_delta.y > 150) {
+ if (!map._active) {
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_DOWNLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_DOWNRIGHT;
+ break;
+ }
+ }
+ } else if (_delta.y < -150) {
+ if (!map._active) {
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_UPLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_UPRIGHT;
+ break;
+ }
+ }
+ }
+ } else {
+ // Major movement is vertical, so set the sequence for up and down,
+ // and set the delta Y depending on the direction
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_UP;
+ _delta.y = speed.y * -FIXED_INT_MULTIPLIER;
+ } else {
+ _sequenceNumber = WALK_DOWN;
+ _delta.y = speed.y * FIXED_INT_MULTIPLIER;
+ }
+
+ // If we're on the overhead map, set the sequence so we keep moving
+ // in the same direction
+ if (map._active)
+ _sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection;
+
+ // Set the delta x
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y);
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER))
+ _delta.x = -_delta.x;
+
+ _walkCount = delta.y / speed.y;
+ }
+ }
+
+ // See if the new walk sequence is the same as the old. If it's a new one,
+ // we need to reset the frame number to zero so its animation starts at
+ // its beginning. Otherwise, if it's the same sequence, we can leave it
+ // as is, so it keeps the animation going at wherever it was up to
+ if (_sequenceNumber != _oldWalkSequence)
+ _frameNumber = 0;
+ _oldWalkSequence = _sequenceNumber;
+
+ if (!_walkCount)
+ gotoStand();
+
+ // If the sequence is the same as when we started, then Holmes was
+ // standing still and we're trying to re-stand him, so reset Holmes'
+ // rame to the old frame number from before it was reset to 0
+ if (_sequenceNumber == oldDirection)
+ _frameNumber = oldFrame;
+}
+
+void ScalpelPerson::walkToCoords(const Point32 &destPos, int destDir) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER + 10, destPos.y / FIXED_INT_MULTIPLIER);
+ people._allowWalkAbort = true;
+ goAllTheWay();
+
+ // Keep calling doBgAnim until the walk is done
+ do {
+ events.pollEventsAndWait();
+ scene.doBgAnim();
+ } while (!_vm->shouldQuit() && _walkCount);
+
+ if (!talk._talkToAbort) {
+ // Put character exactly on destination position, and set direction
+ _position = destPos;
+ _sequenceNumber = destDir;
+ gotoStand();
+
+ // Draw Holmes facing the new direction
+ scene.doBgAnim();
+
+ if (!talk._talkToAbort)
+ events.setCursor(oldCursor);
+ }
+}
+
+Common::Point ScalpelPerson::getSourcePoint() const {
+ return Common::Point(_position.x / FIXED_INT_MULTIPLIER + frameWidth() / 2,
+ _position.y / FIXED_INT_MULTIPLIER);
+}
+
+void ScalpelPerson::synchronize(Serializer &s) {
+ if (_walkCount)
+ gotoStand();
+
+ s.syncAsSint32LE(_position.x);
+ s.syncAsSint32LE(_position.y);
+}
+
+/*----------------------------------------------------------------*/
+
+ScalpelPeople::ScalpelPeople(SherlockEngine *vm) : People(vm) {
+ _data.push_back(new ScalpelPerson());
+}
+
+void ScalpelPeople::setTalking(int speaker) {
+ Resources &res = *_vm->_res;
+
+ // If no speaker is specified, then we can exit immediately
+ if (speaker == -1)
+ return;
+
+ if (_portraitsOn) {
+ delete _talkPics;
+ Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait);
+ _talkPics = new ImageFile(filename);
+
+ // Load portrait sequences
+ Common::SeekableReadStream *stream = res.load("sequence.txt");
+ stream->seek(speaker * MAX_FRAME);
+
+ int idx = 0;
+ do {
+ _portrait._sequences[idx] = stream->readByte();
+ ++idx;
+ } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]);
+
+ delete stream;
+
+ _portrait._maxFrames = idx;
+ _portrait._frameNumber = 0;
+ _portrait._sequenceNumber = 0;
+ _portrait._images = _talkPics;
+ _portrait._imageFrame = &(*_talkPics)[0];
+ _portrait._position = Common::Point(_portraitSide, 10);
+ _portrait._delta = Common::Point(0, 0);
+ _portrait._oldPosition = Common::Point(0, 0);
+ _portrait._goto = Common::Point(0, 0);
+ _portrait._flags = 5;
+ _portrait._status = 0;
+ _portrait._misc = 0;
+ _portrait._allow = 0;
+ _portrait._type = ACTIVE_BG_SHAPE;
+ _portrait._name = " ";
+ _portrait._description = " ";
+ _portrait._examine = " ";
+ _portrait._walkCount = 0;
+
+ if (_holmesFlip || _speakerFlip) {
+ _portrait._flags |= 2;
+
+ _holmesFlip = false;
+ _speakerFlip = false;
+ }
+
+ if (_portraitSide == 20)
+ _portraitSide = 220;
+ else
+ _portraitSide = 20;
+
+ _portraitLoaded = true;
+ }
+}
+
+void ScalpelPeople::synchronize(Serializer &s) {
+ (*this)[HOLMES].synchronize(s);
+ s.syncAsSint16LE(_holmesQuotient);
+ s.syncAsByte(_holmesOn);
+
+ if (s.isLoading()) {
+ _savedPos = _data[HOLMES]->_position;
+ _savedPos._facing = _data[HOLMES]->_sequenceNumber;
+ }
+}
+
+void ScalpelPeople::setTalkSequence(int speaker, int sequenceNum) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ if (speaker) {
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1) {
+ Object &obj = scene._bgShapes[objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to copy too many talk frames");
+ } else {
+ for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
+ obj._sequences[idx] = people._characters[speaker]._talkSequences[idx];
+ if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1])
+ return;
+
+ obj._frameNumber = 0;
+ obj._sequenceNumber = 0;
+ }
+ }
+ }
+ }
+}
+
+bool ScalpelPeople::loadWalk() {
+ bool result = false;
+
+ if (_data[HOLMES]->_walkLoaded) {
+ return false;
+ } else {
+ if (!IS_3DO) {
+ _data[HOLMES]->_images = new ImageFile("walk.vgs");
+ } else {
+ // Load walk.anim on 3DO, which is a cel animation file
+ _data[HOLMES]->_images = new ImageFile3DO("walk.anim", kImageFile3DOType_CelAnimation);
+ }
+ _data[HOLMES]->setImageFrame();
+ _data[HOLMES]->_walkLoaded = true;
+
+ result = true;
+ }
+
+ _forceWalkReload = false;
+ return result;
+}
+
+const Common::Point ScalpelPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
+ Scene &scene = *_vm->_scene;
+ Common::Point walkDest = destPos;
+
+ // The destination isn't in a zone
+ if (walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1))
+ walkDest.x = SHERLOCK_SCREEN_WIDTH - 2;
+
+ // Trace a line between the centroid of the found closest zone to
+ // the destination, to find the point at which the zone will be left
+ const Common::Rect &destRect = scene._zones[zoneId];
+ const Common::Point destCenter((destRect.left + destRect.right) / 2,
+ (destRect.top + destRect.bottom) / 2);
+ const Common::Point delta = walkDest - destCenter;
+ Point32 pt(destCenter.x * FIXED_INT_MULTIPLIER, destCenter.y * FIXED_INT_MULTIPLIER);
+
+ // Move along the line until the zone is left
+ do {
+ pt += delta;
+ } while (destRect.contains(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER));
+
+ // Set the new walk destination to the last point that was in the
+ // zone just before it was left
+ return Common::Point((pt.x - delta.x * 2) / FIXED_INT_MULTIPLIER,
+ (pt.y - delta.y * 2) / FIXED_INT_MULTIPLIER);
+}
+
+void ScalpelPeople::setListenSequence(int speaker, int sequenceNum) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ // Don't bother doing anything if no specific speaker is specified
+ if (speaker == -1)
+ return;
+
+ if (speaker) {
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1) {
+ Object &obj = scene._bgShapes[objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to copy too few still frames");
+ } else {
+ for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
+ obj._sequences[idx] = people._characters[speaker]._stillSequences[idx];
+ if (idx > 0 && !people._characters[speaker]._talkSequences[idx] &&
+ !people._characters[speaker]._talkSequences[idx - 1])
+ break;
+ }
+
+ obj._frameNumber = 0;
+ obj._seqTo = 0;
+ }
+ }
+ }
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_people.h b/engines/sherlock/scalpel/scalpel_people.h
new file mode 100644
index 0000000000..2ab6f5bc7d
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_people.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_PEOPLE_H
+#define SHERLOCK_SCALPEL_PEOPLE_H
+
+#include "common/scummsys.h"
+#include "sherlock/people.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+// Animation sequence identifiers for characters
+enum ScalpelSequences {
+ WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4,
+ STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8,
+ WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11,
+ STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14,
+ STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4
+};
+
+class ScalpelPerson : public Person {
+public:
+ ScalpelPerson() : Person() {}
+ virtual ~ScalpelPerson() {}
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * This adjusts the sprites position, as well as its animation sequence:
+ */
+ virtual void adjustSprite();
+
+ /**
+ * Bring a moving character to a standing position
+ */
+ virtual void gotoStand();
+
+ /**
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
+ */
+ virtual void setWalking();
+
+ /**
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir);
+
+ /**
+ * Get the source position for a character potentially affected by scaling
+ */
+ virtual Common::Point getSourcePoint() const;
+};
+
+class ScalpelPeople : public People {
+public:
+ ScalpelPeople(SherlockEngine *vm);
+ virtual ~ScalpelPeople() {}
+
+ ScalpelPerson &operator[](PeopleId id) { return *(ScalpelPerson *)_data[id]; }
+ ScalpelPerson &operator[](int idx) { return *(ScalpelPerson *)_data[idx]; }
+
+ /**
+ * Setup the data for an animating speaker portrait at the top of the screen
+ */
+ void setTalking(int speaker);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Change the sequence of the scene background object associated with the specified speaker.
+ */
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1);
+
+ /**
+ * Restrict passed point to zone using Sherlock's positioning rules
+ */
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos);
+
+ /**
+ * Load the walking images for Sherlock
+ */
+ virtual bool loadWalk();
+
+ /**
+ * 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
+ */
+ virtual void setListenSequence(int speaker, int sequenceNum = 1);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_saveload.cpp b/engines/sherlock/scalpel/scalpel_saveload.cpp
new file mode 100644
index 0000000000..01ba149813
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_saveload.cpp
@@ -0,0 +1,261 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+const int ENV_POINTS[6][3] = {
+ { 41, 80, 61 }, // Exit
+ { 81, 120, 101 }, // Load
+ { 121, 160, 141 }, // Save
+ { 161, 200, 181 }, // Up
+ { 201, 240, 221 }, // Down
+ { 241, 280, 261 } // Quit
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelSaveManager::ScalpelSaveManager(SherlockEngine *vm, const Common::String &target) : SaveManager(vm, target) {
+}
+
+void ScalpelSaveManager::drawInterface() {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // Create a list of savegame slots
+ createSavegameList();
+
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+
+ screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10),
+ ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
+ screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10),
+ ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load");
+ screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10),
+ ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save");
+ screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10),
+ ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up");
+ screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10),
+ ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down");
+ screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10),
+ ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit");
+
+ if (!_savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up");
+
+ if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down");
+
+ for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) {
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%s", _savegames[idx].c_str());
+ }
+
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow();
+ }
+
+ _envMode = SAVEMODE_NONE;
+}
+
+int ScalpelSaveManager::getHighlightedButton() const {
+ Common::Point pt = _vm->_events->mousePos();
+
+ for (int idx = 0; idx < 6; ++idx) {
+ if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y
+ && pt.y < (CONTROLS_Y + 10))
+ return idx;
+ }
+
+ return -1;
+}
+
+void ScalpelSaveManager::highlightButtons(int btnIndex) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+
+ screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit");
+
+ if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2)))
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load");
+ else
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load");
+
+ if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1)))
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save");
+ else
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save");
+
+ if (btnIndex == 3 && _savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up");
+ else
+ if (_savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up");
+
+ if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5))
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down");
+ else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5))
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down");
+
+ color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit");
+}
+
+bool ScalpelSaveManager::checkGameOnScreen(int slot) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ // Check if it's already on-screen
+ if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) {
+ _savegameIndex = slot;
+
+ screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+
+ for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) {
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%s", _savegames[idx].c_str());
+ }
+
+ screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT));
+
+ byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up");
+
+ color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down");
+
+ return true;
+ }
+
+ return false;
+}
+
+bool ScalpelSaveManager::promptForDescription(int slot) {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int xp, yp;
+ bool flag = false;
+
+ screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit");
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load");
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save");
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up");
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down");
+ screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit");
+
+ Common::String saveName = _savegames[slot];
+ if (isSlotEmpty(slot)) {
+ // It's an empty slot, so start off with an empty save name
+ saveName = "";
+
+ yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
+ screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND);
+ }
+
+ screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1);
+ screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str());
+ xp = 24 + screen.stringWidth(saveName);
+ yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
+
+ int done = 0;
+ do {
+ while (!_vm->shouldQuit() && !events.kbHit()) {
+ scene.doBgAnim();
+
+ if (talk._talkToAbort)
+ return false;
+
+ // Allow event processing
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ flag = !flag;
+ if (flag)
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ else
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ }
+ if (_vm->shouldQuit())
+ return false;
+
+ // Get the next keypress
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) {
+ // Delete character of save name
+ screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1,
+ xp + 8, yp + 9), INV_BACKGROUND);
+ xp -= screen.charWidth(saveName.lastChar());
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ saveName.deleteLastChar();
+
+ } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) {
+ done = 1;
+
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ done = -1;
+
+ } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50
+ && (xp + screen.charWidth(keyState.ascii)) < 308) {
+ char c = (char)keyState.ascii;
+
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c);
+ xp += screen.charWidth(c);
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ saveName += c;
+ }
+ } while (!done);
+
+ if (done == 1) {
+ // Enter key perssed
+ _savegames[slot] = saveName;
+ } else {
+ done = 0;
+ _envMode = SAVEMODE_NONE;
+ highlightButtons(-1);
+ }
+
+ return done == 1;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_saveload.h b/engines/sherlock/scalpel/scalpel_saveload.h
new file mode 100644
index 0000000000..6b035cace3
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_saveload.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_SAVELOAD_H
+#define SHERLOCK_SCALPEL_SAVELOAD_H
+
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+extern const int ENV_POINTS[6][3];
+
+class ScalpelSaveManager: public SaveManager {
+public:
+ SaveMode _envMode;
+public:
+ ScalpelSaveManager(SherlockEngine *vm, const Common::String &target);
+ virtual ~ScalpelSaveManager() {}
+
+ /**
+ * Shows the in-game dialog interface for loading and saving games
+ */
+ void drawInterface();
+
+ /**
+ * Return the index of the button the mouse is over, if any
+ */
+ int getHighlightedButton() const;
+
+ /**
+ * Handle highlighting buttons
+ */
+ void highlightButtons(int btnIndex);
+
+ /**
+ * Make sure that the selected savegame is on-screen
+ */
+ bool checkGameOnScreen(int slot);
+
+ /**
+ * Prompts the user to enter a description in a given slot
+ */
+ bool promptForDescription(int slot);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp
index b6a42419d8..b2c7339363 100644
--- a/engines/sherlock/scalpel/scalpel_scene.cpp
+++ b/engines/sherlock/scalpel/scalpel_scene.cpp
@@ -21,18 +21,130 @@
*/
#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/events.h"
#include "sherlock/people.h"
#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
namespace Sherlock {
namespace Scalpel {
+const int FS_TRANS[8] = {
+ STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT
+};
+
+/*----------------------------------------------------------------*/
+
+bool ScalpelScene::loadScene(const Common::String &filename) {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ bool result = Scene::loadScene(filename);
+
+ if (!_vm->isDemo()) {
+ // Reset the previous map location and position on overhead map
+ map._oldCharPoint = _currentScene;
+
+ map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER;
+
+ }
+
+ return result;
+}
+
+void ScalpelScene::drawAllShapes() {
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+
+ // Restrict drawing window
+ screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
+
+ // Draw all active shapes which are behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame,
+ _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all active shapes which are normal and behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are normal and behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw any active characters
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Person &p = people[idx];
+ if (p._type == CHARACTER && p._walkLoaded) {
+ bool flipped = IS_SERRATED_SCALPEL && (
+ p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT ||
+ p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT ||
+ p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT);
+
+ screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER,
+ p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped);
+ }
+ }
+
+ // Draw all static and active shapes that are NORMAL and are in front of the player
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _bgShapes[idx]._misc == NORMAL_FORWARD)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
+ _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active canimations that are NORMAL and are in front of the player
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _canimShapes[idx]._misc == NORMAL_FORWARD)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active shapes that are FORWARD
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ _bgShapes[idx]._oldPosition = _bgShapes[idx]._position;
+ _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(),
+ _bgShapes[idx].frameHeight());
+
+ if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _bgShapes[idx]._misc == FORWARD)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
+ _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active canimations that are forward
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _canimShapes[idx]._misc == FORWARD)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ screen.resetDisplayBounds();
+}
+
void ScalpelScene::checkBgShapes() {
People &people = *_vm->_people;
- Person &holmes = people._player;
+ Person &holmes = people[HOLMES];
Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
// Call the base scene method to handle bg shapes
@@ -57,9 +169,9 @@ void ScalpelScene::checkBgShapes() {
void ScalpelScene::doBgAnimCheckCursor() {
Inventory &inv = *_vm->_inventory;
Events &events = *_vm->_events;
- Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
Common::Point mousePos = events.mousePos();
+ events.animateCursorIfNeeded();
if (ui._menuMode == LOOK_MODE) {
if (mousePos.y > CONTROLS_Y1)
@@ -80,11 +192,6 @@ void ScalpelScene::doBgAnimCheckCursor() {
events.setCursor(ARROW);
}
}
-
- if (sound._diskSoundPlaying && !*sound._soundIsOn) {
- // Loaded sound just finished playing
- sound.freeDigiSound();
- }
}
void ScalpelScene::doBgAnim() {
@@ -94,6 +201,8 @@ void ScalpelScene::doBgAnim() {
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
+ doBgAnimCheckCursor();
+
screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
talk._talkToAbort = false;
@@ -116,18 +225,18 @@ void ScalpelScene::doBgAnim() {
_canimShapes[idx].checkObject();
}
- if (_currentScene == 12)
- vm.eraseMirror12();
+ if (_currentScene == DRAWING_ROOM)
+ vm.eraseBrumwellMirror();
// Restore the back buffer from the back buffer 2 in the changed area
- Common::Rect bounds(people[AL]._oldPosition.x, people[AL]._oldPosition.y,
- people[AL]._oldPosition.x + people[AL]._oldSize.x,
- people[AL]._oldPosition.y + people[AL]._oldSize.y);
+ Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
+ people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
+ people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y);
Common::Point pt(bounds.left, bounds.top);
- if (people[AL]._type == CHARACTER)
+ if (people[HOLMES]._type == CHARACTER)
screen.restoreBackground(bounds);
- else if (people[AL]._type == REMOVE)
+ else if (people[HOLMES]._type == REMOVE)
screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds);
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
@@ -182,14 +291,14 @@ void ScalpelScene::doBgAnim() {
_canimShapes[idx].adjustObject();
}
- if (people[AL]._type == CHARACTER && people._holmesOn)
- people[AL].adjustSprite();
+ if (people[HOLMES]._type == CHARACTER && people._holmesOn)
+ people[HOLMES].adjustSprite();
// Flag the bg shapes which need to be redrawn
checkBgShapes();
- if (_currentScene == 12)
- vm.doMirror12();
+ if (_currentScene == DRAWING_ROOM)
+ vm.doBrumwellMirror();
// Draw all active shapes which are behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
@@ -222,16 +331,16 @@ void ScalpelScene::doBgAnim() {
}
// Draw the person if not animating
- if (people[AL]._type == CHARACTER && people[AL]._walkLoaded) {
+ if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded) {
// If Holmes is too far to the right, move him back so he's on-screen
- int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[AL]._imageFrame->_frame.w;
- int tempX = MIN(people[AL]._position.x / FIXED_INT_MULTIPLIER, xRight);
-
- bool flipped = people[AL]._sequenceNumber == WALK_LEFT || people[AL]._sequenceNumber == STOP_LEFT ||
- people[AL]._sequenceNumber == WALK_UPLEFT || people[AL]._sequenceNumber == STOP_UPLEFT ||
- people[AL]._sequenceNumber == WALK_DOWNRIGHT || people[AL]._sequenceNumber == STOP_DOWNRIGHT;
- screen._backBuffer->transBlitFrom(*people[AL]._imageFrame,
- Common::Point(tempX, people[AL]._position.y / FIXED_INT_MULTIPLIER - people[AL]._imageFrame->_frame.h), flipped);
+ int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w;
+ int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight);
+
+ bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT ||
+ people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT ||
+ people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT;
+ screen._backBuffer->transBlitFrom(*people[HOLMES]._imageFrame,
+ Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped);
}
// Draw all static and active shapes are NORMAL and are in front of the person
@@ -281,25 +390,25 @@ void ScalpelScene::doBgAnim() {
_animating = 0;
screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
} else {
- if (people[AL]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) {
- if (people[AL]._type == REMOVE) {
+ if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) {
+ if (people[HOLMES]._type == REMOVE) {
screen.slamRect(Common::Rect(
- people[AL]._oldPosition.x, people[AL]._oldPosition.y,
- people[AL]._oldPosition.x + people[AL]._oldSize.x,
- people[AL]._oldPosition.y + people[AL]._oldSize.y
+ people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
+ people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
+ people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y
));
- people[AL]._type = INVALID;
+ people[HOLMES]._type = INVALID;
} else {
- screen.flushImage(people[AL]._imageFrame,
- Common::Point(people[AL]._position.x / FIXED_INT_MULTIPLIER,
- people[AL]._position.y / FIXED_INT_MULTIPLIER - people[AL].frameHeight()),
- &people[AL]._oldPosition.x, &people[AL]._oldPosition.y,
- &people[AL]._oldSize.x, &people[AL]._oldSize.y);
+ screen.flushImage(people[HOLMES]._imageFrame,
+ Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()),
+ &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y,
+ &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
}
}
- if (_currentScene == 12)
- vm.flushMirror12();
+ if (_currentScene == DRAWING_ROOM)
+ vm.flushBrumwellMirror();
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
@@ -376,6 +485,262 @@ void ScalpelScene::doBgAnim() {
}
}
+int ScalpelScene::startCAnim(int cAnimNum, int playRate) {
+ Events &events = *_vm->_events;
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ Resources &res = *_vm->_res;
+ Talk &talk = *_vm->_talk;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+ Point32 tpPos, walkPos;
+ int tpDir, walkDir;
+ int tFrames = 0;
+ int gotoCode = -1;
+
+ // Validation
+ if (cAnimNum >= (int)_cAnim.size())
+ // number out of bounds
+ return -1;
+ if (_canimShapes.size() >= 3 || playRate == 0)
+ // Too many active animations, or invalid play rate
+ return 0;
+
+ CAnim &cAnim = _cAnim[cAnimNum];
+ if (playRate < 0) {
+ // Reverse direction
+ walkPos = cAnim._teleport[0];
+ walkDir = cAnim._teleport[0]._facing;
+ tpPos = cAnim._goto[0];
+ tpDir = cAnim._goto[0]._facing;
+ } else {
+ // Forward direction
+ walkPos = cAnim._goto[0];
+ walkDir = cAnim._goto[0]._facing;
+ tpPos = cAnim._teleport[0];
+ tpDir = cAnim._teleport[0]._facing;
+ }
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ if (walkPos.x != -1) {
+ // Holmes must walk to the walk point before the cAnimation is started
+ if (people[HOLMES]._position != walkPos)
+ people[HOLMES].walkToCoords(walkPos, walkDir);
+ }
+
+ if (talk._talkToAbort)
+ return 1;
+
+ // Add new anim shape entry for displaying the animation
+ _canimShapes.push_back(Object());
+ Object &cObj = _canimShapes[_canimShapes.size() - 1];
+
+ // Copy the canimation into the bgShapes type canimation structure so it can be played
+ cObj._allow = cAnimNum + 1; // Keep track of the parent structure
+ cObj._name = _cAnim[cAnimNum]._name; // Copy name
+
+ // Remove any attempt to draw object frame
+ if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100)
+ cAnim._sequences[0] = 0;
+
+ cObj._sequences = cAnim._sequences;
+ cObj._images = nullptr;
+ cObj._position = cAnim._position;
+ cObj._delta = Common::Point(0, 0);
+ cObj._type = cAnim._type;
+ cObj._flags = cAnim._flags;
+
+ cObj._maxFrames = 0;
+ cObj._frameNumber = -1;
+ cObj._sequenceNumber = cAnimNum;
+ cObj._oldPosition = Common::Point(0, 0);
+ cObj._oldSize = Common::Point(0, 0);
+ cObj._goto = Common::Point(0, 0);
+ cObj._status = 0;
+ cObj._misc = 0;
+ cObj._imageFrame = nullptr;
+
+ if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) {
+ if (tpPos.x != -1)
+ people[HOLMES]._type = REMOVE;
+
+ Common::String fname = cAnim._name + ".vgs";
+ if (!res.isInCache(fname)) {
+ // Set up RRM scene data
+ Common::SeekableReadStream *roomStream = res.load(_roomFilename);
+ roomStream->seek(cAnim._dataOffset);
+ //rrmStream->seek(44 + cAnimNum * 4);
+ //rrmStream->seek(rrmStream->readUint32LE());
+
+ // Load the canimation into the cache
+ Common::SeekableReadStream *imgStream = !_compressed ? roomStream->readStream(cAnim._dataSize) :
+ Resources::decompressLZ(*roomStream, cAnim._dataSize);
+ res.addToCache(fname, *imgStream);
+
+ delete imgStream;
+ delete roomStream;
+ }
+
+ // Now load the resource as an image
+ if (!IS_3DO) {
+ cObj._images = new ImageFile(fname);
+ } else {
+ cObj._images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat);
+ }
+ cObj._imageFrame = &(*cObj._images)[0];
+ cObj._maxFrames = cObj._images->size();
+
+ int frames = 0;
+ if (playRate < 0) {
+ // Reverse direction
+ // Count number of frames
+ while (frames < MAX_FRAME && cObj._sequences[frames])
+ ++frames;
+ } else {
+ // Forward direction
+ BaseObject::_countCAnimFrames = true;
+
+ while (cObj._type == ACTIVE_BG_SHAPE) {
+ cObj.checkObject();
+ ++frames;
+
+ if (frames >= 1000)
+ error("CAnim has infinite loop sequence");
+ }
+
+ if (frames > 1)
+ --frames;
+
+ BaseObject::_countCAnimFrames = false;
+
+ cObj._type = cAnim._type;
+ cObj._frameNumber = -1;
+ cObj._position = cAnim._position;
+ cObj._delta = Common::Point(0, 0);
+ }
+
+ // Return if animation has no frames in it
+ if (frames == 0)
+ return -2;
+
+ ++frames;
+ int repeat = ABS(playRate);
+ int dir;
+
+ if (playRate < 0) {
+ // Play in reverse
+ dir = -2;
+ cObj._frameNumber = frames - 3;
+ } else {
+ dir = 0;
+ }
+
+ tFrames = frames - 1;
+ int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1;
+
+ while (--frames) {
+ if (frames == pauseFrame)
+ ui.printObjectDesc();
+
+ doBgAnim();
+
+ // Repeat same frame
+ int temp = repeat;
+ while (--temp > 0) {
+ cObj._frameNumber--;
+ doBgAnim();
+
+ if (_vm->shouldQuit())
+ return 0;
+ }
+
+ cObj._frameNumber += dir;
+ }
+
+ people[HOLMES]._type = CHARACTER;
+ }
+
+ // Teleport to ending coordinates if necessary
+ if (tpPos.x != -1) {
+ people[HOLMES]._position = tpPos; // Place the player
+ people[HOLMES]._sequenceNumber = tpDir;
+ people[HOLMES].gotoStand();
+ }
+
+ if (playRate < 0)
+ // Reverse direction - set to end sequence
+ cObj._frameNumber = tFrames - 1;
+
+ if (cObj._frameNumber <= 26)
+ gotoCode = cObj._sequences[cObj._frameNumber + 3];
+
+ // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it
+ if (_canimShapes.indexOf(cObj) != -1)
+ cObj.checkObject();
+
+ if (gotoCode > 0 && !talk._talkToAbort) {
+ _goToScene = gotoCode;
+
+ if (_goToScene < 97 && map[_goToScene].x) {
+ map._overPos = map[_goToScene];
+ }
+ }
+
+ people.loadWalk();
+
+ if (tpPos.x != -1 && !talk._talkToAbort) {
+ // Teleport to ending coordinates
+ people[HOLMES]._position = tpPos;
+ people[HOLMES]._sequenceNumber = tpDir;
+
+ people[HOLMES].gotoStand();
+ }
+
+ events.setCursor(oldCursor);
+
+ return 1;
+}
+
+int ScalpelScene::closestZone(const Common::Point &pt) {
+ int dist = 1000;
+ int zone = -1;
+
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ Common::Point zc((_zones[idx].left + _zones[idx].right) / 2,
+ (_zones[idx].top + _zones[idx].bottom) / 2);
+ int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y);
+
+ if (d < dist) {
+ // Found a closer zone
+ dist = d;
+ zone = idx;
+ }
+ }
+
+ return zone;
+}
+
+int ScalpelScene::findBgShape(const Common::Point &pt) {
+ if (!_doBgAnimDone)
+ // New frame hasn't been drawn yet
+ return -1;
+
+ for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN
+ && o._aType <= PERSON) {
+ if (o.getNewBounds().contains(pt))
+ return idx;
+ } else if (o._type == NO_SHAPE) {
+ if (o.getNoShapeBounds().contains(pt))
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
} // End of namespace Scalpel
} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_scene.h b/engines/sherlock/scalpel/scalpel_scene.h
index e5a442f44f..8fe3b66b38 100644
--- a/engines/sherlock/scalpel/scalpel_scene.h
+++ b/engines/sherlock/scalpel/scalpel_scene.h
@@ -35,16 +35,43 @@ namespace Sherlock {
namespace Scalpel {
+extern const int FS_TRANS[8];
+
+enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19,
+ LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55,
+ BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 };
+
class ScalpelScene : public Scene {
private:
void doBgAnimCheckCursor();
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.
+ */
+ virtual bool loadScene(const Common::String &filename);
+
+ /**
* 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
*/
virtual void checkBgShapes();
+
+ /**
+ * Draw all the shapes, people and NPCs in the correct order
+ */
+ virtual void drawAllShapes();
+
+ /**
+ * Returns the index of the closest zone to a given point.
+ */
+ virtual int closestZone(const Common::Point &pt);
public:
ScalpelScene(SherlockEngine *vm) : Scene(vm) {}
@@ -52,6 +79,22 @@ public:
* Draw all objects and characters.
*/
virtual void doBgAnim();
+
+ /**
+ * 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
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1);
+
+ /**
+ * Attempts to find a background shape within the passed bounds. If found,
+ * it will return the shape number, or -1 on failure.
+ */
+ virtual int findBgShape(const Common::Point &pt);
};
} // End of namespace Scalpel
diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp
new file mode 100644
index 0000000000..2096dabcdf
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_screen.cpp
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) {
+}
+
+void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
+ const Common::String &str) {
+
+ Surface &bb = *_backBuffer;
+ bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP);
+ bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP);
+ bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM);
+ bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM);
+ bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE);
+
+ gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ gPrint(Common::Point(textX + charWidth(str[0]), bounds.top),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+}
+
+void ScalpelScreen::buttonPrint(const Common::Point &pt, byte color, bool slamIt,
+ const Common::String &str) {
+ int xStart = pt.x - stringWidth(str) / 2;
+
+ if (color == COMMAND_FOREGROUND) {
+ // First character needs to be highlighted
+ if (slamIt) {
+ print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ print(Common::Point(xStart + charWidth(str[0]), pt.y + 1),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+ } else {
+ gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ gPrint(Common::Point(xStart + charWidth(str[0]), pt.y),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+ }
+ } else if (slamIt) {
+ print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str());
+ } else {
+ gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str());
+ }
+}
+
+void ScalpelScreen::makePanel(const Common::Rect &r) {
+ _backBuffer->fillRect(r, BUTTON_MIDDLE);
+ _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP);
+ _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP);
+ _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP);
+ _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
+
+ _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM);
+ _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM);
+}
+
+void ScalpelScreen::makeField(const Common::Rect &r) {
+ _backBuffer->fillRect(r, BUTTON_MIDDLE);
+ _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP);
+ _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM);
+ _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_screen.h b/engines/sherlock/scalpel/scalpel_screen.h
new file mode 100644
index 0000000000..472fe9e220
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_screen.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_SCREEN_H
+#define SHERLOCK_SCALPEL_SCREEN_H
+
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+class ScalpelScreen : public Screen {
+public:
+ ScalpelScreen(SherlockEngine *vm);
+ virtual ~ScalpelScreen() {}
+
+ /**
+ * Draws a button for use in the inventory, talk, and examine dialogs.
+ */
+ void makeButton(const Common::Rect &bounds, int textX, const Common::String &str);
+
+ /**
+ * Prints an interface command with the first letter highlighted to indicate
+ * what keyboard shortcut is associated with it
+ */
+ void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str);
+
+ /**
+ * Draw a panel in the back buffer with a raised area effect around the edges
+ */
+ void makePanel(const Common::Rect &r);
+
+ /**
+ * Draw a field in the back buffer with a raised area effect around the edges,
+ * suitable for text input.
+ */
+ void makeField(const Common::Rect &r);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp
new file mode 100644
index 0000000000..2dda817445
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_talk.cpp
@@ -0,0 +1,935 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_talk.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/screen.h"
+#include "sherlock/scalpel/3do/movie_decoder.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+const byte SCALPEL_OPCODES[] = {
+ 128, // OP_SWITCH_SPEAKER
+ 129, // OP_RUN_CANIMATION
+ 130, // OP_ASSIGN_PORTRAIT_LOCATION
+ 131, // OP_PAUSE
+ 132, // OP_REMOVE_PORTRAIT
+ 133, // OP_CLEAR_WINDOW
+ 134, // OP_ADJUST_OBJ_SEQUENCE
+ 135, // OP_WALK_TO_COORDS
+ 136, // OP_PAUSE_WITHOUT_CONTROL
+ 137, // OP_BANISH_WINDOW
+ 138, // OP_SUMMON_WINDOW
+ 139, // OP_SET_FLAG
+ 140, // OP_SFX_COMMAND
+ 141, // OP_TOGGLE_OBJECT
+ 142, // OP_STEALTH_MODE_ACTIVE
+ 143, // OP_IF_STATEMENT
+ 144, // OP_ELSE_STATEMENT
+ 145, // OP_END_IF_STATEMENT
+ 146, // OP_STEALTH_MODE_DEACTIVATE
+ 147, // OP_TURN_HOLMES_OFF
+ 148, // OP_TURN_HOLMES_ON
+ 149, // OP_GOTO_SCENE
+ 150, // OP_PLAY_PROLOGUE
+ 151, // OP_ADD_ITEM_TO_INVENTORY
+ 152, // OP_SET_OBJECT
+ 153, // OP_CALL_TALK_FILE
+ 143, // OP_MOVE_MOUSE
+ 155, // OP_DISPLAY_INFO_LINE
+ 156, // OP_CLEAR_INFO_LINE
+ 157, // OP_WALK_TO_CANIMATION
+ 158, // OP_REMOVE_ITEM_FROM_INVENTORY
+ 159, // OP_ENABLE_END_KEY
+ 160, // OP_DISABLE_END_KEY
+ 161, // OP_END_TEXT_WINDOW
+ 0, // OP_MOUSE_ON_OFF
+ 0, // OP_SET_WALK_CONTROL
+ 0, // OP_SET_TALK_SEQUENCE
+ 0, // OP_PLAY_SONG
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
+ 0, // OP_SET_NPC_PATH_DEST
+ 0, // OP_NEXT_SONG
+ 0, // OP_SET_NPC_PATH_PAUSE
+ 0, // OP_PASSWORD
+ 0, // OP_SET_SCENE_ENTRY_FLAG
+ 0, // OP_WALK_NPC_TO_CANIM
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 0, // OP_SET_NPC_TALK_FILE
+ 0, // OP_TURN_NPC_OFF
+ 0, // OP_TURN_NPC_ON
+ 0, // OP_NPC_DESC_ON_OFF
+ 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES
+ 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
+ 0, // OP_ENABLE_TALK_INTERRUPTS
+ 0, // OP_DISABLE_TALK_INTERRUPTS
+ 0, // OP_SET_NPC_INFO_LINE
+ 0, // OP_SET_NPC_POSITION
+ 0, // OP_NPC_PATH_LABEL
+ 0, // OP_PATH_GOTO_LABEL
+ 0, // OP_PATH_IF_FLAG_GOTO_LABEL
+ 0, // OP_NPC_WALK_GRAPHICS
+ 0, // OP_NPC_VERB
+ 0, // OP_NPC_VERB_CANIM
+ 0, // OP_NPC_VERB_SCRIPT
+ 0, // OP_RESTORE_PEOPLE_SEQUENCE
+ 0, // OP_NPC_VERB_TARGET
+ 0, // OP_TURN_SOUNDS_OFF
+ 0 // OP_NULL
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) {
+ static OpcodeMethod OPCODE_METHODS[] = {
+ (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker,
+ (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation,
+ (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation,
+
+ (OpcodeMethod)&ScalpelTalk::cmdPause,
+ (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait,
+ (OpcodeMethod)&ScalpelTalk::cmdClearWindow,
+ (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence,
+ (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords,
+ (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl,
+ (OpcodeMethod)&ScalpelTalk::cmdBanishWindow,
+ (OpcodeMethod)&ScalpelTalk::cmdSummonWindow,
+ (OpcodeMethod)&ScalpelTalk::cmdSetFlag,
+ (OpcodeMethod)&ScalpelTalk::cmdSfxCommand,
+
+ (OpcodeMethod)&ScalpelTalk::cmdToggleObject,
+ (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate,
+ (OpcodeMethod)&ScalpelTalk::cmdIf,
+ (OpcodeMethod)&ScalpelTalk::cmdElse,
+ nullptr,
+ (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate,
+ (OpcodeMethod)&ScalpelTalk::cmdHolmesOff,
+ (OpcodeMethod)&ScalpelTalk::cmdHolmesOn,
+ (OpcodeMethod)&ScalpelTalk::cmdGotoScene,
+ (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue,
+
+ (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory,
+ (OpcodeMethod)&ScalpelTalk::cmdSetObject,
+ (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile,
+ (OpcodeMethod)&ScalpelTalk::cmdMoveMouse,
+ (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine,
+ (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine,
+ (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation,
+ (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory,
+ (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey,
+ (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey,
+
+ (OpcodeMethod)&ScalpelTalk::cmdEndTextWindow,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
+ };
+
+ _opcodeTable = OPCODE_METHODS;
+ _opcodes = SCALPEL_OPCODES;
+
+ if (vm->getLanguage() == Common::DE_DEU || vm->getLanguage() == Common::ES_ESP) {
+ // The German and Spanish versions use a different opcode range
+ static byte opcodes[sizeof(SCALPEL_OPCODES)];
+ for (uint idx = 0; idx < sizeof(SCALPEL_OPCODES); ++idx)
+ opcodes[idx] = SCALPEL_OPCODES[idx] ? SCALPEL_OPCODES[idx] + 47 : 0;
+
+ _opcodes = opcodes;
+ }
+
+}
+
+void ScalpelTalk::talkTo(const Common::String filename) {
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+
+ Talk::talkTo(filename);
+
+ if (filename == "Tube59c") {
+ // WORKAROUND: Original game bug causes the results of testing the powdery substance
+ // to disappear too quickly. Introduce a delay to allow it to be properly displayed
+ ui._menuCounter = 30;
+ }
+}
+
+void ScalpelTalk::talkInterface(const byte *&str) {
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // If the window isn't yet open, draw the window before printing starts
+ if (!ui._windowOpen && _noTextYet) {
+ _noTextYet = false;
+ drawInterface();
+
+ if (_talkTo != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ }
+ }
+
+ // If it's the first line, display the speaker
+ if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) {
+ // If the window is open, display the name directly on-screen.
+ // Otherwise, simply draw it on the back buffer
+ if (ui._windowOpen) {
+ screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s",
+ people._characters[_speaker & 127]._name);
+ } else {
+ screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s",
+ people._characters[_speaker & 127]._name);
+ _openTalkWindow = true;
+ }
+
+ _yp += 9;
+ }
+
+ // Find amount of text that will fit on the line
+ int width = 0, idx = 0;
+ do {
+ width += screen.charWidth(str[idx]);
+ ++idx;
+ ++_charCount;
+ } while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx])));
+
+ if (str[idx] || width >= 298) {
+ if ((!isOpcode(str[idx])) && str[idx] != '{') {
+ --idx;
+ --_charCount;
+ }
+ } else {
+ _endStr = true;
+ }
+
+ // If word wrap is needed, find the start of the current word
+ if (width >= 298) {
+ while (str[idx] != ' ') {
+ --idx;
+ --_charCount;
+ }
+ }
+
+ // Print the line
+ Common::String lineStr((const char *)str, (const char *)str + idx);
+
+ // If the speaker indicates a description file, print it in yellow
+ if (_speaker != -1) {
+ if (ui._windowOpen) {
+ screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ } else {
+ screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ _openTalkWindow = true;
+ }
+ } else {
+ if (ui._windowOpen) {
+ screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ } else {
+ screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ _openTalkWindow = true;
+ }
+ }
+
+ // Move to end of displayed line
+ str += idx;
+
+ // If line wrap occurred, then move to after the separating space between the words
+ if ((!isOpcode(str[0])) && str[0] != '{')
+ ++str;
+
+ _yp += 9;
+ ++_line;
+
+ // Certain different conditions require a wait
+ if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) ||
+ (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) ||
+ _endStr) {
+ _wait = 1;
+ }
+
+ byte v = (str >= _scriptEnd ? 0 : str[0]);
+ if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
+ v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] ||
+ v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] ||
+ v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) {
+ _wait = 1;
+ }
+}
+
+OpcodeReturn ScalpelTalk::cmdSwitchSpeaker(const byte *&str) {
+ ScalpelPeople &people = *(ScalpelPeople *)_vm->_people;
+ UserInterface &ui = *_vm->_ui;
+
+ if (!(_speaker & SPEAKER_REMOVE))
+ people.clearTalking();
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ ui.clearWindow();
+ _yp = CONTROLS_Y + 12;
+ _charCount = _line = 0;
+
+ _speaker = *++str - 1;
+ people.setTalking(_speaker);
+ pullSequence();
+ pushSequence(_speaker);
+ people.setTalkSequence(_speaker);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdGotoScene(const byte *&str) {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ scene._goToScene = str[1] - 1;
+
+ if (scene._goToScene != OVERHEAD_MAP) {
+ // Not going to the map overview
+ map._oldCharPoint = scene._goToScene;
+ map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
+
+ // Run a canimation?
+ if (str[2] > 100) {
+ people._savedPos = PositionFacing(160, 100, str[2]);
+ } else {
+ int32 posX = (str[3] - 1) * 256 + str[4] - 1;
+ int32 posY = str[5] - 1;
+ people._savedPos = PositionFacing(posX, posY, str[2] - 1);
+ }
+ }
+
+ str += 6;
+
+ _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1;
+ _scriptSaveIndex = str - _scriptStart;
+ _endStr = true;
+ _wait = 0;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) {
+ People &people = *_vm->_people;
+
+ ++str;
+ switch (str[0] & 15) {
+ case 1:
+ people._portraitSide = 20;
+ break;
+ case 2:
+ people._portraitSide = 220;
+ break;
+ case 3:
+ people._portraitSide = 120;
+ break;
+ default:
+ break;
+ }
+
+ if (str[0] > 15)
+ people._speakerFlip = true;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) {
+ UserInterface &ui = *_vm->_ui;
+
+ ui._infoFlag = true;
+ ui.clearInfo();
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) {
+ UserInterface &ui = *_vm->_ui;
+
+ ui.clearWindow();
+ _yp = CONTROLS_Y + 12;
+ _charCount = _line = 0;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) {
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+ Common::String tempString;
+
+ ++str;
+ for (int idx = 0; idx < str[0]; ++idx)
+ tempString += str[idx + 1];
+ str += str[0];
+
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str());
+ ui._menuCounter = 30;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) {
+ // If this is encountered here, it means that a preceeding IF statement was found,
+ // and evaluated to true. Now all the statements for the true block are finished,
+ // so skip over the block of code that would have executed if the result was false
+ _wait = 0;
+ do {
+ ++str;
+ } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) {
+ ++str;
+ int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0);
+ ++str;
+ _wait = 0;
+
+ bool result = flag < 0x8000;
+ if (_vm->readFlags(flag & 0x7fff) != result) {
+ do {
+ ++str;
+ } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
+
+ if (!str[0])
+ _endStr = true;
+ }
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) {
+ Events &events = *_vm->_events;
+
+ ++str;
+ events.warpMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2]));
+ if (_talkToAbort)
+ return RET_EXIT;
+ str += 3;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) {
+ Animation &anim = *_vm->_animation;
+ Common::String tempString;
+
+ ++str;
+ for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
+ tempString += str[idx];
+
+ anim.play(tempString, false, 1, 3, true, 4);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) {
+ People &people = *_vm->_people;
+
+ if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
+ people.clearTalking();
+ pullSequence();
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ _speaker |= SPEAKER_REMOVE;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdWalkToCoords(const byte *&str) {
+ People &people = *_vm->_people;
+ ++str;
+
+ people[HOLMES].walkToCoords(Point32(((str[0] - 1) * 256 + str[1] - 1) * FIXED_INT_MULTIPLIER,
+ str[2] * FIXED_INT_MULTIPLIER), str[3] - 1);
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 3;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) {
+ Sound &sound = *_vm->_sound;
+ Common::String tempString;
+
+ ++str;
+ if (sound._voices) {
+ for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
+ tempString += str[idx];
+ sound.playSpeech(tempString);
+
+ // Set voices to wait for more
+ sound._voices = 2;
+ }
+
+ _wait = 1;
+ str += 7;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ drawInterface();
+ events._pressed = events._released = false;
+ events.clearKeyboard();
+ _noTextYet = false;
+
+ if (_speaker != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ }
+
+ return RET_SUCCESS;
+}
+
+void ScalpelTalk::talkWait(const byte *&str) {
+ UserInterface &ui = *_vm->_ui;
+ bool pauseFlag = _pauseFlag;
+
+ Talk::talkWait(str);
+
+ // Clear the window unless the wait was due to a PAUSE command
+ if (!pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) {
+ if (!_talkStealth)
+ ui.clearWindow();
+ _yp = CONTROLS_Y + 12;
+ _charCount = _line = 0;
+ }
+}
+
+void ScalpelTalk::nothingToSay() {
+ error("Character had no talk options available");
+}
+
+void ScalpelTalk::switchSpeaker() {
+ // If it's the 3DO, pass on to start the actor's conversation movie
+ if (IS_3DO)
+ talk3DOMovieTrigger(_3doSpeechIndex++);
+}
+
+void ScalpelTalk::talk3DOMovieTrigger(int subIndex) {
+ // Find out a few things that we need
+ int userSelector = _vm->_ui->_selector;
+ int scriptSelector = _scriptSelect;
+ int selector = 0;
+ int roomNr = _vm->_scene->_currentScene;
+
+ if (userSelector >= 0) {
+ // User-selected dialog
+ selector = userSelector;
+ } else {
+ if (scriptSelector >= 0) {
+ // Script-selected dialog
+ selector = scriptSelector;
+ subIndex--; // for scripts we adjust subIndex, b/c we won't get called from doTalkControl()
+ } else {
+ warning("talk3DOMovieTrigger: unable to find selector");
+ return;
+ }
+ }
+
+ // Make a quick update, so that current text is shown on screen
+ _vm->_screen->update();
+
+ // Figure out that movie filename
+ Common::String movieFilename;
+
+ movieFilename = _scriptName;
+ movieFilename.deleteChar(1); // remove 2nd character of scriptname
+ // cut scriptname to 6 characters
+ while (movieFilename.size() > 6) {
+ movieFilename.deleteChar(6);
+ }
+
+ movieFilename.insertChar(selector + 'a', movieFilename.size());
+ movieFilename.insertChar(subIndex + 'a', movieFilename.size());
+ movieFilename = Common::String::format("movies/%02d/%s.stream", roomNr, movieFilename.c_str());
+
+ warning("3DO movie player:");
+ warning("room: %d", roomNr);
+ warning("script: %s", _scriptName.c_str());
+ warning("selector: %d", selector);
+ warning("subindex: %d", subIndex);
+
+ Scalpel3DOMoviePlay(movieFilename.c_str(), Common::Point(5, 5));
+
+ // Restore screen HACK
+ _vm->_screen->makeAllDirty();
+}
+
+void ScalpelTalk::drawInterface() {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Surface &bb = *screen._backBuffer;
+
+ bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+
+ if (_talkTo != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+
+ screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10),
+ 119 - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10),
+ 159 - screen.stringWidth(fixedText_Up) / 2, fixedText_Up);
+ screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10),
+ 200 - screen.stringWidth(fixedText_Down) / 2, fixedText_Down);
+ } else {
+ int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE);
+ screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10),
+ 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE);
+ screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P");
+ }
+}
+
+bool ScalpelTalk::displayTalk(bool slamIt) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ int yp = CONTROLS_Y + 14;
+ int lineY = -1;
+ _moreTalkDown = _moreTalkUp = false;
+
+ for (uint idx = 0; idx < _statements.size(); ++idx) {
+ _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1;
+ }
+
+ if (_talkIndex) {
+ for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) {
+ if (_statements[idx]._talkMap != -1)
+ _moreTalkUp = true;
+ }
+ }
+
+ // Display the up arrow and enable Up button if the first option is scrolled off-screen
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ if (_moreTalkUp) {
+ if (slamIt) {
+ screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
+ } else {
+ screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Up);
+ }
+ } else {
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up);
+ screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND);
+ } else {
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11,
+ 15, CONTROLS_Y + 22), INV_BACKGROUND);
+ }
+ }
+
+ // Loop through the statements
+ bool done = false;
+ for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) {
+ Statement &statement = _statements[idx];
+
+ if (statement._talkMap != -1) {
+ bool flag = _talkHistory[_converseNum][idx];
+ lineY = talkLine(idx, statement._talkMap, flag ? (byte)TALK_NULL : (byte)INV_FOREGROUND,
+ yp, slamIt);
+
+ if (lineY != -1) {
+ statement._talkPos.top = yp;
+ yp = lineY;
+ statement._talkPos.bottom = yp;
+
+ if (yp == SHERLOCK_SCREEN_HEIGHT)
+ done = true;
+ } else {
+ done = true;
+ }
+ }
+ }
+
+ // Display the down arrow and enable down button if there are more statements available down off-screen
+ if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) {
+ _moreTalkDown = true;
+
+ if (slamIt) {
+ screen.print(Common::Point(5, 190), INV_FOREGROUND, "|");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
+ } else {
+ screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Down);
+ }
+ } else {
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down);
+ screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
+ } else {
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
+ }
+ }
+
+ return done;
+}
+
+int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) {
+ Screen &screen = *_vm->_screen;
+ int idx = lineNum;
+ Common::String msg, number;
+ bool numberFlag = false;
+
+ // Get the statement to display as well as optional number prefix
+ if (idx < SPEAKER_REMOVE) {
+ number = Common::String::format("%d.", stateNum + 1);
+ numberFlag = true;
+ } else {
+ idx -= SPEAKER_REMOVE;
+ }
+ msg = _statements[idx]._statement;
+
+ // Handle potentially multiple lines needed to display entire statement
+ const char *lineStartP = msg.c_str();
+ int maxWidth = 298 - (numberFlag ? 18 : 0);
+ for (;;) {
+ // Get as much of the statement as possible will fit on the
+ Common::String sLine;
+ const char *lineEndP = lineStartP;
+ int width = 0;
+ do {
+ width += screen.charWidth(*lineEndP);
+ } while (*++lineEndP && width < maxWidth);
+
+ // Check if we need to wrap the line
+ if (width >= maxWidth) {
+ // Work backwards to the prior word's end
+ while (*--lineEndP != ' ')
+ ;
+
+ sLine = Common::String(lineStartP, lineEndP++);
+ } else {
+ // Can display remainder of the statement on the current line
+ sLine = Common::String(lineStartP);
+ }
+
+
+ if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) {
+ // Need to directly display on-screen?
+ if (slamIt) {
+ // See if a numer prefix is needed or not
+ if (numberFlag) {
+ // Are we drawing the first line?
+ if (lineStartP == msg.c_str()) {
+ // We are, so print the number and then the text
+ screen.print(Common::Point(16, lineY), color, "%s", number.c_str());
+ }
+
+ // Draw the line with an indent
+ screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str());
+ } else {
+ screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str());
+ }
+ } else {
+ if (numberFlag) {
+ if (lineStartP == msg.c_str()) {
+ screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str());
+ }
+
+ screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str());
+ } else {
+ screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str());
+ }
+ }
+
+ // Move to next line, if any
+ lineY += 9;
+ lineStartP = lineEndP;
+
+ if (!*lineEndP)
+ break;
+ } else {
+ // We're close to the bottom of the screen, so stop display
+ lineY = -1;
+ break;
+ }
+ }
+
+ if (lineY == -1 && lineStartP != msg.c_str())
+ lineY = SHERLOCK_SCREEN_HEIGHT;
+
+ // Return the Y position of the next line to follow this one
+ return lineY;
+}
+
+void ScalpelTalk::showTalk() {
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL;
+
+ clearSequences();
+ pushSequence(_talkTo);
+ people.setListenSequence(_talkTo);
+
+ ui._selector = ui._oldSelector = -1;
+
+ if (!ui._windowOpen) {
+ // Draw the talk interface on the back buffer
+ drawInterface();
+ displayTalk(false);
+ } else {
+ displayTalk(true);
+ }
+
+ // If the window is already open, simply draw. Otherwise, do it
+ // to the back buffer and then summon the window
+ if (ui._windowOpen) {
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, fixedText_Exit);
+ } else {
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, fixedText_Exit);
+
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow();
+ }
+
+ ui._windowOpen = true;
+ }
+}
+
+OpcodeReturn ScalpelTalk::cmdCallTalkFile(const byte *&str) {
+ Common::String tempString;
+
+ ++str;
+ for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
+ tempString += str[idx];
+ str += 8;
+
+ int scriptCurrentIndex = str - _scriptStart;
+
+ // Save the current script position and new talk file
+ if (_scriptStack.size() < 9) {
+ ScriptStackEntry rec1;
+ rec1._name = _scriptName;
+ rec1._currentIndex = scriptCurrentIndex;
+ rec1._select = _scriptSelect;
+ _scriptStack.push(rec1);
+
+ // Push the new talk file onto the stack
+ ScriptStackEntry rec2;
+ rec2._name = tempString;
+ rec2._currentIndex = 0;
+ rec2._select = 100;
+ _scriptStack.push(rec2);
+ } else {
+ error("Script stack overflow");
+ }
+
+ _scriptMoreFlag = 1;
+ _endStr = true;
+ _wait = 0;
+
+ return RET_SUCCESS;
+}
+
+void ScalpelTalk::pushSequenceEntry(Object *obj) {
+ Scene &scene = *_vm->_scene;
+ SequenceEntry seqEntry;
+ seqEntry._objNum = scene._bgShapes.indexOf(*obj);
+
+ if (seqEntry._objNum != -1) {
+ for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
+ seqEntry._sequences.push_back(obj->_sequences[idx]);
+
+ seqEntry._frameNumber = obj->_frameNumber;
+ seqEntry._seqTo = obj->_seqTo;
+ }
+
+ _sequenceStack.push(seqEntry);
+ if (_scriptStack.size() >= 5)
+ error("script stack overflow");
+}
+
+void ScalpelTalk::pullSequence(int slot) {
+ Scene &scene = *_vm->_scene;
+
+ if (_sequenceStack.empty())
+ return;
+
+ SequenceEntry seq = _sequenceStack.pop();
+ if (seq._objNum != -1) {
+ Object &obj = scene._bgShapes[seq._objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to restore too few frames");
+ } else {
+ for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
+ obj._sequences[idx] = seq._sequences[idx];
+
+ obj._frameNumber = seq._frameNumber;
+ obj._seqTo = seq._seqTo;
+ }
+ }
+}
+
+void ScalpelTalk::clearSequences() {
+ _sequenceStack.clear();
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_talk.h b/engines/sherlock/scalpel/scalpel_talk.h
new file mode 100644
index 0000000000..31f78cbf85
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_talk.h
@@ -0,0 +1,143 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_TALK_H
+#define SHERLOCK_SCALPEL_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"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+class ScalpelTalk : public Talk {
+private:
+ Common::Stack<SequenceEntry> _sequenceStack;
+
+ OpcodeReturn cmdSwitchSpeaker(const byte *&str);
+ OpcodeReturn cmdAssignPortraitLocation(const byte *&str);
+ OpcodeReturn cmdGotoScene(const byte *&str);
+ OpcodeReturn cmdCallTalkFile(const byte *&str);
+ OpcodeReturn cmdClearInfoLine(const byte *&str);
+ OpcodeReturn cmdClearWindow(const byte *&str);
+ OpcodeReturn cmdDisplayInfoLine(const byte *&str);
+ OpcodeReturn cmdElse(const byte *&str);
+ OpcodeReturn cmdIf(const byte *&str);
+ OpcodeReturn cmdMoveMouse(const byte *&str);
+ OpcodeReturn cmdPlayPrologue(const byte *&str);
+ OpcodeReturn cmdRemovePortrait(const byte *&str);
+ OpcodeReturn cmdSfxCommand(const byte *&str);
+ OpcodeReturn cmdSummonWindow(const byte *&str);
+ OpcodeReturn cmdWalkToCoords(const byte *&str);
+protected:
+ /**
+ * Display the talk interface window
+ */
+ virtual void talkInterface(const byte *&str);
+
+ /**
+ * Pause when displaying a talk dialog on-screen
+ */
+ virtual void talkWait(const byte *&str);
+
+ /**
+ * Called when the active speaker is switched
+ */
+ virtual void switchSpeaker();
+
+ /**
+ * Called when a character being spoken to has no talk options to display
+ */
+ virtual void nothingToSay();
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk();
+public:
+ ScalpelTalk(SherlockEngine *vm);
+ virtual ~ScalpelTalk() {}
+
+ /**
+ * 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.
+ */
+ virtual void talkTo(const Common::String filename);
+
+ /**
+ * Draws the interface for conversation display
+ */
+ void drawInterface();
+
+ /**
+ * Display a list of statements in a window at the bottom of the screen that the
+ * player can select from.
+ */
+ bool displayTalk(bool slamIt);
+
+ /**
+ * Prints a single conversation option in the interface window
+ */
+ int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt);
+
+ /**
+ * Trigger to play a 3DO talk dialog movie
+ */
+ void talk3DOMovieTrigger(int subIndex);
+
+ /**
+ * Push the details of a passed object onto the saved sequences stack
+ */
+ virtual void pushSequenceEntry(Object *obj);
+
+ /**
+ * Pulls a background object sequence from the sequence stack and restore's the
+ * object's sequence
+ */
+ virtual void pullSequence(int slot = -1);
+
+ /**
+ * Returns true if the script stack is empty
+ */
+ virtual bool isSequencesEmpty() const { return _scriptStack.empty(); }
+
+ /**
+ * Clears the stack of pending object sequences associated with speakers in the scene
+ */
+ virtual void clearSequences();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
index 295cddb3c9..a67d464a11 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.cpp
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -21,8 +21,16 @@
*/
#include "sherlock/scalpel/scalpel_user_interface.h"
-#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_inventory.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_talk.h"
#include "sherlock/scalpel/settings.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/sherlock.h"
namespace Sherlock {
@@ -60,33 +68,22 @@ const char COMMANDS[13] = "LMTPOCIUGJFS";
const char INVENTORY_COMMANDS[9] = { "ELUG-+,." };
const char *const PRESS_KEY_FOR_MORE = "Press any Key for More.";
const char *const PRESS_KEY_TO_CONTINUE = "Press any Key to Continue.";
-
-const char *const MOPEN[] = {
- "This cannot be opened", "It is already open", "It is locked", "Wait for Watson", " ", "."
-};
-const char *const MCLOSE[] = {
- "This cannot be closed", "It is already closed", "The safe door is in the way"
-};
-const char *const MMOVE[] = {
- "This cannot be moved", "It is bolted to the floor", "It is too heavy", "The other crate is in the way"
-};
-const char *const MPICK[] = {
- "Nothing of interest here", "It is bolted down", "It is too big to carry", "It is too heavy",
- "I think a girl would be more your type", "Those flowers belong to Penny", "She's far too young for you!",
- "I think a girl would be more your type!", "Government property for official use only"
-};
-const char *const MUSE[] = {
- "You can't do that", "It had no effect", "You can't reach it", "OK, the door looks bigger! Happy?",
- "Doors don't smoke"
-};
+const int UI_OFFSET_3DO = 16; // (320 - 288) / 2
/*----------------------------------------------------------------*/
ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) {
if (_vm->_interactiveFl) {
- _controls = new ImageFile("menu.all");
- _controlPanel = new ImageFile("controls.vgs");
+ if (!IS_3DO) {
+ // PC
+ _controls = new ImageFile("menu.all");
+ _controlPanel = new ImageFile("controls.vgs");
+ } else {
+ // 3DO
+ _controls = new ImageFile3DO("menu.all", kImageFile3DOType_RoomFormat);
+ _controlPanel = new ImageFile3DO("controls.vgs", kImageFile3DOType_RoomFormat);
+ }
} else {
_controls = nullptr;
_controlPanel = nullptr;
@@ -94,8 +91,6 @@ ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm
_keyPress = '\0';
_lookHelp = 0;
- _bgFound = 0;
- _oldBgFound = -1;
_help = _oldHelp = 0;
_key = _oldKey = '\0';
_temp = _oldTemp = 0;
@@ -113,18 +108,20 @@ ScalpelUserInterface::~ScalpelUserInterface() {
}
void ScalpelUserInterface::reset() {
- _oldKey = -1;
+ UserInterface::reset();
_help = _oldHelp = -1;
- _oldTemp = _temp = -1;
}
void ScalpelUserInterface::drawInterface(int bufferNum) {
Screen &screen = *_vm->_screen;
+ const ImageFrame &src = (*_controlPanel)[0];
+ int16 x = (!IS_3DO) ? 0 : UI_OFFSET_3DO;
+
if (bufferNum & 1)
- screen._backBuffer1.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y));
+ screen._backBuffer1.transBlitFrom(src, Common::Point(x, CONTROLS_Y));
if (bufferNum & 2)
- screen._backBuffer2.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y));
+ screen._backBuffer2.transBlitFrom(src, Common::Point(x, CONTROLS_Y));
if (bufferNum == 3)
screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK);
}
@@ -141,7 +138,7 @@ void ScalpelUserInterface::handleInput() {
whileMenuCounter();
Common::Point pt = events.mousePos();
- _bgFound = scene.findBgShape(Common::Rect(pt.x, pt.y, pt.x + 1, pt.y + 1));
+ _bgFound = scene.findBgShape(pt);
_keyPress = '\0';
// Check kbd and set the mouse released flag if Enter or space is pressed.
@@ -279,7 +276,7 @@ void ScalpelUserInterface::handleInput() {
}
if (events._released && personFound)
- talk.talk(_bgFound);
+ talk.initTalk(_bgFound);
else if (personFound)
lookScreen(pt);
else if (_bgFound < 1000)
@@ -316,9 +313,9 @@ void ScalpelUserInterface::handleInput() {
// Mouse clicked in script zone
events._pressed = events._released = false;
} else {
- people._walkDest = pt;
people._allowWalkAbort = false;
- people.goAllTheWay();
+ people[HOLMES]._walkDest = pt;
+ people[HOLMES].goAllTheWay();
}
if (_oldKey != -1) {
@@ -385,6 +382,7 @@ void ScalpelUserInterface::handleInput() {
void ScalpelUserInterface::depressButton(int num) {
Screen &screen = *_vm->_screen;
Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
ImageFrame &frame = (*_controls)[num];
screen._backBuffer1.transBlitFrom(frame, pt);
@@ -392,10 +390,17 @@ void ScalpelUserInterface::depressButton(int num) {
}
void ScalpelUserInterface::restoreButton(int num) {
+ Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
+
Graphics::Surface &frame = (*_controls)[num]._frame;
+ // Reset the cursor
+ events.setCursor(ARROW);
+
+ // Restore the UI on the back buffer
screen._backBuffer1.blitFrom(screen._backBuffer2, pt,
Common::Rect(pt.x, pt.y, pt.x + 90, pt.y + 19));
screen.slamArea(pt.x, pt.y, pt.x + frame.w, pt.y + frame.h);
@@ -441,6 +446,7 @@ void ScalpelUserInterface::toggleButton(int num) {
ImageFrame &frame = (*_controls)[num];
Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
screen._backBuffer1.transBlitFrom(frame, pt);
screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height);
}
@@ -495,7 +501,7 @@ void ScalpelUserInterface::examine() {
scene.startCAnim(_cNum, canimSpeed);
} else if (obj._lookPosition.y != 0) {
// Need to walk to the object to be examined
- people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100), obj._lookFacing);
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
}
if (!talk._talkToAbort) {
@@ -536,7 +542,7 @@ void ScalpelUserInterface::lookScreen(const Common::Point &pt) {
Common::String tempStr;
// Don't display anything for right button command
- if ((events._rightPressed || events._rightPressed) && !events._pressed)
+ if ((events._rightPressed || events._rightReleased) && !events._pressed)
return;
if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) {
@@ -666,9 +672,9 @@ void ScalpelUserInterface::lookInv() {
void ScalpelUserInterface::doEnvControl() {
Events &events = *_vm->_events;
- SaveManager &saves = *_vm->_saves;
+ ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves;
Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Talk &talk = *_vm->_talk;
Common::Point mousePos = events.mousePos();
static const char ENV_COMMANDS[7] = "ELSUDQ";
@@ -968,9 +974,10 @@ void ScalpelUserInterface::doEnvControl() {
void ScalpelUserInterface::doInvControl() {
Events &events = *_vm->_events;
- Inventory &inv = *_vm->_inventory;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Talk &talk = *_vm->_talk;
int colors[8];
Common::Point mousePos = events.mousePos();
@@ -993,15 +1000,20 @@ void ScalpelUserInterface::doInvControl() {
if (events._pressed || events._released) {
events.clearKeyboard();
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
if (found != -1)
// If a slot highlighted, set its color
colors[found] = COMMAND_HIGHLIGHTED;
- screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, "Exit");
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, fixedText_Exit);
if (found >= 0 && found <= 3) {
- screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, "Look");
- screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, "Use");
- screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, "Give");
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, fixedText_Give);
inv._invMode = (InvMode)found;
_selector = -1;
}
@@ -1063,7 +1075,7 @@ void ScalpelUserInterface::doInvControl() {
}
if (_selector != -1)
- inv.highlight(_selector, 235);
+ inv.highlight(_selector, BUTTON_BACKGROUND);
_oldSelector = _selector;
}
@@ -1177,10 +1189,10 @@ void ScalpelUserInterface::doInvControl() {
bool giveFl = (tempMode >= INVMODE_GIVE);
if (_selector >= 0)
// Use/Give inv object with scene object
- checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, MUSE, _find, giveFl);
+ checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, kFixedTextAction_Use, _find, giveFl);
else
// Now inv object has been highlighted
- checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", MUSE, _find, giveFl);
+ checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", kFixedTextAction_Use, _find, giveFl);
_selector = _oldSelector = -1;
}
@@ -1191,7 +1203,7 @@ void ScalpelUserInterface::doInvControl() {
void ScalpelUserInterface::doLookControl() {
Events &events = *_vm->_events;
- Inventory &inv = *_vm->_inventory;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
Screen &screen = *_vm->_screen;
_key = _oldKey = -1;
@@ -1206,6 +1218,7 @@ void ScalpelUserInterface::doLookControl() {
} else if (!_lookHelp) {
// Need to close the window and depress the Look button
Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
+ offsetButton3DO(pt, 0);
screen._backBuffer2.blitFrom((*_controls)[0], pt);
banishWindow(true);
@@ -1252,8 +1265,8 @@ void ScalpelUserInterface::doLookControl() {
void ScalpelUserInterface::doMainControl() {
Events &events = *_vm->_events;
- Inventory &inv = *_vm->_inventory;
- SaveManager &saves = *_vm->_saves;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves;
Common::Point pt = events.mousePos();
if ((events._pressed || events._released) && pt.y > CONTROLS_Y) {
@@ -1264,6 +1277,10 @@ void ScalpelUserInterface::doMainControl() {
for (_temp = 0; (_temp < 12) && (_key == -1); ++_temp) {
Common::Rect r(MENU_POINTS[_temp][0], MENU_POINTS[_temp][1],
MENU_POINTS[_temp][2], MENU_POINTS[_temp][3]);
+ if (IS_3DO && _temp >= 0 && _temp <= 2) {
+ r.left += UI_OFFSET_3DO - 1;
+ r.right += UI_OFFSET_3DO - 1;
+ }
if (r.contains(pt))
_key = COMMANDS[_temp];
}
@@ -1403,7 +1420,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) {
switch (allowed) {
case ALLOW_OPEN:
- checkAction(obj._aOpen, MOPEN, _temp);
+ checkAction(obj._aOpen, _temp, kFixedTextAction_Open);
if (_menuMode != TALK_MODE && !talk._talkToAbort) {
_menuMode = STD_MODE;
restoreButton(OPEN_MODE - 1);
@@ -1412,7 +1429,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) {
break;
case ALLOW_CLOSE:
- checkAction(obj._aClose, MCLOSE, _temp);
+ checkAction(obj._aClose, _temp, kFixedTextAction_Close);
if (_menuMode != TALK_MODE && !talk._talkToAbort) {
_menuMode = STD_MODE;
restoreButton(CLOSE_MODE - 1);
@@ -1421,7 +1438,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) {
break;
case ALLOW_MOVE:
- checkAction(obj._aMove, MMOVE, _temp);
+ checkAction(obj._aMove, _temp, kFixedTextAction_Move);
if (_menuMode != TALK_MODE && !talk._talkToAbort) {
_menuMode = STD_MODE;
restoreButton(MOVE_MODE - 1);
@@ -1448,7 +1465,7 @@ void ScalpelUserInterface::doPickControl() {
// Don't allow characters to be picked up
if (_bgFound < 1000) {
- scene._bgShapes[_bgFound].pickUpObject(MPICK);
+ scene._bgShapes[_bgFound].pickUpObject(kFixedTextAction_Pick);
if (!talk._talkToAbort && _menuMode != TALK_MODE) {
_key = _oldKey = -1;
@@ -1462,34 +1479,39 @@ void ScalpelUserInterface::doPickControl() {
void ScalpelUserInterface::doTalkControl() {
Events &events = *_vm->_events;
- Journal &journal = *_vm->_journal;
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal;
+ ScalpelPeople &people = *(ScalpelPeople *)_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Sound &sound = *_vm->_sound;
- Talk &talk = *_vm->_talk;
+ ScalpelTalk &talk = *(ScalpelTalk *)_vm->_talk;
Common::Point mousePos = events.mousePos();
_key = _oldKey = -1;
_keyboardInput = false;
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+
if (events._pressed || events._released) {
events.clearKeyboard();
// Handle button printing
if (mousePos.x > 99 && mousePos.x < 138 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && !_endKeyActive)
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Exit");
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Exit);
else if (_endKeyActive)
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "Exit");
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit);
if (mousePos.x > 140 && mousePos.x < 170 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkUp)
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up);
else if (talk._moreTalkUp)
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "Up");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
if (mousePos.x > 181&& mousePos.x < 220 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkDown)
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down);
else if (talk._moreTalkDown)
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
bool found = false;
for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) {
@@ -1574,9 +1596,9 @@ void ScalpelUserInterface::doTalkControl() {
screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2));
} else if (_selector != -1) {
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit");
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, "Up");
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, "Down");
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down);
// If the reply is new, add it to the journal
if (!talk._talkHistory[talk._converseNum][_selector]) {
@@ -1615,15 +1637,18 @@ void ScalpelUserInterface::doTalkControl() {
people.setTalking(0);
if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) {
- sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY);
+ sound.playSpeech(talk._statements[_selector]._voiceFile);
// Set voices as an indicator for waiting
sound._voices = 2;
- sound._speechOn = *sound._soundIsOn;
} else {
- sound._speechOn = false;
+ sound._speechPlaying = false;
}
+ if (IS_3DO)
+ // Trigger to play 3DO movie
+ talk.talk3DOMovieTrigger(0);
+
talk.waitForMore(talk._statements[_selector]._statement.size());
if (talk._talkToAbort)
return;
@@ -1670,9 +1695,9 @@ void ScalpelUserInterface::doTalkControl() {
!talk._statements[select]._statement.hasPrefix("^")) {
// Not a reply first file, so display the new selections
if (_endKeyActive)
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "Exit");
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit);
else
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit");
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit);
talk.displayTalk(true);
events.setCursor(ARROW);
@@ -1714,7 +1739,7 @@ void ScalpelUserInterface::doTalkControl() {
void ScalpelUserInterface::journalControl() {
Events &events = *_vm->_events;
- Journal &journal = *_vm->_journal;
+ ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
bool doneFlag = false;
@@ -1762,8 +1787,8 @@ void ScalpelUserInterface::journalControl() {
void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool firstTime) {
Events &events = *_vm->_events;
- Inventory &inv = *_vm->_inventory;
- Screen &screen = *_vm->_screen;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Talk &talk = *_vm->_talk;
if (str.hasPrefix("_")) {
@@ -1787,6 +1812,7 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first
// menu area, and draw the controls onto it
Surface tempSurface((*_controls)[0]._frame.w, (*_controls)[0]._frame.h);
Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
+ offsetButton3DO(pt, 0);
tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0),
Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h()));
@@ -1975,8 +2001,7 @@ void ScalpelUserInterface::summonWindow(bool slideUp, int height) {
Screen &screen = *_vm->_screen;
// Extract the window that's been drawn on the back buffer
- Surface tempSurface(SHERLOCK_SCREEN_WIDTH,
- (SHERLOCK_SCREEN_HEIGHT - height));
+ Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - height);
Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r);
@@ -2055,13 +2080,14 @@ void ScalpelUserInterface::banishWindow(bool slideUp) {
}
void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::String &invName,
- const char *const messages[], int objNum, bool giveMode) {
+ FixedTextActionId fixedTextActionId, int objNum, bool giveMode) {
Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
Inventory &inv = *_vm->_inventory;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
- bool printed = messages == nullptr;
+ bool printed = fixedTextActionId == kFixedTextAction_Invalid;
if (objNum >= 1000) {
// Holmes was specified, so do nothing
@@ -2113,7 +2139,7 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
if (!talk._talkToAbort) {
Object &obj = scene._bgShapes[objNum];
for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
- if (obj.checkNameForCodes(action._names[idx], messages)) {
+ if (obj.checkNameForCodes(action._names[idx], fixedTextActionId)) {
if (!talk._talkToAbort)
printed = true;
}
@@ -2134,10 +2160,11 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
if (giveMode) {
screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you.");
- } else if (messages == nullptr) {
+ } else if (fixedTextActionId == kFixedTextAction_Invalid) {
screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that.");
} else {
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[0]);
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0);
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str());
}
_infoFlag = true;
@@ -2147,136 +2174,15 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
events.setCursor(ARROW);
}
-void ScalpelUserInterface::checkAction(ActionType &action, const char *const messages[], int objNum) {
- Events &events = *_vm->_events;
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
- Common::Point pt(-1, -1);
-
- if (objNum >= 1000)
- // Ignore actions done on characters
- return;
-
- if (!action._cAnimSpeed) {
- // Invalid action, to print error message
- _infoFlag = true;
- clearInfo();
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[action._cAnimNum]);
- _infoFlag = true;
-
- // Set how long to show the message
- _menuCounter = 30;
- } else {
- Object &obj = scene._bgShapes[objNum];
-
- int cAnimNum;
- if (action._cAnimNum == 0)
- // Really a 10
- cAnimNum = 9;
- else
- cAnimNum = action._cAnimNum - 1;
-
- int dir = -1;
- if (action._cAnimNum != 99) {
- CAnim &anim = scene._cAnim[cAnimNum];
-
- if (action._cAnimNum != 99) {
- if (action._cAnimSpeed & REVERSE_DIRECTION) {
- pt = anim._teleportPos;
- dir = anim._teleportDir;
- } else {
- pt = anim._goto;
- dir = anim._gotoDir;
- }
- }
- } else {
- pt = Common::Point(-1, -1);
- dir = -1;
- }
-
- // Has a value, so do action
- // Show wait cursor whilst walking to object and doing action
- events.setCursor(WAIT);
- bool printed = false;
-
- for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
- if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
- && toupper(action._names[nameIdx][1]) == 'W') {
- if (obj.checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), messages)) {
- if (!talk._talkToAbort)
- printed = true;
- }
- }
- }
-
- bool doCAnim = true;
- for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
- if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) {
- char ch = toupper(action._names[nameIdx][1]);
-
- if (ch == 'T' || ch == 'B') {
- printed = true;
- if (pt.x != -1)
- // Holmes needs to walk to object before the action is done
- people.walkToCoords(pt, dir);
-
- if (!talk._talkToAbort) {
- // Ensure Holmes is on the exact intended location
- people[AL]._position = pt;
- people[AL]._sequenceNumber = dir;
- people.gotoStand(people[AL]);
-
- talk.talkTo(action._names[nameIdx].c_str() + 2);
- if (ch == 'T')
- doCAnim = false;
- }
- }
- }
- }
-
- if (doCAnim && !talk._talkToAbort) {
- if (pt.x != -1)
- // Holmes needs to walk to object before the action is done
- people.walkToCoords(pt, dir);
- }
-
- for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
- if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
- && toupper(action._names[nameIdx][1]) == 'F') {
- if (obj.checkNameForCodes(action._names[nameIdx].c_str() + 2, messages)) {
- if (!talk._talkToAbort)
- printed = true;
- }
- }
- }
-
- if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99)
- scene.startCAnim(cAnimNum, action._cAnimSpeed);
-
- if (!talk._talkToAbort) {
- for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) {
- if (obj.checkNameForCodes(action._names[nameIdx], messages)) {
- if (!talk._talkToAbort)
- printed = true;
- }
- }
-
- // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set
- if (scene._goToScene != 1 && !printed && !talk._talkToAbort) {
- _infoFlag = true;
- clearInfo();
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done...");
-
- // Set how long to show the message
- _menuCounter = 30;
- }
- }
+void ScalpelUserInterface::offsetButton3DO(Common::Point &pt, int num) {
+ if (IS_3DO) {
+ if (num >= 0 && num <= 2)
+ pt.x += 15;
+ else if (num >= 6 && num <= 8)
+ pt.x -= 4;
+ else if (num >= 9 && num <= 11)
+ pt.x -= 8;
}
-
- // Reset cursor back to arrow
- events.setCursor(ARROW);
}
} // End of namespace Scalpel
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h
index 552bcf00c5..9a55189a66 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.h
+++ b/engines/sherlock/scalpel/scalpel_user_interface.h
@@ -49,9 +49,8 @@ class ScalpelUserInterface: public UserInterface {
private:
char _keyPress;
int _lookHelp;
- int _bgFound, _oldBgFound;
int _help, _oldHelp;
- char _key, _oldKey;
+ int _key, _oldKey;
int _temp, _oldTemp;
int _oldLook;
bool _keyboardInput;
@@ -85,7 +84,7 @@ private:
void lookScreen(const Common::Point &pt);
/**
- * Gets the item in the inventory the mouse is on and display's it's description
+ * Gets the item in the inventory the mouse is on and display's its description
*/
void lookInv();
@@ -136,15 +135,10 @@ private:
/**
* Checks to see whether a USE action is valid on the given object
*/
- void checkUseAction(const UseType *use, const Common::String &invName, const char *const messages[],
+ void checkUseAction(const UseType *use, const Common::String &invName, FixedTextActionId fixedTextActionId,
int objNum, bool giveMode);
/**
- * Called for OPEN, CLOSE, and MOVE actions are being done
- */
- void checkAction(ActionType &action, const char *const messages[], int objNum);
-
- /**
* Print the previously selected object's decription
*/
void printObjectDesc(const Common::String &str, bool firstTime);
@@ -172,6 +166,8 @@ public:
* of the highlighted object
*/
void examine();
+
+ void offsetButton3DO(Common::Point &pt, int num);
public:
/**
* Resets the user interface
diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp
index aa8033d25e..f6769a4b99 100644
--- a/engines/sherlock/scalpel/settings.cpp
+++ b/engines/sherlock/scalpel/settings.cpp
@@ -22,7 +22,9 @@
#include "sherlock/sherlock.h"
#include "sherlock/scalpel/settings.h"
+#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
namespace Sherlock {
@@ -56,7 +58,7 @@ static const char *const SETUP_NAMES[12] = {
void Settings::drawInteface(bool flag) {
People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Sound &sound = *_vm->_sound;
Music &music = *_vm->_music;
UserInterface &ui = *_vm->_ui;
@@ -138,7 +140,7 @@ void Settings::drawInteface(bool flag) {
int Settings::drawButtons(const Common::Point &pt, int _key) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Music &music = *_vm->_music;
Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp
index 239543b017..64539b941a 100644
--- a/engines/sherlock/scalpel/tsage/logo.cpp
+++ b/engines/sherlock/scalpel/tsage/logo.cpp
@@ -156,11 +156,13 @@ ScalpelEngine *Object::_vm;
Object::Object() {
_vm = nullptr;
- _isAnimating = false;
+ _isAnimating = _finished = false;
_frame = 0;
_numFrames = 0;
_frameChange = 0;
_angle = _changeCtr = 0;
+ _walkStartFrame = 0;
+ _majorDiff = _minorDiff = 0;
}
void Object::setVisage(int visage, int strip) {
@@ -221,8 +223,15 @@ void Object::update() {
Screen &screen = *_vm->_screen;
if (_visage.isLoaded()) {
- if (isMoving())
- move();
+ if (isMoving()) {
+ uint32 currTime = _vm->_events->getFrameCounter();
+ if (_walkStartFrame <= currTime) {
+ int moveRate = 10;
+ int frameInc = 60 / moveRate;
+ _walkStartFrame = currTime + frameInc;
+ move();
+ }
+ }
if (_isAnimating) {
if (_frame < _visage.getFrameCount())
@@ -304,7 +313,6 @@ bool Object::isMoving() const {
void Object::move() {
Common::Point currPos = _position;
Common::Point moveDiff(5, 3);
- int yDiff = 0;
int percent = 100;
if (dontMove())
@@ -335,9 +343,7 @@ void Object::move() {
currPos.y += ySign;
_majorDiff -= ABS(xAmount);
-
- }
- else {
+ } else {
int yAmount = _moveSign.y * moveDiff.y * percent / 100;
if (!yAmount)
yAmount = _moveSign.y;
@@ -387,15 +393,16 @@ bool Logo::show(ScalpelEngine *vm) {
while (!logo->finished()) {
logo->nextFrame();
- events.wait(2);
- events.setButtonState();
-
// Erase areas from previous frame, and update and re-draw objects
for (int idx = 0; idx < 4; ++idx)
logo->_objects[idx].erase();
for (int idx = 0; idx < 4; ++idx)
logo->_objects[idx].update();
+ events.delay(10);
+ events.setButtonState();
+ ++logo->_frameCounter;
+
interrupted = vm->shouldQuit() || events.kbHit() || events._pressed;
if (interrupted) {
// Keyboard or mouse button pressed, so break out of logo display
@@ -412,8 +419,21 @@ Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") {
Object::_vm = vm;
Visage::_tLib = &_lib;
+ _finished = false;
+
// Initialize counter
_counter = 0;
+
+ // Initialize wait frame counters
+ _waitFrames = 0;
+ _waitStartFrame = 0;
+
+ // Initialize animation counters
+ _animateObject = 0;
+ _animateStartFrame = 0;
+ _animateFrameDelay = 0;
+ _animateFrames = NULL;
+ _animateFrame = 0;
// Save a copy of the original palette
_vm->_screen->getPalette(_originalPalette);
@@ -437,12 +457,55 @@ Logo::~Logo() {
}
bool Logo::finished() const {
- return _counter >= 442;
+ return _finished;
}
+const AnimationFrame handFrames[] = {
+ { 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 },
+ { 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 },
+ { 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 },
+ { 11, 158, 121 }, { 0, 0, 0 }
+};
+
+const AnimationFrame companyFrames[] = {
+ { 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 },
+ { 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 },
+ { 0, 0, 0 }
+};
+
void Logo::nextFrame() {
Screen &screen = *_vm->_screen;
+ if (_waitFrames) {
+ uint32 currFrame = _frameCounter;
+ if (currFrame - _waitStartFrame < _waitFrames) {
+ return;
+ }
+ _waitStartFrame = 0;
+ _waitFrames = 0;
+ }
+
+ if (_animateFrames) {
+ uint32 currFrame = _frameCounter;
+ if (currFrame > _animateStartFrame + _animateFrameDelay) {
+ AnimationFrame animationFrame = _animateFrames[_animateFrame];
+ if (animationFrame.frame) {
+ _objects[_animateObject]._frame = animationFrame.frame;
+ _objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y);
+ _animateStartFrame += _animateFrameDelay;
+ _animateFrame++;
+ } else {
+ _animateObject = 0;
+ _animateFrameDelay = 0;
+ _animateFrames = NULL;
+ _animateStartFrame = 0;
+ _animateFrame = 0;
+ }
+ }
+ if (_animateFrames)
+ return;
+ }
+
switch (_counter++) {
case 0:
// Load the background and fade it in
@@ -480,160 +543,87 @@ void Logo::nextFrame() {
// Fade out the background but keep the shapes visible
fade(_palette2);
screen._backBuffer1.clear();
- screen.clear();
}
+ waitFrames(10);
break;
- case 13: {
+ case 4:
// Load the new palette
byte palette[PALETTE_SIZE];
Common::copy(&_palette2[0], &_palette2[PALETTE_SIZE], &palette[0]);
_lib.getPalette(palette, 12);
+ screen.clear();
screen.setPalette(palette);
- break;
- }
- case 14:
+ // Morph into the EA logo
_objects[0].setVisage(12, 1);
_objects[0]._frame = 1;
_objects[0]._numFrames = 7;
+ _objects[0].setAnimMode(true);
_objects[0]._position = Common::Point(170, 142);
_objects[0].setDestination(Common::Point(158, 71));
break;
- case 15:
+ case 5:
// Wait until the logo has expanded upwards to form EA logo
if (_objects[0].isMoving())
--_counter;
break;
- case 16:
+ case 6:
fade(_palette3, 40);
break;
- case 20:
+ case 7:
// Show the 'Electronic Arts' company name
_objects[1].setVisage(14, 1);
_objects[1]._frame = 1;
_objects[1]._position = Common::Point(152, 98);
+ waitFrames(120);
break;
- case 140:
+ case 8:
// Start sequence of positioning and size hand cursor in an arc
_objects[2].setVisage(18, 1);
- _objects[2]._frame = 1;
- _objects[2]._position = Common::Point(33, 91);
- break;
-
- case 145:
- _objects[2]._frame = 2;
- _objects[2]._position = Common::Point(44, 124);
- break;
-
- case 150:
- _objects[2]._frame = 3;
- _objects[2]._position = Common::Point(64, 153);
- break;
-
- case 155:
- _objects[2]._frame = 4;
- _objects[2]._position = Common::Point(87, 174);
- break;
-
- case 160:
- _objects[2]._frame = 5;
- _objects[2]._position = Common::Point(114, 191);
- break;
-
- case 165:
- _objects[2]._frame = 6;
- _objects[2]._position = Common::Point(125, 184);
- break;
-
- case 170:
- _objects[2]._frame = 7;
- _objects[2]._position = Common::Point(154, 187);
- break;
-
- case 175:
- _objects[2]._frame = 8;
- _objects[2]._position = Common::Point(181, 182);
- break;
-
- case 180:
- _objects[2]._frame = 9;
- _objects[2]._position = Common::Point(191, 167);
- break;
-
- case 185:
- _objects[2]._frame = 10;
- _objects[2]._position = Common::Point(190, 150);
- break;
-
- case 190:
- _objects[2]._frame = 11;
- _objects[2]._position = Common::Point(182, 139);
- break;
-
- case 195:
- _objects[2]._frame = 11;
- _objects[2]._position = Common::Point(170, 130);
- break;
-
- case 200:
- _objects[2]._frame = 11;
- _objects[2]._position = Common::Point(158, 121);
+ startAnimation(2, 5, &handFrames[0]);
break;
- case 205:
+ case 9:
// Show a highlighting of the company name
+ _objects[1].remove();
+ _objects[2].erase();
_objects[2].remove();
_objects[3].setVisage(19, 1);
- _objects[3]._position = Common::Point(155, 94);
+ startAnimation(3, 8, &companyFrames[0]);
break;
- case 213:
- _objects[3]._frame = 2;
- _objects[3]._position = Common::Point(155, 94);
+ case 10:
+ waitFrames(180);
break;
- case 221:
- _objects[1].remove();
+ case 11:
+ _finished = true;
break;
- case 222:
- _objects[3]._frame = 3;
- _objects[3]._position = Common::Point(155, 94);
- break;
-
- case 230:
- _objects[3]._frame = 4;
- _objects[3]._position = Common::Point(155, 94);
- break;
-
- case 238:
- _objects[3]._frame = 5;
- _objects[3]._position = Common::Point(155, 94);
- break;
-
- case 246:
- _objects[3]._frame = 6;
- _objects[3]._position = Common::Point(155, 94);
+ default:
break;
+ }
+}
- case 254:
- _objects[3]._frame = 7;
- _objects[3]._position = Common::Point(155, 94);
- break;
+void Logo::waitFrames(uint frames) {
+ _waitFrames = frames;
+ _waitStartFrame = _frameCounter;
+}
- case 262:
- _objects[3]._frame = 8;
- _objects[3]._position = Common::Point(155, 94);
- break;
+void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) {
+ _animateObject = object;
+ _animateFrameDelay = frameDelay;
+ _animateFrames = frames;
+ _animateStartFrame = _frameCounter;
+ _animateFrame = 1;
- default:
- break;
- }
+ _objects[object]._frame = frames[0].frame;
+ _objects[object]._position = Common::Point(frames[0].x, frames[0].y);
}
void Logo::loadBackground() {
diff --git a/engines/sherlock/scalpel/tsage/logo.h b/engines/sherlock/scalpel/tsage/logo.h
index 68b7353a15..8e47ea42a1 100644
--- a/engines/sherlock/scalpel/tsage/logo.h
+++ b/engines/sherlock/scalpel/tsage/logo.h
@@ -185,16 +185,30 @@ public:
void remove() { _visage.clear(); }
};
+struct AnimationFrame {
+ int frame;
+ int x;
+ int y;
+};
+
class Logo {
private:
ScalpelEngine *_vm;
TLib _lib;
- int _counter;
+ int _counter, _frameCounter;
+ bool _finished;
byte _originalPalette[PALETTE_SIZE];
byte _palette1[PALETTE_SIZE];
byte _palette2[PALETTE_SIZE];
byte _palette3[PALETTE_SIZE];
Object _objects[4];
+ uint _waitFrames;
+ uint32 _waitStartFrame;
+ int _animateObject;
+ uint32 _animateStartFrame;
+ uint _animateFrameDelay;
+ const AnimationFrame *_animateFrames;
+ uint _animateFrame;
Logo(ScalpelEngine *vm);
~Logo();
@@ -204,6 +218,19 @@ private:
bool finished() const;
/**
+ * Wait for a number of frames. Note that the frame count in _events is
+ * not the same as the number of calls to nextFrame().
+ */
+ void waitFrames(uint frames);
+
+ /**
+ * Start an animation sequence. Used for sequences that are described
+ * one frame at a time because they do unusual things, or run at
+ * unusual rates.
+ */
+ void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames);
+
+ /**
* Load the background for the scene
*/
void loadBackground();
diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp
index 76fb7ae3a7..4e40032df9 100644
--- a/engines/sherlock/scene.cpp
+++ b/engines/sherlock/scene.cpp
@@ -22,21 +22,16 @@
#include "sherlock/scene.h"
#include "sherlock/sherlock.h"
+#include "sherlock/screen.h"
#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_scene.h"
-#include "sherlock/screen.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
namespace Sherlock {
-static const int FS_TRANS[8] = {
- STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN,
- STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT
-};
-
-/*----------------------------------------------------------------*/
-
BgFileHeader::BgFileHeader() {
_numStructs = -1;
_numImages = -1;
@@ -82,6 +77,16 @@ void BgFileHeaderInfo::load(Common::SeekableReadStream &s) {
_filename = Common::String(buffer);
}
+void BgFileHeaderInfo::load3DO(Common::SeekableReadStream &s) {
+ _filesize = s.readUint32BE();
+ _maxFrames = s.readByte();
+
+ char buffer[9];
+ s.read(buffer, 9);
+ _filename = Common::String(buffer);
+ s.skip(2); // only on 3DO!
+}
+
/*----------------------------------------------------------------*/
void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
@@ -102,14 +107,31 @@ void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
if (!isRoseTattoo)
_allow = s.readSint16LE();
- _people.x = s.readSint16LE();
- _people.y = s.readSint16LE();
- _peopleDir = s.readUint16LE();
+ _newPosition.x = s.readSint16LE();
+ _newPosition.y = s.readSint16LE();
+ _newPosition._facing = s.readUint16LE();
if (isRoseTattoo)
_allow = s.readSint16LE();
}
+void Exit::load3DO(Common::SeekableReadStream &s) {
+ left = s.readSint16BE();
+ top = s.readSint16BE();
+ setWidth(s.readUint16BE());
+ setHeight(s.readUint16BE());
+
+ _image = 0;
+ _scene = s.readSint16BE();
+
+ _allow = s.readSint16BE();
+
+ _newPosition.x = s.readSint16BE();
+ _newPosition.y = s.readSint16BE();
+ _newPosition._facing = s.readUint16BE();
+ s.skip(2); // Filler
+}
+
/*----------------------------------------------------------------*/
void SceneEntry::load(Common::SeekableReadStream &s) {
@@ -119,6 +141,13 @@ void SceneEntry::load(Common::SeekableReadStream &s) {
_allow = s.readByte();
}
+void SceneEntry::load3DO(Common::SeekableReadStream &s) {
+ _startPosition.x = s.readSint16BE();
+ _startPosition.y = s.readSint16BE();
+ _startDir = s.readByte();
+ _allow = s.readByte();
+}
+
void SceneSound::load(Common::SeekableReadStream &s) {
char buffer[9];
s.read(buffer, 8);
@@ -128,6 +157,10 @@ void SceneSound::load(Common::SeekableReadStream &s) {
_priority = s.readByte();
}
+void SceneSound::load3DO(Common::SeekableReadStream &s) {
+ load(s);
+}
+
/*----------------------------------------------------------------*/
int ObjectArray::indexOf(const Object &obj) const {
@@ -153,6 +186,18 @@ void ScaleZone::load(Common::SeekableReadStream &s) {
/*----------------------------------------------------------------*/
+void WalkArray::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ _pointsCount = (int8)s.readByte();
+
+ for (int idx = 0; idx < _pointsCount; ++idx) {
+ int x = s.readSint16LE();
+ int y = isRoseTattoo ? s.readSint16LE() : s.readByte();
+ push_back(Common::Point(x, y));
+ }
+}
+
+/*----------------------------------------------------------------*/
+
Scene *Scene::init(SherlockEngine *vm) {
if (vm->getGameID() == GType_SerratedScalpel)
return new Scalpel::ScalpelScene(vm);
@@ -161,25 +206,32 @@ Scene *Scene::init(SherlockEngine *vm) {
}
Scene::Scene(SherlockEngine *vm): _vm(vm) {
- for (int idx = 0; idx < SCENES_COUNT; ++idx)
- Common::fill(&_sceneStats[idx][0], &_sceneStats[idx][65], false);
+ _sceneStats = new bool *[SCENES_COUNT];
+ _sceneStats[0] = new bool[SCENES_COUNT * (MAX_BGSHAPES + 1)];
+ Common::fill(&_sceneStats[0][0], &_sceneStats[0][SCENES_COUNT * (MAX_BGSHAPES + 1)], false);
+ for (int idx = 1; idx < SCENES_COUNT; ++idx) {
+ _sceneStats[idx] = _sceneStats[idx - 1] + (MAX_BGSHAPES + 1);
+ }
+
_currentScene = -1;
_goToScene = -1;
_loadingSavedGame = false;
_walkedInScene = false;
_version = 0;
- _lzwMode = false;
+ _compressed = false;
_invGraphicItems = 0;
_cAnimFramePause = 0;
_restoreFlag = false;
_animating = 0;
_doBgAnimDone = true;
_tempFadeStyle = 0;
- _exitZone = -1;
+ _doBgAnimDone = false;
}
Scene::~Scene() {
freeScene();
+ delete[] _sceneStats[0];
+ delete[] _sceneStats;
}
void Scene::selectScene() {
@@ -193,12 +245,10 @@ void Scene::selectScene() {
ui._windowOpen = ui._infoFlag = false;
ui._menuMode = STD_MODE;
- // Free any previous scene
- freeScene();
-
// Load the scene
Common::String sceneFile = Common::String::format("res%02d", _goToScene);
- _rrmName = Common::String::format("res%02d.rrm", _goToScene);
+ // _rrmName gets set during loadScene()
+ // _rrmName is for ScalpelScene::startCAnim
_currentScene = _goToScene;
_goToScene = -1;
@@ -210,8 +260,8 @@ void Scene::selectScene() {
_tempFadeStyle = 0;
}
- people._walkDest = Common::Point(people[AL]._position.x / FIXED_INT_MULTIPLIER,
- people[AL]._position.y / FIXED_INT_MULTIPLIER);
+ people[HOLMES]._walkDest = Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
_restoreFlag = true;
events.clearEvents();
@@ -226,7 +276,9 @@ void Scene::freeScene() {
if (_currentScene == -1)
return;
+ _vm->_ui->clearWindow();
_vm->_talk->freeTalkVars();
+ _vm->_talk->clearSequences();
_vm->_inventory->freeInv();
_vm->_music->freeSong();
_vm->_sound->freeLoadedSounds();
@@ -238,7 +290,7 @@ void Scene::freeScene() {
_sequenceBuffer.clear();
_descText.clear();
- _walkData.clear();
+ _walkPoints.clear();
_cAnim.clear();
_bgShapes.clear();
_zones.clear();
@@ -253,14 +305,11 @@ void Scene::freeScene() {
bool Scene::loadScene(const Common::String &filename) {
Events &events = *_vm->_events;
- Map &map = *_vm->_map;
Music &music = *_vm->_music;
People &people = *_vm->_people;
Resources &res = *_vm->_res;
SaveManager &saves = *_vm->_saves;
Screen &screen = *_vm->_screen;
- Sound &sound = *_vm->_sound;
- Talk &talk = *_vm->_talk;
UserInterface &ui = *_vm->_ui;
bool flag;
@@ -276,175 +325,492 @@ bool Scene::loadScene(const Common::String &filename) {
_cAnim.clear();
_sequenceBuffer.clear();
- // Check if it's a scene we need to keep trakc 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);
+ //
+ // Load the room resource file for the scene
+ //
+
+ if (!IS_3DO) {
+ // PC version
+ Common::String roomFilename = filename + ".rrm";
+ _roomFilename = roomFilename;
+
+ flag = _vm->_res->exists(roomFilename);
+ if (flag) {
+ Common::SeekableReadStream *rrmStream = _vm->_res->load(roomFilename);
+
+ rrmStream->seek(39);
+ if (IS_SERRATED_SCALPEL) {
+ _version = rrmStream->readByte();
+ _compressed = _version == 10;
+ } else {
+ _compressed = rrmStream->readByte() > 0;
}
- }
- }
- if (IS_ROSE_TATTOO) {
- // Set the NPC paths for the scene
- setNPCPath(0);
-
- // Handle loading music for the scene
- if (sound._midiDrvLoaded) {
- if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3)
- sound._nextSongName = Common::String::format("res%02d", _currentScene);
-
- // If it's a new song, then start it up
- if (sound._currentSongName.compareToIgnoreCase(sound._nextSongName)) {
- if (music.loadSong(sound._nextSongName)) {
- sound.setMIDIVolume(sound._musicVolume);
- if (music._musicOn)
- music.startSong();
+ // Go to header and read it in
+ rrmStream->seek(rrmStream->readUint32LE());
+
+ BgFileHeader bgHeader;
+ bgHeader.load(*rrmStream, IS_ROSE_TATTOO);
+ _invGraphicItems = bgHeader._numImages + 1;
+
+ if (IS_ROSE_TATTOO) {
+ // Resize the screen if necessary
+ int fullWidth = SHERLOCK_SCREEN_WIDTH + bgHeader._scrollSize;
+ if (screen._backBuffer1.w() != fullWidth) {
+ screen._backBuffer1.create(fullWidth, SHERLOCK_SCREEN_HEIGHT);
+ screen._backBuffer2.create(fullWidth, SHERLOCK_SCREEN_HEIGHT);
}
- }
- }
- }
- //
- // Load the room resource file for the scene
- //
+ // Handle initializing the palette
+ screen.initPaletteFade(bgHeader._bytesWritten);
+ rrmStream->read(screen._cMap, PALETTE_SIZE);
+ paletteLoaded();
+ screen.translatePalette(screen._cMap);
- Common::String rrmFile = filename + ".rrm";
- flag = _vm->_res->exists(rrmFile);
- if (flag) {
- Common::SeekableReadStream *rrmStream = _vm->_res->load(rrmFile);
+ // Read in background
+ if (_compressed) {
+ res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ rrmStream->read(screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT);
+ }
+ }
- rrmStream->seek(39);
- if (IS_SERRATED_SCALPEL) {
- _version = rrmStream->readByte();
- _lzwMode = _version == 10;
- } else {
- _lzwMode = rrmStream->readByte() > 0;
- }
+ // Read in the shapes header info
+ Common::Array<BgFileHeaderInfo> bgInfo;
+ bgInfo.resize(bgHeader._numStructs);
- // Go to header and read it in
- rrmStream->seek(rrmStream->readUint32LE());
+ for (uint idx = 0; idx < bgInfo.size(); ++idx)
+ bgInfo[idx].load(*rrmStream);
- BgFileHeader bgHeader;
- bgHeader.load(*rrmStream, IS_ROSE_TATTOO);
- _invGraphicItems = bgHeader._numImages + 1;
+ // Read information
+ if (IS_ROSE_TATTOO) {
+ // Load shapes
+ Common::SeekableReadStream *infoStream = !_compressed ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625);
- if (IS_ROSE_TATTOO) {
- screen.initPaletteFade(bgHeader._bytesWritten);
- rrmStream->read(screen._cMap, PALETTE_SIZE);
- screen.translatePalette(screen._cMap);
- screen.setupBGArea(screen._cMap);
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*infoStream, true);
- screen.initScrollVars();
+ if (_compressed)
+ delete infoStream;
- // Read in background
- if (_lzwMode) {
- res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
+ // Load description text
+ _descText.resize(bgHeader._descSize);
+ if (_compressed)
+ res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize);
+ else
+ rrmStream->read(&_descText[0], bgHeader._descSize);
+
+ // Load sequences
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ if (_compressed)
+ res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize);
+ else
+ rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+ } else if (!_compressed) {
+ // Serrated Scalpel uncompressed info
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*rrmStream, false);
+
+ if (bgHeader._descSize) {
+ _descText.resize(bgHeader._descSize);
+ rrmStream->read(&_descText[0], bgHeader._descSize);
+ }
+
+ if (bgHeader._seqSize) {
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+ }
} else {
- rrmStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
+ // Serrated Scalpel compressed info
+ Common::SeekableReadStream *infoStream;
+
+ // Read shapes
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569);
+
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*infoStream, false);
+
+ delete infoStream;
+
+ // Read description texts
+ if (bgHeader._descSize) {
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize);
+
+ _descText.resize(bgHeader._descSize);
+ infoStream->read(&_descText[0], bgHeader._descSize);
+
+ delete infoStream;
+ }
+
+ // Read sequences
+ if (bgHeader._seqSize) {
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize);
+
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+
+ delete infoStream;
+ }
}
- }
- // Read in the shapes header info
- Common::Array<BgFileHeaderInfo> bgInfo;
- bgInfo.resize(bgHeader._numStructs);
+ // Set up the list of images used by the scene
+ _images.resize(bgHeader._numImages + 1);
+ for (int idx = 0; idx < bgHeader._numImages; ++idx) {
+ _images[idx + 1]._filesize = bgInfo[idx]._filesize;
+ _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames;
- for (uint idx = 0; idx < bgInfo.size(); ++idx)
- bgInfo[idx].load(*rrmStream);
+ // Read in the image data
+ Common::SeekableReadStream *imageStream = _compressed ?
+ res.decompress(*rrmStream, bgInfo[idx]._filesize) :
+ rrmStream->readStream(bgInfo[idx]._filesize);
+
+ _images[idx + 1]._images = new ImageFile(*imageStream);
- // Read information
- if (IS_ROSE_TATTOO) {
- // Load shapes
- Common::SeekableReadStream *infoStream = !_lzwMode ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625);
+ delete imageStream;
+ }
- _bgShapes.resize(bgHeader._numStructs);
- for (int idx = 0; idx < bgHeader._numStructs; ++idx)
- _bgShapes[idx].load(*infoStream, _vm->getGameID() == GType_RoseTattoo);
+ // Set up the bgShapes
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx) {
+ _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images;
+ _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr :
+ &(*_bgShapes[idx]._images)[0];
+
+ _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]);
+ _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset];
+ _bgShapes[idx]._misc = 0;
+ _bgShapes[idx]._seqCounter = 0;
+ _bgShapes[idx]._seqCounter2 = 0;
+ _bgShapes[idx]._seqStack = 0;
+ _bgShapes[idx]._frameNumber = -1;
+ _bgShapes[idx]._oldPosition = Common::Point(0, 0);
+ _bgShapes[idx]._oldSize = Common::Point(1, 1);
+ }
- if (_lzwMode)
- delete infoStream;
+ // Load in cAnim list
+ _cAnim.clear();
+ if (bgHeader._numcAnimations) {
+ int animSize = IS_SERRATED_SCALPEL ? 65 : 47;
+ Common::SeekableReadStream *cAnimStream = _compressed ?
+ res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) :
+ rrmStream->readStream(animSize * bgHeader._numcAnimations);
+
+ // Load cAnim offset table as well
+ uint32 *cAnimOffsetTablePtr = new uint32[bgHeader._numcAnimations];
+ uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr;
+ memset(cAnimOffsetTablePtr, 0, bgHeader._numcAnimations * sizeof(uint32));
+ if (IS_SERRATED_SCALPEL) {
+ // Save current stream offset
+ int32 curOffset = rrmStream->pos();
+ rrmStream->seek(44); // Seek to cAnim-Offset-Table
+ for (uint16 curCAnim = 0; curCAnim < bgHeader._numcAnimations; curCAnim++) {
+ *cAnimOffsetPtr = rrmStream->readUint32LE();
+ cAnimOffsetPtr++;
+ }
+ // Seek back to original stream offset
+ rrmStream->seek(curOffset);
+ }
+ // TODO: load offset table for Rose Tattoo as well
- // Load description text
- _descText.resize(bgHeader._descSize);
- if (_lzwMode)
- res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize);
- else
- rrmStream->read(&_descText[0], bgHeader._descSize);
-
- // Load sequences
- _sequenceBuffer.resize(bgHeader._seqSize);
- if (_lzwMode)
- res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize);
- else
- rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
- } else if (!_lzwMode) {
- // Serrated Scalpel uncompressed info
- _bgShapes.resize(bgHeader._numStructs);
- for (int idx = 0; idx < bgHeader._numStructs; ++idx)
- _bgShapes[idx].load(*rrmStream, false);
-
- if (bgHeader._descSize) {
- _descText.resize(bgHeader._descSize);
- rrmStream->read(&_descText[0], bgHeader._descSize);
+ // Go to the start of the cAnimOffsetTable
+ cAnimOffsetPtr = cAnimOffsetTablePtr;
+
+ _cAnim.resize(bgHeader._numcAnimations);
+ for (uint idx = 0; idx < _cAnim.size(); ++idx) {
+ _cAnim[idx].load(*cAnimStream, IS_ROSE_TATTOO, *cAnimOffsetPtr);
+ cAnimOffsetPtr++;
+ }
+
+ delete cAnimStream;
+ delete[] cAnimOffsetTablePtr;
}
- if (bgHeader._seqSize) {
- _sequenceBuffer.resize(bgHeader._seqSize);
- rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+
+
+ // Read in the room bounding areas
+ int size = rrmStream->readUint16LE();
+ Common::SeekableReadStream *boundsStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, size);
+
+ _zones.resize(size / 10);
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ _zones[idx].left = boundsStream->readSint16LE();
+ _zones[idx].top = boundsStream->readSint16LE();
+ _zones[idx].setWidth(boundsStream->readSint16LE() + 1);
+ _zones[idx].setHeight(boundsStream->readSint16LE() + 1);
+ boundsStream->skip(2); // Skip unused scene number field
}
- } else {
- // Serrated Scalpel compressed info
- Common::SeekableReadStream *infoStream;
- // Read shapes
- infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569);
+ if (_compressed)
+ delete boundsStream;
- _bgShapes.resize(bgHeader._numStructs);
- for (int idx = 0; idx < bgHeader._numStructs; ++idx)
- _bgShapes[idx].load(*infoStream, false);
+ // Ensure we've reached the path version byte
+ if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251))
+ error("Invalid scene path data");
- delete infoStream;
+ // Load the walk directory and walk data
+ assert(_zones.size() < MAX_ZONES);
- // Read description texts
- if (bgHeader._descSize) {
- infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize);
- _descText.resize(bgHeader._descSize);
- infoStream->read(&_descText[0], bgHeader._descSize);
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ Common::fill(&_walkDirectory[idx1][0], &_walkDirectory[idx1][MAX_ZONES], 0);
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2)
+ _walkDirectory[idx1][idx2] = rrmStream->readSint16LE();
+ }
- delete infoStream;
+ // Read in the walk data
+ size = rrmStream->readUint16LE();
+ Common::SeekableReadStream *walkStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, size);
+
+ int startPos = walkStream->pos();
+ while ((walkStream->pos() - startPos) < size) {
+ _walkPoints.push_back(WalkArray());
+ _walkPoints[_walkPoints.size() - 1]._fileOffset = walkStream->pos() - startPos;
+ _walkPoints[_walkPoints.size() - 1].load(*walkStream, IS_ROSE_TATTOO);
}
- // Read sequences
- if (bgHeader._seqSize) {
- infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize);
+ if (_compressed)
+ delete walkStream;
+
+ // Translate the file offsets of the walk directory to indexes in the loaded walk data
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) {
+ int fileOffset = _walkDirectory[idx1][idx2];
+ if (fileOffset == -1)
+ continue;
+
+ uint dataIndex = 0;
+ while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset)
+ ++dataIndex;
+ assert(dataIndex < _walkPoints.size());
+ _walkDirectory[idx1][idx2] = dataIndex;
+ }
+ }
- _sequenceBuffer.resize(bgHeader._seqSize);
- infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+ if (IS_ROSE_TATTOO) {
+ // Read in the entrance
+ _entrance.load(*rrmStream);
- delete infoStream;
+ // Load scale zones
+ _scaleZones.resize(rrmStream->readByte());
+ for (uint idx = 0; idx < _scaleZones.size(); ++idx)
+ _scaleZones[idx].load(*rrmStream);
+ }
+
+ // Read in the exits
+ int numExits = rrmStream->readByte();
+ _exits.resize(numExits);
+
+ for (int idx = 0; idx < numExits; ++idx)
+ _exits[idx].load(*rrmStream, IS_ROSE_TATTOO);
+
+ if (IS_SERRATED_SCALPEL)
+ // Read in the entrance
+ _entrance.load(*rrmStream);
+
+ // Initialize sound list
+ int numSounds = rrmStream->readByte();
+ _sounds.resize(numSounds);
+
+ for (int idx = 0; idx < numSounds; ++idx)
+ _sounds[idx].load(*rrmStream);
+
+ loadSceneSounds();
+
+ if (IS_ROSE_TATTOO) {
+ // Load the object sound list
+ char buffer[27];
+
+ _objSoundList.resize(rrmStream->readUint16LE());
+ for (uint idx = 0; idx < _objSoundList.size(); ++idx) {
+ rrmStream->read(buffer, 27);
+ _objSoundList[idx] = Common::String(buffer);
+ }
+ } else {
+ // Read in palette
+ rrmStream->read(screen._cMap, PALETTE_SIZE);
+ screen.translatePalette(screen._cMap);
+ Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap);
+
+ // Read in the background
+ Common::SeekableReadStream *bgStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+
+ bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+
+ if (_compressed)
+ delete bgStream;
}
+
+ // Backup the image and set the palette
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+ screen.setPalette(screen._cMap);
+
+ delete rrmStream;
}
- // Set up the list of images used by the scene
- _images.resize(bgHeader._numImages + 1);
- for (int idx = 0; idx < bgHeader._numImages; ++idx) {
+ } else {
+ // === 3DO version ===
+ _roomFilename = "rooms/" + filename + ".rrm";
+ flag = _vm->_res->exists(_roomFilename);
+ if (!flag)
+ error("loadScene: 3DO room data file not found");
+
+ Common::SeekableReadStream *roomStream = _vm->_res->load(_roomFilename);
+ uint32 roomStreamSize = roomStream->size();
+
+ // there should be at least all bytes of the header data
+ if (roomStreamSize < 128)
+ error("loadScene: 3DO room data file is too small");
+
+ // Read 3DO header
+ roomStream->skip(4); // UINT32: offset graphic data?
+ uint16 header3DO_numStructs = roomStream->readUint16BE();
+ uint16 header3DO_numImages = roomStream->readUint16BE();
+ uint16 header3DO_numAnimations = roomStream->readUint16BE();
+ roomStream->skip(6);
+
+ uint32 header3DO_bgInfo_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgInfo_size = roomStream->readUint32BE();
+ uint32 header3DO_bgShapes_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgShapes_size = roomStream->readUint32BE();
+ uint32 header3DO_descriptions_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_descriptions_size = roomStream->readUint32BE();
+ uint32 header3DO_sequence_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_sequence_size = roomStream->readUint32BE();
+ uint32 header3DO_cAnim_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_cAnim_size = roomStream->readUint32BE();
+ uint32 header3DO_roomBounding_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_roomBounding_size = roomStream->readUint32BE();
+ uint32 header3DO_walkDirectory_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_walkDirectory_size = roomStream->readUint32BE();
+ uint32 header3DO_walkData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_walkData_size = roomStream->readUint32BE();
+ uint32 header3DO_exits_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_exits_size = roomStream->readUint32BE();
+ uint32 header3DO_entranceData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_entranceData_size = roomStream->readUint32BE();
+ uint32 header3DO_soundList_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_soundList_size = roomStream->readUint32BE();
+ //uint32 header3DO_unknown_offset = roomStream->readUint32BE() + 0x80;
+ //uint32 header3DO_unknown_size = roomStream->readUint32BE();
+ roomStream->skip(8); // Skip over unknown offset+size
+ uint32 header3DO_bgGraphicData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgGraphicData_size = roomStream->readUint32BE();
+
+ // Calculate amount of entries
+ int32 header3DO_soundList_count = header3DO_soundList_size / 9;
+
+ _invGraphicItems = header3DO_numImages + 1;
+
+ // Verify all offsets
+ if (header3DO_bgInfo_offset >= roomStreamSize)
+ error("loadScene: 3DO bgInfo offset points outside of room file");
+ if (header3DO_bgInfo_size > (roomStreamSize - header3DO_bgInfo_offset))
+ error("loadScene: 3DO bgInfo size goes beyond room file");
+ if (header3DO_bgShapes_offset >= roomStreamSize)
+ error("loadScene: 3DO bgShapes offset points outside of room file");
+ if (header3DO_bgShapes_size > (roomStreamSize - header3DO_bgShapes_offset))
+ error("loadScene: 3DO bgShapes size goes beyond room file");
+ if (header3DO_descriptions_offset >= roomStreamSize)
+ error("loadScene: 3DO descriptions offset points outside of room file");
+ if (header3DO_descriptions_size > (roomStreamSize - header3DO_descriptions_offset))
+ error("loadScene: 3DO descriptions size goes beyond room file");
+ if (header3DO_sequence_offset >= roomStreamSize)
+ error("loadScene: 3DO sequence offset points outside of room file");
+ if (header3DO_sequence_size > (roomStreamSize - header3DO_sequence_offset))
+ error("loadScene: 3DO sequence size goes beyond room file");
+ if (header3DO_cAnim_offset >= roomStreamSize)
+ error("loadScene: 3DO cAnim offset points outside of room file");
+ if (header3DO_cAnim_size > (roomStreamSize - header3DO_cAnim_offset))
+ error("loadScene: 3DO cAnim size goes beyond room file");
+ if (header3DO_roomBounding_offset >= roomStreamSize)
+ error("loadScene: 3DO roomBounding offset points outside of room file");
+ if (header3DO_roomBounding_size > (roomStreamSize - header3DO_roomBounding_offset))
+ error("loadScene: 3DO roomBounding size goes beyond room file");
+ if (header3DO_walkDirectory_offset >= roomStreamSize)
+ error("loadScene: 3DO walkDirectory offset points outside of room file");
+ if (header3DO_walkDirectory_size > (roomStreamSize - header3DO_walkDirectory_offset))
+ error("loadScene: 3DO walkDirectory size goes beyond room file");
+ if (header3DO_walkData_offset >= roomStreamSize)
+ error("loadScene: 3DO walkData offset points outside of room file");
+ if (header3DO_walkData_size > (roomStreamSize - header3DO_walkData_offset))
+ error("loadScene: 3DO walkData size goes beyond room file");
+ if (header3DO_exits_offset >= roomStreamSize)
+ error("loadScene: 3DO exits offset points outside of room file");
+ if (header3DO_exits_size > (roomStreamSize - header3DO_exits_offset))
+ error("loadScene: 3DO exits size goes beyond room file");
+ if (header3DO_entranceData_offset >= roomStreamSize)
+ error("loadScene: 3DO entranceData offset points outside of room file");
+ if (header3DO_entranceData_size > (roomStreamSize - header3DO_entranceData_offset))
+ error("loadScene: 3DO entranceData size goes beyond room file");
+ if (header3DO_soundList_offset >= roomStreamSize)
+ error("loadScene: 3DO soundList offset points outside of room file");
+ if (header3DO_soundList_size > (roomStreamSize - header3DO_soundList_offset))
+ error("loadScene: 3DO soundList size goes beyond room file");
+ if (header3DO_bgGraphicData_offset >= roomStreamSize)
+ error("loadScene: 3DO bgGraphicData offset points outside of room file");
+ if (header3DO_bgGraphicData_size > (roomStreamSize - header3DO_bgGraphicData_offset))
+ error("loadScene: 3DO bgGraphicData size goes beyond room file");
+
+ // === BGINFO === read in the shapes header info
+ Common::Array<BgFileHeaderInfo> bgInfo;
+
+ uint32 expected3DO_bgInfo_size = header3DO_numStructs * 16;
+ if (expected3DO_bgInfo_size != header3DO_bgInfo_size) // Security check
+ error("loadScene: 3DO bgInfo size mismatch");
+
+ roomStream->seek(header3DO_bgInfo_offset);
+ bgInfo.resize(header3DO_numStructs);
+ for (uint idx = 0; idx < bgInfo.size(); ++idx)
+ bgInfo[idx].load3DO(*roomStream);
+
+ // === BGSHAPES === read in the shapes info
+ uint32 expected3DO_bgShapes_size = header3DO_numStructs * 588;
+ if (expected3DO_bgShapes_size != header3DO_bgShapes_size) // Security check
+ error("loadScene: 3DO bgShapes size mismatch");
+
+ roomStream->seek(header3DO_bgShapes_offset);
+ _bgShapes.resize(header3DO_numStructs);
+ for (int idx = 0; idx < header3DO_numStructs; ++idx)
+ _bgShapes[idx].load3DO(*roomStream);
+
+ // === DESCRIPTION === read description text
+ if (header3DO_descriptions_size) {
+ roomStream->seek(header3DO_descriptions_offset);
+ _descText.resize(header3DO_descriptions_size);
+ roomStream->read(&_descText[0], header3DO_descriptions_size);
+ }
+
+ // === SEQUENCE === read sequence buffer
+ if (header3DO_sequence_size) {
+ roomStream->seek(header3DO_sequence_offset);
+ _sequenceBuffer.resize(header3DO_sequence_size);
+ roomStream->read(&_sequenceBuffer[0], header3DO_sequence_size);
+ }
+
+ // === IMAGES === set up the list of images used by the scene
+ roomStream->seek(header3DO_bgGraphicData_offset);
+ _images.resize(header3DO_numImages + 1);
+ for (int idx = 0; idx < header3DO_numImages; ++idx) {
_images[idx + 1]._filesize = bgInfo[idx]._filesize;
_images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames;
- // Read in the image data
- Common::SeekableReadStream *imageStream = _lzwMode ?
- res.decompress(*rrmStream, bgInfo[idx]._filesize) :
- rrmStream->readStream(bgInfo[idx]._filesize);
+ // Read image data into memory
+ Common::SeekableReadStream *imageStream = roomStream->readStream(bgInfo[idx]._filesize);
- _images[idx + 1]._images = new ImageFile(*imageStream);
+ // Load image data into an ImageFile array as room file data
+ // which is basically a fixed header, followed by a raw cel header, followed by regular cel data
+ _images[idx + 1]._images = new ImageFile3DO(*imageStream, true);
delete imageStream;
}
- // Set up the bgShapes
- for (int idx = 0; idx < bgHeader._numStructs; ++idx) {
+ // === BGSHAPES === Set up the bgShapes
+ for (int idx = 0; idx < header3DO_numStructs; ++idx) {
_bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images;
_bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr :
&(*_bgShapes[idx]._images)[0];
@@ -460,121 +826,169 @@ bool Scene::loadScene(const Common::String &filename) {
_bgShapes[idx]._oldSize = Common::Point(1, 1);
}
- // Load in cAnim list
+ // === CANIM === read cAnim list
_cAnim.clear();
- if (bgHeader._numcAnimations) {
- int animSize = IS_SERRATED_SCALPEL ? 65 : 47;
- Common::SeekableReadStream *canimStream = _lzwMode ?
- res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) :
- rrmStream->readStream(animSize * bgHeader._numcAnimations);
+ if (header3DO_numAnimations) {
+ roomStream->seek(header3DO_cAnim_offset);
+ Common::SeekableReadStream *cAnimStream = roomStream->readStream(header3DO_cAnim_size);
+
+ uint32 *cAnimOffsetTablePtr = new uint32[header3DO_numAnimations];
+ uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr;
+ uint32 cAnimOffset = 0;
+ memset(cAnimOffsetTablePtr, 0, header3DO_numAnimations * sizeof(uint32));
+
+ // Seek to end of graphics data and load cAnim offset table from there
+ roomStream->seek(header3DO_bgGraphicData_offset + header3DO_bgGraphicData_size);
+ for (uint16 curCAnim = 0; curCAnim < header3DO_numAnimations; curCAnim++) {
+ cAnimOffset = roomStream->readUint32BE();
+ if (cAnimOffset >= roomStreamSize)
+ error("loadScene: 3DO cAnim entry offset points outside of room file");
+
+ *cAnimOffsetPtr = cAnimOffset;
+ cAnimOffsetPtr++;
+ }
- _cAnim.resize(bgHeader._numcAnimations);
- for (uint idx = 0; idx < _cAnim.size(); ++idx)
- _cAnim[idx].load(*canimStream, IS_ROSE_TATTOO);
+ // Go to the start of the cAnimOffsetTable
+ cAnimOffsetPtr = cAnimOffsetTablePtr;
- delete canimStream;
+ _cAnim.resize(header3DO_numAnimations);
+ for (uint idx = 0; idx < _cAnim.size(); ++idx) {
+ _cAnim[idx].load3DO(*cAnimStream, *cAnimOffsetPtr);
+ cAnimOffsetPtr++;
+ }
+
+ delete cAnimStream;
+ delete[] cAnimOffsetTablePtr;
}
- // Read in the room bounding areas
- int size = rrmStream->readUint16LE();
- Common::SeekableReadStream *boundsStream = !_lzwMode ? rrmStream :
- res.decompress(*rrmStream, size);
+ // === BOUNDING AREAS === Read in the room bounding areas
+ int roomBoundingCount = header3DO_roomBounding_size / 12;
+ uint32 expected3DO_roomBounding_size = roomBoundingCount * 12;
+ if (expected3DO_roomBounding_size != header3DO_roomBounding_size)
+ error("loadScene: 3DO roomBounding size mismatch");
- _zones.resize(size / 10);
+ roomStream->seek(header3DO_roomBounding_offset);
+ _zones.resize(roomBoundingCount);
for (uint idx = 0; idx < _zones.size(); ++idx) {
- _zones[idx].left = boundsStream->readSint16LE();
- _zones[idx].top = boundsStream->readSint16LE();
- _zones[idx].setWidth(boundsStream->readSint16LE() + 1);
- _zones[idx].setHeight(boundsStream->readSint16LE() + 1);
- boundsStream->skip(2); // Skip unused scene number field
+ _zones[idx].left = roomStream->readSint16BE();
+ _zones[idx].top = roomStream->readSint16BE();
+ _zones[idx].setWidth(roomStream->readSint16BE() + 1);
+ _zones[idx].setHeight(roomStream->readSint16BE() + 1);
+ roomStream->skip(4); // skip UINT32
}
- if (_lzwMode)
- delete boundsStream;
-
- // Ensure we've reached the path version byte
- if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251))
- error("Invalid scene path data");
+ // === WALK DIRECTORY === Load the walk directory
+ uint32 expected3DO_walkDirectory_size = _zones.size() * _zones.size() * 2;
+ if (expected3DO_walkDirectory_size != header3DO_walkDirectory_size)
+ error("loadScene: 3DO walkDirectory size mismatch");
- // Load the walk directory
+ roomStream->seek(header3DO_walkDirectory_offset);
assert(_zones.size() < MAX_ZONES);
for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
for (uint idx2 = 0; idx2 < _zones.size(); ++idx2)
- _walkDirectory[idx1][idx2] = rrmStream->readSint16LE();
+ _walkDirectory[idx1][idx2] = roomStream->readSint16BE();
}
- // Read in the walk data
- size = rrmStream->readUint16LE();
- Common::SeekableReadStream *walkStream = !_lzwMode ? rrmStream :
- res.decompress(*rrmStream, size);
-
- _walkData.resize(size);
- walkStream->read(&_walkData[0], size);
-
- if (_lzwMode)
- delete walkStream;
+ // === WALK DATA === Read in the walk data
+ roomStream->seek(header3DO_walkData_offset);
- if (IS_ROSE_TATTOO) {
- // Read in the entrance
- _entrance.load(*rrmStream);
-
- // Load scale zones
- _scaleZones.resize(rrmStream->readByte());
- for (uint idx = 0; idx < _scaleZones.size(); ++idx)
- _scaleZones[idx].load(*rrmStream);
+ int startPos = roomStream->pos();
+ while ((roomStream->pos() - startPos) < (int)header3DO_walkData_size) {
+ _walkPoints.push_back(WalkArray());
+ _walkPoints[_walkPoints.size() - 1]._fileOffset = roomStream->pos() - startPos;
+ _walkPoints[_walkPoints.size() - 1].load(*roomStream, false);
}
- // Read in the exits
- _exitZone = -1;
- int numExits = rrmStream->readByte();
- _exits.resize(numExits);
-
- for (int idx = 0; idx < numExits; ++idx)
- _exits[idx].load(*rrmStream, IS_ROSE_TATTOO);
-
- if (IS_SERRATED_SCALPEL)
- // Read in the entrance
- _entrance.load(*rrmStream);
-
- // Initialize sound list
- int numSounds = rrmStream->readByte();
- _sounds.resize(numSounds);
-
- for (int idx = 0; idx < numSounds; ++idx)
- _sounds[idx].load(*rrmStream);
-
- loadSceneSounds();
-
- if (IS_ROSE_TATTOO) {
- // Load the object sound list
- char buffer[27];
-
- _objSoundList.resize(rrmStream->readUint16LE());
- for (uint idx = 0; idx < _objSoundList.size(); ++idx) {
- rrmStream->read(buffer, 27);
- _objSoundList[idx] = Common::String(buffer);
+ // Translate the file offsets of the walk directory to indexes in the loaded walk data
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) {
+ int fileOffset = _walkDirectory[idx1][idx2];
+ if (fileOffset == -1)
+ continue;
+
+ uint dataIndex = 0;
+ while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset)
+ ++dataIndex;
+ assert(dataIndex < _walkPoints.size());
+ _walkDirectory[idx1][idx2] = dataIndex;
}
- } else {
- // Read in palette
- rrmStream->read(screen._cMap, PALETTE_SIZE);
- screen.translatePalette(screen._cMap);
- Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap);
+ }
- // Read in the background
- Common::SeekableReadStream *bgStream = !_lzwMode ? rrmStream :
- res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+ // === EXITS === Read in the exits
+ roomStream->seek(header3DO_exits_offset);
+
+ int exitsCount = header3DO_exits_size / 20;
+
+ _exits.resize(exitsCount);
+ for (int idx = 0; idx < exitsCount; ++idx)
+ _exits[idx].load3DO(*roomStream);
+
+ // === ENTRANCE === Read in the entrance
+ if (header3DO_entranceData_size != 8)
+ error("loadScene: 3DO entranceData size mismatch");
+
+ roomStream->seek(header3DO_entranceData_offset);
+ _entrance.load3DO(*roomStream);
+
+ // === SOUND LIST === Initialize sound list
+ roomStream->seek(header3DO_soundList_offset);
+ _sounds.resize(header3DO_soundList_count);
+ for (int idx = 0; idx < header3DO_soundList_count; ++idx)
+ _sounds[idx].load3DO(*roomStream);
+
+ delete roomStream;
+
+ // === BACKGROUND PICTURE ===
+ // load from file rooms\[filename].bg
+ // it's uncompressed 15-bit RGB555 data
+
+ Common::String roomBackgroundFilename = "rooms/" + filename + ".bg";
+ flag = _vm->_res->exists(roomBackgroundFilename);
+ if (!flag)
+ error("loadScene: 3DO room background file not found (%s)", roomBackgroundFilename.c_str());
+
+ Common::File roomBackgroundStream;
+ if (!roomBackgroundStream.open(roomBackgroundFilename))
+ error("Could not open file - %s", roomBackgroundFilename.c_str());
+
+ int totalPixelCount = SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT;
+ uint16 *roomBackgroundDataPtr = NULL;
+ uint16 *pixelSourcePtr = NULL;
+ uint16 *pixelDestPtr = (uint16 *)screen._backBuffer1.getPixels();
+ uint16 curPixel = 0;
+ uint32 roomBackgroundStreamSize = roomBackgroundStream.size();
+ uint32 expectedBackgroundSize = totalPixelCount * 2;
+
+ // Verify file size of background file
+ if (expectedBackgroundSize != roomBackgroundStreamSize)
+ error("loadScene: 3DO room background file not expected size");
+
+ roomBackgroundDataPtr = new uint16[totalPixelCount];
+ roomBackgroundStream.read(roomBackgroundDataPtr, roomBackgroundStreamSize);
+ roomBackgroundStream.close();
+
+ // Convert data from RGB555 to RGB565
+ pixelSourcePtr = roomBackgroundDataPtr;
+ for (int pixels = 0; pixels < totalPixelCount; pixels++) {
+ curPixel = READ_BE_UINT16(pixelSourcePtr++);
+
+ byte curPixelRed = (curPixel >> 10) & 0x1F;
+ byte curPixelGreen = (curPixel >> 5) & 0x1F;
+ byte curPixelBlue = curPixel & 0x1F;
+ *pixelDestPtr = ((curPixelRed << 11) | (curPixelGreen << 6) | (curPixelBlue));
+ pixelDestPtr++;
+ }
- bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+ delete[] roomBackgroundDataPtr;
- if (_lzwMode)
- delete bgStream;
- }
+#if 0
+ // code to show the background
+ screen.blitFrom(screen._backBuffer1);
+ _vm->_events->wait(10000);
+#endif
- // Backup the image and set the palette
+ // Backup the image
screen._backBuffer2.blitFrom(screen._backBuffer1);
- screen.setPalette(screen._cMap);
-
- delete rrmStream;
}
// Handle drawing any on-screen interface
@@ -622,17 +1036,6 @@ bool Scene::loadScene(const Common::String &filename) {
_walkedInScene = false;
saves._justLoaded = false;
- if (!_vm->isDemo()) {
- // Reset the previous map location and position on overhead map
- map._oldCharPoint = _currentScene;
-
- if (IS_SERRATED_SCALPEL) {
- map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER;
- map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER;
-
- }
- }
-
events.clearEvents();
return flag;
}
@@ -645,11 +1048,11 @@ void Scene::loadSceneSounds() {
}
void Scene::checkSceneStatus() {
- if (_sceneStats[_currentScene][64]) {
- for (uint idx = 0; idx < 64; ++idx) {
+ if (_sceneStats[_currentScene][MAX_BGSHAPES]) {
+ for (int idx = 0; idx < MAX_BGSHAPES; ++idx) {
bool flag = _sceneStats[_currentScene][idx];
- if (idx < _bgShapes.size()) {
+ if (idx < (int)_bgShapes.size()) {
Object &obj = _bgShapes[idx];
if (flag) {
@@ -671,7 +1074,7 @@ void Scene::checkSceneStatus() {
void Scene::saveSceneStatus() {
// Flag any objects for the scene that have been altered
- int count = MIN((int)_bgShapes.size(), 64);
+ int count = MIN((int)_bgShapes.size(), MAX_BGSHAPES);
for (int idx = 0; idx < count; ++idx) {
Object &obj = _bgShapes[idx];
_sceneStats[_currentScene][idx] = obj._type == HIDDEN || obj._type == REMOVE
@@ -679,7 +1082,7 @@ void Scene::saveSceneStatus() {
}
// Flag scene as having been visited
- _sceneStats[_currentScene][64] = true;
+ _sceneStats[_currentScene][MAX_BGSHAPES] = true;
}
void Scene::checkSceneFlags(bool flag) {
@@ -687,9 +1090,15 @@ void Scene::checkSceneFlags(bool flag) {
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
+ bool objectFlag = true;
- if (o._requiredFlag) {
- if (!_vm->readFlags(_bgShapes[idx]._requiredFlag)) {
+ if (o._requiredFlag[0] || o._requiredFlag[1]) {
+ if (o._requiredFlag[0] != 0)
+ objectFlag = _vm->readFlags(o._requiredFlag[0]);
+ if (o._requiredFlag[1] != 0)
+ objectFlag &= _vm->readFlags(o._requiredFlag[1]);
+
+ if (!objectFlag) {
// Kill object
if (o._type != HIDDEN && o._type != INVALID) {
if (o._images == nullptr || o._images->size() == 0)
@@ -699,7 +1108,7 @@ void Scene::checkSceneFlags(bool flag) {
// Flag it as needing to be hidden after first erasing it
o._type = mode;
}
- } else if (_bgShapes[idx]._requiredFlag > 0) {
+ } else if (IS_ROSE_TATTOO || o._requiredFlag[0] > 0) {
// Restore object
if (o._images == nullptr || o._images->size() == 0)
o._type = NO_SHAPE;
@@ -750,18 +1159,29 @@ void Scene::transitionToScene() {
SaveManager &saves = *_vm->_saves;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
- Common::Point &hSavedPos = people._hSavedPos;
- int &hSavedFacing = people._hSavedFacing;
+ Point32 &hSavedPos = people._savedPos;
+ int &hSavedFacing = people._savedPos._facing;
if (hSavedPos.x < 1) {
// No exit information from last scene-check entrance info
if (_entrance._startPosition.x < 1) {
// No entrance info either, so use defaults
- hSavedPos = Common::Point(16000, 10000);
- hSavedFacing = 4;
+ if (IS_SERRATED_SCALPEL) {
+ hSavedPos = Point32(160 * FIXED_INT_MULTIPLIER, 100 * FIXED_INT_MULTIPLIER);
+ hSavedFacing = 4;
+ } else {
+ hSavedPos = people[HOLMES]._position;
+ hSavedFacing = people[HOLMES]._sequenceNumber;
+ }
} else {
// setup entrance info
- hSavedPos = _entrance._startPosition;
+ hSavedPos.x = _entrance._startPosition.x * FIXED_INT_MULTIPLIER;
+ hSavedPos.y = _entrance._startPosition.y * FIXED_INT_MULTIPLIER;
+ if (IS_SERRATED_SCALPEL) {
+ hSavedPos.x /= 100;
+ hSavedPos.y /= 100;
+ }
+
hSavedFacing = _entrance._startDir;
}
} else {
@@ -769,7 +1189,11 @@ void Scene::transitionToScene() {
// Note: If a savegame was just loaded, then the data is already correct.
// Otherwise, this is a linked scene or entrance info, and must be translated
if (hSavedFacing < 8 && !saves._justLoaded) {
- hSavedFacing = FS_TRANS[hSavedFacing];
+ if (IS_ROSE_TATTOO)
+ hSavedFacing = Tattoo::FS_TRANS[hSavedFacing];
+ else
+ hSavedFacing = Scalpel::FS_TRANS[hSavedFacing];
+
hSavedPos.x *= FIXED_INT_MULTIPLIER;
hSavedPos.y *= FIXED_INT_MULTIPLIER;
}
@@ -777,13 +1201,15 @@ void Scene::transitionToScene() {
int cAnimNum = -1;
- if (hSavedFacing < 101) {
- // Standard info, so set it
- people[PLAYER]._position = hSavedPos;
- people[PLAYER]._sequenceNumber = hSavedFacing;
- } else {
- // It's canimation information
- cAnimNum = hSavedFacing - 101;
+ if (!saves._justLoaded) {
+ if (hSavedFacing < 101) {
+ // Standard info, so set it
+ people[HOLMES]._position = hSavedPos;
+ people[HOLMES]._sequenceNumber = hSavedFacing;
+ } else {
+ // It's canimation information
+ cAnimNum = hSavedFacing - 101;
+ }
}
// Reset positioning for next load
@@ -792,9 +1218,14 @@ void Scene::transitionToScene() {
if (cAnimNum != -1) {
// Prevent Holmes from being drawn
- people[PLAYER]._position = Common::Point(0, 0);
+ people[HOLMES]._position = Common::Point(0, 0);
}
+ // If the scene is capable of scrolling, set the current scroll so that whoever has control
+ // of the scroll code is in the middle of the screen
+ if (screen._backBuffer1.w() > SHERLOCK_SCREEN_WIDTH)
+ people[people._walkControl].centerScreenOnPerson();
+
for (uint objIdx = 0; objIdx < _bgShapes.size(); ++objIdx) {
Object &obj = _bgShapes[objIdx];
@@ -811,8 +1242,8 @@ void Scene::transitionToScene() {
}
if (Common::Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y).contains(
- Common::Point(people[PLAYER]._position.x / FIXED_INT_MULTIPLIER,
- people[PLAYER]._position.y / FIXED_INT_MULTIPLIER))) {
+ Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER))) {
// Current point is already inside box - impact occurred on
// a previous call. So simply do nothing except talk until the
// player is clear of the box
@@ -844,22 +1275,30 @@ void Scene::transitionToScene() {
updateBackground();
// Actually do the transition
- if (screen._fadeStyle)
- screen.randomTransition();
- else
- screen.blitFrom(screen._backBuffer1);
+ if (screen._fadeStyle) {
+ if (!IS_3DO) {
+ // do pixel-transition for PC
+ screen.randomTransition();
+ } else {
+ // fade in for 3DO
+ screen.clear();
+ screen.fadeIntoScreen3DO(3);
+ }
+ } else {
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
screen.update();
// Start any initial animation for the scene
if (cAnimNum != -1) {
CAnim &c = _cAnim[cAnimNum];
- Common::Point pt = c._goto;
+ PositionFacing pt = c._goto[0];
- c._goto = Common::Point(-1, -1);
- people[AL]._position = Common::Point(0, 0);
+ c._goto[0].x = c._goto[0].y = -1;
+ people[HOLMES]._position = Common::Point(0, 0);
startCAnim(cAnimNum, 1);
- c._goto = pt;
+ c._goto[0] = pt;
}
}
@@ -892,92 +1331,6 @@ void Scene::updateBackground() {
drawAllShapes();
}
-void Scene::drawAllShapes() {
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
-
- // Restrict drawing window
- screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
-
- // Draw all active shapes which are behind the person
- for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
- if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND)
- screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all canimations which are behind the person
- for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
- if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND)
- screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame,
- _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all active shapes which are normal and behind the person
- for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
- if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND)
- screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all canimations which are normal and behind the person
- for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
- if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND)
- screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
- _canimShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw any active characters
- for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- Person &p = people[idx];
- if (p._type == CHARACTER && p._walkLoaded) {
- bool flipped = IS_SERRATED_SCALPEL && (
- p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT ||
- p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT ||
- p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT);
-
- screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER,
- p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped);
- }
- }
-
- // Draw all static and active shapes that are NORMAL and are in front of the player
- for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
- if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
- _bgShapes[idx]._misc == NORMAL_FORWARD)
- screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
- _bgShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all static and active canimations that are NORMAL and are in front of the player
- for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
- if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
- _canimShapes[idx]._misc == NORMAL_FORWARD)
- screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
- _canimShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all static and active shapes that are FORWARD
- for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
- _bgShapes[idx]._oldPosition = _bgShapes[idx]._position;
- _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(),
- _bgShapes[idx].frameHeight());
-
- if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
- _bgShapes[idx]._misc == FORWARD)
- screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
- _bgShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- // Draw all static and active canimations that are forward
- for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
- if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
- _canimShapes[idx]._misc == FORWARD)
- screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
- _canimShapes[idx]._flags & OBJ_FLIPPED);
- }
-
- screen.resetDisplayBounds();
-}
-
Exit *Scene::checkForExit(const Common::Rect &r) {
for (uint idx = 0; idx < _exits.size(); ++idx) {
if (_exits[idx].intersects(r))
@@ -987,238 +1340,6 @@ Exit *Scene::checkForExit(const Common::Rect &r) {
return nullptr;
}
-int Scene::startCAnim(int cAnimNum, int playRate) {
- Events &events = *_vm->_events;
- Map &map = *_vm->_map;
- People &people = *_vm->_people;
- Resources &res = *_vm->_res;
- Talk &talk = *_vm->_talk;
- UserInterface &ui = *_vm->_ui;
- Common::Point tpPos, walkPos;
- int tpDir, walkDir;
- int tFrames = 0;
- int gotoCode = -1;
-
- // Validation
- if (cAnimNum >= (int)_cAnim.size())
- // number out of bounds
- return -1;
- if (_canimShapes.size() >= 3 || playRate == 0)
- // Too many active animations, or invalid play rate
- return 0;
-
- CAnim &cAnim = _cAnim[cAnimNum];
- if (playRate < 0) {
- // Reverse direction
- walkPos = cAnim._teleportPos;
- walkDir = cAnim._teleportDir;
- tpPos = cAnim._goto;
- tpDir = cAnim._gotoDir;
- } else {
- // Forward direction
- walkPos = cAnim._goto;
- walkDir = cAnim._gotoDir;
- tpPos = cAnim._teleportPos;
- tpDir = cAnim._teleportDir;
- }
-
- CursorId oldCursor = events.getCursor();
- events.setCursor(WAIT);
-
- if (walkPos.x != -1) {
- // Holmes must walk to the walk point before the cAnimation is started
- if (people[AL]._position != walkPos)
- people.walkToCoords(walkPos, walkDir);
- }
-
- if (talk._talkToAbort)
- return 1;
-
- // Add new anim shape entry for displaying the animation
- _canimShapes.push_back(Object());
- Object &cObj = _canimShapes[_canimShapes.size() - 1];
-
- // Copy the canimation into the bgShapes type canimation structure so it can be played
- cObj._allow = cAnimNum + 1; // Keep track of the parent structure
- cObj._name = _cAnim[cAnimNum]._name; // Copy name
-
- // Remove any attempt to draw object frame
- if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100)
- cAnim._sequences[0] = 0;
-
- cObj._sequences = cAnim._sequences;
- cObj._images = nullptr;
- cObj._position = cAnim._position;
- cObj._delta = Common::Point(0, 0);
- cObj._type = cAnim._type;
- cObj._flags = cAnim._flags;
-
- cObj._maxFrames = 0;
- cObj._frameNumber = -1;
- cObj._sequenceNumber = cAnimNum;
- cObj._oldPosition = Common::Point(0, 0);
- cObj._oldSize = Common::Point(0, 0);
- cObj._goto = Common::Point(0, 0);
- cObj._status = 0;
- cObj._misc = 0;
- cObj._imageFrame = nullptr;
-
- if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) {
- if (tpPos.x != -1)
- people[AL]._type = REMOVE;
-
- Common::String fname = cAnim._name + ".vgs";
- if (!res.isInCache(fname)) {
- // Set up RRM scene data
- Common::SeekableReadStream *rrmStream = res.load(_rrmName);
- rrmStream->seek(44 + cAnimNum * 4);
- rrmStream->seek(rrmStream->readUint32LE());
-
- // Load the canimation into the cache
- Common::SeekableReadStream *imgStream = !_lzwMode ? rrmStream->readStream(cAnim._size) :
- Resources::decompressLZ(*rrmStream, cAnim._size);
- res.addToCache(fname, *imgStream);
-
- delete imgStream;
- delete rrmStream;
- }
-
- // Now load the resource as an image
- cObj._images = new ImageFile(fname);
- cObj._imageFrame = &(*cObj._images)[0];
- cObj._maxFrames = cObj._images->size();
-
- int frames = 0;
- if (playRate < 0) {
- // Reverse direction
- // Count number of frames
- while (cObj._sequences[frames] && frames < MAX_FRAME)
- ++frames;
- } else {
- // Forward direction
- Object::_countCAnimFrames = true;
-
- while (cObj._type == ACTIVE_BG_SHAPE) {
- cObj.checkObject();
- ++frames;
-
- if (frames >= 1000)
- error("CAnim has infinite loop sequence");
- }
-
- if (frames > 1)
- --frames;
-
- Object::_countCAnimFrames = false;
-
- cObj._type = cAnim._type;
- cObj._frameNumber = -1;
- cObj._position = cAnim._position;
- cObj._delta = Common::Point(0, 0);
- }
-
- // Return if animation has no frames in it
- if (frames == 0)
- return -2;
-
- ++frames;
- int repeat = ABS(playRate);
- int dir;
-
- if (playRate < 0) {
- // Play in reverse
- dir = -2;
- cObj._frameNumber = frames - 3;
- } else {
- dir = 0;
- }
-
- tFrames = frames - 1;
- int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1;
-
- while (--frames) {
- if (frames == pauseFrame)
- ui.printObjectDesc();
-
- doBgAnim();
-
- // Repeat same frame
- int temp = repeat;
- while (--temp > 0) {
- cObj._frameNumber--;
- doBgAnim();
-
- if (_vm->shouldQuit())
- return 0;
- }
-
- cObj._frameNumber += dir;
- }
-
- people[AL]._type = CHARACTER;
- }
-
- // Teleport to ending coordinates if necessary
- if (tpPos.x != -1) {
- people[AL]._position = tpPos; // Place the player
- people[AL]._sequenceNumber = tpDir;
- people.gotoStand(people[AL]);
- }
-
- if (playRate < 0)
- // Reverse direction - set to end sequence
- cObj._frameNumber = tFrames - 1;
-
- if (cObj._frameNumber <= 26)
- gotoCode = cObj._sequences[cObj._frameNumber + 3];
-
- // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it
- if (_canimShapes.indexOf(cObj) != -1)
- cObj.checkObject();
-
- if (gotoCode > 0 && !talk._talkToAbort) {
- _goToScene = gotoCode;
-
- if (_goToScene < 97 && map[_goToScene].x) {
- map._overPos = map[_goToScene];
- }
- }
-
- people.loadWalk();
-
- if (tpPos.x != -1 && !talk._talkToAbort) {
- // Teleport to ending coordinates
- people[AL]._position = tpPos;
- people[AL]._sequenceNumber = tpDir;
-
- people.gotoStand(people[AL]);
- }
-
- events.setCursor(oldCursor);
-
- return 1;
-}
-
-int Scene::findBgShape(const Common::Rect &r) {
- if (!_doBgAnimDone)
- // New frame hasn't been drawn yet
- return -1;
-
- for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
- Object &o = _bgShapes[idx];
- if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN
- && o._aType <= PERSON) {
- if (r.intersects(o.getNewBounds()))
- return idx;
- } else if (o._type == NO_SHAPE) {
- if (r.intersects(o.getNoShapeBounds()))
- return idx;
- }
- }
-
- return -1;
-}
-
int Scene::checkForZones(const Common::Point &pt, int zoneType) {
int matches = 0;
@@ -1247,26 +1368,7 @@ int Scene::whichZone(const Common::Point &pt) {
return -1;
}
-int Scene::closestZone(const Common::Point &pt) {
- int dist = 1000;
- int zone = -1;
-
- for (uint idx = 0; idx < _zones.size(); ++idx) {
- Common::Point zc((_zones[idx].left + _zones[idx].right) / 2,
- (_zones[idx].top + _zones[idx].bottom) / 2);
- int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y);
-
- if (d < dist) {
- // Found a closer zone
- dist = d;
- zone = idx;
- }
- }
-
- return zone;
-}
-
-void Scene::synchronize(Common::Serializer &s) {
+void Scene::synchronize(Serializer &s) {
if (s.isSaving())
saveSceneStatus();
@@ -1277,37 +1379,16 @@ void Scene::synchronize(Common::Serializer &s) {
_loadingSavedGame = true;
}
- for (int sceneNum = 0; sceneNum < SCENES_COUNT; ++sceneNum) {
- for (int flag = 0; flag < 65; ++flag) {
+ for (int sceneNum = 1; sceneNum < SCENES_COUNT; ++sceneNum) {
+ for (int flag = 0; flag <= MAX_BGSHAPES; ++flag) {
s.syncAsByte(_sceneStats[sceneNum][flag]);
}
}
}
-void Scene::setNPCPath(int npc) {
- People &people = *_vm->_people;
- Talk &talk = *_vm->_talk;
-
- people[npc].clearNPC();
- people[npc]._name = 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 (uint idx = 0; idx < MAX_NPC; ++idx)
- people[idx + 1]._type = INVALID;
-
- // Call the path script for the scene
- Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene);
- talk.talkTo(pathFile);
-}
-
void Scene::checkBgShapes() {
People &people = *_vm->_people;
- Person &holmes = people._player;
+ Person &holmes = people[HOLMES];
Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
// Iterate through the shapes
diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h
index 37a1b32740..f75dfb40cd 100644
--- a/engines/sherlock/scene.h
+++ b/engines/sherlock/scene.h
@@ -33,7 +33,6 @@
namespace Sherlock {
-#define SCENES_COUNT 63
#define MAX_ZONES 40
#define INFO_LINE 140
@@ -73,14 +72,14 @@ struct BgFileHeaderInfo {
* Load the data for the object
*/
void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
};
class Exit: public Common::Rect {
public:
int _scene;
int _allow;
- Common::Point _people;
- int _peopleDir;
+ PositionFacing _newPosition;
Common::String _dest;
int _image; // Arrow image to use
@@ -89,6 +88,7 @@ public:
* Load the data for the object
*/
void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+ void load3DO(Common::SeekableReadStream &s);
};
struct SceneEntry {
@@ -100,6 +100,7 @@ struct SceneEntry {
* Load the data for the object
*/
void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
};
struct SceneSound {
@@ -110,6 +111,7 @@ struct SceneSound {
* Load the data for the object
*/
void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
};
class ObjectArray : public Common::Array<Object> {
@@ -128,33 +130,24 @@ public:
void load(Common::SeekableReadStream &s);
};
-struct SceneTripEntry {
- bool _flag;
- int _sceneNumber;
- int _numTimes;
+class WalkArray : public Common::Array < Common::Point > {
+public:
+ int _pointsCount;
+ int _fileOffset;
+
+ WalkArray() : _pointsCount(0), _fileOffset(-1) {}
- SceneTripEntry() : _flag(false), _sceneNumber(0), _numTimes(0) {}
- SceneTripEntry(bool flag, int sceneNumber, int numTimes) : _flag(flag),
- _sceneNumber(sceneNumber), _numTimes(numTimes) {}
+ /**
+ * Load data for the walk array entry
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
};
class Scene {
private:
- Common::String _rrmName;
bool _loadingSavedGame;
/**
- * Loads the data associated for a given scene. The .BGD 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::String &filename);
-
- /**
* Loads sounds for the scene
*/
void loadSceneSounds();
@@ -185,6 +178,18 @@ private:
void saveSceneStatus();
protected:
SherlockEngine *_vm;
+ Common::String _roomFilename;
+
+ /**
+ * 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.
+ */
+ virtual bool loadScene(const Common::String &filename);
/**
* Checks all the background shapes. If a background shape is animating,
@@ -196,29 +201,32 @@ protected:
/**
* Draw all the shapes, people and NPCs in the correct order
*/
- void drawAllShapes();
+ virtual void drawAllShapes() = 0;
+
+ /**
+ * Called by loadScene when the palette is loaded for Rose Tattoo
+ */
+ virtual void paletteLoaded() {}
Scene(SherlockEngine *vm);
public:
int _currentScene;
int _goToScene;
- bool _sceneStats[SCENES_COUNT][65];
- bool _savedStats[SCENES_COUNT][9];
+ bool **_sceneStats;
bool _walkedInScene;
int _version;
- bool _lzwMode;
+ bool _compressed;
int _invGraphicItems;
Common::String _comments;
Common::Array<char> _descText;
Common::Array<Common::Rect> _zones;
- Common::Array<Object> _bgShapes;
+ ObjectArray _bgShapes;
Common::Array<CAnim> _cAnim;
Common::Array<byte> _sequenceBuffer;
Common::Array<SceneImage> _images;
int _walkDirectory[MAX_ZONES][MAX_ZONES];
- Common::Array<byte> _walkData;
+ Common::Array<WalkArray> _walkPoints;
Common::Array<Exit> _exits;
- int _exitZone;
SceneEntry _entrance;
Common::Array<SceneSound> _sounds;
ObjectArray _canimShapes;
@@ -229,7 +237,6 @@ public:
bool _doBgAnimDone;
int _tempFadeStyle;
int _cAnimFramePause;
- Common::Array<SceneTripEntry> _sceneTripCounters;
public:
static Scene *init(SherlockEngine *vm);
virtual ~Scene();
@@ -240,11 +247,6 @@ public:
void selectScene();
/**
- * Fres all the graphics and other dynamically allocated data for the scene
- */
- void freeScene();
-
- /**
* Check the scene's objects against the game flags. If false is passed,
* it means the scene has just been loaded. A value of true means that the scene
* is in use (ie. not just loaded)
@@ -257,28 +259,12 @@ public:
Exit *checkForExit(const Common::Rect &r);
/**
- * 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);
-
- /**
* Scans through the object list to find one with a matching name, and will
* call toggleHidden with all matches found. Returns the numer of matches found
*/
int toggleObject(const Common::String &name);
/**
- * 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::Rect &r);
-
- /**
* Checks to see if the given position in the scene belongs to a given zone type.
* If it is, the zone is activated and used just like a TAKL zone or aFLAG_SET zone.
*/
@@ -290,21 +276,25 @@ public:
int whichZone(const Common::Point &pt);
/**
+ * Fres all the graphics and other dynamically allocated data for the scene
+ */
+ virtual void freeScene();
+
+ /**
* Returns the index of the closest zone to a given point.
*/
- int closestZone(const Common::Point &pt);
+ virtual int closestZone(const Common::Point &pt) = 0;
/**
- * Synchronize the data for a savegame
+ * Attempts to find a background shape within the passed bounds. If found,
+ * it will return the shape number, or -1 on failure.
*/
- void synchronize(Common::Serializer &s);
+ virtual int findBgShape(const Common::Point &pt) = 0;
/**
- * 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
+ * Synchronize the data for a savegame
*/
- void setNPCPath(int npc);
+ virtual void synchronize(Serializer &s);
public:
/**
* Draw all objects and characters.
@@ -316,6 +306,16 @@ public:
* to be drawn
*/
virtual void updateBackground();
+
+ /**
+ * 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
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1) = 0;
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp
index a3af5559c7..4233bca0cb 100644
--- a/engines/sherlock/screen.cpp
+++ b/engines/sherlock/screen.cpp
@@ -22,55 +22,41 @@
#include "sherlock/screen.h"
#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_screen.h"
#include "common/system.h"
#include "common/util.h"
#include "graphics/palette.h"
namespace Sherlock {
+Screen *Screen::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelScreen(vm);
+ else
+ return new Screen(vm);
+}
+
Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->getHeight()), _vm(vm),
_backBuffer1(g_system->getWidth(), g_system->getHeight()),
_backBuffer2(g_system->getWidth(), g_system->getHeight()),
_backBuffer(&_backBuffer1) {
_transitionSeed = 1;
_fadeStyle = false;
- _font = nullptr;
- _fontHeight = 0;
Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0);
Common::fill(&_sMap[0], &_sMap[PALETTE_SIZE], 0);
Common::fill(&_tMap[0], &_tMap[PALETTE_SIZE], 0);
+
+ // Set up the initial font
setFont(IS_SERRATED_SCALPEL ? 1 : 4);
// Rose Tattoo specific fields
_fadeBytesRead = _fadeBytesToRead = 0;
_oldFadePercent = 0;
- _scrollSize = 0;
- _scrollSpeed = 0;
- _currentScroll = 0;
- _targetScroll = 0;
_flushScreen = false;
}
Screen::~Screen() {
- delete _font;
-}
-
-void Screen::setFont(int fontNumb) {
- // Interactive demo doesn't use fonts
- if (!_vm->_interactiveFl)
- return;
-
- _fontNumber = fontNumb;
- Common::String fname = Common::String::format("FONT%d.VGS", fontNumb + 1);
-
- // Discard any previous font and read in new one
- delete _font;
- _font = new ImageFile(fname);
-
- // Iterate through the frames to find the tallest font character
- _fontHeight = 0;
- for (uint idx = 0; idx < _font->size(); ++idx)
- _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h);
+ Fonts::free();
}
void Screen::update() {
@@ -91,6 +77,10 @@ void Screen::update() {
_dirtyRects.clear();
}
+void Screen::makeAllDirty() {
+ addDirtyRect(Common::Rect(0, 0, this->w(), this->h()));
+}
+
void Screen::getPalette(byte palette[PALETTE_SIZE]) {
g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT);
}
@@ -229,14 +219,133 @@ void Screen::verticalTransition() {
}
}
-void Screen::restoreBackground(const Common::Rect &r) {
- if (r.width() > 0 && r.height() > 0) {
- Common::Rect tempRect = r;
- tempRect.clip(Common::Rect(0, 0, this->w(), SHERLOCK_SCENE_HEIGHT));
+void Screen::fadeIntoScreen3DO(int speed) {
+ Events &events = *_vm->_events;
+ uint16 *currentScreenBasePtr = (uint16 *)getPixels();
+ uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels();
+ uint16 currentScreenPixel = 0;
+ uint16 targetScreenPixel = 0;
+
+ uint16 currentScreenPixelRed = 0;
+ uint16 currentScreenPixelGreen = 0;
+ uint16 currentScreenPixelBlue = 0;
+
+ uint16 targetScreenPixelRed = 0;
+ uint16 targetScreenPixelGreen = 0;
+ uint16 targetScreenPixelBlue = 0;
+
+ uint16 screenWidth = this->w();
+ uint16 screenHeight = this->h();
+ uint16 screenX = 0;
+ uint16 screenY = 0;
+ uint16 pixelsChanged = 0;
+
+ _dirtyRects.clear();
+
+ do {
+ pixelsChanged = 0;
+ uint16 *currentScreenPtr = currentScreenBasePtr;
+ uint16 *targetScreenPtr = targetScreenBasePtr;
+
+ for (screenY = 0; screenY < screenHeight; screenY++) {
+ for (screenX = 0; screenX < screenWidth; screenX++) {
+ currentScreenPixel = *currentScreenPtr;
+ targetScreenPixel = *targetScreenPtr;
+
+ if (currentScreenPixel != targetScreenPixel) {
+ // pixel doesn't match, adjust accordingly
+ currentScreenPixelRed = currentScreenPixel & 0xF800;
+ currentScreenPixelGreen = currentScreenPixel & 0x07E0;
+ currentScreenPixelBlue = currentScreenPixel & 0x001F;
+ targetScreenPixelRed = targetScreenPixel & 0xF800;
+ targetScreenPixelGreen = targetScreenPixel & 0x07E0;
+ targetScreenPixelBlue = targetScreenPixel & 0x001F;
+
+ if (currentScreenPixelRed != targetScreenPixelRed) {
+ if (currentScreenPixelRed < targetScreenPixelRed) {
+ currentScreenPixelRed += 0x0800;
+ } else {
+ currentScreenPixelRed -= 0x0800;
+ }
+ }
+ if (currentScreenPixelGreen != targetScreenPixelGreen) {
+ // Adjust +2/-2 because we are running RGB555 at RGB565
+ if (currentScreenPixelGreen < targetScreenPixelGreen) {
+ currentScreenPixelGreen += 0x0040;
+ } else {
+ currentScreenPixelGreen -= 0x0040;
+ }
+ }
+ if (currentScreenPixelBlue != targetScreenPixelBlue) {
+ if (currentScreenPixelBlue < targetScreenPixelBlue) {
+ currentScreenPixelBlue += 0x0001;
+ } else {
+ currentScreenPixelBlue -= 0x0001;
+ }
+ }
+ *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
+ pixelsChanged++;
+ }
+
+ currentScreenPtr++;
+ targetScreenPtr++;
+ }
+ }
+
+ // Too much considered dirty at the moment
+ addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
- if (tempRect.isValidRect())
- _backBuffer1.blitFrom(_backBuffer2, Common::Point(tempRect.left, tempRect.top), tempRect);
+ events.pollEvents();
+ events.delay(10 * speed);
+ } while ((pixelsChanged) && (!_vm->shouldQuit()));
+}
+
+void Screen::blitFrom3DOcolorLimit(uint16 limitColor) {
+ uint16 *currentScreenPtr = (uint16 *)getPixels();
+ uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels();
+ uint16 currentScreenPixel = 0;
+
+ uint16 screenWidth = this->w();
+ uint16 screenHeight = this->h();
+ uint16 screenX = 0;
+ uint16 screenY = 0;
+
+ uint16 currentScreenPixelRed = 0;
+ uint16 currentScreenPixelGreen = 0;
+ uint16 currentScreenPixelBlue = 0;
+
+ uint16 limitPixelRed = limitColor & 0xF800;
+ uint16 limitPixelGreen = limitColor & 0x07E0;
+ uint16 limitPixelBlue = limitColor & 0x001F;
+
+ for (screenY = 0; screenY < screenHeight; screenY++) {
+ for (screenX = 0; screenX < screenWidth; screenX++) {
+ currentScreenPixel = *targetScreenPtr;
+
+ currentScreenPixelRed = currentScreenPixel & 0xF800;
+ currentScreenPixelGreen = currentScreenPixel & 0x07E0;
+ currentScreenPixelBlue = currentScreenPixel & 0x001F;
+
+ if (currentScreenPixelRed < limitPixelRed)
+ currentScreenPixelRed = limitPixelRed;
+ if (currentScreenPixelGreen < limitPixelGreen)
+ currentScreenPixelGreen = limitPixelGreen;
+ if (currentScreenPixelBlue < limitPixelBlue)
+ currentScreenPixelBlue = limitPixelBlue;
+
+ *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
+ currentScreenPtr++;
+ targetScreenPtr++;
+ }
}
+
+ // Too much considered dirty at the moment
+ addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
+}
+
+void Screen::restoreBackground(const Common::Rect &r) {
+ if (r.width() > 0 && r.height() > 0)
+ _backBuffer1.blitFrom(_backBuffer2, Common::Point(r.left, r.top), r);
}
void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) {
@@ -245,14 +354,33 @@ void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) {
void Screen::slamRect(const Common::Rect &r) {
if (r.width() && r.height() > 0) {
- Common::Rect tempRect = r;
- tempRect.clip(Common::Rect(0, 0, this->w(), this->h()));
+ Common::Rect srcRect = r, destRect = r;
+
+ destRect.translate(-_currentScroll.x, -_currentScroll.y);
+
+ if (destRect.left < 0) {
+ srcRect.left += -destRect.left;
+ destRect.left = 0;
+ }
+ if (destRect.top < 0) {
+ srcRect.top += -destRect.top;
+ destRect.top = 0;
+ }
+ if (destRect.right > SHERLOCK_SCREEN_WIDTH) {
+ srcRect.right -= (destRect.left - SHERLOCK_SCREEN_WIDTH);
+ destRect.right = SHERLOCK_SCREEN_WIDTH;
+ }
+ if (destRect.bottom > SHERLOCK_SCREEN_HEIGHT) {
+ srcRect.bottom -= (destRect.bottom - SHERLOCK_SCREEN_HEIGHT);
+ destRect.bottom = SHERLOCK_SCREEN_HEIGHT;
+ }
- if (tempRect.isValidRect())
- blitFrom(*_backBuffer, Common::Point(tempRect.left, tempRect.top), tempRect);
+ if (srcRect.isValidRect())
+ blitFrom(*_backBuffer, Common::Point(destRect.left, destRect.top), srcRect);
}
}
+
void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
int16 *width, int16 *height) {
Common::Point imgPos = pt + frame->_offset;
@@ -283,7 +411,7 @@ void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, i
void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
int16 *width, int16 *height, int scaleVal) {
- Common::Point imgPos = pt + frame->_offset;
+ Common::Point imgPos(pt.x + frame->sDrawXOffset(scaleVal), pt.y + frame->sDrawYOffset(scaleVal));
Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->sDrawXSize(scaleVal),
imgPos.y + frame->sDrawYSize(scaleVal));
Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height);
@@ -310,6 +438,28 @@ void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *
*height = newBounds.height();
}
+void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal) {
+ Common::Point newPos(newBounds.left, newBounds.top);
+ Common::Point newSize(newBounds.width(), newBounds.height());
+
+ if (scaleVal == SCALE_THRESHOLD)
+ flushImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y);
+ else
+ flushScaleImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y, scaleVal);
+
+ // Transfer the pos and size amounts into a single bounds rect
+ newBounds = Common::Rect(newPos.x, newPos.y, newPos.x + newSize.x, newPos.y + newSize.y);
+}
+
+void Screen::blockMove(const Common::Rect &r) {
+ Common::Rect bounds = r;
+ slamRect(bounds);
+}
+
+void Screen::blockMove() {
+ blockMove(Common::Rect(0, 0, w(), h()));
+}
+
void Screen::print(const Common::Point &pt, byte color, const char *formatStr, ...) {
// Create the string to display
va_list args;
@@ -349,37 +499,8 @@ void Screen::gPrint(const Common::Point &pt, byte color, const char *formatStr,
writeString(str, pt, color);
}
-int Screen::stringWidth(const Common::String &str) {
- int width = 0;
-
- for (const char *c = str.c_str(); *c; ++c)
- width += charWidth(*c);
-
- return width;
-}
-
-int Screen::charWidth(char c) {
- if (c == ' ')
- return 5;
- else if (Common::isPrint(c))
- return (*_font)[c - 33]._frame.w + 1;
- else
- return 0;
-}
-
-void Screen::writeString(const Common::String &str, const Common::Point &pt, byte color) {
- Common::Point charPos = pt;
-
- for (const char *c = str.c_str(); *c; ++c) {
- if (*c == ' ')
- charPos.x += 5;
- else {
- assert(Common::isPrint(*c));
- ImageFrame &frame = (*_font)[*c - 33];
- _backBuffer->transBlitFrom(frame, charPos, false, color);
- charPos.x += frame._frame.w + 1;
- }
- }
+void Screen::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) {
+ Fonts::writeString(_backBuffer, str, pt, overrideColor);
}
void Screen::vgaBar(const Common::Rect &r, int color) {
@@ -387,67 +508,8 @@ void Screen::vgaBar(const Common::Rect &r, int color) {
slamRect(r);
}
-void Screen::makeButton(const Common::Rect &bounds, int textX,
- const Common::String &str) {
-
- Surface &bb = *_backBuffer;
- bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP);
- bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP);
- bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM);
- bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM);
- bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE);
-
- gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]);
- gPrint(Common::Point(textX + charWidth(str[0]), bounds.top),
- COMMAND_FOREGROUND, "%s", str.c_str() + 1);
-}
-
-void Screen::buttonPrint(const Common::Point &pt, byte color, bool slamIt,
- const Common::String &str) {
- int xStart = pt.x - stringWidth(str) / 2;
-
- if (color == COMMAND_FOREGROUND) {
- // First character needs to be highlighted
- if (slamIt) {
- print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]);
- print(Common::Point(xStart + charWidth(str[0]), pt.y + 1),
- COMMAND_FOREGROUND, "%s", str.c_str() + 1);
- } else {
- gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]);
- gPrint(Common::Point(xStart + charWidth(str[0]), pt.y),
- COMMAND_FOREGROUND, "%s", str.c_str() + 1);
- }
- } else if (slamIt) {
- print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str());
- } else {
- gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str());
- }
-}
-
-void Screen::makePanel(const Common::Rect &r) {
- _backBuffer->fillRect(r, BUTTON_MIDDLE);
- _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP);
- _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP);
- _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP);
- _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
-
- _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM);
- _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM);
- _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM);
- _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM);
-}
-
-void Screen::makeField(const Common::Rect &r) {
- _backBuffer->fillRect(r, BUTTON_MIDDLE);
- _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM);
- _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP);
- _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM);
- _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
-}
-
void Screen::setDisplayBounds(const Common::Rect &r) {
- assert(r.left == 0 && r.top == 0);
- _sceneSurface.setPixels(_backBuffer1.getPixels(), r.width(), r.height());
+ _sceneSurface.setPixels(_backBuffer1.getBasePtr(r.left, r.top), r.width(), r.height(), _backBuffer1.getPixelFormat());
_backBuffer = &_sceneSurface;
}
@@ -461,7 +523,7 @@ Common::Rect Screen::getDisplayBounds() {
Common::Rect(0, 0, this->w(), this->h());
}
-void Screen::synchronize(Common::Serializer &s) {
+void Screen::synchronize(Serializer &s) {
int fontNumb = _fontNumber;
s.syncAsByte(fontNumb);
if (s.isLoading())
@@ -484,22 +546,6 @@ int Screen::fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSiz
return totalSize;
}
-/**
- * Creates a grey-scale version of the passed palette
- */
-void Screen::setupBGArea(const byte cMap[PALETTE_SIZE]) {
- warning("TODO");
-}
-
-/**
- * Initializes scroll variables
- */
-void Screen::initScrollVars() {
- _scrollSize = 0;
- _currentScroll = 0;
- _targetScroll = 0;
-}
-
void Screen::translatePalette(byte palette[PALETTE_SIZE]) {
for (int idx = 0; idx < PALETTE_SIZE; ++idx)
palette[idx] = VGA_COLOR_TRANS(palette[idx]);
diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h
index 8fda9cbb9c..2e0cef72ca 100644
--- a/engines/sherlock/screen.h
+++ b/engines/sherlock/screen.h
@@ -25,33 +25,25 @@
#include "common/list.h"
#include "common/rect.h"
-#include "common/serializer.h"
#include "sherlock/surface.h"
#include "sherlock/resources.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
#define PALETTE_SIZE 768
#define PALETTE_COUNT 256
#define VGA_COLOR_TRANS(x) ((x) * 255 / 63)
+#define BG_GREYSCALE_RANGE_END 229
enum {
+ BLACK = 0,
INFO_BLACK = 1,
- INFO_FOREGROUND = 11,
- INFO_BACKGROUND = 1,
BORDER_COLOR = 237,
- INV_FOREGROUND = 14,
- INV_BACKGROUND = 1,
- COMMAND_HIGHLIGHTED = 10,
- COMMAND_FOREGROUND = 15,
COMMAND_BACKGROUND = 4,
- COMMAND_NULL = 248,
- BUTTON_TOP = 233,
- BUTTON_MIDDLE = 244,
- BUTTON_BOTTOM = 248,
+ BUTTON_BACKGROUND = 235,
TALK_FOREGROUND = 12,
- TALK_NULL = 16,
- PEN_COLOR = 250
+ TALK_NULL = 16
};
class SherlockEngine;
@@ -59,18 +51,13 @@ class SherlockEngine;
class Screen : public Surface {
private:
SherlockEngine *_vm;
- int _fontNumber;
Common::List<Common::Rect> _dirtyRects;
uint32 _transitionSeed;
- ImageFile *_font;
- int _fontHeight;
Surface _sceneSurface;
// Rose Tattoo fields
int _fadeBytesRead, _fadeBytesToRead;
int _oldFadePercent;
- byte _lookupTable[PALETTE_COUNT];
- byte _lookupTable1[PALETTE_COUNT];
private:
/**
* Merges together overlapping dirty areas of the screen
@@ -81,11 +68,6 @@ private:
* Returns the union of two dirty area rectangles
*/
bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2);
-
- /**
- * Draws the given string into the back buffer using the images stored in _font
- */
- void writeString(const Common::String &str, const Common::Point &pt, byte color);
protected:
/**
* Adds a rectangle to the list of modified areas of the screen during the
@@ -99,22 +81,22 @@ public:
byte _cMap[PALETTE_SIZE];
byte _sMap[PALETTE_SIZE];
byte _tMap[PALETTE_SIZE];
- int _currentScroll, _targetScroll;
- int _scrollSize, _scrollSpeed;
bool _flushScreen;
+ Common::Point _currentScroll;
public:
+ static Screen *init(SherlockEngine *vm);
Screen(SherlockEngine *vm);
virtual ~Screen();
/**
- * Set the font to use for writing text on the screen
+ * Handles updating any dirty areas of the screen Surface object to the physical screen
*/
- void setFont(int fontNumber);
+ void update();
/**
- * Handles updating any dirty areas of the screen Surface object to the physical screen
+ * Makes the whole screen dirty, Hack for 3DO movie playing
*/
- void update();
+ void makeAllDirty();
/**
* Return the currently active palette
@@ -152,6 +134,13 @@ public:
void verticalTransition();
/**
+ * Fade backbuffer 1 into screen (3DO RGB!)
+ */
+ void fadeIntoScreen3DO(int speed);
+
+ void blitFrom3DOcolorLimit(uint16 color);
+
+ /**
* Prints the text passed onto the back buffer at the given position and color.
* The string is then blitted to the screen
*/
@@ -192,41 +181,24 @@ public:
int16 *width, int16 *height, int scaleVal);
/**
- * Returns the width of a string in pixels
- */
- int stringWidth(const Common::String &str);
-
- /**
- * Returns the width of a character in pixels
+ * Variation of flushImage/flushScaleImage that takes in and updates a rect
*/
- int charWidth(char c);
+ void flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal);
/**
- * Fills an area on the back buffer, and then copies it to the screen
+ * Copies data from the back buffer to the screen
*/
- void vgaBar(const Common::Rect &r, int color);
+ void blockMove(const Common::Rect &r);
/**
- * Draws a button for use in the inventory, talk, and examine dialogs.
+ * Copies the entire screen from the back buffer
*/
- void makeButton(const Common::Rect &bounds, int textX, const Common::String &str);
+ void blockMove();
/**
- * Prints an interface command with the first letter highlighted to indicate
- * what keyboard shortcut is associated with it
- */
- void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str);
-
- /**
- * Draw a panel in the back buffer with a raised area effect around the edges
- */
- void makePanel(const Common::Rect &r);
-
- /**
- * Draw a field in the back buffer with a raised area effect around the edges,
- * suitable for text input.
+ * Fills an area on the back buffer, and then copies it to the screen
*/
- void makeField(const Common::Rect &r);
+ void vgaBar(const Common::Rect &r, int color);
/**
* Sets the active back buffer pointer to a restricted sub-area of the first back buffer
@@ -243,22 +215,22 @@ public:
*/
Common::Rect getDisplayBounds();
- int fontNumber() const { return _fontNumber; }
-
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
+
+ /**
+ * Draws the given string into the back buffer using the images stored in _font
+ */
+ virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor);
+
// Rose Tattoo specific methods
void initPaletteFade(int bytesToRead);
int fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize);
- void setupBGArea(const byte cMap[PALETTE_SIZE]);
-
- void initScrollVars();
-
/**
* Translate a palette from 6-bit RGB values to full 8-bit values suitable for passing
* to the underlying palette manager
diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp
index d3a409a67b..ae77c91009 100644
--- a/engines/sherlock/sherlock.cpp
+++ b/engines/sherlock/sherlock.cpp
@@ -33,6 +33,7 @@ SherlockEngine::SherlockEngine(OSystem *syst, const SherlockGameDescription *gam
_animation = nullptr;
_debugger = nullptr;
_events = nullptr;
+ _fixedText = nullptr;
_inventory = nullptr;
_journal = nullptr;
_map = nullptr;
@@ -56,13 +57,14 @@ SherlockEngine::~SherlockEngine() {
delete _animation;
delete _debugger;
delete _events;
+ delete _fixedText;
delete _journal;
delete _map;
- delete _music;
delete _people;
delete _saves;
delete _scene;
delete _screen;
+ delete _music;
delete _sound;
delete _talk;
delete _ui;
@@ -71,11 +73,15 @@ SherlockEngine::~SherlockEngine() {
}
void SherlockEngine::initialize() {
- DebugMan.addDebugChannel(kDebugScript, "scripts", "Script debug level");
+ DebugMan.addDebugChannel(kDebugLevelScript, "scripts", "Script debug level");
+ DebugMan.addDebugChannel(kDebugLevelAdLibDriver, "AdLib", "AdLib driver debugging");
+ DebugMan.addDebugChannel(kDebugLevelMT32Driver, "MT32", "MT32 driver debugging");
+ DebugMan.addDebugChannel(kDebugLevelMusic, "Music", "Music debugging");
+ Fonts::setVm(this);
ImageFile::setVm(this);
- Object::setVm(this);
- Sprite::setVm(this);
+ ImageFile3DO::setVm(this);
+ BaseObject::setVm(this);
if (isDemo()) {
Common::File f;
@@ -87,22 +93,29 @@ void SherlockEngine::initialize() {
_res = new Resources(this);
_animation = new Animation(this);
- _debugger = new Debugger(this);
+ _debugger = Debugger::init(this);
_events = new Events(this);
- _inventory = new Inventory(this);
- _map = new Map(this);
+ _fixedText = FixedText::init(this);
+ _inventory = Inventory::init(this);
+ _map = Map::init(this);
_music = new Music(this, _mixer);
- _journal = new Journal(this);
- _people = new People(this);
- _saves = new SaveManager(this, _targetName);
+ _journal = Journal::init(this);
+ _people = People::init(this);
+ _saves = SaveManager::init(this, _targetName);
_scene = Scene::init(this);
- _screen = new Screen(this);
+ _screen = Screen::init(this);
_sound = new Sound(this, _mixer);
_talk = Talk::init(this);
_ui = UserInterface::init(this);
// Load game settings
loadConfig();
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ // Disable portraits on 3DO
+ // 3DO does not include portrait data
+ _people->_portraitsOn = false;
+ }
}
Common::Error SherlockEngine::run() {
@@ -115,7 +128,7 @@ Common::Error SherlockEngine::run() {
// If requested, load a savegame instead of showing the intro
if (ConfMan.hasKey("save_slot")) {
int saveSlot = ConfMan.getInt("save_slot");
- if (saveSlot >= 1 && saveSlot <= MAX_SAVEGAME_SLOTS)
+ if (saveSlot >= 0 && saveSlot <= MAX_SAVEGAME_SLOTS)
_loadGameSlot = saveSlot;
}
@@ -166,7 +179,7 @@ void SherlockEngine::sceneLoop() {
// Handle any input from the keyboard or mouse
handleInput();
- if (_people->_hSavedPos.x == -1) {
+ if (_people->_savedPos.x == -1) {
_canLoadSave = true;
_scene->doBgAnim();
_canLoadSave = false;
@@ -175,11 +188,10 @@ void SherlockEngine::sceneLoop() {
_scene->freeScene();
_people->freeWalk();
-
}
void SherlockEngine::handleInput() {
- _canLoadSave = true;
+ _canLoadSave = _ui->_menuMode == STD_MODE || _ui->_menuMode == LAB_MODE;
_events->pollEventsAndWait();
_canLoadSave = false;
@@ -203,11 +215,15 @@ void SherlockEngine::setFlags(int flagNum) {
_scene->checkSceneFlags(true);
}
+void SherlockEngine::setFlagsDirect(int flagNum) {
+ _flags[ABS(flagNum)] = flagNum >= 0;
+}
+
void SherlockEngine::loadConfig() {
// Load sound settings
syncSoundSettings();
- ConfMan.registerDefault("font", 1);
+ ConfMan.registerDefault("font", getGameID() == GType_SerratedScalpel ? 1 : 4);
_screen->setFont(ConfMan.getInt("font"));
if (getGameID() == GType_SerratedScalpel)
@@ -240,7 +256,7 @@ void SherlockEngine::syncSoundSettings() {
_music->syncMusicSettings();
}
-void SherlockEngine::synchronize(Common::Serializer &s) {
+void SherlockEngine::synchronize(Serializer &s) {
for (uint idx = 0; idx < _flags.size(); ++idx)
s.syncAsByte(_flags[idx]);
}
@@ -263,4 +279,4 @@ Common::Error SherlockEngine::saveGameState(int slot, const Common::String &desc
return Common::kNoError;
}
-} // End of namespace Comet
+} // End of namespace Sherlock
diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h
index e71c729893..c05680eb08 100644
--- a/engines/sherlock/sherlock.h
+++ b/engines/sherlock/sherlock.h
@@ -35,6 +35,7 @@
#include "sherlock/animation.h"
#include "sherlock/debugger.h"
#include "sherlock/events.h"
+#include "sherlock/fixed_text.h"
#include "sherlock/inventory.h"
#include "sherlock/journal.h"
#include "sherlock/map.h"
@@ -51,7 +52,10 @@
namespace Sherlock {
enum {
- kDebugScript = 1 << 0
+ kDebugLevelScript = 1 << 0,
+ kDebugLevelAdLibDriver = 2 << 0,
+ kDebugLevelMT32Driver = 3 << 0,
+ kDebugLevelMusic = 4 << 0
};
enum GameType {
@@ -61,7 +65,14 @@ enum GameType {
#define SHERLOCK_SCREEN_WIDTH _vm->_screen->w()
#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h()
+#define SHERLOCK_SCENE_WIDTH _vm->_screen->_backBuffer1.w()
#define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480)
+#define SCENES_COUNT (IS_SERRATED_SCALPEL ? 63 : 101)
+#define MAX_BGSHAPES (IS_SERRATED_SCALPEL ? 64 : 150)
+
+#define COL_INFO_FOREGROUND (IS_SERRATED_SCALPEL ? (byte)Scalpel::INFO_FOREGROUND : (byte)Tattoo::INFO_FOREGROUND)
+#define COL_PEN_COLOR (IS_SERRATED_SCALPEL ? (byte)Scalpel::PEN_COLOR : (byte)Tattoo::PEN_COLOR)
+#define COL_PEN_HIGHLIGHT (IS_SERRATED_SCALPEL ? 15 : 129)
struct SherlockGameDescription;
@@ -78,11 +89,6 @@ private:
* Handle all player input
*/
void handleInput();
-
- /**
- * Load game configuration esttings
- */
- void loadConfig();
protected:
/**
* Does basic initialization of the game engine
@@ -97,11 +103,17 @@ protected:
* Returns a list of features the game itself supports
*/
virtual bool hasFeature(EngineFeature f) const;
+
+ /**
+ * Load game configuration esttings
+ */
+ virtual void loadConfig();
public:
const SherlockGameDescription *_gameDescription;
Animation *_animation;
Debugger *_debugger;
Events *_events;
+ FixedText *_fixedText;
Inventory *_inventory;
Journal *_journal;
Map *_map;
@@ -156,6 +168,11 @@ public:
virtual void syncSoundSettings();
/**
+ * Saves game configuration information
+ */
+ virtual void saveConfig();
+
+ /**
* Returns whether the version is a demo
*/
virtual bool isDemo() const;
@@ -171,6 +188,11 @@ public:
Common::Platform getPlatform() const;
/**
+ * Return the game's language
+ */
+ Common::Language getLanguage() const;
+
+ /**
* Return a random number
*/
int getRandomNumber(int limit) { return _randomSource.getRandomNumber(limit - 1); }
@@ -189,18 +211,20 @@ public:
void setFlags(int flagNum);
/**
- * Saves game configuration information
+ * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive.
+ * @remarks We don't use the global setFlags method because we don't want to check scene flags
*/
- void saveConfig();
+ void setFlagsDirect(int flagNum);
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
+ void synchronize(Serializer &s);
};
#define IS_ROSE_TATTOO (_vm->getGameID() == GType_RoseTattoo)
#define IS_SERRATED_SCALPEL (_vm->getGameID() == GType_SerratedScalpel)
+#define IS_3DO (_vm->getPlatform() == Common::kPlatform3DO)
} // End of namespace Sherlock
diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp
index 390576e98e..fd51462bc0 100644
--- a/engines/sherlock/sound.cpp
+++ b/engines/sherlock/sound.cpp
@@ -27,6 +27,8 @@
#include "common/algorithm.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
namespace Sherlock {
@@ -53,17 +55,19 @@ static const uint8 creativeADPCM_AdjustMap[64] = {
Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
_digitized = false;
_voices = 0;
- _diskSoundPlaying = false;
_soundPlaying = false;
- _soundIsOn = &_soundPlaying;
+ _speechPlaying = false;
_curPriority = 0;
- _digiBuf = nullptr;
- _midiDrvLoaded = false;
- _musicVolume = 0;
+ _soundVolume = 255;
_soundOn = true;
_speechOn = true;
+ if (IS_3DO) {
+ // 3DO: we don't need to prepare anything for sound
+ return;
+ }
+
_vm->_res->addToCache("MUSIC.LIB");
if (!_vm->_interactiveFl)
_vm->_res->addToCache("TITLE.SND");
@@ -85,12 +89,13 @@ Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
void Sound::syncSoundSettings() {
_digitized = !ConfMan.getBool("mute");
- _voices = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute") ? 1 : 0;
+ _speechOn = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute");
+ _voices = _speechOn ? 1 : 0;
}
void Sound::loadSound(const Common::String &name, int priority) {
// No implementation required in ScummVM
- warning("loadSound");
+ //warning("loadSound");
}
byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) {
@@ -116,59 +121,34 @@ byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) {
}
bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) {
- Resources &res = *_vm->_res;
stopSound();
Common::String filename = name;
- if (!filename.contains('.'))
- filename += ".SND";
-
- Common::String libFilename(libraryFilename);
- Common::SeekableReadStream *stream = libFilename.empty() ? res.load(filename) : res.load(filename, libFilename);
-
- if (!stream)
- error("Unable to find sound file '%s'", filename.c_str());
-
- stream->skip(2);
- int size = stream->readUint32BE();
- int rate = stream->readUint16BE();
- byte *data = (byte *)malloc(size);
- byte *ptr = data;
- stream->read(ptr, size);
- delete stream;
-
- assert(size > 2);
-
- byte *decoded = (byte *)malloc((size - 1) * 2);
-
- // Holmes uses Creative ADPCM 4-bit data
- int counter = 0;
- byte reference = ptr[0];
- int16 scale = 0;
-
- for(int i = 1; i < size; i++) {
- decoded[counter++] = decodeSample((ptr[i]>>4)&0x0f, reference, scale);
- decoded[counter++] = decodeSample((ptr[i]>>0)&0x0f, reference, scale);
+ if (!filename.contains('.')) {
+ if (!IS_3DO) {
+ if (IS_SERRATED_SCALPEL) {
+ filename += ".SND";
+ } else {
+ filename += ".WAV";
+ }
+ } else {
+ // 3DO uses .aiff extension
+ filename += ".AIFF";
+ if (!filename.contains('/')) {
+ // if no directory was given, use the room sounds directory
+ filename = "rooms/sounds/" + filename;
+ }
+ }
}
- free(data);
-
-#if 0
- // Debug : used to dump files
- Common::DumpFile outFile;
- outFile.open(filename);
- outFile.write(decoded, (size - 2) * 2);
- outFile.flush();
- outFile.close();
-#endif
+ Audio::SoundHandle soundHandle = (IS_SERRATED_SCALPEL) ? _scalpelEffectsHandle : getFreeSoundHandle();
+ if (!playSoundResource(filename, libraryFilename, Audio::Mixer::kSFXSoundType, soundHandle))
+ error("Could not find sound resource - %s", filename.c_str());
- Audio::AudioStream *audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_effectsHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume);
_soundPlaying = true;
_curPriority = priority;
if (waitType == WAIT_RETURN_IMMEDIATELY) {
- _diskSoundPlaying = true;
return true;
}
@@ -180,22 +160,23 @@ bool Sound::playSound(const Common::String &name, WaitType waitType, int priorit
retval = false;
break;
}
- } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(_effectsHandle));
+ } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(soundHandle));
_soundPlaying = false;
- _mixer->stopHandle(_effectsHandle);
+ _mixer->stopHandle(soundHandle);
return retval;
}
void Sound::playLoadedSound(int bufNum, WaitType waitType) {
- if (_mixer->isSoundHandleActive(_effectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority))
- return;
+ if (IS_SERRATED_SCALPEL) {
+ if (_mixer->isSoundHandleActive(_scalpelEffectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority))
+ return;
- stopSound();
- playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority);
+ stopSound();
+ }
- return;
+ playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority);
}
void Sound::freeLoadedSounds() {
@@ -205,23 +186,117 @@ void Sound::freeLoadedSounds() {
}
void Sound::stopSound() {
- _mixer->stopHandle(_effectsHandle);
-}
-
-void Sound::stopSndFuncPtr(int v1, int v2) {
- // TODO
- warning("TODO: Sound::stopSndFuncPtr");
+ if (IS_SERRATED_SCALPEL) {
+ _mixer->stopHandle(_scalpelEffectsHandle);
+ } else {
+ for (int i = 0; i < MAX_MIXER_CHANNELS; i++)
+ _mixer->stopHandle(_tattooEffectsHandle[i]);
+ }
}
void Sound::freeDigiSound() {
- delete[] _digiBuf;
- _digiBuf = nullptr;
- _diskSoundPlaying = false;
_soundPlaying = false;
}
-void Sound::setMIDIVolume(int volume) {
- // TODO
+Audio::SoundHandle Sound::getFreeSoundHandle() {
+ for (int i = 0; i < MAX_MIXER_CHANNELS; i++) {
+ if (!_mixer->isSoundHandleActive(_tattooEffectsHandle[i]))
+ return _tattooEffectsHandle[i];
+ }
+
+ error("getFreeSoundHandle: No sound handle found");
+}
+
+void Sound::setVolume(int volume) {
+ warning("TODO: setVolume - %d", volume);
+}
+
+void Sound::playSpeech(const Common::String &name) {
+ Resources &res = *_vm->_res;
+ Scene &scene = *_vm->_scene;
+ stopSpeech();
+
+ // TODO: Technically Scalpel has an sfx command which I've set to call this method because it sets the
+ // _voice variable as if it were speech. Need to do a play-through of Scalpel and see if it's ever called.
+ // If so, will need to enhance this method to handle the Serrated Scalpel voice resources
+ assert(IS_ROSE_TATTOO);
+
+ // Figure out which speech library to use
+ Common::String libraryName = Common::String::format("speech%02d.lib", scene._currentScene);
+ if ((!scumm_strnicmp(name.c_str(), "SLVE12S", 7)) || (!scumm_strnicmp(name.c_str(), "WATS12X", 7))
+ || (!scumm_strnicmp(name.c_str(), "HOLM12X", 7)))
+ libraryName = "SPEECH12.LIB";
+
+ // If the speech library file doesn't even exist, then we can't play anything
+ Common::File f;
+ if (!f.exists(libraryName))
+ return;
+
+ // Ensure the given library is in the cache
+ res.addToCache(libraryName);
+
+ if (playSoundResource(name, libraryName, Audio::Mixer::kSpeechSoundType, _speechHandle))
+ _speechPlaying = true;
+}
+
+void Sound::stopSpeech() {
+ _mixer->stopHandle(_speechHandle);
+ _speechPlaying = false;
+}
+
+bool Sound::isSpeechPlaying() {
+ _speechPlaying = _mixer->isSoundHandleActive(_speechHandle);
+ return _speechPlaying;
+}
+
+bool Sound::playSoundResource(const Common::String &name, const Common::String &libFilename,
+ Audio::Mixer::SoundType soundType, Audio::SoundHandle &handle) {
+ Resources &res = *_vm->_res;
+ Common::SeekableReadStream *stream = libFilename.empty() ? res.load(name) : res.load(name, libFilename, true);
+ if (!stream)
+ return false;
+
+ Audio::AudioStream *audioStream;
+ if (IS_ROSE_TATTOO && soundType == Audio::Mixer::kSpeechSoundType) {
+ audioStream = Audio::makeRawStream(stream, 11025, Audio::FLAG_UNSIGNED);
+ } else if (IS_3DO) {
+ // 3DO: AIFF file
+ audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
+ } else if (IS_SERRATED_SCALPEL) {
+ stream->skip(2);
+ int size = stream->readUint32BE();
+ int rate = stream->readUint16BE();
+ byte *data = (byte *)malloc(size);
+ byte *ptr = data;
+ stream->read(ptr, size);
+ delete stream;
+
+ assert(size > 2);
+
+ byte *decoded = (byte *)malloc((size - 1) * 2);
+
+ // Holmes uses Creative ADPCM 4-bit data
+ int counter = 0;
+ byte reference = ptr[0];
+ int16 scale = 0;
+
+ for (int i = 1; i < size; i++) {
+ decoded[counter++] = decodeSample((ptr[i] >> 4) & 0x0f, reference, scale);
+ decoded[counter++] = decodeSample((ptr[i] >> 0) & 0x0f, reference, scale);
+ }
+
+ free(data);
+
+ audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+ } else {
+ audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
+ }
+
+ if (!audioStream)
+ return false;
+
+ _mixer->playStream(soundType, &handle, audioStream, -1, Audio::Mixer::kMaxChannelVolume);
+ return true;
}
} // End of namespace Sherlock
diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h
index e1c0777763..b2d1584e85 100644
--- a/engines/sherlock/sound.h
+++ b/engines/sherlock/sound.h
@@ -39,26 +39,37 @@ enum WaitType {
WAIT_RETURN_IMMEDIATELY = 0, WAIT_FINISH = 1, WAIT_KBD_OR_FINISH = 2
};
+#define MAX_MIXER_CHANNELS 10
+
class Sound {
private:
SherlockEngine *_vm;
Audio::Mixer *_mixer;
- Audio::SoundHandle _effectsHandle;
+ Audio::SoundHandle _scalpelEffectsHandle;
+ Audio::SoundHandle _tattooEffectsHandle[MAX_MIXER_CHANNELS];
+ Audio::SoundHandle _speechHandle;
int _curPriority;
+ /**
+ * Decode a sound sample
+ */
byte decodeSample(byte sample, byte& reference, int16& scale);
+
+ /**
+ * Handle playing a sound or speech
+ */
+ bool playSoundResource(const Common::String &name, const Common::String &libFilename,
+ Audio::Mixer::SoundType soundType, Audio::SoundHandle &handle);
public:
bool _digitized;
int _voices;
bool _soundOn;
bool _speechOn;
- bool _diskSoundPlaying;
bool _soundPlaying;
- bool *_soundIsOn;
- byte *_digiBuf;
- bool _midiDrvLoaded;
- Common::String _currentSongName, _nextSongName;
- int _musicVolume;
+ bool _speechPlaying;
+ int _soundVolume;
+
+ Common::String _talkSoundFile;
public:
Sound(SherlockEngine *vm, Audio::Mixer *mixer);
@@ -92,9 +103,26 @@ public:
*/
void stopSound();
- void stopSndFuncPtr(int v1, int v2);
void freeDigiSound();
- void setMIDIVolume(int volume);
+
+ Audio::SoundHandle getFreeSoundHandle();
+
+ void setVolume(int volume);
+
+ /**
+ * Play a specified voice resource
+ */
+ void playSpeech(const Common::String &name);
+
+ /**
+ * Stop any currently playing speech
+ */
+ void stopSpeech();
+
+ /**
+ * Returns true if speech is currently playing
+ */
+ bool isSpeechPlaying();
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp
index 5a9e59e01b..b56692c704 100644
--- a/engines/sherlock/surface.cpp
+++ b/engines/sherlock/surface.cpp
@@ -28,11 +28,11 @@
namespace Sherlock {
-Surface::Surface(uint16 width, uint16 height) : _freePixels(true) {
+Surface::Surface(uint16 width, uint16 height) : Fonts(), _freePixels(true) {
create(width, height);
}
-Surface::Surface() : _freePixels(false) {
+Surface::Surface() : Fonts(), _freePixels(false) {
}
Surface::~Surface() {
@@ -44,10 +44,18 @@ void Surface::create(uint16 width, uint16 height) {
if (_freePixels)
_surface.free();
- _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ if (_vm->getPlatform() == Common::kPlatform3DO) {
+ _surface.create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ } else {
+ _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ }
_freePixels = true;
}
+Graphics::PixelFormat Surface::getPixelFormat() {
+ return _surface.format;
+}
+
void Surface::blitFrom(const Surface &src) {
blitFrom(src, Common::Point(0, 0));
}
@@ -92,17 +100,51 @@ void Surface::blitFrom(const Surface &src, const Common::Point &pt, const Common
}
void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt,
- bool flipped, int overrideColor) {
- transBlitFrom(src._frame, pt + src._offset, flipped, overrideColor);
+ bool flipped, int overrideColor, int scaleVal) {
+ Common::Point drawPt(pt.x + src.sDrawXOffset(scaleVal), pt.y + src.sDrawYOffset(scaleVal));
+ transBlitFrom(src._frame, drawPt, flipped, overrideColor, scaleVal);
}
void Surface::transBlitFrom(const Surface &src, const Common::Point &pt,
- bool flipped, int overrideColor) {
+ bool flipped, int overrideColor, int scaleVal) {
const Graphics::Surface &s = src._surface;
- transBlitFrom(s, pt, flipped, overrideColor);
+ transBlitFrom(s, pt, flipped, overrideColor, scaleVal);
}
void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
+ bool flipped, int overrideColor, int scaleVal) {
+ if (scaleVal == SCALE_THRESHOLD) {
+ transBlitFromUnscaled(src, pt, flipped, overrideColor);
+ return;
+ }
+
+ int destWidth = src.w * SCALE_THRESHOLD / scaleVal;
+ int destHeight = src.h * SCALE_THRESHOLD / scaleVal;
+
+ // Loop through drawing output lines
+ for (int destY = pt.y, scaleYCtr = 0; destY < (pt.y + destHeight); ++destY, scaleYCtr += scaleVal) {
+ if (destY < 0 || destY >= this->h())
+ continue;
+ const byte *srcLine = (const byte *)src.getBasePtr(0, scaleYCtr / SCALE_THRESHOLD);
+ byte *destLine = (byte *)getBasePtr(pt.x, destY);
+
+ // Loop through drawing individual rows
+ for (int xCtr = 0, scaleXCtr = 0; xCtr < destWidth; ++xCtr, scaleXCtr += scaleVal) {
+ int destX = pt.x + xCtr;
+ if (destX < 0 || destX >= this->w())
+ continue;
+
+ byte srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD];
+ if (srcVal != TRANSPARENCY)
+ destLine[xCtr] = srcVal;
+ }
+ }
+
+ // Mark the affected area
+ addDirtyRect(Common::Rect(pt.x, pt.y, pt.x + destWidth, pt.y + destHeight));
+}
+
+void Surface::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt,
bool flipped, int overrideColor) {
Common::Rect drawRect(0, 0, src.w, src.h);
Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h);
@@ -120,19 +162,42 @@ void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &p
addDirtyRect(Common::Rect(destPt.x, destPt.y, destPt.x + drawRect.width(),
destPt.y + drawRect.height()));
- // Draw loop
- const int TRANSPARENCY = 0xFF;
- for (int yp = 0; yp < drawRect.height(); ++yp) {
- const byte *srcP = (const byte *)src.getBasePtr(
- flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
- byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp);
-
- for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) {
- if (*srcP != TRANSPARENCY)
- *destP = overrideColor ? overrideColor : *srcP;
-
- srcP = flipped ? srcP - 1 : srcP + 1;
+ switch (src.format.bytesPerPixel) {
+ case 1:
+ // 8-bit palettized: Draw loop
+ assert(_surface.format.bytesPerPixel == 1); // Security check
+ for (int yp = 0; yp < drawRect.height(); ++yp) {
+ const byte *srcP = (const byte *)src.getBasePtr(
+ flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
+ byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp);
+
+ for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) {
+ if (*srcP != TRANSPARENCY)
+ *destP = overrideColor ? overrideColor : *srcP;
+
+ srcP = flipped ? srcP - 1 : srcP + 1;
+ }
}
+ break;
+ case 2:
+ // 3DO 15-bit RGB565: Draw loop
+ assert(_surface.format.bytesPerPixel == 2); // Security check
+ for (int yp = 0; yp < drawRect.height(); ++yp) {
+ const uint16 *srcP = (const uint16 *)src.getBasePtr(
+ flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
+ uint16 *destP = (uint16 *)getBasePtr(destPt.x, destPt.y + yp);
+
+ for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) {
+ if (*srcP) // RGB 0, 0, 0 -> transparent on 3DO
+ *destP = *srcP; // overrideColor ? overrideColor : *srcP;
+
+ srcP = flipped ? srcP - 1 : srcP + 1;
+ }
+ }
+ break;
+ default:
+ error("Surface: unsupported bytesperpixel");
+ break;
}
}
@@ -145,6 +210,10 @@ void Surface::fillRect(const Common::Rect &r, byte color) {
addDirtyRect(r);
}
+void Surface::fill(uint16 color) {
+ _surface.fillRect(Common::Rect(_surface.w, _surface.h), color);
+}
+
bool Surface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) {
if (destBounds.left >= _surface.w || destBounds.top >= _surface.h ||
destBounds.right <= 0 || destBounds.bottom <= 0)
@@ -185,17 +254,28 @@ void Surface::free() {
}
}
-void Surface::setPixels(byte *pixels, int width, int height) {
- _surface.format = Graphics::PixelFormat::createFormatCLUT8();
- _surface.w = _surface.pitch = width;
+void Surface::setPixels(byte *pixels, int width, int height, Graphics::PixelFormat pixelFormat) {
+ _surface.format = pixelFormat;
+ _surface.w = width;
_surface.h = height;
+ _surface.pitch = width * pixelFormat.bytesPerPixel;
_surface.setPixels(pixels);
}
-void Surface::maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX) {
- // TODO
- error("TODO: maskArea");
+void Surface::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) {
+ Fonts::writeString(this, str, pt, overrideColor);
}
+void Surface::writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2) {
+ writeString(str, Common::Point(pt.x, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x, pt.y + 1), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y + 1), overrideColor1);
+ writeString(str, Common::Point(pt.x, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y + 1), overrideColor2);
+}
} // End of namespace Sherlock
diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h
index 663f87f37f..385fb1793e 100644
--- a/engines/sherlock/surface.h
+++ b/engines/sherlock/surface.h
@@ -24,13 +24,18 @@
#define SHERLOCK_GRAPHICS_H
#include "common/rect.h"
+#include "common/platform.h"
#include "graphics/surface.h"
+#include "sherlock/fonts.h"
namespace Sherlock {
+#define SCALE_THRESHOLD 0x100
+#define TRANSPARENCY 255
+
struct ImageFrame;
-class Surface {
+class Surface: public Fonts {
private:
bool _freePixels;
@@ -45,14 +50,16 @@ private:
void blitFrom(const Graphics::Surface &src);
/**
- * Draws a surface at a given position within this surface
+ * Draws a sub-section of a surface at a given position within this surface
*/
- void blitFrom(const Graphics::Surface &src, const Common::Point &pt);
+ void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds);
/**
- * Draws a sub-section of a surface at a given position within this surface
+ * Draws a surface at a given position within this surface with transparency
*/
- void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds);
+ void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped,
+ int overrideColor);
+
protected:
Graphics::Surface _surface;
@@ -68,6 +75,8 @@ public:
*/
void create(uint16 width, uint16 height);
+ Graphics::PixelFormat getPixelFormat();
+
/**
* Copy a surface into this one
*/
@@ -99,22 +108,27 @@ public:
void blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds);
/**
+ * Draws a surface at a given position within this surface
+ */
+ void blitFrom(const Graphics::Surface &src, const Common::Point &pt);
+
+ /**
* Draws an image frame at a given position within this surface with transparency
*/
void transBlitFrom(const ImageFrame &src, const Common::Point &pt,
- bool flipped = false, int overrideColor = 0);
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
/**
* Draws a surface at a given position within this surface with transparency
*/
void transBlitFrom(const Surface &src, const Common::Point &pt,
- bool flipped = false, int overrideColor = 0);
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
/**
* Draws a surface at a given position within this surface with transparency
*/
void transBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
- bool flipped = false, int overrideColor = 0);
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
/**
* Fill a given area of the surface with a given color
@@ -126,10 +140,10 @@ public:
*/
void fillRect(const Common::Rect &r, byte color);
- void maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX);
+ void fill(uint16 color);
/**
- * Clear the screen
+ * Clear the surface
*/
void clear();
@@ -139,9 +153,20 @@ public:
void free();
/**
+ * Returns true if the surface is empty
+ */
+ bool empty() const { return _surface.getPixels() == nullptr; }
+
+ /**
* Set the pixels for the surface to an existing data block
*/
- void setPixels(byte *pixels, int width, int height);
+ void setPixels(byte *pixels, int width, int height, Graphics::PixelFormat format);
+
+ /**
+ * Draws the given string into the back buffer using the images stored in _font
+ */
+ virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor);
+ void writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2);
inline uint16 w() const { return _surface.w; }
inline uint16 h() const { return _surface.h; }
@@ -149,6 +174,7 @@ public:
inline byte *getPixels() { return (byte *)_surface.getPixels(); }
inline byte *getBasePtr(int x, int y) { return (byte *)_surface.getBasePtr(x, y); }
inline const byte *getBasePtr(int x, int y) const { return (const byte *)_surface.getBasePtr(x, y); }
+ inline Graphics::Surface &getRawSurface() { return _surface; }
inline void hLine(int x, int y, int x2, uint32 color) { _surface.hLine(x, y, x2, color); }
inline void vLine(int x, int y, int y2, uint32 color) { _surface.vLine(x, y, y2, color); }
};
diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp
index 59897e2c2a..fa00b9d715 100644
--- a/engines/sherlock/talk.cpp
+++ b/engines/sherlock/talk.cpp
@@ -23,162 +23,29 @@
#include "sherlock/talk.h"
#include "sherlock/sherlock.h"
#include "sherlock/screen.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_talk.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_talk.h"
namespace Sherlock {
-#define SPEAKER_REMOVE 0x80
-
-const byte SCALPEL_OPCODES[] = {
- 128, // OP_SWITCH_SPEAKER
- 129, // OP_RUN_CANIMATION
- 130, // OP_ASSIGN_PORTRAIT_LOCATION
- 131, // OP_PAUSE
- 132, // OP_REMOVE_PORTRAIT
- 133, // OP_CLEAR_WINDOW
- 134, // OP_ADJUST_OBJ_SEQUENCE
- 135, // OP_WALK_TO_COORDS
- 136, // OP_PAUSE_WITHOUT_CONTROL
- 137, // OP_BANISH_WINDOW
- 138, // OP_SUMMON_WINDOW
- 139, // OP_SET_FLAG
- 140, // OP_SFX_COMMAND
- 141, // OP_TOGGLE_OBJECT
- 142, // OP_STEALTH_MODE_ACTIVE
- 143, // OP_IF_STATEMENT
- 144, // OP_ELSE_STATEMENT
- 145, // OP_END_IF_STATEMENT
- 146, // OP_STEALTH_MODE_DEACTIVATE
- 147, // OP_TURN_HOLMES_OFF
- 148, // OP_TURN_HOLMES_ON
- 149, // OP_GOTO_SCENE
- 150, // OP_PLAY_PROLOGUE
- 151, // OP_ADD_ITEM_TO_INVENTORY
- 152, // OP_SET_OBJECT
- 153, // OP_CALL_TALK_FILE
- 143, // OP_MOVE_MOUSE
- 155, // OP_DISPLAY_INFO_LINE
- 156, // OP_CLEAR_INFO_LINE
- 157, // OP_WALK_TO_CANIMATION
- 158, // OP_REMOVE_ITEM_FROM_INVENTORY
- 159, // OP_ENABLE_END_KEY
- 160, // OP_DISABLE_END_KEY
- 161, // OP_CARRIAGE_RETURN
- 0, // OP_MOUSE_ON_OFF
- 0, // OP_SET_WALK_CONTROL
- 0, // OP_SET_TALK_SEQUENCE
- 0, // OP_PLAY_SONG
- 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
- 0, // OP_SET_NPC_PATH_DEST
- 0, // OP_NEXT_SONG
- 0, // OP_SET_NPC_PATH_PAUSE
- 0, // OP_PASSWORD
- 0, // OP_SET_SCENE_ENTRY_FLAG
- 0, // OP_WALK_NPC_TO_CANIM
- 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
- 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
- 0, // OP_SET_NPC_TALK_FILE
- 0, // OP_TURN_NPC_OFF
- 0, // OP_TURN_NPC_ON
- 0, // OP_NPC_DESC_ON_OFF
- 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES
- 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
- 0, // OP_ENABLE_TALK_INTERRUPTS
- 0, // OP_DISABLE_TALK_INTERRUPTS
- 0, // OP_SET_NPC_INFO_LINE
- 0, // OP_SET_NPC_POSITION
- 0, // OP_NPC_PATH_LABEL
- 0, // OP_PATH_GOTO_LABEL
- 0, // OP_PATH_IF_FLAG_GOTO_LABEL
- 0, // OP_NPC_WALK_GRAPHICS
- 0, // OP_NPC_VERB
- 0, // OP_NPC_VERB_CANIM
- 0, // OP_NPC_VERB_SCRIPT
- 0, // OP_RESTORE_PEOPLE_SEQUENCE
- 0, // OP_NPC_VERB_TARGET
- 0 // OP_TURN_SOUNDS_OFF
-};
-
-const byte TATTOO_OPCODES[] = {
- 170, // OP_SWITCH_SPEAKER
- 171, // OP_RUN_CANIMATION
- 0, // OP_ASSIGN_PORTRAIT_LOCATION
- 173, // OP_PAUSE
- 0, // OP_REMOVE_PORTRAIT
- 0, // OP_CLEAR_WINDOW
- 176, // OP_ADJUST_OBJ_SEQUENCE
- 177, // OP_WALK_TO_COORDS
- 178, // OP_PAUSE_WITHOUT_CONTROL
- 179, // OP_BANISH_WINDOW
- 0, // OP_SUMMON_WINDOW
- 181, // OP_SET_FLAG
- 0, // OP_SFX_COMMAND
- 183, // OP_TOGGLE_OBJECT
- 184, // OP_STEALTH_MODE_ACTIVE
- 0, // OP_IF_STATEMENT
- 0, // OP_ELSE_STATEMENT
- 0, // OP_END_IF_STATEMENT
- 188, // OP_STEALTH_MODE_DEACTIVATE
- 189, // OP_TURN_HOLMES_OFF
- 190, // OP_TURN_HOLMES_ON
- 191, // OP_GOTO_SCENE
- 0, // OP_PLAY_PROLOGUE
- 193, // OP_ADD_ITEM_TO_INVENTORY
- 194, // OP_SET_OBJECT
- 172, // OP_CALL_TALK_FILE
- 0, // OP_MOVE_MOUSE
- 0, // OP_DISPLAY_INFO_LINE
- 0, // OP_CLEAR_INFO_LINE
- 199, // OP_WALK_TO_CANIMATION
- 200, // OP_REMOVE_ITEM_FROM_INVENTORY
- 201, // OP_ENABLE_END_KEY
- 202, // OP_DISABLE_END_KEY
- 0, // OP_CARRIAGE_RETURN
- 174, // OP_MOUSE_ON_OFF
- 175, // OP_SET_WALK_CONTROL
- 180, // OP_SET_TALK_SEQUENCE
- 182, // OP_PLAY_SONG
- 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
- 192, // OP_SET_NPC_PATH_DEST
- 195, // OP_NEXT_SONG
- 196, // OP_SET_NPC_PATH_PAUSE
- 197, // OP_PASSWORD
- 198, // OP_SET_SCENE_ENTRY_FLAG
- 185, // OP_WALK_NPC_TO_CANIM
- 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
- 205, // OP_SET_NPC_TALK_FILE
- 206, // OP_TURN_NPC_OFF
- 207, // OP_TURN_NPC_ON
- 208, // OP_NPC_DESC_ON_OFF
- 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES
- 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
- 211, // OP_ENABLE_TALK_INTERRUPTS
- 212, // OP_DISABLE_TALK_INTERRUPTS
- 213, // OP_SET_NPC_INFO_LINE
- 214, // OP_SET_NPC_POSITION
- 215, // OP_NPC_PATH_LABEL
- 216, // OP_PATH_GOTO_LABEL
- 217, // OP_PATH_IF_FLAG_GOTO_LABEL
- 218, // OP_NPC_WALK_GRAPHICS
- 220, // OP_NPC_VERB
- 221, // OP_NPC_VERB_CANIM
- 222, // OP_NPC_VERB_SCRIPT
- 224, // OP_RESTORE_PEOPLE_SEQUENCE
- 226, // OP_NPC_VERB_TARGET
- 227 // OP_TURN_SOUNDS_OFF
-};
-
-/*----------------------------------------------------------------*/
-
SequenceEntry::SequenceEntry() {
_objNum = 0;
- _frameNumber = 0;
+ _obj = nullptr;
+ _seqStack = 0;
_seqTo = 0;
+ _sequenceNumber = _frameNumber = 0;
+ _seqCounter = _seqCounter2 = 0;
}
/*----------------------------------------------------------------*/
-void Statement::synchronize(Common::SeekableReadStream &s) {
+void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
int length;
length = s.readUint16LE();
@@ -212,6 +79,7 @@ void Statement::synchronize(Common::SeekableReadStream &s) {
_portraitSide = s.readByte();
_quotient = s.readUint16LE();
+ _journal = isRoseTattoo ? s.readByte() : 0;
}
/*----------------------------------------------------------------*/
@@ -222,26 +90,17 @@ TalkHistoryEntry::TalkHistoryEntry() {
/*----------------------------------------------------------------*/
-TalkSequences::TalkSequences(const byte *data) {
- Common::copy(data, data + MAX_TALK_SEQUENCES, _data);
-}
-
-void TalkSequences::clear() {
- Common::fill(&_data[0], &_data[MAX_TALK_SEQUENCES], 0);
-}
-
-/*----------------------------------------------------------------*/
-
Talk *Talk::init(SherlockEngine *vm) {
if (vm->getGameID() == GType_SerratedScalpel)
- return new ScalpelTalk(vm);
+ return new Scalpel::ScalpelTalk(vm);
else
- return new TattooTalk(vm);
+ return new Tattoo::TattooTalk(vm);
}
Talk::Talk(SherlockEngine *vm) : _vm(vm) {
_talkCounter = 0;
_talkToAbort = false;
+ _openTalkWindow = false;
_speaker = 0;
_talkIndex = 0;
_talkTo = 0;
@@ -252,7 +111,9 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) {
_moreTalkDown = _moreTalkUp = false;
_scriptMoreFlag = 0;
_scriptSaveIndex = -1;
- _opcodes = IS_SERRATED_SCALPEL ? SCALPEL_OPCODES : TATTOO_OPCODES;
+ _opcodes = nullptr;
+ _opcodeTable = nullptr;
+ _3doSpeechIndex = -1;
_charCount = 0;
_line = 0;
@@ -261,15 +122,19 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) {
_pauseFlag = false;
_seqCount = 0;
_scriptStart = _scriptEnd = nullptr;
+ _endStr = _noTextYet = false;
+
+ _talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500);
}
-void Talk::talkTo(const Common::String &filename) {
+void Talk::talkTo(const Common::String filename) {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
Journal &journal = *_vm->_journal;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
+ Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
Common::Rect savedBounds = screen.getDisplayBounds();
bool abortFlag = false;
@@ -280,7 +145,11 @@ void Talk::talkTo(const Common::String &filename) {
// If there any canimations currently running, or a portrait is being cleared,
// save the filename for later executing when the canimation is done
- if (scene._canimShapes.size() > 0 || people._clearingThePortrait) {
+ bool ongoingAnim = scene._canimShapes.size() > 0;
+ if (IS_ROSE_TATTOO) {
+ ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active();
+ }
+ if (ongoingAnim || people._clearingThePortrait) {
// Make sure we're not in the middle of a script
if (!_scriptMoreFlag) {
_scriptName = filename;
@@ -301,13 +170,13 @@ void Talk::talkTo(const Common::String &filename) {
// Turn on the Exit option
ui._endKeyActive = true;
- if (people[AL]._walkCount || people._walkTo.size() > 0) {
- // Only interrupt if an action if trying to do an action, and not just
- // if the player is walking around the scene
+ if (people[HOLMES]._walkCount || (!people[HOLMES]._walkTo.empty() &&
+ (IS_SERRATED_SCALPEL || people._allowWalkAbort))) {
+ // Only interrupt if trying to do an action, and not just if player is walking around the scene
if (people._allowWalkAbort)
abortFlag = true;
- people.gotoStand(people._player);
+ people[HOLMES].gotoStand();
}
if (_talkToAbort)
@@ -327,13 +196,19 @@ void Talk::talkTo(const Common::String &filename) {
}
}
- while (!_sequenceStack.empty())
+ if (IS_ROSE_TATTOO) {
pullSequence();
+ } else {
+ while (!isSequencesEmpty())
+ pullSequence();
+ }
if (IS_SERRATED_SCALPEL) {
// Restore any pressed button
if (!ui._windowOpen && savedMode != STD_MODE)
- ((Scalpel::ScalpelUserInterface *)_vm->_ui)->restoreButton((int)(savedMode - 1));
+ static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1));
+ } else {
+ static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
}
// Clear the ui counter so that anything displayed on the info line
@@ -462,9 +337,19 @@ void Talk::talkTo(const Common::String &filename) {
_scriptSelect = select;
_speaker = _talkTo;
- Statement &statement = _statements[select];
+ // Set up the talk file extension
+ if (IS_ROSE_TATTOO && sound._speechOn && _scriptMoreFlag != 1)
+ sound._talkSoundFile += Common::String::format("%02dB", select + 1);
+
+ // Make a copy of the statement (in case the script frees the statement list), and then execute it
+ Statement statement = _statements[select];
doScript(_statements[select]._reply);
+ if (IS_ROSE_TATTOO) {
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
+ people[idx]._misc = 0;
+ }
+
if (_talkToAbort)
return;
@@ -502,40 +387,9 @@ void Talk::talkTo(const Common::String &filename) {
// If the new conversion is a reply first, then we don't need
// to display any choices, since the reply needs to be shown
- if (!newStatement._statement.hasPrefix("*") &&
- !newStatement._statement.hasPrefix("^")) {
- clearSequences();
- pushSequence(_talkTo);
- setStillSeq(_talkTo);
+ if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) {
_talkIndex = select;
- ui._selector = ui._oldSelector = -1;
-
- if (!ui._windowOpen) {
- // Draw the talk interface on the back buffer
- drawInterface();
- displayTalk(false);
- } else {
- displayTalk(true);
- }
-
- byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL;
-
- // If the window is already open, simply draw. Otherwise, do it
- // to the back buffer and then summon the window
- if (ui._windowOpen) {
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, "Exit");
- } else {
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, "Exit");
-
- if (!ui._slideWindows) {
- screen.slamRect(Common::Rect(0, CONTROLS_Y,
- SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
- } else {
- ui.summonWindow();
- }
-
- ui._windowOpen = true;
- }
+ showTalk();
// Break out of loop now that we're waiting for player input
events.setCursor(ARROW);
@@ -545,7 +399,6 @@ void Talk::talkTo(const Common::String &filename) {
if (_talkTo != -1 && !_talkHistory[_converseNum][select])
journal.record(_converseNum, select, true);
_talkHistory[_converseNum][select] = true;
-
}
ui._key = ui._oldKey = Scalpel::COMMANDS[TALK_MODE - 1];
@@ -555,13 +408,17 @@ void Talk::talkTo(const Common::String &filename) {
} else {
freeTalkVars();
- if (!ui._lookScriptFlag) {
- ui.drawInterface(2);
- ui.banishWindow();
- ui._windowBounds.top = CONTROLS_Y1;
- ui._menuMode = STD_MODE;
+ if (IS_SERRATED_SCALPEL) {
+ if (!ui._lookScriptFlag) {
+ ui.drawInterface(2);
+ ui._menuMode = STD_MODE;
+ ui._windowBounds.top = CONTROLS_Y1;
+ }
+ } else {
+ ui._menuMode = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_labTableScene ? LAB_MODE : STD_MODE;
}
+ ui.banishWindow();
break;
}
}
@@ -582,27 +439,21 @@ void Talk::talkTo(const Common::String &filename) {
// previous script can continue
popStack();
- if (_vm->getGameID() == GType_SerratedScalpel && filename == "Tube59c") {
- // WORKAROUND: Original game bug causes the results of testing the powdery substance
- // to disappear too quickly. Introduce a delay to allow it to be properly displayed
- ui._menuCounter = 30;
- }
-
events.setCursor(ARROW);
}
-void Talk::talk(int objNum) {
+void Talk::initTalk(int objNum) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
UserInterface &ui = *_vm->_ui;
- Object &obj = scene._bgShapes[objNum];
ui._windowBounds.top = CONTROLS_Y;
ui._infoFlag = true;
_speaker = SPEAKER_REMOVE;
- loadTalkFile(scene._bgShapes[objNum]._name);
+
+ Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name;
+ loadTalkFile(talkFilename);
// Find the first statement with the correct flags
int select = -1;
@@ -612,8 +463,15 @@ void Talk::talk(int objNum) {
break;
}
}
- if (select == -1)
- error("No entry matched all required flags");
+
+ if (select == -1) {
+ freeTalkVars();
+ if (!scumm_strnicmp(talkFilename.c_str(), "PATH", 4))
+ error("No entries found to execute in path file");
+
+ nothingToSay();
+ return;
+ }
// See if the statement is a stealth mode reply
Statement &statement = _statements[select];
@@ -623,35 +481,44 @@ void Talk::talk(int objNum) {
// Start talk in stealth mode
_talkStealth = 2;
- talkTo(obj._name);
+ talkTo(talkFilename);
} else if (statement._statement.hasPrefix("*")) {
// Character being spoken to will speak first
- clearSequences();
- pushSequence(_talkTo);
- setStillSeq(_talkTo);
+ if (objNum > 1000) {
+ (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
+ } else {
+ Object &obj = scene._bgShapes[objNum];
+ clearSequences();
+ pushSequence(_talkTo);
+ people.setListenSequence(_talkTo, 129);
- events.setCursor(WAIT);
- if (obj._lookPosition.y != 0)
- // Need to walk to character first
- people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100),
- obj._lookFacing);
- events.setCursor(ARROW);
+ events.setCursor(WAIT);
+ if (obj._lookPosition.y != 0)
+ // Need to walk to character first
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
+ events.setCursor(ARROW);
+ }
if (!_talkToAbort)
- talkTo(obj._name);
+ talkTo(talkFilename);
} else {
// Holmes will be speaking first
- clearSequences();
- pushSequence(_talkTo);
- setStillSeq(_talkTo);
-
_talkToFlag = false;
- events.setCursor(WAIT);
- if (obj._lookPosition.y != 0)
- // Walk over to person to talk to
- people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100),
- obj._lookFacing);
- events.setCursor(ARROW);
+
+ if (objNum > 1000) {
+ (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
+ } else {
+ Object &obj = scene._bgShapes[objNum];
+ clearSequences();
+ pushSequence(_talkTo);
+ people.setListenSequence(_talkTo, 129);
+
+ events.setCursor(WAIT);
+ if (obj._lookPosition.y != 0)
+ // Walk over to person to talk to
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
+ events.setCursor(ARROW);
+ }
if (!_talkToAbort) {
// See if walking over triggered a conversation
@@ -662,21 +529,11 @@ void Talk::talk(int objNum) {
pullSequence();
}
} else {
- drawInterface();
-
- events._pressed = events._released = false;
_talkIndex = select;
- displayTalk(false);
- ui._selector = ui._oldSelector = -1;
+ showTalk();
- if (!ui._slideWindows) {
- screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH,
- SHERLOCK_SCREEN_HEIGHT));
- } else {
- ui.summonWindow();
- }
-
- ui._windowOpen = true;
+ // Break out of loop now that we're waiting for player input
+ events.setCursor(ARROW);
}
_talkToFlag = -1;
@@ -709,6 +566,12 @@ void Talk::loadTalkFile(const Common::String &filename) {
Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" :
Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk";
+ // Create the base of the sound filename used for talking in Rose Tattoo
+ if (IS_ROSE_TATTOO && _scriptMoreFlag != 1)
+ sound._talkSoundFile = Common::String(filename.c_str(), filename.c_str() + 7) + ".";
+ else if (IS_3DO)
+ _3doSpeechIndex = 1;
+
// Open the talk file for reading
Common::SeekableReadStream *talkStream = res.load(talkFile);
_converseNum = res.resourceIndex();
@@ -716,7 +579,7 @@ void Talk::loadTalkFile(const Common::String &filename) {
_statements.resize(talkStream->readByte());
for (uint idx = 0; idx < _statements.size(); ++idx)
- _statements[idx].synchronize(*talkStream);
+ _statements[idx].load(*talkStream, IS_ROSE_TATTOO);
delete talkStream;
@@ -765,317 +628,15 @@ void Talk::setTalkMap() {
}
}
-void Talk::drawInterface() {
- Screen &screen = *_vm->_screen;
- Surface &bb = *screen._backBuffer;
-
- bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
- bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10,
- SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2,
- SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
- bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2,
- SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
-
- if (_talkTo != -1) {
- screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10),
- 119 - screen.stringWidth("Exit") / 2, "Exit");
- screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10),
- 159 - screen.stringWidth("Up") / 2, "Up");
- screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10),
- 200 - screen.stringWidth("Down") / 2, "Down");
- } else {
- int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE);
- screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10),
- 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE);
- screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P");
- }
-}
-
-bool Talk::displayTalk(bool slamIt) {
- Screen &screen = *_vm->_screen;
- int yp = CONTROLS_Y + 14;
- int lineY = -1;
- _moreTalkDown = _moreTalkUp = false;
-
- for (uint idx = 0; idx < _statements.size(); ++idx) {
- _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1;
- }
-
- if (_talkIndex) {
- for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) {
- if (_statements[idx]._talkMap != -1)
- _moreTalkUp = true;
- }
- }
-
- // Display the up arrow and enable Up button if the first option is scrolled off-screen
- if (_moreTalkUp) {
- if (slamIt) {
- screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~");
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "Up");
- } else {
- screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~");
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, "Up");
- }
- } else {
- if (slamIt) {
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, "Up");
- screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND);
- } else {
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up");
- screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11,
- 15, CONTROLS_Y + 22), INV_BACKGROUND);
- }
- }
-
- // Loop through the statements
- bool done = false;
- for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) {
- Statement &statement = _statements[idx];
-
- if (statement._talkMap != -1) {
- bool flag = _talkHistory[_converseNum][idx];
- lineY = talkLine(idx, statement._talkMap, flag ? TALK_NULL : INV_FOREGROUND,
- yp, slamIt);
-
- if (lineY != -1) {
- statement._talkPos.top = yp;
- yp = lineY;
- statement._talkPos.bottom = yp;
-
- if (yp == SHERLOCK_SCREEN_HEIGHT)
- done = true;
- } else {
- done = true;
- }
- }
- }
-
- // Display the down arrow and enable down button if there are more statements available down off-screen
- if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) {
- _moreTalkDown = true;
-
- if (slamIt) {
- screen.print(Common::Point(5, 190), INV_FOREGROUND, "|");
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down");
- } else {
- screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|");
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, "Down");
- }
- } else {
- if (slamIt) {
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, "Down");
- screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
- } else {
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down");
- screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
- }
- }
-
- return done;
-}
-
-int Talk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) {
- Screen &screen = *_vm->_screen;
- int idx = lineNum;
- Common::String msg, number;
- bool numberFlag = false;
-
- // Get the statement to display as well as optional number prefix
- if (idx < SPEAKER_REMOVE) {
- number = Common::String::format("%d.", stateNum + 1);
- numberFlag = true;
- } else {
- idx -= SPEAKER_REMOVE;
- }
- msg = _statements[idx]._statement;
-
- // Handle potentially multiple lines needed to display entire statement
- const char *lineStartP = msg.c_str();
- int maxWidth = 298 - (numberFlag ? 18 : 0);
- for (;;) {
- // Get as much of the statement as possible will fit on the
- Common::String sLine;
- const char *lineEndP = lineStartP;
- int width = 0;
- do {
- width += screen.charWidth(*lineEndP);
- } while (*++lineEndP && width < maxWidth);
-
- // Check if we need to wrap the line
- if (width >= maxWidth) {
- // Work backwards to the prior word's end
- while (*--lineEndP != ' ')
- ;
-
- sLine = Common::String(lineStartP, lineEndP++);
- } else {
- // Can display remainder of the statement on the current line
- sLine = Common::String(lineStartP);
- }
-
-
- if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) {
- // Need to directly display on-screen?
- if (slamIt) {
- // See if a numer prefix is needed or not
- if (numberFlag) {
- // Are we drawing the first line?
- if (lineStartP == msg.c_str()) {
- // We are, so print the number and then the text
- screen.print(Common::Point(16, lineY), color, "%s", number.c_str());
- }
-
- // Draw the line with an indent
- screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str());
- } else {
- screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str());
- }
- } else {
- if (numberFlag) {
- if (lineStartP == msg.c_str()) {
- screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str());
- }
-
- screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str());
- } else {
- screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str());
- }
- }
-
- // Move to next line, if any
- lineY += 9;
- lineStartP = lineEndP;
-
- if (!*lineEndP)
- break;
- } else {
- // We're close to the bottom of the screen, so stop display
- lineY = -1;
- break;
- }
- }
-
- if (lineY == -1 && lineStartP != msg.c_str())
- lineY = SHERLOCK_SCREEN_HEIGHT;
-
- // Return the Y position of the next line to follow this one
- return lineY;
-}
-
-void Talk::clearSequences() {
- _sequenceStack.clear();
-}
-
-void Talk::pullSequence() {
- Scene &scene = *_vm->_scene;
-
- if (_sequenceStack.empty())
- return;
-
- SequenceEntry seq = _sequenceStack.pop();
- if (seq._objNum != -1) {
- Object &obj = scene._bgShapes[seq._objNum];
-
- if (obj._seqSize < MAX_TALK_SEQUENCES) {
- warning("Tried to restore too few frames");
- } else {
- for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
- obj._sequences[idx] = seq._sequences[idx];
-
- obj._frameNumber = seq._frameNumber;
- obj._seqTo = seq._seqTo;
- }
- }
-}
-
void Talk::pushSequence(int speaker) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
// Only proceed if a speaker is specified
- if (speaker == -1)
- return;
-
- SequenceEntry seqEntry;
- if (!speaker) {
- seqEntry._objNum = -1;
- } else {
- seqEntry._objNum = people.findSpeaker(speaker);
-
- if (seqEntry._objNum != -1) {
- Object &obj = scene._bgShapes[seqEntry._objNum];
- for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
- seqEntry._sequences.push_back(obj._sequences[idx]);
-
- seqEntry._frameNumber = obj._frameNumber;
- seqEntry._seqTo = obj._seqTo;
- }
- }
-
- _sequenceStack.push(seqEntry);
- if (_scriptStack.size() >= 5)
- error("script stack overflow");
-}
-
-void Talk::setSequence(int speaker) {
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
-
- // If no speaker is specified, then nothing needs to be done
- if (speaker == -1)
- return;
-
- if (speaker) {
+ if (speaker != -1) {
int objNum = people.findSpeaker(speaker);
- if (objNum != -1) {
- Object &obj = scene._bgShapes[objNum];
-
- if (obj._seqSize < MAX_TALK_SEQUENCES) {
- warning("Tried to copy too many talk frames");
- } else {
- for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
- obj._sequences[idx] = people._characters[speaker]._talkSequences[idx];
- if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1])
- return;
-
- obj._frameNumber = 0;
- obj._sequenceNumber = 0;
- }
- }
- }
- }
-}
-
-void Talk::setStillSeq(int speaker) {
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
-
- // Don't bother doing anything if no specific speaker is specified
- if (speaker == -1)
- return;
-
- if (speaker) {
- int objNum = people.findSpeaker(speaker);
- if (objNum != -1) {
- Object &obj = scene._bgShapes[objNum];
-
- if (obj._seqSize < MAX_TALK_SEQUENCES) {
- warning("Tried to copy too few still frames");
- } else {
- for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
- obj._sequences[idx] = people._characters[speaker]._stillSequences[idx];
- if (idx > 0 && !people._characters[speaker]._talkSequences[idx] &&
- !people._characters[speaker]._talkSequences[idx - 1])
- break;
- }
-
- obj._frameNumber = 0;
- obj._seqTo = 0;
- }
- }
+ if (objNum != -1)
+ pushSequenceEntry(&scene._bgShapes[objNum]);
}
}
@@ -1084,14 +645,12 @@ void Talk::doScript(const Common::String &script) {
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
UserInterface &ui = *_vm->_ui;
- bool openTalkWindow = false;
_savedSequences.clear();
_scriptStart = (const byte *)script.c_str();
_scriptEnd = _scriptStart + script.size();
const byte *str = _scriptStart;
- _yp = CONTROLS_Y + 12;
_charCount = 0;
_line = 0;
_wait = 0;
@@ -1099,6 +658,21 @@ void Talk::doScript(const Common::String &script) {
_seqCount = 0;
_noTextYet = true;
_endStr = false;
+ _openTalkWindow = false;
+
+ if (IS_SERRATED_SCALPEL)
+ _yp = CONTROLS_Y + 12;
+ else
+ _yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11;
+
+ if (IS_ROSE_TATTOO) {
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx];
+ p._savedNpcSequence = p._sequenceNumber;
+ p._savedNpcFrame = p._frameNumber;
+ p._resetNPCPath = true;
+ }
+ }
if (_scriptMoreFlag) {
_scriptMoreFlag = 0;
@@ -1110,48 +684,60 @@ void Talk::doScript(const Common::String &script) {
_talkStealth = 2;
_speaker |= SPEAKER_REMOVE;
} else {
- pushSequence(_speaker);
- ui.clearWindow();
+ if (IS_SERRATED_SCALPEL)
+ pushSequence(_speaker);
+ if (IS_SERRATED_SCALPEL || ui._windowOpen)
+ ui.clearWindow();
// Need to switch speakers?
if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) {
_speaker = str[1] - 1;
- str += 2;
- pullSequence();
- pushSequence(_speaker);
- setSequence(_speaker);
+
+ if (IS_SERRATED_SCALPEL) {
+ str += 2;
+ pullSequence();
+ pushSequence(_speaker);
+ } else {
+ str += 3;
+ }
+
+ people.setTalkSequence(_speaker);
} else {
- setSequence(_speaker);
+ people.setTalkSequence(_speaker);
}
- // Assign portrait location?
- if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) {
- switch (str[1] & 15) {
- case 1:
- people._portraitSide = 20;
- break;
- case 2:
- people._portraitSide = 220;
- break;
- case 3:
- people._portraitSide = 120;
- break;
- default:
- break;
+ if (IS_SERRATED_SCALPEL) {
+ // Assign portrait location?
+ if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) {
+ switch (str[1] & 15) {
+ case 1:
+ people._portraitSide = 20;
+ break;
+ case 2:
+ people._portraitSide = 220;
+ break;
+ case 3:
+ people._portraitSide = 120;
+ break;
+ default:
+ break;
- }
+ }
- if (str[1] > 15)
- people._speakerFlip = true;
- str += 2;
- }
+ if (str[1] > 15)
+ people._speakerFlip = true;
+ str += 2;
+ }
- // Remove portrait?
- if (str[0] == _opcodes[OP_REMOVE_PORTRAIT]) {
- _speaker = 255;
- } else {
- // Nope, so set the first speaker
- people.setTalking(_speaker);
+ if (IS_SERRATED_SCALPEL) {
+ // Remove portrait?
+ if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) {
+ _speaker = -1;
+ } else {
+ // Nope, so set the first speaker
+ ((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker);
+ }
+ }
}
}
@@ -1166,9 +752,9 @@ void Talk::doScript(const Common::String &script) {
// Start of comment, so skip over it
while (*str++ != '}')
;
- } else if (c >= 128 && c <= 227 && _opcodeTable[c - 128]) {
+ } else if (isOpcode(c)) {
// Handle control code
- switch ((this->*_opcodeTable[c - 128])(str)) {
+ switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) {
case RET_EXIT:
return;
case RET_CONTINUE:
@@ -1179,107 +765,12 @@ void Talk::doScript(const Common::String &script) {
++str;
} else {
- // If the window isn't yet open, draw the window before printing starts
- if (!ui._windowOpen && _noTextYet) {
- _noTextYet = false;
- drawInterface();
-
- if (_talkTo != -1) {
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit");
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up");
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down");
- }
- }
-
- // If it's the first line, display the speaker
- if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) {
- // If the window is open, display the name directly on-screen.
- // Otherwise, simply draw it on the back buffer
- if (ui._windowOpen) {
- screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s",
- people._characters[_speaker & 127]._name);
- } else {
- screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s",
- people._characters[_speaker & 127]._name);
- openTalkWindow = true;
- }
-
- _yp += 9;
- }
-
- // Find amount of text that will fit on the line
- int width = 0, idx = 0;
- do {
- width += screen.charWidth(str[idx]);
- ++idx;
- ++_charCount;
- } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < _opcodes[0]);
-
- if (str[idx] || width >= 298) {
- if (str[idx] < _opcodes[0] && str[idx] != '{') {
- --idx;
- --_charCount;
- }
- } else {
- _endStr = true;
- }
-
- // If word wrap is needed, find the start of the current word
- if (width >= 298) {
- while (str[idx] != ' ') {
- --idx;
- --_charCount;
- }
- }
-
- // Print the line
- Common::String lineStr((const char *)str, (const char *)str + idx);
-
- // If the speaker indicates a description file, print it in yellow
- if (_speaker != -1) {
- if (ui._windowOpen) {
- screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
- } else {
- screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
- openTalkWindow = true;
- }
- } else {
- if (ui._windowOpen) {
- screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
- } else {
- screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
- openTalkWindow = true;
- }
- }
-
- // Move to end of displayed line
- str += idx;
-
- // If line wrap occurred, then move to after the separating space between the words
- if (str[0] < _opcodes[0] && str[0] != '{')
- ++str;
-
- _yp += 9;
- ++_line;
-
- // Certain different conditions require a wait
- if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) ||
- (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) ||
- _endStr) {
- _wait = 1;
- }
-
- byte v = (str >= _scriptEnd ? 0 : str[0]);
- if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
- v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] ||
- v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] ||
- v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) {
- _wait = 1;
- }
+ // Handle drawing the talk interface with the text
+ talkInterface(str);
}
// Open window if it wasn't already open, and text has already been printed
- if ((openTalkWindow && _wait) || (openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_CARRIAGE_RETURN])) {
+ if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) {
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
@@ -1287,34 +778,12 @@ void Talk::doScript(const Common::String &script) {
}
ui._windowOpen = true;
- openTalkWindow = false;
+ _openTalkWindow = false;
}
- if (_wait) {
+ if (_wait)
// Handling pausing
- if (!_pauseFlag && _charCount < 160)
- _charCount = 160;
-
- _wait = waitForMore(_charCount);
- if (_wait == -1)
- _endStr = true;
-
- // If a key was pressed to finish the window, see if further voice files should be skipped
- if (_wait >= 0 && _wait < 254) {
- if (str[0] == _opcodes[OP_SFX_COMMAND])
- str += 9;
- }
-
- // Clear the window unless the wait was due to a PAUSE command
- if (!_pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) {
- if (!_talkStealth)
- ui.clearWindow();
- _yp = CONTROLS_Y + 12;
- _charCount = _line = 0;
- }
-
- _pauseFlag = false;
- }
+ talkWait(str);
} while (!_vm->shouldQuit() && !_endStr);
if (_wait != -1) {
@@ -1329,8 +798,13 @@ void Talk::doScript(const Common::String &script) {
}
pullSequence();
- if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
- people.clearTalking();
+
+ if (IS_SERRATED_SCALPEL) {
+ if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
+ people.clearTalking();
+ } else {
+ static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
+ }
}
}
@@ -1342,14 +816,23 @@ int Talk::waitForMore(int delay) {
UserInterface &ui = *_vm->_ui;
CursorId oldCursor = events.getCursor();
int key2 = 254;
+ bool playingSpeech = false;
// Unless we're in stealth mode, show the appropriate cursor
if (!_talkStealth) {
events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW);
}
+ // Handle playing any speech associated with the text being displayed
+ switchSpeaker();
+ if (sound._speechOn && IS_ROSE_TATTOO) {
+ sound.playSpeech(sound._talkSoundFile);
+ sound._talkSoundFile.setChar(sound._talkSoundFile.lastChar() + 1, sound._talkSoundFile.size() - 1);
+ }
+ playingSpeech = sound.isSpeechPlaying();
+
do {
- if (sound._speechOn && !*sound._soundIsOn)
+ if (IS_SERRATED_SCALPEL && sound._speechOn && !sound.isSpeechPlaying())
people._portrait._frameNumber = -1;
scene.doBgAnim();
@@ -1360,11 +843,21 @@ int Talk::waitForMore(int delay) {
events._released = true;
} else {
// See if there's been a button press
+ events.pollEventsAndWait();
events.setButtonState();
if (events.kbHit()) {
Common::KeyState keyState = events.getKey();
- if (Common::isPrint(keyState.ascii))
+ if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) {
+ // Skip out of the introduction
+ _vm->setFlags(-76);
+ _vm->setFlags(396);
+ scene._goToScene = 1;
+ }
+ break;
+
+ } else if (Common::isPrint(keyState.ascii))
key2 = keyState.keycode;
}
@@ -1378,18 +871,17 @@ int Talk::waitForMore(int delay) {
if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth)
--delay;
- // If there are voices playing, reset delay so that they keep playing
- if (sound._voices == 2 && *sound._soundIsOn)
+ if (playingSpeech && !sound.isSpeechPlaying())
delay = 0;
- } while (!_vm->shouldQuit() && key2 == 254 && (delay || (sound._voices == 2 && *sound._soundIsOn))
+ } while (!_vm->shouldQuit() && key2 == 254 && (delay || (playingSpeech && sound.isSpeechPlaying()))
&& !events._released && !events._rightReleased);
// If voices was set 2 to indicate a voice file was place, then reset it back to 1
if (sound._voices == 2)
sound._voices = 1;
- if (delay > 0 && sound._diskSoundPlaying)
- sound.stopSndFuncPtr(0, 0);
+ if (delay > 0 && sound.isSpeechPlaying())
+ sound.stopSpeech();
// Adjust _talkStealth mode:
// mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0
@@ -1406,13 +898,22 @@ int Talk::waitForMore(int delay) {
break;
}
- sound._speechOn = false;
+
+ sound.stopSpeech();
events.setCursor(_talkToAbort ? ARROW : oldCursor);
events._pressed = events._released = false;
return key2;
}
+bool Talk::isOpcode(byte checkCharacter) {
+ if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99)))
+ return false; // outside of range
+ if (_opcodeTable[checkCharacter - _opcodes[0]])
+ return true;
+ return false;
+}
+
void Talk::popStack() {
if (!_scriptStack.empty()) {
ScriptStackEntry scriptEntry = _scriptStack.pop();
@@ -1423,8 +924,8 @@ void Talk::popStack() {
}
}
-void Talk::synchronize(Common::Serializer &s) {
- for (int idx = 0; idx < MAX_TALK_FILES; ++idx) {
+void Talk::synchronize(Serializer &s) {
+ for (uint idx = 0; idx < _talkHistory.size(); ++idx) {
TalkHistoryEntry &he = _talkHistory[idx];
for (int flag = 0; flag < 16; ++flag)
@@ -1507,42 +1008,6 @@ OpcodeReturn Talk::cmdBanishWindow(const byte *&str) {
return RET_SUCCESS;
}
-OpcodeReturn Talk::cmdCallTalkFile(const byte *&str) {
- Common::String tempString;
-
- ++str;
- for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
- tempString += str[idx];
- str += 8;
-
- int scriptCurrentIndex = str - _scriptStart;
-
- // Save the current script position and new talk file
- if (_scriptStack.size() < 9) {
- ScriptStackEntry rec1;
- rec1._name = _scriptName;
- rec1._currentIndex = scriptCurrentIndex;
- rec1._select = _scriptSelect;
- _scriptStack.push(rec1);
-
- // Push the new talk file onto the stack
- ScriptStackEntry rec2;
- rec2._name = tempString;
- rec2._currentIndex = 0;
- rec2._select = 100;
- _scriptStack.push(rec2);
- }
- else {
- error("Script stack overflow");
- }
-
- _scriptMoreFlag = 1;
- _endStr = true;
- _wait = 0;
-
- return RET_SUCCESS;
-}
-
OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) {
_vm->_ui->_endKeyActive = false;
return RET_SUCCESS;
@@ -1553,44 +1018,20 @@ OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) {
return RET_SUCCESS;
}
-OpcodeReturn Talk::cmdGotoScene(const byte *&str) {
- Map &map = *_vm->_map;
- People &people = *_vm->_people;
- Scene &scene = *_vm->_scene;
- scene._goToScene = str[1] - 1;
-
- if (scene._goToScene != 100) {
- // Not going to the map overview
- map._oldCharPoint = scene._goToScene;
- map._overPos.x = map[scene._goToScene].x * 100 - 600;
- map._overPos.y = map[scene._goToScene].y * 100 + 900;
-
- // Run a canimation?
- if (str[2] > 100) {
- people._hSavedFacing = str[2];
- people._hSavedPos = Common::Point(160, 100);
- }
- }
- str += 6;
-
- _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1;
- _scriptSaveIndex = str - _scriptStart;
- _endStr = true;
- _wait = 0;
-
+OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) {
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdHolmesOff(const byte *&str) {
People &people = *_vm->_people;
- people[PLAYER]._type = REMOVE;
+ people[HOLMES]._type = REMOVE;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdHolmesOn(const byte *&str) {
People &people = *_vm->_people;
- people[PLAYER]._type = CHARACTER;
+ people[HOLMES]._type = CHARACTER;
return RET_SUCCESS;
}
@@ -1607,6 +1048,8 @@ OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) {
Scene &scene = *_vm->_scene;
++str;
+ events.incWaitCounter();
+
for (int idx = 0; idx < (str[0] - 1); ++idx) {
scene.doBgAnim();
if (_talkToAbort)
@@ -1617,6 +1060,9 @@ OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) {
events.setButtonState();
}
+ events.decWaitCounter();
+
+ _endStr = false;
return RET_SUCCESS;
}
@@ -1643,7 +1089,9 @@ OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) {
return RET_EXIT;
// Check if next character is changing side or changing portrait
- if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]))
+ _wait = 0;
+ if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] ||
+ (IS_SERRATED_SCALPEL && str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])))
_wait = 1;
return RET_SUCCESS;
@@ -1697,28 +1145,6 @@ OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) {
return RET_SUCCESS;
}
-OpcodeReturn Talk::cmdSwitchSpeaker(const byte *&str) {
- People &people = *_vm->_people;
- UserInterface &ui = *_vm->_ui;
-
- if (!(_speaker & SPEAKER_REMOVE))
- people.clearTalking();
- if (_talkToAbort)
- return RET_EXIT;
-
- ui.clearWindow();
- _yp = CONTROLS_Y + 12;
- _charCount = _line = 0;
-
- _speaker = *++str - 1;
- people.setTalking(_speaker);
- pullSequence();
- pushSequence(_speaker);
- setSequence(_speaker);
-
- return RET_SUCCESS;
-}
-
OpcodeReturn Talk::cmdToggleObject(const byte *&str) {
Scene &scene = *_vm->_scene;
Common::String tempString;
@@ -1739,397 +1165,26 @@ OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) {
++str;
CAnim &animation = scene._cAnim[str[0] - 1];
- people.walkToCoords(animation._goto, animation._gotoDir);
+ people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing);
return _talkToAbort ? RET_EXIT : RET_SUCCESS;
}
-OpcodeReturn Talk::cmdWalkToCoords(const byte *&str) {
- People &people = *_vm->_people;
- ++str;
-
- people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100,
- str[2] * 100), str[3] - 1);
- if (_talkToAbort)
- return RET_EXIT;
-
- str += 3;
- return RET_SUCCESS;
-}
-
-/*----------------------------------------------------------------*/
-
-ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) {
- static OpcodeMethod OPCODE_METHODS[] = {
- (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker,
- (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation,
- (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation,
-
- (OpcodeMethod)&ScalpelTalk::cmdPause,
- (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait,
- (OpcodeMethod)&ScalpelTalk::cmdClearWindow,
- (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence,
- (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords,
- (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl,
- (OpcodeMethod)&ScalpelTalk::cmdBanishWindow,
- (OpcodeMethod)&ScalpelTalk::cmdSummonWindow,
- (OpcodeMethod)&ScalpelTalk::cmdSetFlag,
- (OpcodeMethod)&ScalpelTalk::cmdSfxCommand,
-
- (OpcodeMethod)&ScalpelTalk::cmdToggleObject,
- (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate,
- (OpcodeMethod)&ScalpelTalk::cmdIf,
- (OpcodeMethod)&ScalpelTalk::cmdElse,
- nullptr,
- (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate,
- (OpcodeMethod)&ScalpelTalk::cmdHolmesOff,
- (OpcodeMethod)&ScalpelTalk::cmdHolmesOn,
- (OpcodeMethod)&ScalpelTalk::cmdGotoScene,
- (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue,
-
- (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory,
- (OpcodeMethod)&ScalpelTalk::cmdSetObject,
- (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile,
- (OpcodeMethod)&ScalpelTalk::cmdMoveMouse,
- (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine,
- (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine,
- (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation,
- (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory,
- (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey,
- (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey,
-
- (OpcodeMethod)&ScalpelTalk::cmdCarriageReturn,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
- };
-
- _opcodeTable = OPCODE_METHODS;
-}
-
-OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) {
- People &people = *_vm->_people;
-
- ++str;
- switch (str[0] & 15) {
- case 1:
- people._portraitSide = 20;
- break;
- case 2:
- people._portraitSide = 220;
- break;
- case 3:
- people._portraitSide = 120;
- break;
- default:
- break;
- }
-
- if (str[0] > 15)
- people._speakerFlip = true;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) {
- UserInterface &ui = *_vm->_ui;
-
- ui._infoFlag = true;
- ui.clearInfo();
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) {
- UserInterface &ui = *_vm->_ui;
-
- ui.clearWindow();
- _yp = CONTROLS_Y + 12;
- _charCount = _line = 0;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) {
- Screen &screen = *_vm->_screen;
- UserInterface &ui = *_vm->_ui;
- Common::String tempString;
-
- ++str;
- for (int idx = 0; idx < str[0]; ++idx)
- tempString += str[idx + 1];
- str += str[0];
-
- screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str());
- ui._menuCounter = 30;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) {
- // If this is encountered here, it means that a preceeding IF statement was found,
- // and evaluated to true. Now all the statements for the true block are finished,
- // so skip over the block of code that would have executed if the result was false
- _wait = 0;
- do {
- ++str;
- } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
-
- return RET_SUCCESS;
-}
+void Talk::talkWait(const byte *&str) {
+ if (!_pauseFlag && _charCount < 160)
+ _charCount = 160;
-OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) {
- ++str;
- int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0);
- ++str;
- _wait = 0;
+ _wait = waitForMore(_charCount);
+ if (_wait == -1)
+ _endStr = true;
- bool result = flag < 0x8000;
- if (_vm->readFlags(flag & 0x7fff) != result) {
- do {
- ++str;
- } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
-
- if (!str[0])
- _endStr = true;
+ // If a key was pressed to finish the window, see if further voice files should be skipped
+ if (_wait >= 0 && _wait < 254) {
+ if (str[0] == _opcodes[OP_SFX_COMMAND])
+ str += 9;
}
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) {
- Events &events = *_vm->_events;
-
- ++str;
- events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2]));
- if (_talkToAbort)
- return RET_EXIT;
- str += 3;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) {
- Animation &anim = *_vm->_animation;
- Common::String tempString;
-
- ++str;
- for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
- tempString += str[idx];
-
- anim.play(tempString, 1, 3, true, 4);
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) {
- People &people = *_vm->_people;
-
- if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
- people.clearTalking();
- pullSequence();
- if (_talkToAbort)
- return RET_EXIT;
-
- _speaker |= SPEAKER_REMOVE;
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) {
- Sound &sound = *_vm->_sound;
- Common::String tempString;
-
- ++str;
- if (sound._voices) {
- for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
- tempString += str[idx];
- sound.playSound(tempString, WAIT_RETURN_IMMEDIATELY);
-
- // Set voices to wait for more
- sound._voices = 2;
- sound._speechOn = (*sound._soundIsOn);
- }
-
- _wait = 1;
- str += 7;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) {
- Events &events = *_vm->_events;
- Screen &screen = *_vm->_screen;
-
- drawInterface();
- events._pressed = events._released = false;
- events.clearKeyboard();
- _noTextYet = false;
-
- if (_speaker != -1) {
- screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit");
- screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up");
- screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down");
- }
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn ScalpelTalk::cmdCarriageReturn(const byte *&str) {
- return RET_SUCCESS;
-}
-
-/*----------------------------------------------------------------*/
-
-TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm) {
- static OpcodeMethod OPCODE_METHODS[] = {
- nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
- (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker,
-
- (OpcodeMethod)&TattooTalk::cmdRunCAnimation,
- (OpcodeMethod)&TattooTalk::cmdCallTalkFile,
- (OpcodeMethod)&TattooTalk::cmdPause,
- (OpcodeMethod)&TattooTalk::cmdMouseOnOff,
- (OpcodeMethod)&TattooTalk::cmdSetWalkControl,
- (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence,
- (OpcodeMethod)&TattooTalk::cmdWalkToCoords,
- (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl,
- (OpcodeMethod)&TattooTalk::cmdBanishWindow,
- (OpcodeMethod)&TattooTalk::cmdSetTalkSequence,
-
- (OpcodeMethod)&TattooTalk::cmdSetFlag,
- (OpcodeMethod)&TattooTalk::cmdPlaySong,
- (OpcodeMethod)&TattooTalk::cmdToggleObject,
- (OpcodeMethod)&TattooTalk::cmdStealthModeActivate,
- (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation,
- (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords,
- (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
- (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate,
- (OpcodeMethod)&TattooTalk::cmdHolmesOff,
- (OpcodeMethod)&TattooTalk::cmdHolmesOn,
-
- (OpcodeMethod)&TattooTalk::cmdGotoScene,
- (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest,
- (OpcodeMethod)&TattooTalk::cmdAddItemToInventory,
- (OpcodeMethod)&TattooTalk::cmdSetObject,
- (OpcodeMethod)&TattooTalk::cmdNextSong,
- (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause,
- (OpcodeMethod)&TattooTalk::cmdPassword,
- (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag,
- (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation,
- (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory,
-
- (OpcodeMethod)&TattooTalk::cmdEnableEndKey,
- (OpcodeMethod)&TattooTalk::cmdDisableEndKey,
- nullptr,
- (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
- (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile,
- (OpcodeMethod)&TattooTalk::cmdSetNPCOff,
- (OpcodeMethod)&TattooTalk::cmdSetNPCOn,
- (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff,
- (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes,
- (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes,
-
- (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable,
- (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable,
- (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine,
- (OpcodeMethod)&TattooTalk::cmdSetNPCPosition,
- (OpcodeMethod)&TattooTalk::cmdNPCLabelSet,
- (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto,
- (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto,
- (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics,
- nullptr,
- (OpcodeMethod)&TattooTalk::cmdSetNPCVerb,
-
- (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation,
- (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript,
- nullptr,
- (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence,
- (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget,
- (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff
- };
-
- _opcodeTable = OPCODE_METHODS;
-}
-
-OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) { error("TODO: script opcode"); }
-
-OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) {
- Sound &sound = *_vm->_sound;
-
- // Get the name of the next song to play
- ++str;
- sound._nextSongName = "";
- for (int idx = 0; idx < 8; ++idx) {
- if (str[idx] != '~')
- sound._nextSongName += str[idx];
- else
- break;
- }
- str += 7;
-
- return RET_SUCCESS;
-}
-
-OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdPassword(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) { error("TODO: script opcode"); }
-
-OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) {
- ++str;
- int npc = *str - 1;
- People &people = *_vm->_people;
- Person &person = people[npc];
-
- // Build up walk library name for the given NPC
- person._walkVGSName = "";
- for (int idx = 0; idx < 8; ++idx) {
- if (str[idx + 1] != '~')
- person._walkVGSName += str[idx + 1];
- else
- break;
- }
- person._walkVGSName += ".VGS";
-
- people._forceWalkReload = true;
- str += 8;
-
- return RET_SUCCESS;
+ _pauseFlag = false;
}
-OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) { error("TODO: script opcode"); }
-OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) { error("TODO: script opcode"); }
-
} // End of namespace Sherlock
diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h
index ebfe8f1732..a22a39db94 100644
--- a/engines/sherlock/talk.h
+++ b/engines/sherlock/talk.h
@@ -26,14 +26,15 @@
#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/objects.h"
+#include "sherlock/saveload.h"
namespace Sherlock {
+#define SPEAKER_REMOVE 0x80
#define MAX_TALK_SEQUENCES 11
-#define MAX_TALK_FILES 500
enum {
OP_SWITCH_SPEAKER = 0,
@@ -69,7 +70,7 @@ enum {
OP_REMOVE_ITEM_FROM_INVENTORY = 30,
OP_ENABLE_END_KEY = 31,
OP_DISABLE_END_KEY = 32,
- OP_CARRIAGE_RETURN = 33,
+ OP_END_TEXT_WINDOW = 33,
OP_MOUSE_OFF_ON = 34,
OP_SET_WALK_CONTROL = 35,
@@ -82,27 +83,29 @@ enum {
OP_NEED_PASSWORD = 42,
OP_SET_SCENE_ENTRY_FLAG = 43,
OP_WALK_NPC_TO_CANIM = 44,
- OP_WALK_HOLMES_AND_NPC_TO_COORDS = 45,
- OP_SET_NPC_TALK_FILE = 46,
- OP_TURN_NPC_OFF = 47,
- OP_TURN_NPC_ON = 48,
- OP_NPC_DESC_ON_OFF = 49,
- OP_NPC_PATH_PAUSE_TAKING_NOTES = 50,
- OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 51,
- OP_ENABLE_TALK_INTERRUPTS = 52,
- OP_DISABLE_TALK_INTERRUPTS = 53,
- OP_SET_NPC_INFO_LINE = 54,
- OP_SET_NPC_POSITION = 54,
- OP_NPC_PATH_LABEL = 55,
- OP_PATH_GOTO_LABEL = 56,
- OP_PATH_IF_FLAG_GOTO_LABEL = 57,
- OP_NPC_WALK_GRAPHICS = 58,
- OP_NPC_VERB = 59,
- OP_NPC_VERB_CANIM = 60,
- OP_NPC_VERB_SCRIPT = 61,
- OP_RESTORE_PEOPLE_SEQUENCE = 62,
- OP_NPC_VERB_TARGET = 63,
- OP_TURN_SOUNDS_OFF = 64
+ OP_WALK_NPC_TO_COORDS = 45,
+ OP_WALK_HOLMES_AND_NPC_TO_COORDS = 46,
+ OP_SET_NPC_TALK_FILE = 47,
+ OP_TURN_NPC_OFF = 48,
+ OP_TURN_NPC_ON = 49,
+ OP_NPC_DESC_ON_OFF = 50,
+ OP_NPC_PATH_PAUSE_TAKING_NOTES = 51,
+ OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 52,
+ OP_ENABLE_TALK_INTERRUPTS = 53,
+ OP_DISABLE_TALK_INTERRUPTS = 54,
+ OP_SET_NPC_INFO_LINE = 55,
+ OP_SET_NPC_POSITION = 56,
+ OP_NPC_PATH_LABEL = 57,
+ OP_PATH_GOTO_LABEL = 58,
+ OP_PATH_IF_FLAG_GOTO_LABEL = 59,
+ OP_NPC_WALK_GRAPHICS = 60,
+ OP_NPC_VERB = 61,
+ OP_NPC_VERB_CANIM = 62,
+ OP_NPC_VERB_SCRIPT = 63,
+ OP_RESTORE_PEOPLE_SEQUENCE = 64,
+ OP_NPC_VERB_TARGET = 65,
+ OP_TURN_SOUNDS_OFF = 66,
+ OP_NULL = 67
};
enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 };
@@ -116,8 +119,13 @@ typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str);
struct SequenceEntry {
int _objNum;
Common::Array<byte> _sequences;
- int _frameNumber;
- int _seqTo;
+ Object *_obj; // Pointer to the bgshape that these values go to
+ short _frameNumber; // Frame number in frame sequence to draw
+ short _sequenceNumber; // Start frame of sequences that are repeated
+ int _seqStack; // Allows gosubs to return to calling frame
+ int _seqTo; // Allows 1-5, 8-3 type sequences encoded
+ int _seqCounter; // How many times this sequence has been executed
+ int _seqCounter2;
SequenceEntry();
};
@@ -139,11 +147,12 @@ struct Statement {
int _quotient;
int _talkMap;
Common::Rect _talkPos;
+ int _journal;
/**
* Load the data for a single statement within a talk file
*/
- void synchronize(Common::SeekableReadStream &s);
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
};
struct TalkHistoryEntry {
@@ -153,16 +162,6 @@ struct TalkHistoryEntry {
bool &operator[](int index) { return _data[index]; }
};
-struct TalkSequences {
- byte _data[MAX_TALK_SEQUENCES];
-
- TalkSequences() { clear(); }
- TalkSequences(const byte *data);
-
- byte &operator[](int idx) { return _data[idx]; }
- void clear();
-};
-
class Talk {
friend class Scalpel::ScalpelUserInterface;
private:
@@ -170,48 +169,18 @@ private:
* Remove any voice commands from a loaded statement list
*/
void stripVoiceCommands();
-
- /**
- * Form a table of the display indexes for statements
- */
- void setTalkMap();
-
- /**
- * Display a list of statements in a window at the bottom of the screen that the
- * player can select from.
- */
- bool displayTalk(bool slamIt);
-
- /**
- * Prints a single conversation option in the interface window
- */
- int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt);
-
- /**
- * Parses a reply for control codes and display text. The found text is printed within
- * the text window, handles delays, animations, and animating portraits.
- */
- void doScript(const Common::String &script);
-
- /**
- * When the talk window has been displayed, waits a period of time proportional to
- * the amount of text that's been displayed
- */
- int waitForMore(int delay);
protected:
SherlockEngine *_vm;
OpcodeMethod *_opcodeTable;
Common::Stack<SequenceEntry> _savedSequences;
- Common::Stack<SequenceEntry> _sequenceStack;
Common::Stack<ScriptStackEntry> _scriptStack;
- Common::Array<Statement> _statements;
- TalkHistoryEntry _talkHistory[MAX_TALK_FILES];
- int _speaker;
+ Common::Array<TalkHistoryEntry> _talkHistory;
int _talkIndex;
int _scriptSelect;
int _talkStealth;
int _talkToFlag;
int _scriptSaveIndex;
+ int _3doSpeechIndex;
// These fields are used solely by doScript, but are fields because all the script opcodes are
// separate methods now, and need access to these fields
@@ -229,10 +198,9 @@ protected:
OpcodeReturn cmdAddItemToInventory(const byte *&str);
OpcodeReturn cmdAdjustObjectSequence(const byte *&str);
OpcodeReturn cmdBanishWindow(const byte *&str);
- OpcodeReturn cmdCallTalkFile(const byte *&str);
OpcodeReturn cmdDisableEndKey(const byte *&str);
OpcodeReturn cmdEnableEndKey(const byte *&str);
- OpcodeReturn cmdGotoScene(const byte *&str);
+ OpcodeReturn cmdEndTextWindow(const byte *&str);
OpcodeReturn cmdHolmesOff(const byte *&str);
OpcodeReturn cmdHolmesOn(const byte *&str);
OpcodeReturn cmdPause(const byte *&str);
@@ -243,20 +211,61 @@ protected:
OpcodeReturn cmdSetObject(const byte *&str);
OpcodeReturn cmdStealthModeActivate(const byte *&str);
OpcodeReturn cmdStealthModeDeactivate(const byte *&str);
- OpcodeReturn cmdSwitchSpeaker(const byte *&str);
OpcodeReturn cmdToggleObject(const byte *&str);
OpcodeReturn cmdWalkToCAnimation(const byte *&str);
- OpcodeReturn cmdWalkToCoords(const byte *&str);
+protected:
+ /**
+ * Checks, if a character is an opcode
+ */
+ bool isOpcode(byte checkCharacter);
+
+ /**
+ * Form a table of the display indexes for statements
+ */
+ void setTalkMap();
+
+ /**
+ * When the talk window has been displayed, waits a period of time proportional to
+ * the amount of text that's been displayed
+ */
+ int waitForMore(int delay);
+
+ /**
+ * Display the talk interface window
+ */
+ virtual void talkInterface(const byte *&str) = 0;
+
+ /**
+ * Pause when displaying a talk dialog on-screen
+ */
+ virtual void talkWait(const byte *&str);
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk() = 0;
+
+ /**
+ * Called when a character being spoken to has no talk options to display
+ */
+ virtual void nothingToSay() = 0;
+
+ /**
+ * Called when the active speaker is switched
+ */
+ virtual void switchSpeaker() {}
public:
+ Common::Array<Statement> _statements;
bool _talkToAbort;
int _talkCounter;
int _talkTo;
int _scriptMoreFlag;
+ bool _openTalkWindow;
Common::String _scriptName;
bool _moreTalkUp, _moreTalkDown;
int _converseNum;
const byte *_opcodes;
-
+ int _speaker;
public:
static Talk *init(SherlockEngine *vm);
virtual ~Talk() {}
@@ -274,7 +283,13 @@ public:
* 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);
+ virtual void talkTo(const Common::String filename);
+
+ /**
+ * Parses a reply for control codes and display text. The found text is printed within
+ * the text window, handles delays, animations, and animating portraits.
+ */
+ void doScript(const Common::String &script);
/**
* Main method for handling conversations when a character to talk to has been
@@ -282,7 +297,7 @@ public:
* interface window for the conversation and passes on control to give the
* player a list of options to make a selection from
*/
- void talk(int objNum);
+ void initTalk(int objNum);
/**
* Clear loaded talk data
@@ -290,48 +305,26 @@ public:
void freeTalkVars();
/**
- * Draws the interface for conversation display
- */
- void drawInterface();
-
- /**
* Opens the talk file 'talk.tlk' and searches the index for the specified
* conversation. If found, the data for that conversation is loaded
*/
void loadTalkFile(const Common::String &filename);
/**
- * Change the sequence of a background object corresponding to a given speaker.
- * The new sequence will display the character as "listening"
- */
- void setStillSeq(int speaker);
-
- /**
- * Clears the stack of pending object sequences associated with speakers in the scene
- */
- void clearSequences();
-
- /**
- * Pulls a background object sequence from the sequence stack and restore's the
- * object's sequence
- */
- void pullSequence();
-
- /**
* Push the sequence of a background object that's an NPC that needs to be
* saved onto the sequence stack.
*/
void pushSequence(int speaker);
/**
- * Change the sequence of the scene background object associated with the current speaker.
+ * Push the details of a passed object onto the saved sequences stack
*/
- void setSequence(int speaker);
+ virtual void pushSequenceEntry(Object *obj) = 0;
/**
- * Returns true if the script stack is empty
+ * Clears the stack of pending object sequences associated with speakers in the scene
*/
- bool isSequencesEmpty() const { return _scriptStack.empty(); }
+ virtual void clearSequences() = 0;
/**
* Pops an entry off of the script stack
@@ -341,66 +334,34 @@ public:
/**
* Synchronize the data for a savegame
*/
- void synchronize(Common::Serializer &s);
-};
+ void synchronize(Serializer &s);
-class ScalpelTalk : public Talk {
-protected:
- OpcodeReturn cmdAssignPortraitLocation(const byte *&str);
- OpcodeReturn cmdClearInfoLine(const byte *&str);
- OpcodeReturn cmdClearWindow(const byte *&str);
- OpcodeReturn cmdDisplayInfoLine(const byte *&str);
- OpcodeReturn cmdElse(const byte *&str);
- OpcodeReturn cmdIf(const byte *&str);
- OpcodeReturn cmdMoveMouse(const byte *&str);
- OpcodeReturn cmdPlayPrologue(const byte *&str);
- OpcodeReturn cmdRemovePortrait(const byte *&str);
- OpcodeReturn cmdSfxCommand(const byte *&str);
- OpcodeReturn cmdSummonWindow(const byte *&str);
- OpcodeReturn cmdCarriageReturn(const byte *&str);
-public:
- ScalpelTalk(SherlockEngine *vm);
- virtual ~ScalpelTalk() {}
-};
+ /**
+ * Draws the interface for conversation display
+ */
+ virtual void drawInterface() {}
-class TattooTalk : public Talk {
-protected:
- OpcodeReturn cmdMouseOnOff(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);
-public:
- TattooTalk(SherlockEngine *vm);
- virtual ~TattooTalk() {}
+ /**
+ * Display a list of statements in a window at the bottom of the screen that the
+ * player can select from.
+ */
+ virtual bool displayTalk(bool slamIt) { return false; }
+
+ /**
+ * Prints a single conversation option in the interface window
+ */
+ virtual int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { return 0; }
+
+ /**
+ * Pulls a background object sequence from the sequence stack and restore's the
+ * object's sequence
+ */
+ virtual void pullSequence(int slot = -1) = 0;
+
+ /**
+ * Returns true if the script stack is empty
+ */
+ virtual bool isSequencesEmpty() const = 0;
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo.cpp b/engines/sherlock/tattoo/tattoo.cpp
index 368b24bfcd..bfb35565bc 100644
--- a/engines/sherlock/tattoo/tattoo.cpp
+++ b/engines/sherlock/tattoo/tattoo.cpp
@@ -20,20 +20,34 @@
*
*/
-#include "sherlock/tattoo/tattoo.h"
+#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"
namespace Sherlock {
namespace Tattoo {
TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) :
- SherlockEngine(syst, gameDesc) {
- _creditsActive = false;
+ SherlockEngine(syst, gameDesc), _darts(this), _foolscapWidget(this) {
+ _runningProlog = false;
+ _fastMode = false;
+ _allowFastMode = true;
+ _transparentMenus = true;
+ _textWindowsOn = true;
+}
+
+TattooEngine::~TattooEngine() {
}
void TattooEngine::showOpening() {
- // TODO
+ // No implementation - opening is done using in-game scenes
}
void TattooEngine::initialize() {
@@ -42,20 +56,83 @@ void TattooEngine::initialize() {
// Initialize the base engine
SherlockEngine::initialize();
- _flags.resize(100 * 8);
+ // Initialise the global flags
+ _flags.resize(3200);
+ _flags[1] = _flags[4] = _flags[76] = true;
+ _runningProlog = true;
// Add some more files to the cache
_res->addToCache("walk.lib");
+
+ // Set up list of people
+ for (int idx = 0; idx < TATTOO_MAX_PEOPLE; ++idx) {
+ _people->_characters.push_back(PersonData(
+ getLanguage() == Common::FR_FRA ? FRENCH_NAMES[idx] : ENGLISH_NAMES[idx],
+ PORTRAITS[idx], nullptr, nullptr));
+ }
+
+ // Load the inventory
+ loadInventory();
// Starting scene
- _scene->_goToScene = 91;
+ _scene->_goToScene = STARTING_INTRO_SCENE;
// Load an initial palette
loadInitialPalette();
}
void TattooEngine::startScene() {
- // TODO
+ TattooUserInterface &ui = *(TattooUserInterface *)_ui;
+
+ 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::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 OVERHEAD_MAP:
+ case OVERHEAD_MAP2:
+ // Show the map
+ _scene->_currentScene = OVERHEAD_MAP;
+ _scene->_goToScene = _map->show();
+
+ _people->_savedPos = Common::Point(-1, -1);
+ _people->_savedPos._facing = -1;
+ 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() {
@@ -68,14 +145,65 @@ void TattooEngine::loadInitialPalette() {
delete stream;
}
-void TattooEngine::drawCredits() {
- // TODO
+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();
}
-void TattooEngine::eraseCredits() {
- // TODO
+bool TattooEngine::canSaveGameStateCurrently() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_ui;
+ return _canLoadSave && !ui._creditsWidget.active() && !_runningProlog;
}
} // End of namespace Tattoo
-} // End of namespace Scalpel
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo.h b/engines/sherlock/tattoo/tattoo.h
index bb6310dbe3..71872ab1b9 100644
--- a/engines/sherlock/tattoo/tattoo.h
+++ b/engines/sherlock/tattoo/tattoo.h
@@ -24,17 +24,48 @@
#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
@@ -47,21 +78,34 @@ protected:
* Starting a scene within the game
*/
virtual void startScene();
+
+ /**
+ * Load configuration options
+ */
+ virtual void loadConfig();
public:
- bool _creditsActive;
+ bool _runningProlog;
+ bool _fastMode, _allowFastMode;
+ bool _transparentMenus;
+ bool _textWindowsOn;
public:
TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
- virtual ~TattooEngine() {}
+ virtual ~TattooEngine();
+
+ /**
+ * Shows the foolscap puzzle
+ */
+ void doFoolscapPuzzle();
/**
- * Draw credits on the screen
+ * Save the game configuration
*/
- void drawCredits();
+ virtual void saveConfig();
/**
- * Erase any area of the screen covered by credits
+ * Returns true if the game can be saved
*/
- void eraseCredits();
+ virtual bool canSaveGameStateCurrently();
};
} // End of namespace Tattoo
diff --git a/engines/sherlock/tattoo/tattoo_darts.cpp b/engines/sherlock/tattoo/tattoo_darts.cpp
new file mode 100644
index 0000000000..cb4b52b01a
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_darts.cpp
@@ -0,0 +1,1000 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_darts.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+enum {
+ DART_COLOR_FORE = 5,
+ PLAYER_COLOR = 11
+};
+
+static const int STATUS_INFO_X = 430;
+static const int STATUS_INFO_Y = 50;
+static const int STATUS_INFO_WIDTH = 205;
+static const int STATUS_INFO_HEIGHT = 330;
+static const int STATUS2_INFO_X = 510;
+static const int STATUS2_X_ADD = STATUS2_INFO_X - STATUS_INFO_X;
+static const int DART_BAR_VX = 10;
+static const int DART_HEIGHT_Y = 121;
+static const int DART_BAR_SIZE = 150;
+static const int DARTBOARD_LEFT = 73;
+static const int DARTBOARD_TOP = 68;
+static const int DARTBOARD_WIDTH = 257;
+static const int DARTBOARD_HEIGHT = 256;
+static const int DARTBOARD_TOTALX = DARTBOARD_WIDTH * 120 / 100;
+static const int DARTBOARD_TOTALY = DARTBOARD_HEIGHT * 120 / 100;
+static const int DARTBOARD_TOTALTOP = DARTBOARD_TOP - DARTBOARD_WIDTH / 10;
+static const int DARTBOARD_TOTALLEFT = DARTBOARD_LEFT - DARTBOARD_HEIGHT / 10;
+static const int CRICKET_VALUE[7] = { 20, 19, 18, 17, 16, 15, 25 };
+
+Darts::Darts(SherlockEngine *vm) : _vm(vm) {
+ _gameType = GAME_301;
+ _hand1 = _hand2 = nullptr;
+ _dartGraphics = nullptr;
+ _dartsLeft = nullptr;
+ _dartMap = nullptr;
+ _dartBoard = nullptr;
+ Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0);
+ Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0);
+ _score1 = _score2 = 0;
+ _roundNum = 0;
+ _roundScore = 0;
+ _level = 0;
+ _oldDartButtons = false;
+ _handX = 0;
+ _compPlay = 1;
+ _escapePressed = false;
+}
+
+void Darts::playDarts(GameType gameType) {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ int oldFontType = screen.fontNumber();
+ int playerNum = 0;
+ int roundStart, score;
+ int lastDart;
+ int numHits = 0;
+ bool gameOver = false;
+ bool done = false;
+ const char *const NUM_HITS_STR[3] = { "a", FIXED(Double), FIXED(Triple) };
+
+ screen.setFont(7);
+ _spacing = screen.fontHeight() + 2;
+
+ // Load dart graphics and initialize values
+ loadDarts();
+ initDarts();
+
+ while (!done && !_vm->shouldQuit()) {
+ roundStart = score = (playerNum == 0) ? _score1 : _score2;
+
+ showNames(playerNum);
+ showStatus(playerNum);
+ _roundScore = 0;
+
+ for (int idx = 0; idx < 3 && !_vm->shouldQuit(); ++idx) {
+ if (_compPlay == 1)
+ lastDart = throwDart(idx + 1, playerNum * 2); /* Throw one dart */
+ else
+ if (_compPlay == 2)
+ lastDart = throwDart(idx + 1, playerNum + 1); /* Throw one dart */
+ else
+ lastDart = throwDart(idx + 1, 0); /* Throw one dart */
+
+ if (_gameType == GAME_301) {
+ score -= lastDart;
+ _roundScore += lastDart;
+ } else {
+ numHits = lastDart >> 16;
+ if (numHits == 0)
+ numHits = 1;
+ if (numHits > 3)
+ numHits = 3;
+
+ lastDart = lastDart & 0xffff;
+ updateCricketScore(playerNum, lastDart, numHits);
+ score = (playerNum == 0) ? _score1 : _score2;
+ }
+
+ // Special case for ScummVM: I'm making pressing Escape to exit out of the Darts game as a way to skip
+ // it entirely if you don't want to play all the way through it
+ if (_escapePressed) {
+ gameOver = true;
+ done = true;
+ playerNum = 0;
+ }
+
+
+ if (_gameType == GAME_301) {
+ if (playerNum == 0)
+ _score1 = score;
+ else
+ _score2 = score;
+
+ if (score == 0)
+ // Someone won
+ gameOver = true;
+ } else {
+ // check for cricket game over
+ bool allClosed = true;
+ int nOtherScore;
+
+ for (int y = 0; y < 7; y++) {
+ if (_cricketScore[playerNum][y] < 3)
+ allClosed = false;
+ }
+
+ if (allClosed) {
+ nOtherScore = (playerNum == 0) ? _score2 : _score1;
+ if (score >= nOtherScore)
+ gameOver = true;
+ }
+ }
+
+ // Show scores
+ showStatus(playerNum);
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), idx + 1);
+
+ if (_gameType == GAME_301) {
+ if (_vm->getLanguage() == Common::FR_FRA)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s: %d", FIXED(Scored), FIXED(Points), lastDart);
+ else
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %d %s", FIXED(Scored), lastDart, FIXED(Points));
+ } else {
+ if (lastDart != 25)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s %d", FIXED(Hit), NUM_HITS_STR[numHits - 1], lastDart);
+ else
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s %s", FIXED(Hit), NUM_HITS_STR[numHits - 1], FIXED(Bullseye));
+ }
+
+ if (score != 0 && playerNum == 0 && !gameOver)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), 0,
+ "%s", FIXED(PressAKey));
+
+ if (gameOver) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3),
+ 0, "%s", FIXED(GameOver));
+ if (playerNum == 0) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0,
+ "%s %s", FIXED(Holmes), FIXED(Wins));
+ _vm->setFlagsDirect(531);
+ } else {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0,
+ "%s %s!", _opponent.c_str(), FIXED(Wins));
+ _vm->setFlagsDirect(530);
+ }
+
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 5), 0,
+ "%s", FIXED(PressAKey));
+
+ done = true;
+ idx = 10;
+ } else if (_gameType == GAME_301 && score < 0) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0,
+ "%s!", FIXED(Busted));
+
+ // End turn
+ idx = 10;
+ score = roundStart;
+ if (playerNum == 0)
+ _score1 = score;
+ else
+ _score2 = score;
+ }
+
+ // Clear keyboard events
+ events.clearEvents();
+
+ if ((playerNum == 0 && _compPlay == 1) || _compPlay == 0 || done) {
+ if (_escapePressed) {
+ done = true;
+ break;
+ }
+ } else {
+ events.wait(20);
+ }
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.blitFrom(screen._backBuffer1);
+ }
+
+ playerNum ^= 1;
+ if (!playerNum)
+ ++_roundNum;
+
+ if (!done) {
+ screen._backBuffer2.blitFrom((*_dartBoard)[0], Common::Point(0, 0));
+ screen._backBuffer1.blitFrom(screen._backBuffer2);
+ screen.blitFrom(screen._backBuffer2);
+ }
+ }
+
+ // Wait for a keypress
+ do {
+ events.pollEventsAndWait();
+ events.setButtonState();
+ } while (!_vm->shouldQuit() && !events.kbHit() && !events._pressed);
+ events.clearEvents();
+
+ closeDarts();
+ screen.fadeToBlack();
+ screen.setFont(oldFontType);
+
+ // Flag to return to the Billard's Academy scene
+ scene._goToScene = 26;
+}
+
+void Darts::initDarts() {
+ _dartInfo = Common::Rect(430, 245, 430 + 205, 245 + 150);
+ _escapePressed = false;
+
+ if (_gameType == GAME_CRICKET) {
+ _dartInfo = Common::Rect(430, 245, 430 + 205, 245 + 150);
+ }
+
+ Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0);
+ Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0);
+
+ switch (_gameType) {
+ case GAME_501:
+ _score1 = _score2 = 501;
+ _gameType = GAME_301;
+ break;
+
+ case GAME_301:
+ _score1 = _score2 = 301;
+ break;
+
+ default:
+ // Cricket
+ _score1 = _score2 = 0;
+ break;
+ }
+
+ _roundNum = 1;
+
+ if (_level == 9) {
+ // No computer players
+ _compPlay = 0;
+ _level = 0;
+ } else if (_level == 8) {
+ _level = _vm->getRandomNumber(3);
+ _compPlay = 2;
+ } else {
+ // Check for opponent flags
+ for (int idx = 0; idx < 4; ++idx) {
+ if (_vm->readFlags(314 + idx))
+ _level = idx;
+ }
+ }
+
+ _opponent = FIXED(Jock);
+}
+
+void Darts::loadDarts() {
+ Resources &res = *_vm->_res;
+ Screen &screen = *_vm->_screen;
+ byte palette[PALETTE_SIZE];
+
+ // Load images
+ _hand1 = new ImageFile("hand1.vgs");
+ _hand2 = new ImageFile("hand2.vgs");
+ _dartGraphics = new ImageFile("darts.vgs");
+ _dartsLeft = new ImageFile("DartsLft.vgs");
+ _dartMap = new ImageFile("DartMap.vgs");
+ _dartBoard = new ImageFile("DartBd.vgs");
+
+ // Load and set the palette
+ Common::SeekableReadStream *stream = res.load("DartBd.pal");
+ stream->read(palette, PALETTE_SIZE);
+ screen.translatePalette(palette);
+ screen.setPalette(palette);
+ delete stream;
+
+ // Load the initial background
+ screen._backBuffer1.blitFrom((*_dartBoard)[0], Common::Point(0, 0));
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+ screen.blitFrom(screen._backBuffer1);
+}
+
+void Darts::closeDarts() {
+ delete _dartBoard;
+ delete _dartsLeft;
+ delete _dartGraphics;
+ delete _dartMap;
+ delete _hand1;
+ delete _hand2;
+}
+
+void Darts::showNames(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ byte color;
+
+ color = playerNum == 0 ? PLAYER_COLOR : DART_COLOR_FORE;
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", FIXED(Holmes));
+ screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+ screen.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+
+ color = playerNum == 1 ? PLAYER_COLOR : DART_COLOR_FORE;
+ screen.print(Common::Point(STATUS2_INFO_X, STATUS_INFO_Y), 0, "%s", _opponent.c_str());
+ screen._backBuffer1.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+ screen.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+}
+
+void Darts::showStatus(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ const char *const CRICKET_SCORE_NAME[7] = { "20", "19", "18", "17", "16", "15", FIXED(Bull) };
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
+ Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH,
+ STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10));
+ screen.print(Common::Point(STATUS_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score1);
+
+ screen.print(Common::Point(STATUS2_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score2);
+
+ int temp = (_gameType == GAME_CRICKET) ? STATUS_INFO_Y + 10 * _spacing + 5 : STATUS_INFO_Y + 55;
+ screen.print(Common::Point(STATUS_INFO_X, temp), 0, "%s: %d", FIXED(Round), _roundNum);
+
+ if (_gameType == GAME_301) {
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 75), 0, "%s: %d", FIXED(TurnTotal), _roundScore);
+ } else {
+ // Show cricket scores
+ for (int x = 0; x < 7; ++x) {
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 40 + x * _spacing), 0, "%s:", CRICKET_SCORE_NAME[x]);
+
+ for (int y = 0; y < 2; ++y) {
+ switch (CRICKET_SCORE_NAME[y][x]) {
+ case 1:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "/");
+ break;
+ case 2:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "X");
+ break;
+ case 3:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y * STATUS2_X_ADD - 1, STATUS_INFO_Y + 40 + x * _spacing), 0, "X");
+ screen.print(Common::Point(STATUS_INFO_X + 37 + y * STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "O");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ screen.blitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
+ Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH,
+ STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10));
+}
+
+void Darts::erasePowerBars() {
+ Screen &screen = *_vm->_screen;
+
+ // Erase the old power bars and replace them with empty ones
+ screen._backBuffer1.fillRect(Common::Rect(DART_BAR_VX, DART_HEIGHT_Y, DART_BAR_VX + 9, DART_HEIGHT_Y + DART_BAR_SIZE), 0);
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1));
+ screen.slamArea(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, 10, DART_BAR_SIZE + 2);
+}
+
+bool Darts::dartHit() {
+ Events &events = *_vm->_events;
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ // Keyboard check
+ if (events.kbHit()) {
+ if (events.getKey().keycode == Common::KEYCODE_ESCAPE)
+ _escapePressed = true;
+
+ events.clearEvents();
+ return true;
+ }
+
+ bool result = events._pressed && !_oldDartButtons;
+ _oldDartButtons = events._pressed;
+ return result;
+}
+
+int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int idx = 0;
+
+ events.clearEvents();
+ events.delay(100);
+
+ while (!_vm->shouldQuit()) {
+ if (idx >= DART_BAR_SIZE)
+ break;
+
+ if ((goToPower - 1) == idx)
+ break;
+ else if (goToPower == 0) {
+ if (dartHit())
+ break;
+ }
+
+ screen._backBuffer1.hLine(pt.x, pt.y + DART_BAR_SIZE- 1 - idx, pt.x + 8, color);
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(pt.x - 1, pt.y - 1));
+ screen.slamArea(pt.x, pt.y + DART_BAR_SIZE - 1 - idx, 8, 2);
+
+ if (!(idx % 8))
+ events.wait(1);
+
+ ++idx;
+ }
+
+ return MIN(idx * 100 / DART_BAR_SIZE, 100);
+}
+
+int Darts::drawHand(int goToPower, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ const int HAND_OFFSET[2] = { 72, 44 };
+ ImageFile *hands;
+ int hand;
+
+ goToPower = (goToPower * DARTBOARD_WIDTH) / 150;
+
+ if (!computer) {
+ hand = 0;
+ hands = _hand1;
+ } else {
+ hand = 1;
+ hands = _hand2;
+ }
+
+ _handSize.x = (*hands)[0]._offset.x + (*hands)[0]._width;
+ _handSize.y = (*hands)[0]._offset.y + (*hands)[0]._height;
+
+ // Clear keyboard buffer
+ events.clearEvents();
+ events.delay(100);
+
+ Common::Point pt(DARTBOARD_LEFT - HAND_OFFSET[hand], SHERLOCK_SCREEN_HEIGHT - _handSize.y);
+ int x = 0;
+
+ while (!_vm->shouldQuit()) {
+ if (x >= DARTBOARD_WIDTH)
+ break;
+
+ if ((goToPower - 1) == x)
+ break;
+ else if (goToPower == 0) {
+ if (dartHit())
+ break;
+ }
+
+ screen._backBuffer1.transBlitFrom((*hands)[0], pt);
+ screen.slamArea(pt.x - 1, pt.y, _handSize.x + 1, _handSize.y);
+ screen.restoreBackground(Common::Rect(pt.x, pt.y, pt.x + _handSize.x, pt.y + _handSize.y));
+
+ if (!(x % 8))
+ events.wait(1);
+
+ ++x;
+ ++pt.x;
+ }
+
+ _handX = pt.x - 1;
+
+ return MIN(x * 100 / DARTBOARD_WIDTH, 100);
+}
+
+Common::Point Darts::convertFromScreenToScoreCoords(const Common::Point &pt) const {
+ return Common::Point(CLIP((int)pt.x, 0, DARTBOARD_WIDTH), CLIP((int)pt.y, 0, DARTBOARD_HEIGHT));
+}
+
+int Darts::dartScore(const Common::Point &pt) {
+ Common::Point pos(pt.x - DARTBOARD_LEFT, pt.y - DARTBOARD_TOP);
+ if (pos.x < 0 || pos.y < 0)
+ return 0;
+ int score;
+
+ if (pos.x < DARTBOARD_WIDTH && pos.y < DARTBOARD_HEIGHT) {
+ pos = convertFromScreenToScoreCoords(pos);
+ score = *(const byte *)(*_dartMap)[0]._frame.getBasePtr(pos.x, pos.y);
+
+ if (_gameType == GAME_301) {
+ if (score >= 100) {
+ if (score <= 120)
+ // Hit a double
+ score = (score - 100) * 2;
+ else
+ // Hit a triple
+ score = (score - 120) * 3;
+ }
+ } else if (score >= 100) {
+ if (score >= 120)
+ // Hit a double
+ score = (2 << 16) + (score - 100);
+ else
+ // Hit a triple
+ score = (3 << 16) + (score - 120);
+ }
+ } else {
+ score = 0;
+ }
+
+ return score;
+}
+
+void Darts::drawDartThrow(const Common::Point &dartPos, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int cx, cy;
+ int handCy;
+ int drawX = 0, drawY = 0, oldDrawX = 0, oldDrawY = 0;
+ int xSize = 0, ySize = 0, oldxSize = 0, oldySize = 0;
+ int handOCx, handOCy;
+ int ocx, ocy;
+ int handOldxSize, handOldySize;
+ int delta = 9;
+ int dartNum;
+ int hddy;
+
+ // Draw the animation of the hand throwing the dart first
+ // See which hand animation to use
+ ImageFile &hands = !computer ? *_hand1 : *_hand2;
+ int numFrames = !computer ? 14 : 13;
+
+ ocx = ocy = handOCx = handOCy = 0;
+ oldxSize = oldySize = handOldxSize = handOldySize = 1;
+ cx = dartPos.x;
+ cy = SHERLOCK_SCREEN_HEIGHT - _handSize.y - 20;
+
+ hddy = (cy - dartPos.y) / (numFrames - 7);
+ hddy += 2;
+ hddy = hddy * 10 / 8;
+ if (dartPos.y > 275)
+ hddy += 3;
+
+ for (int idx = 0; idx < numFrames; ++idx) {
+ _handSize.x = hands[idx]._offset.x + hands[idx]._width;
+ _handSize.y = hands[idx]._offset.y + hands[idx]._height;
+ handCy = SHERLOCK_SCREEN_HEIGHT - _handSize.y;
+
+ screen._backBuffer1.transBlitFrom(hands[idx], Common::Point(_handX, handCy));
+ screen.slamArea(_handX, handCy, _handSize.x + 1, _handSize.y);
+ screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize);
+ screen.restoreBackground(Common::Rect(_handX, handCy, _handX + _handSize.x, handCy + _handSize.y));
+
+ handOCx = _handX;
+ handOCy = handCy;
+ handOldxSize = _handSize.x;
+ handOldySize = _handSize.y;
+
+ if (idx > 6) {
+ dartNum = idx - 6;
+ if (computer)
+ dartNum += 19;
+
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2;
+ ocy = drawY = cy - (*_dartGraphics)[dartNum]._height;
+
+ // Draw dart
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], dartPos);
+
+ if (drawX < 0) {
+ xSize += drawX;
+ if (xSize < 0)
+ xSize = 1;
+ drawX = 0;
+ }
+
+ if (drawY < 0) {
+ ySize += drawY;
+ if (ySize < 0)
+ ySize = 1;
+ drawY = 0;
+ }
+
+ // Flush the drawn dart to the screen
+ screen.slamArea(drawX, drawY, xSize, ySize);
+ if (oldDrawX != -1)
+ // Flush the erased dart area
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize));
+
+ oldDrawX = drawX;
+ oldDrawY = drawY;
+ oldxSize = xSize;
+ oldySize = ySize;
+
+ cy -= hddy;
+ }
+
+ events.wait(1);
+ }
+
+ // Clear the last little bit of the hand from the screen
+ screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize);
+
+ // Erase the old dart
+ if (oldDrawX != -1)
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize));
+
+ cx = dartPos.x;
+ cy = dartPos.y + 2;
+ oldDrawX = oldDrawY = -1;
+
+ for (int idx = 5; idx <= 23; ++idx) {
+ dartNum = idx - 4;
+ if (computer)
+ dartNum += 19;
+
+ if (idx < 14)
+ cy -= delta--;
+ else
+ if (idx == 14)
+ delta = 1;
+ if (idx > 14)
+ cy += delta++;
+
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2;
+ ocy = drawY = cy - (*_dartGraphics)[dartNum]._height;
+
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(drawX, drawY));
+
+ if (drawX < 0) {
+ xSize += drawX;
+ if (xSize < 0)
+ xSize = 1;
+ drawX = 0;
+ }
+
+ if (drawY < 0) {
+ ySize += drawY;
+ if (ySize < 0)
+ ySize = 1;
+ drawY = 0;
+ }
+
+ // flush the dart
+ screen.slamArea(drawX, drawY, xSize, ySize);
+ if (oldDrawX != -1)
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ if (idx != 23)
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize)); // erase dart
+
+ events.wait(1);
+
+ oldDrawX = drawX;
+ oldDrawY = drawY;
+ oldxSize = xSize;
+ oldySize = ySize;
+ }
+
+ dartNum = 19;
+ if (computer)
+ dartNum += 19;
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ // Draw final dart on the board
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy));
+ screen._backBuffer2.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy));
+ screen.slamArea(ocx, ocy, xSize, ySize);
+}
+
+int Darts::findNumberOnBoard(int aim, Common::Point &pt) {
+ ImageFrame &img = (*_dartMap)[0];
+
+ if ((aim > 20) && ((aim != 25) && (aim != 50))) {
+ if ((aim <= 40) && ((aim & 1) == 0)) {
+ aim /= 2;
+ aim += 100;
+ } else {
+ aim /= 3;
+ aim += 120;
+ }
+ }
+
+ bool done = false;
+ for (int y = 0; y < img._width && !done; ++y) {
+ for (int x = 0; x < img._height && !done; ++x) {
+ byte score = *(const byte *)img._frame.getBasePtr(x, y);
+
+ if (score == aim) {
+ // Found a match. Aim at non-double/triple numbers whenever possible.
+ // ie. Aim at 18 instead of triple 6 or double 9
+ done = true;
+
+ if (aim < 21) {
+ pt.x = x + 10;
+ pt.y = y + 10;
+
+ score = *(const byte *)img._frame.getBasePtr(x, y);
+ if (score != aim)
+ done = false;
+ } else {
+ // Aiming at double or triple
+ pt.x = x + 3;
+ pt.y = y + 3;
+ }
+ }
+ }
+ }
+
+ pt = convertFromScreenToScoreCoords(pt);
+
+ if (aim == 3)
+ pt.y += 30;
+ if (aim == 17)
+ pt.y += 10;
+
+ if (aim == 15) {
+ pt.y += 5;
+ pt.x += 5;
+ }
+
+ pt.y = DARTBOARD_HEIGHT - pt.y;
+ return done;
+}
+
+void Darts::getComputerNumber(int playerNum, Common::Point &targetPos) {
+ int score;
+ int aim = 0;
+ Common::Point pt;
+ bool done = false;
+ int cricketaimset = false;
+ bool shootBull = false;
+
+ score = (playerNum == 0) ? _score1 : _score2;
+
+ if (_gameType == GAME_301) {
+ // Try to hit number
+ aim = score;
+ if(score > 60)
+ shootBull = true;
+ } else {
+ if (_cricketScore[playerNum][6] < 3) {
+ // shoot at bull first
+ aim = CRICKET_VALUE[6];
+ cricketaimset = true;
+ } else {
+ // Now check and shoot in this order: 20,19,18,17,16,15
+ for (int idx = 0; idx < 7; ++idx) {
+ if (_cricketScore[playerNum][idx] < 3) {
+ aim = CRICKET_VALUE[idx];
+ cricketaimset = true;
+ break;
+ }
+ }
+ }
+
+ if (!cricketaimset) {
+ // Everything is closed
+ // just in case we don't get set in loop below, which should never happen
+ aim = 14;
+ for (int idx = 0; idx < 7; ++idx) {
+ if (_cricketScore[playerNum^1][idx] < 3) {
+ // Opponent has this open
+ aim = CRICKET_VALUE[idx];
+
+ if (idx == 6)
+ shootBull = true;
+ }
+ }
+ }
+ }
+
+ if (shootBull) {
+ // Aim at bulls eye
+ targetPos.x = targetPos.y = 75;
+
+ if (_level <= 1) {
+ if (_vm->getRandomNumber(1) == 1) {
+ targetPos.x += (_vm->getRandomNumber(20)-10);
+ targetPos.y += (_vm->getRandomNumber(20)-10);
+ }
+ }
+ } else {
+ // Loop in case number does not exist on board
+ do {
+ done = findNumberOnBoard(aim, pt);
+ --aim;
+ } while (!done);
+
+ pt.x += DARTBOARD_TOTALLEFT * 70 / 100;
+ pt.y += DARTBOARD_TOTALTOP * 70 / 100;
+
+ // old * 3/2
+ targetPos.x = pt.x * 100 / DARTBOARD_TOTALX * 3 / 2;
+ targetPos.y = pt.y * 100 / DARTBOARD_TOTALY * 3 / 2;
+ }
+
+ // the higher the level, the more accurate the throw
+ int v = _vm->getRandomNumber(9);
+ v += _level * 2;
+
+ if (v <= 2) {
+ targetPos.x += _vm->getRandomNumber(70) - 35;
+ targetPos.y += _vm->getRandomNumber(70) - 35;
+ } else if (v <= 4) {
+ targetPos.x += _vm->getRandomNumber(50) - 25;
+ targetPos.y += _vm->getRandomNumber(50) - 25;
+ } else if (v <= 6) {
+ targetPos.x += _vm->getRandomNumber(30) - 15;
+ targetPos.y += _vm->getRandomNumber(30) - 15;
+ } else if (v <= 8) {
+ targetPos.x += _vm->getRandomNumber(20) -10;
+ targetPos.y += _vm->getRandomNumber(20) -10;
+ } else if (v <= 10) {
+ targetPos.x += _vm->getRandomNumber(11) - 5;
+ targetPos.y += _vm->getRandomNumber(11) - 5;
+ }
+
+ if (targetPos.x < 1)
+ targetPos.x = 1;
+ if (targetPos.y < 1)
+ targetPos.y = 1;
+}
+
+int Darts::throwDart(int dartNum, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int height;
+ int horiz;
+ Common::Point targetPos;
+ Common::String temp;
+
+ /* clear keyboard buffer */
+ events.clearEvents();
+
+ erasePowerBars();
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), dartNum);
+
+ drawDartsLeft(dartNum, computer);
+
+ if (!computer) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", FIXED(HitAKey));
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, "%s", FIXED(ToStart));
+ }
+
+ if (!computer) {
+ // Wait for a hit
+ while (!dartHit() && !_vm->shouldQuit())
+ ;
+ if (_escapePressed)
+ return 0;
+ } else {
+ events.wait(1);
+ }
+
+ drawDartsLeft(dartNum + 1, computer);
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+
+ if (computer) {
+ getComputerNumber(computer - 1, targetPos);
+ } else {
+ // Keyboard control
+ targetPos = Common::Point(0, 0);
+ }
+
+ horiz = drawHand(targetPos.x, computer);
+ if (_escapePressed)
+ return 0;
+
+ height = doPowerBar(Common::Point(DART_BAR_VX, DART_HEIGHT_Y), DART_COLOR_FORE, targetPos.y, 1);
+ if (_escapePressed)
+ return 0;
+
+ // Invert height
+ height = 101 - height;
+
+ // Copy power bars to the secondary back buffer
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1),
+ Common::Rect(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, DART_BAR_VX - 1 + 10,
+ DART_HEIGHT_Y - 1 + DART_BAR_SIZE + 2));
+
+ Common::Point dartPos(DARTBOARD_TOTALLEFT + horiz*DARTBOARD_TOTALX / 100,
+ DARTBOARD_TOTALTOP + height * DARTBOARD_TOTALY / 100);
+
+ dartPos.x += 2 - _vm->getRandomNumber(4);
+ dartPos.y += 2 - _vm->getRandomNumber(4);
+
+ drawDartThrow(dartPos, computer);
+ return dartScore(dartPos);
+}
+
+void Darts::doCricketScoreHits(int player, int scoreIndex, int numHits) {
+ while (numHits--) {
+ if (_cricketScore[player][scoreIndex] < 3)
+ _cricketScore[player][scoreIndex]++;
+ else if (_cricketScore[player ^ 1][scoreIndex] < 3) {
+ if (player == 0)
+ _score1 += CRICKET_VALUE[scoreIndex];
+ else
+ _score2 += CRICKET_VALUE[scoreIndex];
+ }
+ }
+}
+
+void Darts::updateCricketScore(int player, int dartVal, int multiplier) {
+ if (dartVal < 15)
+ return;
+
+ if (dartVal <= 20)
+ doCricketScoreHits(player, 20 - dartVal, multiplier);
+ else if (dartVal == 25)
+ doCricketScoreHits(player, 6, multiplier);
+}
+
+void Darts::drawDartsLeft(int dartNum, int computer) {
+ Screen &screen = *_vm->_screen;
+ const int DART_X1[3] = { 391, 451, 507 };
+ const int DART_Y1[3] = { 373, 373, 373 };
+ const int DART_X2[3] = { 393, 441, 502 };
+ const int DART_Y2[3] = { 373, 373, 373 };
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_X1[0], DART_Y1[0]),
+ Common::Rect(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ for (int idx = 2; idx >= dartNum - 1; --idx) {
+ if (computer)
+ screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx + 3], Common::Point(DART_X2[idx], DART_Y2[idx]));
+ else
+ screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx], Common::Point(DART_X1[idx], DART_Y1[idx]));
+ }
+
+ screen.slamArea(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH - DART_X1[0], SHERLOCK_SCREEN_HEIGHT - DART_Y1[0]);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_darts.h b/engines/sherlock/tattoo/tattoo_darts.h
new file mode 100644
index 0000000000..ab6b1c8204
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_darts.h
@@ -0,0 +1,173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
diff --git a/engines/zvision/detection.h b/engines/sherlock/tattoo/tattoo_debugger.cpp
index f80cac79ec..8d59b4c592 100644
--- a/engines/zvision/detection.h
+++ b/engines/sherlock/tattoo/tattoo_debugger.cpp
@@ -20,24 +20,16 @@
*
*/
-#ifndef ZVISION_DETECTION_H
-#define ZVISION_DETECTION_H
+#include "sherlock/tattoo/tattoo_debugger.h"
+#include "sherlock/sherlock.h"
-#include "engines/advancedDetector.h"
+namespace Sherlock {
-namespace ZVision {
-
-enum ZVisionGameId {
- GID_NONE = 0,
- GID_NEMESIS = 1,
- GID_GRANDINQUISITOR = 2
-};
-
-struct ZVisionGameDescription {
- ADGameDescription desc;
- ZVisionGameId gameId;
-};
+namespace Tattoo {
+TattooDebugger::TattooDebugger(SherlockEngine *vm) : Debugger(vm) {
}
-#endif
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_debugger.h b/engines/sherlock/tattoo/tattoo_debugger.h
new file mode 100644
index 0000000000..e729262b11
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_debugger.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~TattooDebugger() {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_TATTOO_DEBUGGER_H */
diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.cpp b/engines/sherlock/tattoo/tattoo_fixed_text.cpp
new file mode 100644
index 0000000000..c9345e44d1
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_fixed_text.cpp
@@ -0,0 +1,211 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+static const char *const FIXED_TEXT_ENGLISH[] = {
+ "Money",
+ "Card",
+ "Tobacco",
+ "Timetable",
+ "Summons",
+ "Foolscap",
+ "Damp Paper",
+ "Bull's Eye",
+
+ "Money",
+ "Card",
+ "Tobacco",
+ "Timetable",
+ "Summons",
+ "Foolscap",
+ "Foolscap",
+ "Bull's Eye Lantern",
+
+ "Open",
+ "Look",
+ "Talk",
+ "Use",
+ "Journal",
+ "Inventory",
+ "Options",
+ "Solve",
+ "with",
+ "No effect...",
+ "This person has nothing to say at the moment",
+ "Picked up",
+
+ "Page %d",
+ "Close Journal",
+ "Search Journal",
+ "Save Journal",
+ "Abort Search",
+ "Search Backwards",
+ "Search Forwards",
+ "Text Not Found !",
+
+ "Holmes",
+ "Jock",
+ "Bull",
+ "Round",
+ "Turn Total",
+ "Dart",
+ "to start",
+ "Hit a key",
+ "Press a key",
+ "bullseye",
+ "GAME OVER",
+ "BUSTED",
+ "Wins",
+ "Scored",
+ "points",
+ "Hit",
+ "Double",
+ "Triple",
+
+ "Apply",
+ "Water",
+ "Heat",
+ "Load Game",
+ "Save Game",
+ "Music",
+ "Sound Effects",
+ "Voices",
+ "Text Windows",
+ "Transparent Menus",
+ "Change Font Style",
+ "Off",
+ "On",
+ "Quit",
+ "Are you sure you",
+ "wish to Quit ?",
+ "Yes",
+ "No",
+ "Enter Password",
+ "Going East"
+};
+
+static const char *const FIXED_TEXT_GERMAN[] = {
+ "Geld",
+ "S. Holmes",
+ "Tabak",
+ "Plan",
+ "Aufforderg.",
+ "Blatt pap.",
+ "Dunstig pap",
+ "Handlampe",
+
+ "Geld",
+ "S. Holmes",
+ "Tabak",
+ "Plan",
+ "Aufforderg.",
+ "Pergament",
+ "Dunstig pap",
+ "Handlampe",
+
+ "ffne",
+ "Schau",
+ "Rede",
+ "Benutze",
+ "Journal",
+ "Inventory",
+ "Options",
+ "Losen",
+ "mit",
+ "Keine Wirkung...",
+ "Diese Person weic im Augenblick nichts zu berichten.",
+
+ "Seite %d",
+ "Schliecen",
+ "Lessen",
+ "In Datei sichern",
+ "Suche abbrechen",
+ "Rbckwarts suchen ",
+ "Vorwarts suchen ",
+ "Text nicht gefunden",
+
+ "Holmes",
+ "Jock",
+ "Bull",
+ "Runde",
+ "Gesamt",
+ "Pfeil",
+ "zum Starten",
+ "Taste dracken",
+ "Taste dracken",
+ "Bullseye",
+ "SPIEL BEENDET",
+ "VERLOREN",
+ "Gewinnt",
+ "Erzielte",
+ "Punkte",
+ "Treffer",
+ "Doppel",
+ "Dreifach",
+
+ "Benutze",
+ "Wasser",
+ "Erhitze",
+ "Spiel laden",
+ "Spiel sichern",
+ "Musik",
+ "Soundeffekte",
+ "Voices",
+ "Textfenster",
+ "Transparente Menbs",
+ "Schriftart andern",
+ "Aus",
+ "An",
+ "Ende",
+ "Spiel beenden? ",
+ "Sind Sie sicher ?",
+ "Ja",
+ "Nein",
+ "Pacwort eingeben",
+ "Going East"
+};
+
+TattooFixedText::TattooFixedText(SherlockEngine *vm) : FixedText(vm) {
+ if (vm->getLanguage() == Common::DE_DEU)
+ _fixedText = FIXED_TEXT_GERMAN;
+ else
+ _fixedText = FIXED_TEXT_ENGLISH;
+}
+
+const char *TattooFixedText::getText(int fixedTextId) {
+ return _fixedText[fixedTextId];
+}
+
+const Common::String TattooFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) {
+ return Common::String();
+}
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.h b/engines/sherlock/tattoo/tattoo_fixed_text.h
new file mode 100644
index 0000000000..3f43678ca2
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_fixed_text.h
@@ -0,0 +1,134 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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_Inv2,
+ kFixedText_Inv3,
+ kFixedText_Inv4,
+ kFixedText_Inv5,
+ kFixedText_Inv6,
+ kFixedText_Inv7,
+ kFixedText_Inv8,
+ kFixedText_InvDesc1,
+ kFixedText_InvDesc2,
+ kFixedText_InvDesc3,
+ kFixedText_InvDesc4,
+ kFixedText_InvDesc5,
+ kFixedText_InvDesc6,
+ kFixedText_InvDesc7,
+ 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_Holmes,
+ kFixedText_Jock,
+ kFixedText_Bull,
+ kFixedText_Round,
+ kFixedText_TurnTotal,
+ kFixedText_Dart,
+ kFixedText_ToStart,
+ kFixedText_HitAKey,
+ kFixedText_PressAKey,
+ kFixedText_Bullseye,
+ kFixedText_GameOver,
+ kFixedText_Busted,
+ kFixedText_Wins,
+ kFixedText_Scored,
+ kFixedText_Points,
+ kFixedText_Hit,
+ kFixedText_Double,
+ kFixedText_Triple,
+
+ 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
+};
+
+class TattooFixedText: public FixedText {
+private:
+ const char *const *_fixedText;
+public:
+ TattooFixedText(SherlockEngine *vm);
+ virtual ~TattooFixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId);
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_inventory.cpp b/engines/sherlock/tattoo/tattoo_inventory.cpp
new file mode 100644
index 0000000000..6bd1822c10
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_inventory.cpp
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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();
+ char c;
+
+ for (int idx = 0; idx < count; ++idx) {
+ Common::String name;
+ while ((c = stream->readByte()) != 0)
+ name += c;
+
+ _names.push_back(name);
+ }
+
+ delete stream;
+
+ loadGraphics();
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_inventory.h b/engines/sherlock/tattoo/tattoo_inventory.h
new file mode 100644
index 0000000000..a18324b785
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_inventory.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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();
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp
new file mode 100644
index 0000000000..29b40096cb
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_journal.cpp
@@ -0,0 +1,939 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_journal.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"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define JOURNAL_BAR_WIDTH 450
+
+TattooJournal::TattooJournal(SherlockEngine *vm) : Journal(vm) {
+ _journalImages = nullptr;
+ _selector = _oldSelector = JH_NONE;
+ _wait = false;
+ _exitJournal = false;
+ _scrollingTimer = 0;
+ _savedIndex = _savedSub = _savedPage = 0;
+
+ loadLocations();
+}
+
+void TattooJournal::show() {
+ Events &events = *_vm->_events;
+ Resources &res = *_vm->_res;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ byte palette[PALETTE_SIZE];
+
+ Common::Point oldScroll = screen._currentScroll;
+ screen._currentScroll = Common::Point(0, 0);
+
+ // Load journal images
+ _journalImages = new ImageFile("journal.vgs");
+
+ // Load palette
+ Common::SeekableReadStream *stream = res.load("journal.pal");
+ stream->read(palette, PALETTE_SIZE);
+ ui.setupBGArea(palette);
+ screen.translatePalette(palette);
+ delete stream;
+
+ // Set screen to black, and set background
+ screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0));
+ screen.empty();
+ screen.setPalette(palette);
+
+ if (_journal.empty()) {
+ _up = _down = false;
+ } else {
+ drawJournal(0, 0);
+ }
+ drawControls(0);
+ screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ _exitJournal = false;
+ _scrollingTimer = 0;
+
+ do {
+ events.pollEventsAndWait();
+ events.setButtonState();
+ _wait = true;
+
+ handleKeyboardEvents();
+ highlightJournalControls(true);
+
+ handleButtons();
+
+ if (_wait)
+ events.wait(2);
+
+ } while (!_vm->shouldQuit() && !_exitJournal);
+
+ // Clear events
+ events.clearEvents();
+
+ // Free the images
+ delete _journalImages;
+ _journalImages = nullptr;
+
+ // Reset back to whatever scroll was active for the screen
+ screen._currentScroll = oldScroll;
+}
+
+void TattooJournal::handleKeyboardEvents() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+
+ if (!events.kbHit())
+ return;
+
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_TAB && (keyState.flags & Common::KBD_SHIFT)) {
+ // Shift tab
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ // See if mouse is over any of the journal controls
+ _selector = JH_NONE;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over an option, move the mouse to that it points to the first option
+ if (_selector == JH_NONE) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2));
+ } else {
+ if (_selector == JH_CLOSE)
+ _selector = JH_PRINT;
+ else
+ --_selector;
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_PAGEUP) {
+ // See if they have Shift held down to go forward 10 pages
+ if (keyState.flags & Common::KBD_SHIFT) {
+ if (_page > 1) {
+ // Scroll Up 10 pages if possible
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ } else {
+ if (_page > 1) {
+ // Scroll Up 1 page
+ drawJournal(1, LINES_PER_PAGE);
+ drawScrollBar();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN) {
+ if (keyState.flags & Common::KBD_SHIFT) {
+ if (_down) {
+ // Scroll down 10 Pages
+ if (_page + 10 > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ } else {
+ if (_down) {
+ // Scroll down 1 page
+ drawJournal(2, LINES_PER_PAGE);
+ drawScrollBar();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_HOME) {
+ // Scroll to start of journal
+ if (_page > 1) {
+ // Go to the beginning of the journal
+ _index = _sub = _up = _down = 0;
+ _page = 1;
+
+ drawFrame();
+ drawJournal(0, 0);
+
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_END) {
+ // Scroll to end of journal
+ if (_down) {
+ // Go to the end of the journal
+ drawJournal(2, 100000);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ } else if (keyState.keycode == Common::KEYCODE_RETURN) {
+ events._pressed = false;
+ events._released = true;
+ events._oldButtons = 0;
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _exitJournal = true;
+ } else if (keyState.keycode == Common::KEYCODE_TAB) {
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCENE_HEIGHT - r.height());
+
+ // See if the mouse is over any of the journal controls
+ _selector = JH_NONE;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over any of the options, move the mouse so that it points to the first option
+ if (_selector == JH_NONE) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2));
+ } else {
+ if (_selector == JH_PRINT)
+ _selector = JH_NONE;
+ else
+ ++_selector;
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+ }
+}
+
+void TattooJournal::handleButtons() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ uint32 frameCounter = events.getFrameCounter();
+ 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) && _selector == JH_THUMBNAIL) {
+ // FIgure out the left of the scrollbar scroll area and paging data
+ const int scrollingWidth = JOURNAL_BAR_WIDTH - BUTTON_SIZE * 2 - 6;
+ const int scrollingLeft = (SHERLOCK_SCREEN_WIDTH - JOURNAL_BAR_WIDTH) / 2 + BUTTON_SIZE + 3;
+ const int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1;
+ const int barWidth = CLIP(scrollingWidth / numPages, BUTTON_SIZE, JOURNAL_BAR_WIDTH - BUTTON_SIZE * 2 - 6);
+
+ const int scrollOffset = mousePos.x - scrollingLeft;
+ const int page = scrollOffset * FIXED_INT_MULTIPLIER / ((scrollingWidth - barWidth) * (FIXED_INT_MULTIPLIER / (numPages - 1))) + 1;
+
+ if (page != _page) {
+ if (page < _page)
+ drawJournal(1, (_page - page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, (page - _page) * LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ } else if (_selector != JH_NONE && events._pressed) {
+ if (frameCounter >= _scrollingTimer) {
+ // Set next scrolling time
+ _scrollingTimer = frameCounter + 6;
+
+ // Handle different scrolling actions
+ switch (_selector) {
+ case JH_SCROLL_LEFT:
+ // Scroll left (1 page back)
+ if (_page > 1) {
+ // Scroll Up
+ drawJournal(1, LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case JH_PAGE_LEFT:
+ // Page left (10 pages back)
+ if (_page > 1) {
+ // Scroll Up 10 Pages if possible
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case JH_PAGE_RIGHT:
+ // Page right (10 pages ahead)
+ if (_down) {
+ // Scroll Down 10 Pages
+ if (_page + 10 > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case JH_SCROLL_RIGHT:
+ // Scroll right (1 Page Ahead)
+ if (_down) {
+ // Scroll Down
+ drawJournal(2, LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (events._released || events._rightReleased) {
+ _scrollingTimer = 0;
+
+ switch (_selector) {
+ case JH_CLOSE:
+ _exitJournal = true;
+ break;
+
+ case JH_SEARCH: {
+ // Search Journal
+ disableControls();
+
+ bool notFound = false;
+ int dir;
+
+ do {
+ if ((dir = getFindName(notFound)) != 0) {
+ _savedIndex = _index;
+ _savedSub = _sub;
+ _savedPage = _page;
+
+ if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0)
+ {
+ _index = _savedIndex;
+ _sub = _savedSub;
+ _page = _savedPage;
+
+ drawFrame();
+ drawJournal(0, 0);
+ notFound = true;
+ } else {
+ break;
+ }
+
+ highlightJournalControls(false);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ break;
+ }
+ } while (!_vm->shouldQuit());
+ break;
+ }
+
+ case JH_PRINT:
+ // Print Journal - not implemented in ScummVM
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void TattooJournal::loadLocations() {
+ Resources &res = *_vm->_res;
+
+ _directory.clear();
+ _locations.clear();
+
+ Common::SeekableReadStream *dir = res.load("talk.lib");
+ dir->skip(4); // Skip header
+
+ // Get the numer of entries
+ _directory.resize(dir->readUint16LE());
+ dir->seek((_directory.size() + 1) * 8, SEEK_CUR);
+
+ // Read in each entry
+ char buffer[17];
+ for (uint idx = 0; idx < _directory.size(); ++idx) {
+ dir->read(buffer, 17);
+ buffer[16] = '\0';
+
+ _directory[idx] = Common::String(buffer);
+ }
+
+ delete dir;
+
+ // Load in the locations stored in journal.txt
+ Common::SeekableReadStream *loc = res.load("journal.txt");
+
+ // Initialize locations
+ _locations.resize(100);
+ for (int idx = 0; idx < 100; ++idx)
+ _locations[idx] = "No Description";
+
+ while (loc->pos() < loc->size()) {
+ // In Rose Tattoo, each location line starts with the location
+ // number, followed by a dot, some spaces and its description
+ // in quotes
+ Common::String line = loc->readLine();
+ Common::String locNumStr;
+ int locNum = 0;
+ int i = 0;
+ Common::String locDesc;
+
+ // Get the location
+ while (Common::isDigit(line[i])) {
+ locNumStr += line[i];
+ i++;
+ }
+ locNum = atoi(locNumStr.c_str()) - 1;
+
+ // Skip the dot, spaces and initial quotation mark
+ while (line[i] == ' ' || line[i] == '.' || line[i] == '\"')
+ i++;
+
+ do {
+ locDesc += line[i];
+ i++;
+ } while (line[i] != '\"');
+
+ _locations[locNum] = locDesc;
+ }
+
+ delete loc;
+}
+
+void TattooJournal::drawFrame() {
+ Screen &screen = *_vm->_screen;
+
+ screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0));
+ drawControls(0);
+
+}
+
+void TattooJournal::drawControls(int mode) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ImageFile &images = *ui._interfaceImages;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, !mode ? (BUTTON_SIZE + screen.fontHeight() + 13) :
+ (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, !mode ? (SHERLOCK_SCREEN_HEIGHT - r.height()) :
+ (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ Common::Rect inner = r;
+ inner.grow(-3);
+
+ if (vm._transparentMenus)
+ ui.makeBGArea(inner);
+ else
+ screen._backBuffer1.fillRect(inner, MENU_BACKGROUND);
+
+ // Draw the four corners of the info box
+ screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height));
+
+ // Draw the top of the info box
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM);
+
+ // Draw the bottom of the info box
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM);
+
+ // Draw the left side of the info box
+ screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP);
+ screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE);
+ screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM);
+
+ // Draw the right side of the info box
+ screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP);
+ screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE);
+ screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM);
+
+ // Draw the sides of the separator bar above the scroll bar
+ int yp = r.top + screen.fontHeight() + 7;
+ screen._backBuffer1.transBlitFrom(images[4], Common::Point(r.left, yp - 1));
+ screen._backBuffer1.transBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1));
+
+ // Draw the bar above the scroll bar
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp + 1, r.right - images[5]._width, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp + 2, r.right - images[5]._width, INFO_BOTTOM);
+
+ if (mode != 2) {
+ // Draw the Bars separating the Journal Commands
+ int xp = r.right / 3;
+ for (int idx = 0; idx < 2; ++idx) {
+ screen._backBuffer1.transBlitFrom(images[6], Common::Point(xp - 2, r.top + 1));
+ screen._backBuffer1.transBlitFrom(images[7], Common::Point(xp - 2, yp - 1));
+
+ screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP);
+ screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE);
+ screen._backBuffer1.hLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM);
+ xp = r.right / 3 * 2;
+ }
+ }
+
+ int savedSelector = _oldSelector;
+ _oldSelector = 100;
+
+ switch (mode) {
+ case 0:
+ highlightJournalControls(false);
+ break;
+ case 1:
+ highlightSearchControls(false);
+ break;
+ default:
+ break;
+ }
+
+ _oldSelector = savedSelector;
+}
+
+void TattooJournal::highlightJournalControls(bool slamIt) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ if ((events._pressed || events._released) && _selector == JH_THUMBNAIL) {
+ if (events._released)
+ _selector = JH_NONE;
+ } else {
+ // Calculate the Scroll Position Bar
+ int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1;
+ int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages;
+ barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6);
+
+ int barX = (numPages <= 1) ? r.left + 3 + BUTTON_SIZE : (r.width() - BUTTON_SIZE * 2 - 6 - barWidth)
+ * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE;
+
+ // See if the mouse is over any of the Journal Controls
+ Common::Rect bounds(r.left, r.top, r.right - 3, r.top + screen.fontHeight() + 7);
+ _selector = JH_NONE;
+ if (bounds.contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ else if (events._pressed && mousePos.y >= (r.top + screen.fontHeight() + 10)
+ && mousePos.y < (r.top + screen.fontHeight() + 10 + BUTTON_SIZE)) {
+ if (mousePos.x >= r.left && mousePos.x < (r.left + BUTTON_SIZE))
+ // Press on the Scroll Left button
+ _selector = JH_SCROLL_LEFT;
+ else if (mousePos.x >= (r.left + BUTTON_SIZE + 3) && mousePos.x < barX)
+ // Press on area to the left of the thumb, for scrolling back 10 pages
+ _selector = JH_PAGE_LEFT;
+ else if (mousePos.x >= (barX + barWidth) && mousePos.x < (r.right - BUTTON_SIZE - 3))
+ // Press on area to the right of the thumb, for scrolling forward 10 pages
+ _selector = JH_PAGE_RIGHT;
+ else if (mousePos.x >= (r.right - BUTTON_SIZE) && mousePos.x < r.right)
+ // Press of the Scroll Right button
+ _selector = JH_SCROLL_RIGHT;
+ else if (mousePos.x >= barX && mousePos.x < (barX + barWidth))
+ // Mouse on thumbnail
+ _selector = JH_THUMBNAIL;
+ }
+ }
+
+ // See if the Search was selected, but is not available
+ if (_journal.empty() && (_selector == JH_SEARCH || _selector == JH_PRINT))
+ _selector = JH_NONE;
+
+ if (_selector == JH_PAGE_LEFT && _oldSelector == JH_PAGE_RIGHT)
+ _selector = JH_PAGE_RIGHT;
+ else if (_selector == JH_PAGE_RIGHT && _oldSelector == JH_PAGE_LEFT)
+ _selector = JH_PAGE_LEFT;
+
+ // See if they're pointing at a different control
+ if (_selector != _oldSelector) {
+ // Print the Journal commands
+ int xp = r.left + r.width() / 6;
+ byte color = (_selector == JH_CLOSE) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(CloseJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(CloseJournal));
+ xp += r.width() / 3;
+
+ if (!_journal.empty())
+ color = (_selector == JH_SEARCH) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+ else
+ color = INFO_BOTTOM;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SearchJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(SearchJournal));
+ xp += r.width() / 3;
+
+ color = INFO_BOTTOM;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SaveJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(SaveJournal));
+
+ // Draw the horizontal scrollbar
+ drawScrollBar();
+
+ if (slamIt)
+ screen.slamRect(r);
+
+ _oldSelector = _selector;
+ }
+}
+
+void TattooJournal::highlightSearchControls(bool slamIt) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+ Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+ const char *SEARCH_COMMANDS[3] = { FIXED(AbortSearch), FIXED(SearchBackwards), FIXED(SearchForwards) };
+
+ // See if the mouse is over any of the Journal Controls
+ _selector = JH_NONE;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + 7 + screen.fontHeight()).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // See if they're pointing at a different control
+ if (_selector != _oldSelector) {
+ // Print the search commands
+ int xp = r.left + r.width() / 6;
+
+ for (int idx = 0; idx < 3; ++idx) {
+ byte color = (_selector == idx) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(SEARCH_COMMANDS[idx]) / 2,
+ r.top + 5), color, "%s", SEARCH_COMMANDS[idx]);
+ xp += r.width() / 3;
+ }
+
+ if (slamIt)
+ screen.slamRect(r);
+
+ _oldSelector = _selector;
+ }
+}
+
+void TattooJournal::drawScrollBar() {
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ bool raised;
+ byte color;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ // Calculate the Scroll Position Bar
+ int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1;
+ int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages;
+ barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6);
+ int barX;
+ if (numPages <= 1) {
+ barX = r.left + 3 + BUTTON_SIZE;
+ } else {
+ barX = (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) *
+ (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE;
+ if (barX + BUTTON_SIZE > r.left + r.width() - BUTTON_SIZE - 3)
+ barX = r.right - BUTTON_SIZE * 2 - 3;
+ }
+
+ // Draw the scroll bar here
+ // Draw the Scroll Left button
+ raised = _selector != JH_SCROLL_LEFT;
+ screen._backBuffer1.fillRect(Common::Rect(r.left, r.top + screen.fontHeight() + 12, r.left + BUTTON_SIZE,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.left + 3, r.top + screen.fontHeight() + 10, r.left + 3 + BUTTON_SIZE,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE), raised);
+
+ color = (_page > 1) ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ screen._backBuffer1.vLine(r.left + 1 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 2 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 3 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 4 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color);
+
+ // Draw the Scroll Right button
+ raised = _selector != JH_SCROLL_RIGHT;
+ screen._backBuffer1.fillRect(Common::Rect(r.right - BUTTON_SIZE - 1, r.top + screen.fontHeight() + 12,
+ r.right - 5, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), raised);
+
+ color = _down ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ screen._backBuffer1.vLine(r.right - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 2 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 3 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 4 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color);
+
+ // Draw the scroll bar
+ screen._backBuffer1.fillRect(Common::Rect(barX + 2, r.top + screen.fontHeight() + 12, barX + barWidth - 3,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(barX, r.top + screen.fontHeight() + 10, barX + barWidth,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE), true);
+}
+
+void TattooJournal::disableControls() {
+ Screen &screen = *_vm->_screen;
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_HEIGHT - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+ const char *JOURNAL_COMMANDS[3] = { FIXED(CloseJournal), FIXED(SearchJournal), FIXED(SaveJournal) };
+
+ // Print the Journal commands
+ int xp = r.left + r.width() / 6;
+ for (int idx = 0; idx < 2; ++idx) {
+ screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5),
+ INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]);
+
+ xp += r.width() / 3;
+ }
+
+ screen.slamRect(r);
+}
+
+int TattooJournal::getFindName(bool printError) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int result = 0;
+ int done = 0;
+ Common::String name;
+ int cursorX, cursorY;
+ bool flag = false;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ // Set the cursors Y position
+ cursorY = r.top + screen.fontHeight() + 12;
+
+ drawControls(1);
+
+ // Backup the area under the text entry
+ Surface bgSurface(r.width() - 6, screen.fontHeight());
+ bgSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY,
+ r.right - 3, cursorY + screen.fontHeight()));
+
+ if (printError) {
+ screen.gPrint(Common::Point(0, cursorY), INFO_TOP, "%s", FIXED(TextNotFound));
+ } else {
+ // If there was a name already entered, copy it to name and display it
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str());
+ name = _find;
+ }
+ }
+
+ screen.slamRect(r);
+
+ if (printError) {
+ // Pause to allow error to be shown
+ int timer = 0;
+
+ do {
+ events.pollEvents();
+ events.setButtonState();
+
+ ++timer;
+ events.wait(2);
+ } while (!_vm->shouldQuit() && !events.kbHit() && !events._released && !events._rightReleased && timer < 40);
+
+ events.clearEvents();
+
+ // Restore the text background
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left, cursorY));
+
+ // If there was a name already entered, copy it to name and display it
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(r.left + 3, cursorY, r.width() - 6, screen.fontHeight());
+ }
+
+ // Set the cursors X position
+ cursorX = r.left + screen.widestChar() + 3 + screen.stringWidth(name);
+
+ do {
+ events._released = events._rightReleased = false;
+
+ while (!events.kbHit() && !events._released && !events._rightReleased) {
+ if (talk._talkToAbort)
+ return 0;
+
+ // See if a key or a mouse button is pressed
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ // Handle blinking cursor
+ flag = !flag;
+ if (flag) {
+ // Draw cursor
+ screen._backBuffer1.fillRect(Common::Rect(cursorX, cursorY, cursorX + 7, cursorY + 8), COMMAND_HIGHLIGHTED);
+ screen.slamArea(cursorX, cursorY, 8, 9);
+ } else {
+ // Erase cursor by restoring background and writing current text
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY));
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str());
+ screen.slamArea(r.left + 3, r.top, r.width() - 3, screen.fontHeight());
+ }
+
+ highlightSearchControls(true);
+
+ events.wait(2);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ Common::Point mousePos = events.mousePos();
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE && !name.empty()) {
+ cursorX -= screen.charWidth(name.lastChar());
+ name.deleteLastChar();
+ }
+
+ if (keyState.keycode == Common::KEYCODE_RETURN)
+ done = 1;
+
+ else if (keyState.keycode == Common::KEYCODE_ESCAPE)
+ done = -1;
+
+ if (keyState.keycode == Common::KEYCODE_TAB) {
+ r = Common::Rect(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ // See if the mouse is over any of the journal controls
+ _selector = JH_NONE;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over any of the options, move the mouse so that it points to the first option
+ if (_selector == JH_NONE) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3, r.top + screen.fontHeight() + 2));
+ } else {
+ if (keyState.keycode & Common::KBD_SHIFT) {
+ if (_selector == JH_CLOSE)
+ _selector = JH_PRINT;
+ else
+ --_selector;
+ } else {
+ if (_selector == JH_PRINT)
+ _selector = JH_CLOSE;
+ else
+ ++_selector;
+ }
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+ }
+
+ if (keyState.ascii && keyState.ascii != '@' && name.size() < 50) {
+ if ((cursorX + screen.charWidth(keyState.ascii)) < (r.right - screen.widestChar() * 3)) {
+ cursorX += screen.charWidth(keyState.ascii);
+ name += toupper(keyState.ascii);
+ }
+ }
+
+ // Redraw the text
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY));
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED,
+ "%s", name.c_str());
+ screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight());
+ }
+
+ if (events._released || events._rightReleased) {
+ switch (_selector) {
+ case JH_CLOSE:
+ done = -1;
+ break;
+ case JH_SEARCH:
+ done = 2;
+ break;
+ case JH_PRINT:
+ done = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ } while (!done);
+
+ if (done != -1) {
+ _find = name;
+ result = done;
+ } else {
+ result = 0;
+ }
+
+ drawFrame();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ return result;
+}
+
+void TattooJournal::record(int converseNum, int statementNum, bool replyOnly) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+
+ // Only record activity in the Journal if the player is Holmes (i.e. we're paast the prologoue)
+ if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES) && !vm._runningProlog)
+ Journal::record(converseNum, statementNum, replyOnly);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_journal.h b/engines/sherlock/tattoo/tattoo_journal.h
new file mode 100644
index 0000000000..96c1c6cab4
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_journal.h
@@ -0,0 +1,114 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#ifndef 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_PRINT = 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);
+public:
+ TattooJournal(SherlockEngine *vm);
+ virtual ~TattooJournal() {}
+
+ /**
+ * Show the journal
+ */
+ void show();
+public:
+ /**
+ * Draw the journal background, frame, and interface buttons
+ */
+ virtual void drawFrame();
+
+ /**
+ * Records statements that are said, in the order which they are said. The player
+ * can then read the journal to review them
+ */
+ virtual void record(int converseNum, int statementNum, bool replyOnly = false);
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_map.cpp b/engines/sherlock/tattoo/tattoo_map.cpp
new file mode 100644
index 0000000000..4bd85bd5c0
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_map.cpp
@@ -0,0 +1,434 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_map.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo.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, 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.blitFrom((*map)[0], Common::Point(0, 0));
+ 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.blitFrom(screen._backBuffer1);
+
+ // Display the built map to the screen
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ // Set initial scroll position
+ _targetScroll = _bigPos;
+ screen._currentScroll = Common::Point(-1, -1);
+
+ 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);
+ }
+
+ 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.w())
+ _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH;
+ if (_targetScroll.y < 0)
+ _targetScroll.y = 0;
+ if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.h())
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+
+ // Check the keyboard
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ switch (keyState.keycode) {
+ case Common::KEYCODE_HOME:
+ _targetScroll.x = 0;
+ _targetScroll.y = 0;
+ break;
+
+ case Common::KEYCODE_END:
+ _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH;
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ _targetScroll.y -= SHERLOCK_SCREEN_HEIGHT;
+ if (_targetScroll.y < 0)
+ _targetScroll.y = 0;
+ break;
+
+ case Common::KEYCODE_PAGEDOWN:
+ _targetScroll.y += SHERLOCK_SCREEN_HEIGHT;
+ if (_targetScroll.y > (screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT))
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+ break;
+
+ case Common::KEYCODE_SPACE:
+ 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());
+
+ 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);
+
+ 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.transBlitFrom(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.w(), screen._backBuffer1.h()));
+
+ if (!r.isEmpty())
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r);
+}
+
+void TattooMap::showCloseUp(int closeUpNum) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+
+ // Reset scroll position
+ screen._currentScroll = Common::Point(0, 0);
+
+ // Get the closeup images
+ Common::String 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(closeUp.x / 100 - picSize.x / 2, closeUp.y / 100 - picSize.y / 2);
+
+ restoreArea(oldBounds);
+ screen._backBuffer1.transBlitFrom(pic[0], pt, false, 0, 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(SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2, SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2,
+ SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width,
+ SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height);
+
+ restoreArea(oldBounds);
+ screen._backBuffer1.transBlitFrom(pic[0], Common::Point(r.left, r.top));
+ screen.slamRect(oldBounds);
+ screen.slamRect(r);
+ events.wait(60);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_map.h b/engines/sherlock/tattoo/tattoo_map.h
new file mode 100644
index 0000000000..1c7173bdb0
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_map.h
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~TattooMap() {}
+
+ /**
+ * Show the map
+ */
+ virtual int show();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_people.cpp b/engines/sherlock/tattoo/tattoo_people.cpp
new file mode 100644
index 0000000000..e3e957e35c
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_people.cpp
@@ -0,0 +1,1503 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_talk.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define FACING_PLAYER 16
+#define NUM_ADJUSTED_WALKS 21
+#define CHARACTERS_INDEX 256
+
+struct AdjustWalk {
+ char _vgsName[9];
+ int _xAdjust;
+ int _flipXAdjust;
+ int _yAdjust;
+} ;
+
+static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = {
+ { "TUPRIGHT", -7, -19, 6 },
+ { "TRIGHT", 8, -14, 0 },
+ { "TDOWNRG", 14, -12, 0 },
+ { "TWUPRIGH", 12, 4, 2 },
+ { "TWRIGHT", 31, -14, 0 },
+ { "TWDOWNRG", 6, -24, 0 },
+ { "HTUPRIGH", 2, -20, 0 },
+ { "HTRIGHT", 28, -20, 0 },
+ { "HTDOWNRG", 8, -2, 0 },
+ { "GTUPRIGH", 4, -12, 0 },
+ { "GTRIGHT", 12, -16, 0 },
+ { "GTDOWNRG", 10, -18, 0 },
+ { "JTUPRIGH", 8, -10, 0 },
+ { "JTRIGHT", 22, -6, 0 },
+ { "JTDOWNRG", 4, -20, 0 },
+ { "CTUPRIGH", 10, 0, 0 },
+ { "CTRIGHT", 26, -22, 0 },
+ { "CTDOWNRI", 16, 4, 0 },
+ { "ITUPRIGH", 0, 0, 0 },
+ { "ITRIGHT", 20, 0, 0 },
+ { "ITDOWNRG", 8, 0, 0 }
+};
+
+static const int WALK_SPEED_X[99] = {
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 90, 90, 90, 90, 90, 91, 90, 90,
+ 90, 90,100, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,100, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,103, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90
+};
+
+static const int WALK_SPEED_Y[99] = {
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 32, 32, 32, 28, 28, 28, 28, 28, 26, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 31, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
+};
+
+static const int WALK_SPEED_DIAG_X[99] = {
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 90, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50
+};
+
+/*----------------------------------------------------------------*/
+
+SavedNPCPath::SavedNPCPath() {
+ Common::fill(&_path[0], &_path[MAX_NPC_PATH], 0);
+ _npcIndex = 0;
+ _npcPause = 0;
+ _npcFacing = 0;
+ _lookHolmes = false;
+}
+
+SavedNPCPath::SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Point32 &position,
+ int npcFacing, bool lookHolmes) : _npcIndex(npcIndex), _npcPause(npcPause), _position(position),
+ _npcFacing(npcFacing), _lookHolmes(lookHolmes) {
+ Common::copy(&path[0], &path[MAX_NPC_PATH], &_path[0]);
+}
+
+/*----------------------------------------------------------------*/
+
+TattooPerson::TattooPerson() : Person() {
+ Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
+ _tempX = _tempScaleVal = 0;
+ _npcIndex = 0;
+ _npcMoved = false;
+ _npcFacing = -1;
+ _resetNPCPath = true;
+ _savedNpcSequence = 0;
+ _savedNpcFrame = 0;
+ _updateNPCPath = true;
+ _npcPause = 0;
+ _lookHolmes = false;
+}
+
+void TattooPerson::freeAltGraphics() {
+ if (_altImages != nullptr) {
+ delete _altImages;
+ _altImages = nullptr;
+ }
+
+ _altSeq = 0;
+}
+
+void TattooPerson::adjustSprite() {
+ People &people = *_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ if (_type == INVALID)
+ return;
+
+ if (_type == CHARACTER && _status) {
+ // Sprite waiting to move, so restart walk
+ _walkCount = _status;
+ _status = 0;
+
+ _walkDest = _walkTo.front();
+ setWalking();
+ } else if (_type == CHARACTER && _walkCount) {
+ if (_walkCount > 10) {
+ _walkDest = _nextDest;
+ setWalking();
+ }
+
+ _position += _delta;
+ if (_walkCount)
+ --_walkCount;
+
+ if (!_walkCount) {
+ // If there are remaining points to walk, move to the next one
+ if (!_walkTo.empty()) {
+ _walkDest = _walkTo.pop();
+ setWalking();
+ } else {
+ gotoStand();
+ }
+ }
+ }
+
+ if (_type != CHARACTER) {
+ if (_position.y > SHERLOCK_SCREEN_HEIGHT)
+ _position.y = SHERLOCK_SCREEN_HEIGHT;
+
+ if (_position.y < UPPER_LIMIT)
+ _position.y = UPPER_LIMIT;
+
+ if (_position.x < LEFT_LIMIT)
+ _position.x = LEFT_LIMIT;
+
+ if (_position.x > RIGHT_LIMIT)
+ _position.x = RIGHT_LIMIT;
+ }
+
+ int frameNum = _frameNumber;
+ if (frameNum == -1)
+ frameNum = 0;
+ int idx = _walkSequences[_sequenceNumber][frameNum];
+ if (idx > _maxFrames)
+ idx = 1;
+
+ // Set the image frame
+ if (_altSeq)
+ _imageFrame = &(*_altImages)[idx - 1];
+ else
+ _imageFrame = &(*_images)[idx - 1];
+
+ // See if the player has come to a stop after clicking on an Arrow zone to leave the scene.
+ // If so, this will set up the exit information for the scene transition
+ if (!_walkCount && ui._exitZone != -1 && scene._walkedInScene && scene._goToScene == -1 &&
+ !_description.compareToIgnoreCase(people[HOLMES]._description)) {
+ Exit &exit = scene._exits[ui._exitZone];
+ scene._goToScene = exit._scene;
+
+ if (exit._newPosition.x != 0) {
+ people._savedPos = exit._newPosition;
+
+ if (people._savedPos._facing > 100 && people._savedPos.x < 1)
+ people._savedPos.x = 100;
+ }
+ }
+}
+
+void TattooPerson::gotoStand() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+
+ // If the misc field is set, then we're running a special talk sequence, so don't interrupt it.
+ if (_misc)
+ return;
+
+ _walkTo.clear();
+ _walkCount = 0;
+ int oldFacing = _sequenceNumber;
+
+ // If the person was talking or listening, just return it to the standing sequence
+ // in the direction they were pointing
+ if (_sequenceNumber >= TALK_UPRIGHT && _sequenceNumber <= LISTEN_UPLEFT) {
+ switch (_sequenceNumber) {
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ _sequenceNumber = STOP_UPRIGHT;
+ break;
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ _sequenceNumber = STOP_RIGHT;
+ break;
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ _sequenceNumber = STOP_DOWNRIGHT;
+ break;
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ _sequenceNumber = STOP_DOWNLEFT;
+ break;
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ _sequenceNumber = STOP_LEFT;
+ break;
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ _sequenceNumber = STOP_UPLEFT;
+ break;
+ default:
+ break;
+ }
+
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ // Set the Frame number to the last frame so we don't move
+ _frameNumber = 0;
+
+ checkWalkGraphics();
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+ return;
+ }
+
+ // If the sprite that is stopping is an NPC and he is supposed to face a certain direction
+ // when he stops, set that direction here
+ int npc = -1;
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (_imageFrame == people[idx]._imageFrame)
+ npc = idx;
+ }
+
+ if (npc != -1 && people[npc]._npcFacing != -1) {
+ if (people[npc]._npcFacing == FACING_PLAYER) {
+ // See where Holmes is with respect to the NPC (x coords)
+ if (people[HOLMES]._position.x < people[npc]._position.x)
+ people[npc]._npcFacing = STOP_LEFT;
+ else
+ people[npc]._npcFacing = STOP_RIGHT;
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (people[HOLMES]._position.y < people[npc]._position.y - (10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is above the NPC so reset the facing to the diagonal ups
+ if (people[npc]._npcFacing == STOP_RIGHT)
+ people[npc]._npcFacing = STOP_UPRIGHT;
+ else
+ people[npc]._npcFacing = STOP_UPLEFT;
+ } else {
+ if (people[HOLMES]._position.y > people[npc]._position.y + (10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC so reset the facing to the diagonal downs
+ if (people[npc]._npcFacing == STOP_RIGHT)
+ people[npc]._npcFacing = STOP_DOWNRIGHT;
+ else
+ people[npc]._npcFacing = STOP_DOWNLEFT;
+ }
+ }
+ }
+
+ _sequenceNumber = people[npc]._npcFacing;
+ } else {
+ switch (_sequenceNumber) {
+ case WALK_UP: _sequenceNumber = STOP_UP; break;
+ case WALK_UPRIGHT: _sequenceNumber = STOP_UPRIGHT; break;
+ case WALK_RIGHT: _sequenceNumber = STOP_RIGHT; break;
+ case WALK_DOWNRIGHT: _sequenceNumber = STOP_DOWNRIGHT; break;
+ case WALK_DOWN: _sequenceNumber = STOP_DOWN; break;
+ case WALK_DOWNLEFT: _sequenceNumber = STOP_DOWNLEFT;break;
+ case WALK_LEFT: _sequenceNumber = STOP_LEFT; break;
+ case WALK_UPLEFT: _sequenceNumber = STOP_UPLEFT; break;
+ }
+ }
+
+ // Only restart the frame number at 0 if the new sequence is different from the last sequence
+ // so we don't let Holmes repeat standing.
+ if (_oldWalkSequence != -1) {
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ _frameNumber = 0;
+ }
+
+ checkWalkGraphics();
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+}
+
+void TattooPerson::setWalking() {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ int oldDirection, oldFrame;
+ Common::Point delta;
+ _nextDest = _walkDest;
+
+ // Flag that player has now walked in the scene
+ scene._walkedInScene = true;
+
+ // Stop any previous walking, since a new dest is being set
+ _walkCount = 0;
+ oldDirection = _sequenceNumber;
+ oldFrame = _frameNumber;
+
+ // Set speed to use horizontal and vertical movement
+ int scaleVal = scene.getScaleVal(_position);
+ Common::Point speed(MAX(WALK_SPEED_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
+ MAX(WALK_SPEED_Y[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2));
+ Common::Point diagSpeed(MAX(WALK_SPEED_DIAG_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
+ MAX((WALK_SPEED_Y[scene._currentScene - 1] - 2) * SCALE_THRESHOLD / scaleVal, 2));
+
+ // If the player is already close to the given destination that no walking is needed,
+ // move to the next straight line segment in the overall walking route, if there is one
+ for (;;) {
+ if (_centerWalk || !_walkTo.empty()) {
+ // Since we want the player to be centered on the ultimate destination, and the player
+ // is drawn from the left side, move the cursor half the width of the player to center it
+ delta = Common::Point(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x,
+ _position.y / FIXED_INT_MULTIPLIER - _walkDest.y);
+
+ int dir;
+ if (ABS(delta.x) > ABS(delta.y))
+ dir = (delta.x < 0) ? WALK_LEFT : WALK_RIGHT;
+ else
+ dir = (delta.y < 0) ? WALK_UP : WALK_DOWN;
+
+ scaleVal = scene.getScaleVal(Point32(_walkDest.x * FIXED_INT_MULTIPLIER,
+ _walkDest.y * FIXED_INT_MULTIPLIER));
+ _walkDest.x -= _stopFrames[dir]->sDrawXSize(scaleVal) / 2;
+ }
+
+ delta = Common::Point(
+ ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
+ ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
+ );
+
+ // If we're ready to move a sufficient distance, that's it. Otherwise,
+ // move onto the next portion of the walk path, if there is one
+ if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
+ break;
+
+ // Pop next walk segment off the walk route stack
+ _walkDest = _walkTo.pop();
+ }
+
+ // If a sufficient move is being done, then start the move
+ if (delta.x > 3 || delta.y) {
+ // See whether the major movement is horizontal or vertical
+ if (delta.x >= delta.y) {
+ // Set the initial frame sequence for the left and right, as well
+ // as setting the delta x depending on direction
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_LEFT;
+ _delta.x = speed.x * -(FIXED_INT_MULTIPLIER / 10);
+ } else {
+ _sequenceNumber = WALK_RIGHT;
+ _delta.x = speed.x * (FIXED_INT_MULTIPLIER / 10);
+ }
+
+ // See if the x delta is too small to be divided by the speed, since
+ // this would cause a divide by zero error
+ if ((delta.x * 10) >= speed.x) {
+ // Det the delta y
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / ((delta.x * 10) / speed.x);
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
+ _delta.y = -_delta.y;
+
+ // Set how many times we should add the delta to the player's position
+ _walkCount = (delta.x * 10) / speed.x;
+ } else {
+ // The delta x was less than the speed (ie. we're really close to
+ // the destination). So set delta to 0 so the player won't move
+ _delta = Point32(0, 0);
+ _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);
+
+ _walkCount = 1;
+ }
+
+ // See if the sequence needs to be changed for diagonal walking
+ if (_delta.y > 1500) {
+ if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
+ _delta.x = _delta.x / speed.x * diagSpeed.x;
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);
+
+ _walkCount = delta.x * 10 / diagSpeed.x;
+ }
+
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_DOWNLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_DOWNRIGHT;
+ break;
+ }
+ } else if (_delta.y < -1500) {
+ if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
+ _delta.x = _delta.x / speed.x * diagSpeed.x;
+ _delta.y = -1 * (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);
+
+ _walkCount = (delta.x * 10) / diagSpeed.x;
+ }
+
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_UPLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_UPRIGHT;
+ break;
+ }
+ }
+ } else {
+ // Major movement is vertical, so set the sequence for up and down,
+ // and set the delta Y depending on the direction
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_UP;
+ _delta.y = speed.y * -(FIXED_INT_MULTIPLIER / 10);
+ } else {
+ speed.y = diagSpeed.y;
+ _sequenceNumber = WALK_DOWN;
+ _delta.y = speed.y * (FIXED_INT_MULTIPLIER / 10);
+ }
+
+ // Set the delta x
+ if (delta.y * 10 / speed.y)
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y * 10 / speed.y);
+ else
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / delta.y;
+
+ if (_walkDest.x < _position.y / FIXED_INT_MULTIPLIER)
+ _delta.x = -_delta.x;
+
+ // Set how many times we should add the delta's to the players position
+ if (delta.y * 10 / speed.y)
+ _walkCount = delta.y * 10 / speed.y;
+ else
+ _walkCount = delta.y;
+ }
+ }
+
+ // See if the new walk sequence is the same as the old. If it's a new one,
+ // we need to reset the frame number to zero so its animation starts at
+ // its beginning. Otherwise, if it's the same sequence, we can leave it
+ // as is, so it keeps the animation going at wherever it was up to
+ if (_sequenceNumber != _oldWalkSequence) {
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldDirection]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+ _frameNumber = 0;
+ }
+
+ checkWalkGraphics();
+ _oldWalkSequence = _sequenceNumber;
+
+ if (!_walkCount && _walkTo.empty())
+ gotoStand();
+
+ // If the sequence is the same as when we started, then Holmes was standing still and we're trying
+ // to re-stand him, so reset Holmes' rame to the old frame number from before it was reset to 0
+ if (_sequenceNumber == oldDirection)
+ _frameNumber = oldFrame;
+}
+
+void TattooPerson::walkToCoords(const Point32 &destPos, int destDir) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER, destPos.y / FIXED_INT_MULTIPLIER);
+
+ bool isHolmes = this == &people[HOLMES];
+ if (isHolmes) {
+ people._allowWalkAbort = true;
+ } else {
+ // Clear the path Variables
+ _npcIndex = _npcPause = 0;
+ Common::fill(_npcPath, _npcPath + 100, 0);
+ _npcFacing = destDir;
+ }
+
+ _centerWalk = false;
+
+ // Only move the person if they're going an appreciable distance
+ if (ABS(_walkDest.x - (_position.x / FIXED_INT_MULTIPLIER)) > 8 ||
+ ABS(_walkDest.y - (_position.y / FIXED_INT_MULTIPLIER)) > 4) {
+ goAllTheWay();
+
+ do {
+ // Keep doing animations whilst walk is in progress
+ events.wait(1);
+ scene.doBgAnim();
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = 1;
+ talk._talkToAbort = true;
+ }
+ }
+ } while (!_vm->shouldQuit() && _walkCount && !talk._talkToAbort);
+ }
+
+ _centerWalk = true;
+ if (!isHolmes)
+ _updateNPCPath = true;
+
+ if (!talk._talkToAbort) {
+ // put character exactly on right spot
+ _position = destPos;
+
+ if (_sequenceNumber != destDir) {
+ // Facing character to correct ending direction
+ _sequenceNumber = destDir;
+ gotoStand();
+ }
+
+ if (!isHolmes)
+ _updateNPCPath = false;
+
+ // Secondary walking wait loop
+ bool done = false;
+ while (!done && !_vm->shouldQuit()) {
+ events.wait(1);
+ scene.doBgAnim();
+
+ // See if we're past the initial goto stand sequence
+ for (int idx = 0; idx < _frameNumber; ++idx) {
+ if (_walkSequences[_sequenceNumber][idx] == 0) {
+ done = true;
+ break;
+ }
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = 1;
+ talk._talkToAbort = true;
+ }
+ }
+ }
+
+ if (!isHolmes)
+ _updateNPCPath = true;
+
+ if (!talk._talkToAbort)
+ events.setCursor(oldCursor);
+ }
+}
+
+void TattooPerson::clearNPC() {
+ Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
+ _npcIndex = 0;
+ _pathStack.clear();
+ _npcName = "";
+}
+
+void TattooPerson::updateNPC() {
+ People &people = *_vm->_people;
+ Talk &talk = *_vm->_talk;
+
+ // If the NPC isn't on, or it's in Talk or Listen Mode, then return without doing anything
+ if (_type != CHARACTER || _sequenceNumber >= TALK_UPRIGHT)
+ return;
+
+ // If the NPC is paused, just decrement his pause counter and exit
+ if (_npcPause) {
+ // Decrement counter
+ --_npcPause;
+
+ // Now see if we need to update the NPC's frame sequence so that he faces Holmes
+ if (_lookHolmes) {
+ // See where Holmes is with respect to the NPC (x coordinate)
+ _npcFacing = (people[HOLMES]._position.x < _position.x) ? STOP_LEFT : STOP_RIGHT;
+
+ // See where Holmes is with respect to the NPC (y coordinate)
+ if (people[HOLMES]._position.y < (_position.y - 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is above the NPC so reset the facing to a diagonal up
+ _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
+ } else if (people[HOLMES]._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC so reset the facing to a diagonal down
+ _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
+ }
+
+ // See if we need to set the old_walk_sequence so the NPC will put his arms
+ // up if he turns another way
+ if (_sequenceNumber != _npcFacing)
+ _oldWalkSequence = _sequenceNumber;
+
+ gotoStand();
+ }
+ } else {
+ // Reset the look flag so the NPC won't face Holmes anymore
+ _lookHolmes = false;
+
+ // See if the NPC is stopped or not. Don't do anything if he's moving
+ if (!_walkCount) {
+ // If there is no new command, reset the path back to the beginning
+ if (!_npcPath[_npcIndex])
+ _npcIndex = 0;
+
+ // The NPC is stopped and any pause he was doing is done. We can now see what
+ // the next command in the NPC path is.
+
+ // Scan Past any NPC Path Labels since they do nothing except mark places for If's and Goto's
+ while (_npcPath[_npcIndex] == NPCPATH_PATH_LABEL)
+ _npcIndex += 2;
+
+ if (_npcPath[_npcIndex]) {
+ _npcFacing = -1;
+
+ switch (_npcPath[_npcIndex]) {
+ case NPCPATH_SET_DEST: {
+ // Set the NPC's new destination
+ int xp = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ _walkDest.x = xp;
+ _walkDest.y = (_npcPath[_npcIndex + 3] - 1) * 256 + _npcPath[_npcIndex + 4] - 1;
+ _npcFacing = _npcPath[_npcIndex + 5] - 1;
+
+ goAllTheWay();
+ _npcIndex += 6;
+ break;
+ }
+
+ case NPCPATH_PAUSE:
+ // Set the NPC to pause where he is
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _npcIndex += 3;
+ break;
+
+ case NPCPATH_SET_TALK_FILE: {
+ // Set the NPC's Talk File to use if Holmes talks to them
+ ++_npcIndex;
+
+ _npcName = "";
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_npcPath[_npcIndex + idx] != '~')
+ _npcName += _npcPath[_npcIndex + idx];
+ else
+ break;
+ }
+
+ _npcIndex += 8;
+ break;
+ }
+
+ case NPCPATH_CALL_TALK_FILE: {
+ // Call a Talk File
+ ++_npcIndex;
+
+ Common::String name;
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_npcPath[_npcIndex + idx] != '~')
+ name += _npcPath[_npcIndex + idx];
+ else
+ break;
+ }
+
+ _npcIndex += 8;
+ talk.talkTo(name);
+ break;
+ }
+
+ case NPCPATH_TAKE_NOTES:
+ // Set the NPC to Pause where he is and set his frame sequences
+ // so he takes notes while he's paused
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _npcIndex += 3;
+ break;
+
+ case NPCPATH_FACE_HOLMES:
+ // Set the NPC to Pause where he is and set his look flag so he will always face Holmes
+ // while he is paused
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _lookHolmes = true;
+ _npcIndex += 3;
+ break;
+
+ //case NPCPATH_PATH_LABEL: // No implementation needed here
+
+ case NPCPATH_GOTO_LABEL: {
+ // Goto NPC Path Label
+ int label = _npcPath[_npcIndex + 1];
+ _npcIndex = 0;
+
+ // Scan through NPC path data to find the label
+ bool found = false;
+ while (!found) {
+ switch (_npcPath[_npcIndex]) {
+ case NPCPATH_SET_DEST:
+ _npcIndex += 6;
+ break;
+ case NPCPATH_PAUSE:
+ case NPCPATH_TAKE_NOTES:
+ case NPCPATH_FACE_HOLMES:
+ _npcIndex += 3;
+ break;
+ case NPCPATH_SET_TALK_FILE:
+ case NPCPATH_CALL_TALK_FILE:
+ _npcIndex += 8;
+ break;
+ case NPCPATH_PATH_LABEL:
+ if (_npcPath[_npcIndex + 1] == label)
+ found = true;
+ _npcIndex += 2;
+ break;
+ case NPCPATH_GOTO_LABEL:
+ _npcIndex += 2;
+ break;
+ case NPCPATH_IFFLAG_GOTO_LABEL:
+ _npcIndex += 4;
+ break;
+ }
+ }
+ break;
+ }
+
+ case NPCPATH_IFFLAG_GOTO_LABEL: {
+ // If FLAG then Goto Label
+ int flag = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1 - (_npcPath[_npcIndex + 2] == 1 ? 1 : 0);
+
+ // Set the value the flag should be for the if statement to succeed
+ bool flagVal = flag < 16384;
+
+ int label = _npcPath[_npcIndex + 3];
+ _npcIndex += 4;
+
+ // If the flag is set Correctly, move the NPC Index to the given label
+ if (_vm->readFlags(flag & 16383) == flagVal) {
+ _npcIndex = 0;
+ bool found = false;
+ while (!found)
+ {
+ switch (_npcPath[_npcIndex])
+ {
+ case NPCPATH_SET_DEST:
+ _npcIndex += 6;
+ break;
+ case NPCPATH_PAUSE:
+ case NPCPATH_TAKE_NOTES:
+ case NPCPATH_FACE_HOLMES:
+ _npcIndex += 3;
+ break;
+ case NPCPATH_SET_TALK_FILE:
+ case NPCPATH_CALL_TALK_FILE:
+ _npcIndex += 8;
+ break;
+ case NPCPATH_PATH_LABEL:
+ if (_npcPath[_npcIndex + 1] == label)
+ found = true;
+ _npcIndex += 2;
+ break;
+ case NPCPATH_GOTO_LABEL:
+ _npcIndex += 2;
+ break;
+ case NPCPATH_IFFLAG_GOTO_LABEL:
+ _npcIndex += 4;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+void TattooPerson::pushNPCPath() {
+ assert(_pathStack.size() < 2);
+ SavedNPCPath savedPath(_npcPath, _npcIndex, _npcPause, _position, _sequenceNumber, _lookHolmes);
+ _pathStack.push(savedPath);
+}
+
+void TattooPerson::pullNPCPath() {
+ // Pop the stack entry and restore the fields
+ SavedNPCPath path = _pathStack.pop();
+ Common::copy(&path._path[0], &path._path[MAX_NPC_PATH], &_npcPath[0]);
+ _npcIndex = path._npcIndex;
+ _npcPause = path._npcPause;
+
+ // Handle the first case if the NPC was paused
+ if (_npcPause) {
+ _npcFacing = path._npcFacing;
+ _lookHolmes = path._lookHolmes;
+
+ // See if the NPC has moved from where they originally were
+ if (path._position != _position) {
+ _walkDest = Point32(path._position.x / FIXED_INT_MULTIPLIER, path._position.y / FIXED_INT_MULTIPLIER);
+ goAllTheWay();
+ _npcPause = 0;
+ _npcIndex -= 3;
+ } else {
+ // See if we need to set the old walk sequence so the NPC will put his arms up if he turns another way
+ if (_npcFacing != _sequenceNumber)
+ _oldWalkSequence = _sequenceNumber;
+
+ gotoStand();
+ }
+ } else {
+ // Handle the second case if the NPC was in motion
+ _npcIndex -= 6;
+ }
+}
+
+Common::Point TattooPerson::getSourcePoint() const {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ int scaleVal = scene.getScaleVal(_position);
+
+ return Common::Point(_position.x / FIXED_INT_MULTIPLIER + _imageFrame->sDrawXSize(scaleVal) / 2,
+ _position.y / FIXED_INT_MULTIPLIER);
+}
+
+void TattooPerson::setObjTalkSequence(int seq) {
+ assert(seq != -1 && _type == CHARACTER);
+
+ if (_seqTo) {
+ // reset to previous value
+ _walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ _sequenceNumber = _gotoSeq;
+ _frameNumber = 0;
+ checkWalkGraphics();
+}
+
+void TattooPerson::checkWalkGraphics() {
+ People &people = *_vm->_people;
+
+ if (_images == nullptr) {
+ freeAltGraphics();
+ return;
+ }
+
+ Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str());
+
+ // Set the adjust depending on if we have to fine tune the x position of this particular graphic
+ _adjust.x = _adjust.y = 0;
+
+ for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) {
+ if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName,
+ strlen(ADJUST_WALKS[idx]._vgsName))) {
+ if (_walkSequences[_sequenceNumber]._horizFlip)
+ _adjust.x = ADJUST_WALKS[idx]._flipXAdjust;
+ else
+ _adjust.x = ADJUST_WALKS[idx]._xAdjust;
+
+ _adjust.y = ADJUST_WALKS[idx]._yAdjust;
+ break;
+ }
+ }
+
+ // See if we're already using Alternate Graphics
+ if (_altSeq) {
+ // See if the VGS file called for is different than the alternate graphics already loaded
+ if (_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) {
+ // Different AltGraphics, Free the old ones
+ freeAltGraphics();
+ }
+ }
+
+ // If there is no Alternate Sequence set, see if we need to load a new one
+ if (!_altSeq) {
+ int npcNum = -1;
+ // Find which NPC this is so we can check the name of the graphics loaded
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (this == &people[idx]) {
+ npcNum = idx;
+ break;
+ }
+ }
+
+ if (npcNum != -1) {
+ // See if the VGS file called for is different than the main graphics which are already loaded
+ if (filename.compareToIgnoreCase(people[npcNum]._walkVGSName) != 0) {
+ // See if this is one of the more used Walk Graphics stored in WALK.LIB
+ for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) {
+ if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) {
+ people._useWalkLib = true;
+ break;
+ }
+ }
+
+ _altImages = new ImageFile(filename);
+ people._useWalkLib = false;
+
+ _altSeq = _sequenceNumber + 1;
+ }
+ }
+ }
+
+ // If this is a different seqeunce from the current sequence, reset the appropriate variables
+ if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) {
+ _seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0;
+ _sequences = &_walkSequences[_sequenceNumber]._sequences[0];
+ _seqSize = _walkSequences[_sequenceNumber]._sequences.size();
+
+ // WORKAROUND: Occassionally when switching to a new walk sequence the existing frame number may be outside
+ // the allowed range for the new sequence. In such cases, reset the frame number
+ if (_frameNumber < 0 || _frameNumber >= (int)_seqSize || _walkSequences[_sequenceNumber][_frameNumber] == 0)
+ _frameNumber = 0;
+ }
+
+ setImageFrame();
+}
+
+void TattooPerson::synchronize(Serializer &s) {
+ if (s.isSaving()) {
+ SpriteType type = (_type == INVALID && _walkLoaded) ? HIDDEN_CHARACTER : _type;
+ s.syncAsSint16LE(type);
+ } else {
+ if (_walkCount)
+ gotoStand();
+
+ s.syncAsSint16LE(_type);
+ }
+
+ s.syncAsSint32LE(_position.x);
+ s.syncAsSint32LE(_position.y);
+ s.syncString(_walkVGSName);
+ s.syncString(_description);
+ s.syncString(_examine);
+
+ // NPC specific properties
+ s.syncBytes(&_npcPath[0], MAX_NPC_PATH);
+ s.syncString(_npcName);
+ s.syncAsSint32LE(_npcPause);
+ s.syncAsByte(_lookHolmes);
+ s.syncAsByte(_updateNPCPath);
+ if (s.isLoading())
+ _npcIndex = 0;
+
+ // Verbs
+ for (int idx = 0; idx < 2; ++idx)
+ _use[idx].synchronize(s);
+}
+
+void TattooPerson::walkHolmesToNPC() {
+ Events &events = *_vm->_events;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ TattooPerson &holmes = people[HOLMES];
+ int facing;
+
+ // Save the character's details
+ pushNPCPath();
+
+ // If the NPC is moving, stop him at his current position
+ if (_walkCount) {
+ // Reset the facing so the NPC will stop facing the direction he was going,
+ // rather than the direction he was supposed to when he finished wlaking
+ _npcFacing = -1;
+ gotoStand();
+ }
+
+ int scaleVal = scene.getScaleVal(_position);
+ ImageFrame &imgFrame = (*holmes._images)[0];
+
+ // Clear the path variables
+ memset(_npcPath, 0, 100);
+
+ // Set the NPC path so he pauses for 250 while looking at Holmes
+ _npcPath[0] = 6;
+ _npcPath[1] = 1;
+ _npcPath[2] = 251;
+ _npcIndex = 0;
+ _npcPause = 250;
+ _lookHolmes = true;
+
+ // See where Holmes is with respect to the NPC (x coords)
+ if (holmes._position.x < _position.x) {
+ holmes._walkDest.x = MAX(_position.x / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal), 0);
+ } else {
+ holmes._walkDest.x = MIN(_position.x / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) * 2,
+ screen._backBuffer1.w() - 1);
+ }
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (holmes._position.y < (_position.y - imgFrame.sDrawXSize(scaleVal) * 500)) {
+ holmes._walkDest.y = MAX(_position.y / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal) / 2, 0);
+ } else {
+ if (holmes._position.y > (_position.y + imgFrame.sDrawXSize(scaleVal) * 500)) {
+ // Holmes is below the NPC
+ holmes._walkDest.y = MIN(_position.y / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) / 2,
+ SHERLOCK_SCREEN_HEIGHT - 1);
+ } else {
+ // Holmes is roughly on the same Y as the NPC
+ holmes._walkDest.y = _position.y / FIXED_INT_MULTIPLIER;
+ }
+ }
+
+ events.setCursor(WAIT);
+
+ _walkDest.x += 10;
+ people._allowWalkAbort = true;
+ holmes.goAllTheWay();
+
+ // Do doBgAnim should be called over and over until walk is done
+ do {
+ events.wait(1);
+ scene.doBgAnim();
+ } while (holmes._walkCount);
+
+ if (!talk._talkToAbort) {
+ // Setup correct direction for Holmes to face
+
+ // See where Holmes is with respect to the NPC (x coords)
+ facing = (holmes._position.x < _position.x) ? STOP_RIGHT : STOP_LEFT;
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (holmes._position.y < (_position.y - (10 * FIXED_INT_MULTIPLIER))) {
+ // Holmes is above the NPC. Reset the facing to the diagonal downs
+ facing = (facing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
+ } else {
+ if (holmes._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC. Reset the facing to the diagonal ups
+ facing = (facing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
+ }
+ }
+
+ holmes._sequenceNumber = facing;
+ holmes.gotoStand();
+
+ events.setCursor(ARROW);
+ }
+}
+
+void TattooPerson::walkBothToCoords(const PositionFacing &holmesDest, const PositionFacing &npcDest) {
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+ TattooPerson &holmes = people[HOLMES];
+ bool holmesStopped = false, npcStopped = false;
+
+ // Save the current cursor and change to the wait cursor
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ holmes._centerWalk = false;
+ _centerWalk = false;
+
+ // Start Holmes walking to his dest
+ holmes._walkDest = Common::Point(holmesDest.x / FIXED_INT_MULTIPLIER + 10, holmesDest.y / FIXED_INT_MULTIPLIER);
+ people._allowWalkAbort = true;
+ holmes.goAllTheWay();
+
+ // Start the NPC walking to their dest
+ _walkDest = Common::Point(npcDest.x / FIXED_INT_MULTIPLIER + 10, npcDest.y / FIXED_INT_MULTIPLIER);
+ goAllTheWay();
+
+ // Clear the path variables
+ _npcIndex = _npcPause = 0;
+ Common::fill(&_npcPath[0], &_npcPath[100], 0);
+ _npcFacing = npcDest._facing;
+
+ // Now loop until both stop walking
+ do {
+ events.pollEvents();
+ scene.doBgAnim();
+
+ if (!holmes._walkCount && !holmesStopped) {
+ // Holmes finished walking
+ holmesStopped = true;
+
+ // Ensure Holmes is on the exact destination spot
+ holmes._position = holmesDest;
+ holmes._sequenceNumber = holmesDest._facing;
+ holmes.gotoStand();
+ }
+
+ if (!_walkCount && !npcStopped) {
+ // NPC finished walking
+ npcStopped = true;
+
+ // Ensure NPC is on the exact destination spot
+ _position = npcDest;
+ _sequenceNumber = npcDest._facing;
+ gotoStand();
+ }
+
+ } while (!_vm->shouldQuit() && (holmes._walkCount || _walkCount));
+
+ holmes._centerWalk = true;
+ _centerWalk = true;
+
+ // Do one last frame draw so that the lsat person to stop will be drawn in their final position
+ scene.doBgAnim();
+
+ _updateNPCPath = true;
+
+ if (!talk._talkToAbort)
+ // Restore original mouse cursor
+ events.setCursor(oldCursor);
+}
+
+void TattooPerson::centerScreenOnPerson() {
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ ui._targetScroll.x = CLIP(_position.x / FIXED_INT_MULTIPLIER - SHERLOCK_SCREEN_WIDTH / 2,
+ 0, screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH);
+ screen._currentScroll = ui._targetScroll;
+
+ // Reset the default look position to the center of the screen
+ ui._lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
+}
+
+/*----------------------------------------------------------------*/
+
+TattooPeople::TattooPeople(SherlockEngine *vm) : People(vm) {
+ for (int idx = 0; idx < 6; ++idx)
+ _data.push_back(new TattooPerson());
+}
+
+void TattooPeople::setListenSequence(int speaker, int sequenceNum) {
+ Scene &scene = *_vm->_scene;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ int objNum = findSpeaker(speaker);
+ if (objNum < CHARACTERS_INDEX && objNum != -1) {
+ // See if the Object has to wait for an Abort Talk Code
+ Object &obj = scene._bgShapes[objNum];
+ if (obj.hasAborts())
+ obj._gotoSeq = sequenceNum;
+ else
+ obj.setObjTalkSequence(sequenceNum);
+ } else if (objNum != -1) {
+ objNum -= CHARACTERS_INDEX;
+ TattooPerson &person = (*this)[objNum];
+
+ int newDir = person._sequenceNumber;
+ switch (person._sequenceNumber) {
+ case WALK_UP:
+ case STOP_UP:
+ case WALK_UPRIGHT:
+ case STOP_UPRIGHT:
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ newDir = LISTEN_UPRIGHT;
+ break;
+ case WALK_RIGHT:
+ case STOP_RIGHT:
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ newDir = LISTEN_RIGHT;
+ break;
+ case WALK_DOWNRIGHT:
+ case STOP_DOWNRIGHT:
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ newDir = LISTEN_DOWNRIGHT;
+ break;
+ case WALK_DOWN:
+ case STOP_DOWN:
+ case WALK_DOWNLEFT:
+ case STOP_DOWNLEFT:
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ newDir = LISTEN_DOWNLEFT;
+ break;
+ case WALK_LEFT:
+ case STOP_LEFT:
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ newDir = LISTEN_LEFT;
+ break;
+ case WALK_UPLEFT:
+ case STOP_UPLEFT:
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ newDir = LISTEN_UPLEFT;
+ break;
+
+ default:
+ break;
+ }
+
+ // See if the NPC's Seq has to wait for an Abort Talk Code
+ if (person.hasAborts()) {
+ person._gotoSeq = newDir;
+ } else {
+ if (person._seqTo) {
+ // Reset to previous value
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ person._sequenceNumber = newDir;
+ person._frameNumber = 0;
+ person.checkWalkGraphics();
+ }
+ }
+}
+
+void TattooPeople::setTalkSequence(int speaker, int sequenceNum) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1 && objNum < CHARACTERS_INDEX) {
+ Object &obj = scene._bgShapes[objNum];
+
+ // See if the Object has to wait for an Abort Talk Code
+ if (obj.hasAborts()) {
+ talk.pushSequenceEntry(&obj);
+ obj._gotoSeq = sequenceNum;
+ } else {
+ obj.setObjTalkSequence(sequenceNum);
+ }
+ } else if (objNum != -1) {
+ objNum -= CHARACTERS_INDEX;
+ TattooPerson &person = people[objNum];
+ int newDir = person._sequenceNumber;
+
+ switch (newDir) {
+ case WALK_UP:
+ case STOP_UP:
+ case WALK_UPRIGHT:
+ case STOP_UPRIGHT:
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ newDir = TALK_UPRIGHT;
+ break;
+ case WALK_RIGHT:
+ case STOP_RIGHT:
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ newDir = TALK_RIGHT;
+ break;
+ case WALK_DOWNRIGHT:
+ case STOP_DOWNRIGHT:
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ newDir = TALK_DOWNRIGHT;
+ break;
+ case WALK_DOWN:
+ case STOP_DOWN:
+ case WALK_DOWNLEFT:
+ case STOP_DOWNLEFT:
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ newDir = TALK_DOWNLEFT;
+ break;
+ case WALK_LEFT:
+ case STOP_LEFT:
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ newDir = TALK_LEFT;
+ break;
+ case WALK_UPLEFT:
+ case STOP_UPLEFT:
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ newDir = TALK_UPLEFT;
+ break;
+ default:
+ break;
+ }
+
+ // See if the NPC's sequence has to wait for an Abort Talk Code
+ if (person.hasAborts()) {
+ person._gotoSeq = newDir;
+ } else {
+ if (person._seqTo) {
+ // Reset to previous value
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ person._sequenceNumber = newDir;
+ person._frameNumber = 0;
+ person.checkWalkGraphics();
+ }
+ }
+}
+
+
+int TattooPeople::findSpeaker(int speaker) {
+ int result = People::findSpeaker(speaker);
+ const char *portrait = _characters[speaker]._portrait;
+
+ // Fallback that Rose Tattoo uses if no speaker was found
+ if (result == -1) {
+ bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES);
+
+ if (_data[HOLMES]->_type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag)))
+ // Return the offset index for the first character
+ return 0 + CHARACTERS_INDEX;
+
+ // Otherwise, scan through the list of the remaining characters to find a name match
+ for (uint idx = 1; idx < _data.size(); ++idx) {
+ TattooPerson &p = (*this)[idx];
+
+ if (p._type == CHARACTER) {
+ Common::String name(p._name.c_str(), p._name.c_str() + 4);
+
+ if (name.equalsIgnoreCase(portrait) && p._npcName[4] >= '0' && p._npcName[4] <= '9')
+ return idx + CHARACTERS_INDEX;
+ }
+ }
+ }
+
+ return result;
+}
+
+void TattooPeople::synchronize(Serializer &s) {
+ s.syncAsByte(_holmesOn);
+
+ for (uint idx = 0; idx < _data.size(); ++idx)
+ (*this)[idx].synchronize(s);
+
+ s.syncAsSint16LE(_holmesQuotient);
+
+ if (s.isLoading()) {
+ _savedPos.x = _data[HOLMES]->_position.x;
+ _savedPos.y = _data[HOLMES]->_position.y;
+ _savedPos._facing = _data[HOLMES]->_sequenceNumber;
+ }
+}
+
+bool TattooPeople::loadWalk() {
+ Resources &res = *_vm->_res;
+ bool result = false;
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Person &person = *_data[idx];
+
+ if (!person._walkLoaded && (person._type == CHARACTER || person._type == HIDDEN_CHARACTER)) {
+ if (person._type == HIDDEN_CHARACTER)
+ person._type = INVALID;
+
+ // See if this is one of the more used Walk Graphics stored in WALK.LIB
+ for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) {
+ if (!person._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) {
+ _useWalkLib = true;
+ break;
+ }
+ }
+
+ // Load the images for the character
+ person._images = new ImageFile(person._walkVGSName, false);
+ person._maxFrames = person._images->size();
+
+ // Load walk sequence data
+ Common::String fname = Common::String(person._walkVGSName.c_str(), strchr(person._walkVGSName.c_str(), '.'));
+ fname += ".SEQ";
+
+ // Load the walk sequence data
+ Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib");
+
+ person._walkSequences.resize(stream->readByte());
+
+ for (uint seqNum = 0; seqNum < person._walkSequences.size(); ++seqNum)
+ person._walkSequences[seqNum].load(*stream);
+
+ // Close the sequences resource
+ delete stream;
+ _useWalkLib = false;
+
+ person._sequences = &person._walkSequences[person._sequenceNumber]._sequences[0];
+ person._seqSize = person._walkSequences[person._sequenceNumber]._sequences.size();
+ person._frameNumber = 0;
+ person.setImageFrame();
+
+ // Set the stop Frames pointers
+ for (int dirNum = 0; dirNum < 8; ++dirNum) {
+ int count = 0;
+ while (person._walkSequences[dirNum + 8][count] != 0)
+ ++count;
+ count += 2;
+ count = person._walkSequences[dirNum + 8][count] - 1;
+ person._stopFrames[dirNum] = &(*person._images)[count];
+ }
+
+ result = true;
+ person._walkLoaded = true;
+ } else if (person._type != CHARACTER) {
+ person._walkLoaded = false;
+ }
+ }
+
+ _forceWalkReload = false;
+ return result;
+}
+
+
+void TattooPeople::pullNPCPaths() {
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ TattooPerson &p = (*this)[idx];
+ if (p._npcMoved) {
+ while (!p._pathStack.empty())
+ p.pullNPCPath();
+ }
+ }
+}
+
+const Common::Point TattooPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Common::Rect &r = scene._zones[zoneId];
+
+ if (destPos.x < 0 || destPos.x > screen._backBuffer1.w())
+ return destPos;
+ else if (destPos.y < r.top && r.left < destPos.x && destPos.x < r.right)
+ return Common::Point(destPos.x, r.top);
+ else if (destPos.y > r.bottom && r.left < destPos.x && destPos.x < r.right)
+ return Common::Point(destPos.x, r.bottom);
+ else if (destPos.x < r.left && r.top < destPos.y && destPos.y < r.bottom)
+ return Common::Point(r.left, destPos.y);
+ else if (destPos.x > r.right && r.top < destPos.y && destPos.y < r.bottom)
+ return Common::Point(r.right, destPos.y);
+
+ // Find which corner of the zone the point is closet to
+ if (destPos.x <= r.left) {
+ return Common::Point(r.left, (destPos.y <= r.top) ? r.top : r.bottom);
+ } else {
+ return Common::Point(r.right, (destPos.y <= r.top) ? r.top : r.bottom);
+ }
+}
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_people.h b/engines/sherlock/tattoo/tattoo_people.h
new file mode 100644
index 0000000000..722c4a9aaa
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_people.h
@@ -0,0 +1,281 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
+ */
+ virtual Common::Point getSourcePoint() const;
+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();
+ virtual ~TattooPerson() {}
+
+ /**
+ * 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:
+ */
+ virtual void adjustSprite();
+
+ /**
+ * Bring a moving character to a standing position
+ */
+ virtual void gotoStand();
+
+ /**
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
+ */
+ virtual void setWalking();
+
+ /**
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir);
+
+ /**
+ * 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.
+ */
+ virtual void setObjTalkSequence(int seq);
+
+ /**
+ * Center the visible screen so that the person is in the center of the screen
+ */
+ virtual void centerScreenOnPerson();
+};
+
+class TattooPeople : public People {
+public:
+ TattooPeople(SherlockEngine *vm);
+ virtual ~TattooPeople() {}
+
+ 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
+ */
+ virtual int findSpeaker(int speaker);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Change the sequence of the scene background object associated with the specified speaker.
+ */
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1);
+
+ /**
+ * Load the walking images for Sherlock
+ */
+ virtual bool loadWalk();
+
+ /**
+ * Restrict passed point to zone using Sherlock's positioning rules
+ */
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos);
+
+ /**
+ * 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
+ */
+ virtual void setListenSequence(int speaker, int sequenceNum = 1);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_resources.cpp b/engines/sherlock/tattoo/tattoo_resources.cpp
new file mode 100644
index 0000000000..3be41e2650
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_resources.cpp
@@ -0,0 +1,329 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_resources.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+const char PORTRAITS[TATTOO_MAX_PEOPLE][5] = {
+ { "HOLM" }, // Sherlock Holmes
+ { "WATS" }, // Dr. Watson
+ { "HUDS" }, // Mrs. Hudson
+ { "FORB" }, // Stanley Forbes
+ { "MYCR" }, // Mycroft Holmes
+ { "WIGG" }, // Wiggins
+ { "BURN" }, // Police Constable Burns
+ { "TRIM" }, // Augustus Trimble
+ { "DALE" }, // Police Constable Daley
+ { "MATR" }, // Matron
+ { "GRAC" }, // Sister Grace
+ { "MCCA" }, // Preston McCabe
+ { "COLL" }, // Bob Colleran
+ { "JONA" }, // Jonas Rigby
+ { "ROAC" }, // Police Constable Roach
+ { "DEWA" }, // James Dewar
+ { "JERE" }, // Sergeant Jeremy Duncan
+ { "GREG" }, // Inspector Gregson
+ { "LEST" }, // Inspector Lestrade
+ { "NEED" }, // Jesse Needhem
+ { "FLEM" }, // Arthur Fleming
+ { "PRAT" }, // Mr. Thomas Pratt
+ { "TILL" }, // Mathilda (Tillie) Mason
+ { "RUSS" }, // Adrian Russell
+ { "WHIT" }, // Eldridge Whitney
+ { "HEPP" }, // Hepplethwaite
+ { "HORA" }, // Horace Silverbridge
+ { "SHER" }, // Old Sherman
+ { "VERN" }, // Maxwell Verner
+ { "REDD" }, // Millicent Redding
+ { "VIRG" }, // Virgil Silverbridge
+ { "GEOR" }, // George O'Keeffe
+ { "LAWT" }, // Lord Denys Lawton
+ { "JENK" }, // Jenkins
+ { "JOCK" }, // Jock Mahoney
+ { "BART" }, // Bartender
+ { "LADY" }, // Lady Cordelia Lockridge
+ { "PETT" }, // Pettigrew
+ { "FANS" }, // Sir Avery Fanshawe
+ { "HODG" }, // Hodgkins
+ { "WILB" }, // Wilbur "Birdy" Heywood
+ { "JACO" }, // Jacob Farthington
+ { "BLED" }, // Philip Bledsoe
+ { "FOWL" }, // Sidney Fowler
+ { "PROF" }, // Professor Theodore Totman
+ { "ROSE" }, // Rose Hinchem
+ { "TALL" }, // Tallboy
+ { "STIT" }, // Ethlebert "Stitch" Rumsey
+ { "FREE" }, // Charles Freedman
+ { "HEMM" }, // Nigel Hemmings
+ { "CART" }, // Fairfax Carter
+ { "WILH" }, // Wilhelm II
+ { "WACH" }, // Wachthund
+ { "WILS" }, // Jonathan Wilson
+ { "DAVE" }, // David Lloyd-Jones
+ { "HARG" }, // Edward Hargrove
+ { "MORI" }, // Professor James Moriarty
+ { "LASC" }, // The Lascar
+ { "PARR" }, // Parrot
+ { "SCAR" }, // Vincent Scarrett
+ { "ALEX" }, // Alexandra
+ { "QUEE" }, // Queen Victoria
+ { "JOHN" }, // John Brown
+ { "PAT1" }, // Patient #1
+ { "PAT2" }, // Patient #2
+ { "PATR" }, // Patron
+ { "QUEN" }, // Queen Victoria
+ { "WITE" }, // Patient in White
+ { "LUSH" }, // Lush
+ { "DRNK" }, // Drunk
+ { "PROS" }, // Prostitute
+ { "MUDL" }, // Mudlark
+ { "GRIN" }, // Grinder
+ { "BOUN" }, // Bouncer
+ { "RATC" }, // Agnes Ratchet
+ { "ALOY" }, // Aloysius Ratchet
+ { "REAL" }, // Real Estate Agent
+ { "CAND" }, // Candy Clerk
+ { "BEAD" }, // Beadle
+ { "PRUS" }, // Prussian
+ { "ROWB" }, // Mrs. Rowbottom
+ { "MSLJ" }, // Miss Lloyd-Jones
+ { "TPAT" }, // Tavern patron
+ { "USER" }, // User
+ { "TOBY" }, // Toby
+ { "STAT" }, // Stationer
+ { "CLRK" }, // Law Clerk
+ { "CLER" }, // Ministry Clerk
+ { "BATH" }, // Bather
+ { "MAID" }, // Maid
+ { "LADF" }, // Lady Fanshawe
+ { "SIDN" }, // Sidney Ratchet
+ { "BOYO" }, // Boy
+ { "PTR2" }, // Second Patron
+ { "BRIT" }, // Constable Brit
+ { "DROV" } // Wagon Driver
+};
+
+const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE] = {
+ "Sherlock Holmes",
+ "Dr. Watson",
+ "Mme. Hudson",
+ "Stanley Forbes",
+ "Mycroft Holmes",
+ "Wiggins",
+ "Sergent Burns",
+ "Augustus Trimble",
+ "Sergent Daley",
+ "Infirmi?re chef",
+ "Mme. Grace",
+ "Preston McCabe",
+ "Bob Colleran",
+ "Jonas Rigby",
+ "Sergent Roach",
+ "James Dewar",
+ "Sergent Jeremy Duncan",
+ "Inspecteur Gregson",
+ "Inspecteur Lestrade",
+ "Jesse Needhem",
+ "Arthur Fleming",
+ "M. Thomas Pratt",
+ "Mathilda (Tillie) Mason",
+ "Adrian Russell",
+ "Eldridge Whitney",
+ "Hepplethwaite",
+ "Horace Silverbridge",
+ "Sherman",
+ "Maxwell Verner",
+ "Millicent Redding",
+ "Virgil Silverbridge",
+ "George O'Keeffe",
+ "Lord Denys Lawton",
+ "Jenkins",
+ "Jock Mahoney",
+ "Serveur",
+ "Lady Cordelia Lockridge",
+ "Pettigrew",
+ "Sir Avery Fanshawe",
+ "Hodgkins",
+ "Wilbur \"Birdy\" Heywood",
+ "Jacob Farthington",
+ "Philip Bledsoe",
+ "Sidney Fowler",
+ "Professeur Theodore Totman",
+ "Rose Hinchem",
+ "Tallboy",
+ "Ethlebert \"Stitch\" Rumsey",
+ "Charles Freedman",
+ "Nigel Hemmings",
+ "Fairfax Carter",
+ "Wilhelm II",
+ "Wachthund",
+ "Jonathan Wilson",
+ "David Lloyd-Jones",
+ "Edward Hargrove",
+ "Misteray",
+ "Le Lascar",
+ "Oiseau",
+ "Vincent Scarrett",
+ "Alexandra",
+ "Queen Victoria",
+ "John Brown",
+ "Patient",
+ "Patient",
+ "Client",
+ "Queen Victoria",
+ "Patient en blanc",
+ "Ivrogne",
+ "Ivrogne",
+ "Belle femme",
+ "Mudlark",
+ "Broyeur",
+ "Videur",
+ "Agnes Ratchet",
+ "Aloysius Ratchet",
+ "Immobilier",
+ "Gar?on",
+ "Beadle",
+ "Prussian",
+ "Mme. Rowbottom",
+ "Mme Lloyd-Jones",
+ "Tavern Client",
+ "User",
+ "Toby",
+ "Papeterie",
+ "Law Clerc",
+ "Ministry Employ?",
+ "Clint du thermes",
+ "Bonne",
+ "Lady Fanshawe",
+ "Sidney Ratchet",
+ "Gar?on",
+ "Client",
+ "Sergent Brit",
+ "Wagon Driver"
+};
+
+const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE] = {
+ "Sherlock Holmes",
+ "Dr. Watson",
+ "Mrs. Hudson",
+ "Stanley Forbes",
+ "Mycroft Holmes",
+ "Wiggins",
+ "Police Constable Burns",
+ "Augustus Trimble",
+ "Police Constable Daley",
+ "Matron",
+ "Sister Grace",
+ "Preston McCabe",
+ "Bob Colleran",
+ "Jonas Rigby",
+ "Police Constable Roach",
+ "James Dewar",
+ "Sergeant Jeremy Duncan",
+ "Inspector Gregson",
+ "Inspector Lestrade",
+ "Jesse Needhem",
+ "Arthur Fleming",
+ "Mr. Thomas Pratt",
+ "Mathilda (Tillie) Mason",
+ "Adrian Russell",
+ "Eldridge Whitney",
+ "Hepplethwaite",
+ "Horace Silverbridge",
+ "Old Sherman",
+ "Maxwell Verner",
+ "Millicent Redding",
+ "Virgil Silverbridge",
+ "George O'Keeffe",
+ "Lord Denys Lawton",
+ "Jenkins",
+ "Jock Mahoney",
+ "Bartender",
+ "Lady Cordelia Lockridge",
+ "Pettigrew",
+ "Sir Avery Fanshawe",
+ "Hodgkins",
+ "Wilbur \"Birdy\" Heywood",
+ "Jacob Farthington",
+ "Philip Bledsoe",
+ "Sidney Fowler",
+ "Professor Theodore Totman",
+ "Rose Hinchem",
+ "Tallboy",
+ "Ethlebert \"Stitch\" Rumsey",
+ "Charles Freedman",
+ "Nigel Hemmings",
+ "Fairfax Carter",
+ "Wilhelm II",
+ "Wachthund",
+ "Jonathan Wilson",
+ "David Lloyd-Jones",
+ "Edward Hargrove",
+ "Misteray",
+ "The Lascar",
+ "Parrot",
+ "Vincent Scarrett",
+ "Alexandra",
+ "Queen Victoria",
+ "John Brown",
+ "A Patient",
+ "A Patient",
+ "Patron",
+ "Queen Victoria",
+ "Patient in white",
+ "Lush",
+ "Drunk",
+ "Prostitute",
+ "Mudlark",
+ "Grinder",
+ "Bouncer",
+ "Agnes Ratchet",
+ "Aloysius Ratchet",
+ "Real Estate Agent",
+ "Candy Clerk",
+ "Beadle",
+ "Prussian",
+ "Mrs. Rowbottom",
+ "Miss Lloyd-Jones",
+ "Tavern patron",
+ "User",
+ "Toby",
+ "Stationer",
+ "Law Clerk",
+ "Ministry Clerk",
+ "Bather",
+ "Maid",
+ "Lady Fanshawe",
+ "Sidney Ratchet",
+ "Boy",
+ "Patron",
+ "Constable Brit",
+ "Wagon Driver"
+};
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_resources.h b/engines/sherlock/tattoo/tattoo_resources.h
new file mode 100644
index 0000000000..b706d90f2d
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_resources.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_RESOURCES_H
+#define SHERLOCK_TATTOO_RESOURCES_H
+
+#include "common/scummsys.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define TATTOO_MAX_PEOPLE 96
+
+extern const char PORTRAITS[TATTOO_MAX_PEOPLE][5];
+extern const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE];
+extern const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE];
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp
index 2a13b2a450..f13d0588df 100644
--- a/engines/sherlock/tattoo/tattoo_scene.cpp
+++ b/engines/sherlock/tattoo/tattoo_scene.cpp
@@ -21,8 +21,10 @@
*/
#include "sherlock/tattoo/tattoo_scene.h"
-#include "sherlock/tattoo/tattoo.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"
@@ -30,28 +32,262 @@ namespace Sherlock {
namespace Tattoo {
-TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm) {
- _arrowZone = -1;
- _mask = _mask1 = nullptr;
- _maskCounter = 0;
+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);
}
-void TattooScene::checkBgShapes() {
- People &people = *_vm->_people;
- Person &holmes = people._player;
- Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
+/*----------------------------------------------------------------*/
+
+TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm), _labWidget(vm) {
+ _labTableScene = false;
+}
+
+bool TattooScene::loadScene(const Common::String &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;
+ }
+
+ // Check if it's a scene we need to keep trakc 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);
+ }
+ }
+ }
+
+ // Set the NPC paths for the scene
+ setNPCPath(WATSON);
+
+ // Handle loading music for the scene
+ if (music._musicOn) {
+ if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3)
+ music._nextSongName = Common::String::format("res%02d", _currentScene);
+
+ // If it's a new song, then start it up
+ if (music._currentSongName.compareToIgnoreCase(music._nextSongName)) {
+ 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.blitFrom(*obj._imageFrame, obj._position);
+ else
+ screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal);
+ }
+ }
+
+ // Draw the animation if it is behind the person
+ if (_activeCAnim.active() && _activeCAnim._zPlacement == BEHIND)
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
+ (_activeCAnim._flags & 4) >> 1, 0, _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.blitFrom(*se._shape->_imageFrame, se._shape->_position);
+ else
+ screen._backBuffer1.transBlitFrom(*se._shape->_imageFrame, se._shape->_position,
+ se._shape->_flags & OBJ_FLIPPED, 0, se._shape->_scaleVal);
+ } else if (se._isAnimation) {
+ // It's an active animation
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
+ (_activeCAnim._flags & 4) >> 1, 0, _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.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
+ - p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, 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.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
+ - p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, 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.blitFrom(*obj._imageFrame, obj._position);
+ else
+ screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal);
+ }
+ }
+
+ // Draw the canimation if it is set as FORWARD
+ if (_activeCAnim.active() && _activeCAnim._zPlacement == FORWARD)
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _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._images && _activeCAnim._zPlacement != REMOVE) {
+ 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)) ?
+ _activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame._frame.h - 1)) ?
NORMAL_FORWARD : NORMAL_BEHIND;
break;
case 2:
@@ -63,9 +299,19 @@ void TattooScene::checkBgShapes() {
}
}
+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;
- UserInterface &ui = *_vm->_ui;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
// If we're in Look Mode, make sure the cursor is the magnifying glass
@@ -77,7 +323,7 @@ void TattooScene::doBgAnimCheckCursor() {
if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) {
CursorId cursorId = ARROW;
- if (ui._menuMode == STD_MODE && _arrowZone != -1 && _currentScene != 90) {
+ 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))
@@ -86,151 +332,80 @@ void TattooScene::doBgAnimCheckCursor() {
}
events.setCursor(cursorId);
- }
-}
-
-void TattooScene::doBgAnimEraseBackground() {
- TattooEngine &vm = *((TattooEngine *)_vm);
- People &people = *_vm->_people;
- Screen &screen = *_vm->_screen;
- TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui);
-
- static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 };
-
- if (_mask != nullptr) {
- if (screen._backBuffer1.w() > screen.w())
- screen.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(screen._currentScroll, 0,
- screen._currentScroll + screen.w(), screen.h()));
- else
- screen.blitFrom(screen._backBuffer1);
-
- switch (_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.w())
- _maskOffset.x = 0;
- }
- break;
-
- default:
- break;
- }
} else {
- // Standard scene without mask, so call user interface to erase any UI elements as necessary
- ui.doBgAnimRestoreUI();
-
- // Restore background for any areas covered by characters and shapes
- for (uint 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 < _bgShapes.size(); ++idx) {
- Object &obj = _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.blitFrom(*obj._imageFrame, 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 (vm._creditsActive)
- vm.eraseCredits();
+ events.animateCursorIfNeeded();
}
-
- for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
- Object &obj = _bgShapes[idx];
-
- if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
- screen._backBuffer1.blitFrom(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) <
- (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) {
-
- screen._targetScroll = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
- SHERLOCK_SCREEN_WIDTH / 8 - 250);
- if (screen._targetScroll < 0)
- screen._targetScroll = 0;
- }
-
- if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll) > (SHERLOCK_SCREEN_WIDTH / 4 * 3)
- && people[people._walkControl]._delta.x > 0)
- screen._targetScroll = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
- SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250);
-
- if (screen._targetScroll > screen._scrollSize)
- screen._targetScroll = screen._scrollSize;
-
- ui.doScroll();
}
void TattooScene::doBgAnim() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui);
doBgAnimCheckCursor();
-// Events &events = *_vm->_events;
- People &people = *_vm->_people;
-// Scene &scene = *_vm->_scene;
- Screen &screen = *_vm->_screen;
- Talk &talk = *_vm->_talk;
-
- screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
talk._talkToAbort = false;
// Check the characters and sprites for updates
- for (uint idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ 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
- doBgAnimEraseBackground();
+ 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;
- Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &obj = _bgShapes[idx];
@@ -238,102 +413,31 @@ void TattooScene::doBgAnimUpdateBgObjectsAndAnim() {
obj.adjustObject();
}
- for (uint idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER)
people[idx].adjustSprite();
}
- if ((_activeCAnim._images != nullptr) && (_activeCAnim._zPlacement != REMOVE)) {
- _activeCAnim.getNextFrame();
- }
-
// Flag the bg shapes which need to be redrawn
checkBgShapes();
drawAllShapes();
-
- if (_mask != nullptr) {
- switch (_currentScene) {
- case 7:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll);
- break;
-
- case 8:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll);
- if (!_vm->readFlags(880))
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300), screen._currentScroll);
- break;
-
- case 18:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 203), screen._currentScroll);
- if (!_vm->readFlags(189))
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + _maskOffset.x, 239), screen._currentScroll);
- break;
-
- case 53:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll);
- break;
-
- case 68:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 203), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + _maskOffset.x, 239), screen._currentScroll);
- break;
- }
- }
+ ui.drawMaskArea(true);
}
-
void TattooScene::updateBackground() {
- People &people = *_vm->_people;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Scene::updateBackground();
- if (_mask != nullptr) {
- switch (_currentScene) {
- case 7:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll);
- break;
-
- case 8:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll);
- if (!_vm->readFlags(880))
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300), screen._currentScroll);
- break;
-
- case 18:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(0, 203), screen._currentScroll);
- if (!_vm->readFlags(189))
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124, 239), screen._currentScroll);
- break;
-
- case 53:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll);
- break;
-
- case 68:
- screen._backBuffer1.maskArea((*_mask)[0], Common::Point(0, 203), screen._currentScroll);
- screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124, 239), screen._currentScroll);
- break;
-
- default:
- break;
- }
- }
+ ui.drawMaskArea(false);
screen._flushScreen = true;
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
- Person &p = people[idx];
+ TattooPerson &p = people[idx];
if (p._type != INVALID) {
if (_goToScene == -1 || _cAnim.size() == 0) {
@@ -341,7 +445,7 @@ void TattooScene::updateBackground() {
screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y);
p._type = INVALID;
} else {
- if (p._tempScaleVal == 256) {
+ 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 {
@@ -360,7 +464,7 @@ void TattooScene::updateBackground() {
if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) {
if (_goToScene == -1) {
- if (obj._scaleVal == 256)
+ if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
@@ -381,7 +485,7 @@ void TattooScene::updateBackground() {
screen.slamRect(obj.getNoShapeBounds());
screen.slamRect(obj.getOldBounds());
} else if (obj._type == HIDE_SHAPE) {
- if (obj._scaleVal == 256)
+ if (obj._scaleVal == SCALE_THRESHOLD)
screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
&obj._oldSize.x, &obj._oldSize.y);
else
@@ -395,6 +499,360 @@ void TattooScene::updateBackground() {
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 neccessary) 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();
+
+ //_activeCAnim._filesize = cAnim._size;
+
+ // 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 Escape key being pressed to abort animation
+ events.pollEvents();
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ _vm->setFlags(-76);
+ _vm->setFlags(396);
+ _goToScene = STARTING_GAME_SCENE;
+ talk._talkToAbort = true;
+ _activeCAnim.close();
+ }
+ }
+ }
+
+ // 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;
+
+ // 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;
+ Scene::synchronize(s);
+
+ if (s.isLoading()) {
+ // In case we were showing the intro prologue or the ending credits, stop them
+ vm._runningProlog = false;
+ ui._creditsWidget.close();
+ }
+}
+
+int TattooScene::closestZone(const Common::Point &pt) {
+ int zone = -1;
+ int dist = 9999;
+ int d;
+
+ 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
+ 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
diff --git a/engines/sherlock/tattoo/tattoo_scene.h b/engines/sherlock/tattoo/tattoo_scene.h
index de28306c1b..678330077c 100644
--- a/engines/sherlock/tattoo/tattoo_scene.h
+++ b/engines/sherlock/tattoo/tattoo_scene.h
@@ -25,39 +25,104 @@
#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:
- int _arrowZone;
- int _maskCounter;
- Common::Point _maskOffset;
-private:
- void doBgAnimCheckCursor();
+ WidgetLab _labWidget;
- void doBgAnimEraseBackground();
+ 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.
+ */
+ virtual bool loadScene(const Common::String &filename);
+
+ /**
* 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
*/
virtual void checkBgShapes();
+
+ /**
+ * Draw all the shapes, people and NPCs in the correct order
+ */
+ virtual void drawAllShapes();
+
+ /**
+ * Called by loadScene when the palette is loaded for Rose Tattoo
+ */
+ virtual void paletteLoaded();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Returns the index of the closest zone to a given point.
+ */
+ virtual int closestZone(const Common::Point &pt);
public:
- ImageFile *_mask, *_mask1;
- CAnimStream _activeCAnim;
+ 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
+ */
+ virtual void freeScene();
+
+ /**
* Draw all objects and characters.
*/
virtual void doBgAnim();
@@ -68,6 +133,21 @@ public:
*/
virtual void updateBackground();
+ /**
+ * 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
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1);
+
+ /**
+ * Attempts to find a background shape within the passed bounds. If found,
+ * it will return the shape number, or -1 on failure.
+ */
+ virtual int findBgShape(const Common::Point &pt);
};
} // End of namespace Tattoo
diff --git a/engines/sherlock/tattoo/tattoo_talk.cpp b/engines/sherlock/tattoo/tattoo_talk.cpp
new file mode 100644
index 0000000000..a4ceca042b
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_talk.cpp
@@ -0,0 +1,1013 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_talk.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 "sherlock/screen.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+static const uint8 DIRECTION_CONVERSION[] = {
+ WALK_RIGHT, WALK_DOWN, WALK_LEFT, WALK_UP, STOP_RIGHT, STOP_DOWN, STOP_LEFT, STOP_UP,
+ WALK_UPRIGHT, WALK_DOWNRIGHT, WALK_UPLEFT, WALK_DOWNLEFT, STOP_UPRIGHT, STOP_UPLEFT,
+ STOP_DOWNRIGHT, STOP_DOWNLEFT
+};
+
+const byte TATTOO_OPCODES[] = {
+ 170, // OP_SWITCH_SPEAKER
+ 171, // OP_RUN_CANIMATION
+ 0, // OP_ASSIGN_PORTRAIT_LOCATION
+ 173, // OP_PAUSE
+ 0, // OP_REMOVE_PORTRAIT
+ 0, // OP_CLEAR_WINDOW
+ 176, // OP_ADJUST_OBJ_SEQUENCE
+ 177, // OP_WALK_HOlMES_TO_COORDS
+ 178, // OP_PAUSE_WITHOUT_CONTROL
+ 179, // OP_BANISH_WINDOW
+ 0, // OP_SUMMON_WINDOW
+ 181, // OP_SET_FLAG
+ 0, // OP_SFX_COMMAND
+ 183, // OP_TOGGLE_OBJECT
+ 184, // OP_STEALTH_MODE_ACTIVE
+ 0, // OP_IF_STATEMENT
+ 0, // OP_ELSE_STATEMENT
+ 0, // OP_END_IF_STATEMENT
+ 188, // OP_STEALTH_MODE_DEACTIVATE
+ 189, // OP_TURN_HOLMES_OFF
+ 190, // OP_TURN_HOLMES_ON
+ 191, // OP_GOTO_SCENE
+ 0, // OP_PLAY_PROLOGUE
+ 193, // OP_ADD_ITEM_TO_INVENTORY
+ 194, // OP_SET_OBJECT
+ 172, // OP_CALL_TALK_FILE
+ 0, // OP_MOVE_MOUSE
+ 0, // OP_DISPLAY_INFO_LINE
+ 0, // OP_CLEAR_INFO_LINE
+ 199, // OP_WALK_TO_CANIMATION
+ 200, // OP_REMOVE_ITEM_FROM_INVENTORY
+ 201, // OP_ENABLE_END_KEY
+ 202, // OP_DISABLE_END_KEY
+ 203, // OP_END_TEXT_WINDOW
+ 174, // OP_MOUSE_ON_OFF
+ 175, // OP_SET_WALK_CONTROL
+ 180, // OP_SET_TALK_SEQUENCE
+ 182, // OP_PLAY_SONG
+ 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
+ 192, // OP_SET_NPC_PATH_DEST
+ 195, // OP_NEXT_SONG
+ 196, // OP_SET_NPC_PATH_PAUSE
+ 197, // OP_PASSWORD
+ 198, // OP_SET_SCENE_ENTRY_FLAG
+ 185, // OP_WALK_NPC_TO_CANIM
+ 186, // OP_WALK_NPC_TO_COORDS
+ 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 205, // OP_SET_NPC_TALK_FILE
+ 206, // OP_TURN_NPC_OFF
+ 207, // OP_TURN_NPC_ON
+ 208, // OP_NPC_DESC_ON_OFF
+ 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES
+ 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
+ 211, // OP_ENABLE_TALK_INTERRUPTS
+ 212, // OP_DISABLE_TALK_INTERRUPTS
+ 213, // OP_SET_NPC_INFO_LINE
+ 214, // OP_SET_NPC_POSITION
+ 215, // OP_NPC_PATH_LABEL
+ 216, // OP_PATH_GOTO_LABEL
+ 217, // OP_PATH_IF_FLAG_GOTO_LABEL
+ 218, // OP_NPC_WALK_GRAPHICS
+ 220, // OP_NPC_VERB
+ 221, // OP_NPC_VERB_CANIM
+ 222, // OP_NPC_VERB_SCRIPT
+ 224, // OP_RESTORE_PEOPLE_SEQUENCE
+ 226, // OP_NPC_VERB_TARGET
+ 227, // OP_TURN_SOUNDS_OFF
+ 225 // OP_NULL
+};
+
+/*----------------------------------------------------------------*/
+
+TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm), _talkWidget(vm), _passwordWidget(vm) {
+ static OpcodeMethod OPCODE_METHODS[] = {
+ (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker,
+
+ (OpcodeMethod)&TattooTalk::cmdRunCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdCallTalkFile,
+ (OpcodeMethod)&TattooTalk::cmdPause,
+ (OpcodeMethod)&TattooTalk::cmdMouseOnOff,
+ (OpcodeMethod)&TattooTalk::cmdSetWalkControl,
+ (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence,
+ (OpcodeMethod)&TattooTalk::cmdWalkHolmesToCoords,
+ (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl,
+ (OpcodeMethod)&TattooTalk::cmdBanishWindow,
+ (OpcodeMethod)&TattooTalk::cmdSetTalkSequence,
+
+ (OpcodeMethod)&TattooTalk::cmdSetFlag,
+ (OpcodeMethod)&TattooTalk::cmdPlaySong,
+ (OpcodeMethod)&TattooTalk::cmdToggleObject,
+ (OpcodeMethod)&TattooTalk::cmdStealthModeActivate,
+ (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate,
+ (OpcodeMethod)&TattooTalk::cmdHolmesOff,
+ (OpcodeMethod)&TattooTalk::cmdHolmesOn,
+
+ (OpcodeMethod)&TattooTalk::cmdGotoScene,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest,
+ (OpcodeMethod)&TattooTalk::cmdAddItemToInventory,
+ (OpcodeMethod)&TattooTalk::cmdSetObject,
+ (OpcodeMethod)&TattooTalk::cmdNextSong,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause,
+ (OpcodeMethod)&TattooTalk::cmdPassword,
+ (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag,
+ (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory,
+
+ (OpcodeMethod)&TattooTalk::cmdEnableEndKey,
+ (OpcodeMethod)&TattooTalk::cmdDisableEndKey,
+ (OpcodeMethod)&TattooTalk::cmdEndTextWindow,
+ (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCOff,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCOn,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes,
+
+ (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable,
+ (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPosition,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelSet,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics,
+ nullptr,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerb,
+
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript,
+ nullptr,
+ (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence,
+ nullptr,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget,
+ (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff
+ };
+
+ _opcodes = TATTOO_OPCODES;
+ _opcodeTable = OPCODE_METHODS;
+}
+
+void TattooTalk::talkTo(const Common::String filename) {
+ Events &events = *_vm->_events;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ // WORKAROUND: Keep wait cursor active until very end of the cutscene of the monkey
+ // stealing the cap, which is finished by calling the 30cuend script
+ if (filename == "wilb29a")
+ events.incWaitCounter();
+
+ Talk::talkTo(filename);
+
+ if (filename == "wilb29a")
+ ui._menuMode = TALK_MODE;
+ if (filename == "30cuend") {
+ events.decWaitCounter();
+ events.setCursor(ARROW);
+ }
+}
+
+void TattooTalk::talkInterface(const byte *&str) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Sound &sound = *_vm->_sound;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ const byte *s = str;
+
+ // Move to past the end of the text string
+ _wait = 1;
+ _charCount = 0;
+ while ((*str < TATTOO_OPCODES[0] || *str == TATTOO_OPCODES[OP_NULL]) && *str) {
+ ++_charCount;
+ ++str;
+ }
+
+ // If speech is on, and text windows (subtitles) are off, then don't show the text window
+ if (!vm._textWindowsOn && sound._speechOn && _speaker != -1)
+ return;
+
+ // Display the text window
+ ui.banishWindow();
+ ui._textWidget.load(Common::String((const char *)s, (const char *)str), _speaker);
+ ui._textWidget.summonWindow();
+}
+
+void TattooTalk::nothingToSay() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ui.putMessage("%s", FIXED(NothingToSay));
+}
+
+void TattooTalk::showTalk() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ people.setListenSequence(_talkTo, 129);
+
+ _talkWidget.load();
+ _talkWidget.summonWindow();
+ _talkWidget.refresh();
+
+ if (ui._menuMode != MESSAGE_MODE)
+ ui._menuMode = TALK_MODE;
+}
+
+OpcodeReturn TattooTalk::cmdSwitchSpeaker(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ ui.clearWindow();
+
+ _yp = screen.fontHeight() + 11;
+ _charCount = _line = 0;
+
+ people.setListenSequence(_speaker, 129);
+ _speaker = *++str - 1;
+ ++str;
+
+ people.setTalkSequence(_speaker, 1);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) {
+ Events &events = *_vm->_events;
+ bool mouseOn = *++str == 2;
+ if (mouseOn)
+ events.showCursor();
+ else
+ events.hideCursor();
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkHolmesToCoords(const byte *&str) {
+ People &people = *_vm->_people;
+ ++str;
+
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ // Negative X
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+
+ people[HOLMES].walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
+ DIRECTION_CONVERSION[str[4] - 1]);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 4;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdGotoScene(const byte *&str) {
+ Map &map = *_vm->_map;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ scene._goToScene = str[1] - 1;
+
+ if (scene._goToScene != OVERHEAD_MAP) {
+ // Not going to the map overview
+ map._oldCharPoint = scene._goToScene;
+
+ // Run a canimation?
+ if (str[2] > 100) {
+ people._savedPos = PositionFacing(160, 100, str[2]);
+ } else {
+ int posX = (str[3] - 1) * 256 + str[4] - 1;
+ if (posX > 16384)
+ posX = -1 * (posX - 16384);
+ int posY = (str[5] - 1) * 256 + str[6] - 1;
+ people._savedPos = PositionFacing(posX, posY, str[2] - 1);
+ }
+
+ _scriptMoreFlag = 1;
+ }
+
+ str += 7;
+ if (scene._goToScene != OVERHEAD_MAP)
+ _scriptSaveIndex = str - _scriptStart;
+
+ _endStr = true;
+ _wait = 0;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) {
+ Music &music = *_vm->_music;
+
+ // Get the name of the next song to play
+ ++str;
+ music._nextSongName = "";
+ for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
+ music._nextSongName += str[idx];
+ str += 7;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 8;
+ person._npcPath[person._npcIndex + 1] = str[1];
+ person._npcIndex += 2;
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 9;
+ for (int i = 1; i <= 3; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 4;
+ str += 3;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 7;
+ person._npcPath[person._npcIndex + 1] = str[1];
+ person._npcIndex += 2;
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdPassword(const byte *&str) {
+ _vm->_ui->clearWindow();
+ _passwordWidget.show();
+ return RET_EXIT;
+}
+
+OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) {
+ Music &music = *_vm->_music;
+ Common::String currentSong = music._currentSongName;
+
+ // Get the name of the song to play
+ music._currentSongName = "";
+ str++;
+ for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
+ music._currentSongName += str[idx];
+ str += 7;
+
+ // Play the song
+ music.loadSong(music._currentSongName);
+
+ // Copy the old song name to _nextSongName so that when the new song is finished, the old song will restart
+ music._nextSongName = currentSong;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) {
+ int npcNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ person._misc = 0;
+
+ if (person._seqTo) {
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+ person._sequenceNumber = person._savedNpcSequence;
+ person._frameNumber = person._savedNpcFrame;
+ person.checkWalkGraphics();
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) {
+ int npcNum = *++str;
+ ++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Person &person = people[npcNum];
+
+ // Copy over the NPC examine text until we reach a stop marker, which is
+ // the same as a start marker, or we reach the end of the file
+ person._examine = "";
+ while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
+ person._examine += *str++;
+
+ // Move past any leftover text till we reach a stop marker
+ while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
+ str++;
+
+ if (!*str)
+ // Reached end of file, so decrement pointer so outer loop will terminate on NULL
+ --str;
+ else
+ // Move past the ending OP_NPC_DEST_ON_OFF opcode
+ ++str;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ person._description = "";
+ int len = *++str;
+ for (int idx = 0; idx < len; ++idx)
+ person._description += str[idx + 1];
+
+ str += len;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int npcNum = *++str;
+ people[npcNum]._type = REMOVE;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int npcNum = *++str;
+ people[npcNum]._type = CHARACTER;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 1;
+ for (int i = 1; i <= 4; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+ person._npcPath[person._npcIndex + 5] = DIRECTION_CONVERSION[str[5] - 1] + 1;
+
+ person._npcIndex += 6;
+ str += 5;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 2;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 5;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 6;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) {
+ int npcNum = *++str - 1;
+ ++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ int posX = (str[0] - 1) * 256 + str[1] - 1;
+ if (posX > 16384)
+ posX = -1 * (posX - 16384);
+ int posY = (str[2] - 1) * 256 + str[3] - 1;
+
+ person._position = Point32(posX * FIXED_INT_MULTIPLIER, posY * FIXED_INT_MULTIPLIER);
+ if (person._seqTo && person._walkLoaded) {
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ assert(str[4] - 1 < 16);
+ person._sequenceNumber = DIRECTION_CONVERSION[str[4] - 1];
+ person._frameNumber = 0;
+
+ if (person._walkLoaded)
+ person.checkWalkGraphics();
+
+ if (person._walkLoaded && person._type == CHARACTER &&
+ person._sequenceNumber >= STOP_UP && person._sequenceNumber <= STOP_UPLEFT) {
+ bool done = false;
+ do {
+ person.checkSprite();
+ for (int x = 0; x < person._frameNumber; x++) {
+ if (person._walkSequences[person._sequenceNumber]._sequences[x] == 0) {
+ done = true;
+ break;
+ }
+ }
+ } while (!done);
+ }
+
+ str += 4;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = NPCPATH_SET_TALK_FILE;
+ for (int i = 1; i <= 8; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 9;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Common::String &verb = people[npcNum]._use[verbNum]._verb;
+
+ // Get the verb name
+ verb = "";
+ for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx)
+ verb += str[idx + 1];
+
+ // Strip off any trailing whitespace
+ while (verb.hasSuffix(" "))
+ verb.deleteLastChar();
+
+ str += 12;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ UseType &useType = people[npcNum]._use[verbNum];
+
+ useType._cAnimNum = (str[1] - 1) & 127;
+ useType._cAnimSpeed = 1 + 128 * (str[1] >= 128);
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ UseType &useType = people[npcNum]._use[verbNum];
+
+ Common::String &name = useType._names[0];
+ name = "*C";
+
+ for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx)
+ name += str[idx + 1];
+
+ useType._cAnimNum = 99;
+ useType._cAnimSpeed = 1;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Common::String &target = people[npcNum]._use[verbNum]._target;
+
+ target = "";
+ for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx)
+ target += str[idx + 1];
+
+ while (target.hasSuffix(" "))
+ target.deleteLastChar();
+
+ str += 12;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) {
+ int npcNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Person &person = people[npcNum];
+
+ // Build up walk library name for the given NPC
+ person._walkVGSName = "";
+ for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx)
+ person._walkVGSName += str[idx + 1];
+
+ person._walkVGSName += ".VGS";
+ people._forceWalkReload = true;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ ++str;
+ int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1);
+
+ int flag1 = flag & 16383;
+ if (flag > 16383)
+ flag1 *= -1;
+
+ str += 2;
+
+ // Make sure that this instance is not already being tracked
+ bool found = false;
+ for (uint idx = 0; idx < scene._sceneTripCounters.size() && !found; ++idx) {
+ SceneTripEntry &entry = scene._sceneTripCounters[idx];
+ if (entry._flag == flag1 && entry._sceneNumber == str[0] - 1)
+ found = true;
+ }
+
+ // Only add it if it's not being tracked already
+ if (!found)
+ scene._sceneTripCounters.push_back(SceneTripEntry(flag1, str[0] - 1, str[1] - 1));
+
+ ++str;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int speaker = str[1] - 1;
+ int sequenceNumber = str[2];
+
+ if (sequenceNumber < 128)
+ people.setTalkSequence(speaker, sequenceNumber);
+ else
+ people.setListenSequence(speaker, sequenceNumber);
+
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ ++str;
+ people._walkControl = str[0] - 1;
+
+ return RET_SUCCESS;
+}
+
+// Dummy opcode
+OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsDisable called"); }
+
+// Dummy opcode
+OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); }
+
+OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode (cmdTurnSoundsOff)"); }
+
+OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int cAnimNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ Scene &scene = *_vm->_scene;
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ person.walkToCoords(anim._goto[1], anim._goto[1]._facing);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int cAnimNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ Scene &scene = *_vm->_scene;
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ person.walkToCoords(anim._goto[1], anim._goto[1]._facing);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) {
+ int npcNum = *++str;
+ str++;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+
+ person.walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
+ DIRECTION_CONVERSION[str[4] - 1]);
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 4;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) {
+ int npcNum = *++str;
+ str++;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ // Get destination position and facing for Holmes
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+ PositionFacing holmesDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[4] - 1]);
+
+ // Get destination position and facing for specified NPC
+ xp = (str[5] - 1) * 256 + str[6] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ yp = (str[7] - 1) * 256 + str[8] - 1;
+ PositionFacing npcDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[9] - 1]);
+
+ person.walkBothToCoords(holmesDest, npcDest);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 9;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdCallTalkFile(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Common::String tempString;
+
+ int npc = *++str;
+ assert(npc >= 1 && npc < MAX_CHARACTERS);
+ TattooPerson &person = people[npc];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ Common::fill(&person._npcPath[0], &person._npcPath[100], 0);
+ }
+
+ // Set the path control code and copy the filename
+ person._npcPath[person._npcIndex] = 4;
+ for (int idx = 1; idx <= 8; ++idx)
+ person._npcPath[person._npcIndex + idx] = str[idx];
+
+ person._npcIndex += 9;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+void TattooTalk::pushSequenceEntry(Object *obj) {
+ // Check if the shape is already on the stack
+ for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ if (_sequenceStack[idx]._obj == obj)
+ return;
+ }
+
+ // Find a free slot and save the details in it
+ for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ SequenceEntry &seq = _sequenceStack[idx];
+ if (seq._obj == nullptr) {
+ seq._obj = obj;
+ seq._frameNumber = obj->_frameNumber;
+ seq._sequenceNumber = obj->_sequenceNumber;
+ seq._seqStack = obj->_seqStack;
+ seq._seqTo = obj->_seqTo;
+ seq._seqCounter = obj->_seqCounter;
+ seq._seqCounter2 = obj->_seqCounter2;
+ return;
+ }
+ }
+
+ error("Ran out of talk sequence stack space");
+}
+
+void TattooTalk::pullSequence(int slot) {
+ People &people = *_vm->_people;
+
+ for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ SequenceEntry &seq = _sequenceStack[idx];
+ if (slot != -1 && idx != slot)
+ continue;
+
+ // Check for an entry in this slot
+ if (seq._obj) {
+ Object &o = *seq._obj;
+
+ // See if we're not supposed to restore it until an Allow Talk Interrupt
+ if (slot == -1 && seq._obj->hasAborts()) {
+ seq._obj->_gotoSeq = -1;
+ seq._obj->_restoreSlot = idx;
+ } else {
+ // Restore the object's sequence information immediately
+ o._frameNumber = seq._frameNumber;
+ o._sequenceNumber = seq._sequenceNumber;
+ o._seqStack = seq._seqStack;
+ o._seqTo = seq._seqTo;
+ o._seqCounter = seq._seqCounter;
+ o._seqCounter2 = seq._seqCounter2;
+ o._gotoSeq = 0;
+ o._talkSeq = 0;
+
+ // Flag the slot as free again
+ seq._obj = nullptr;
+ }
+ }
+ }
+
+ // Handle restoring any character positioning
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Person &person = people[idx];
+
+ if (person._type == CHARACTER && !person._walkSequences.empty() && person._sequenceNumber >= TALK_UPRIGHT
+ && person._sequenceNumber <= LISTEN_UPLEFT) {
+ person.gotoStand();
+
+ bool done = false;
+ do {
+ person.checkSprite();
+ for (int frameNum = 0; frameNum < person._frameNumber; ++frameNum) {
+ if (person._walkSequences[person._sequenceNumber]._sequences[frameNum] == 0)
+ done = true;
+ }
+ } while (!done);
+ }
+ }
+}
+
+bool TattooTalk::isSequencesEmpty() const {
+ for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ if (_sequenceStack[idx]._obj)
+ return false;
+ }
+
+ return true;
+}
+
+void TattooTalk::clearSequences() {
+ for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ _sequenceStack[idx]._obj = nullptr;
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_talk.h b/engines/sherlock/tattoo/tattoo_talk.h
new file mode 100644
index 0000000000..9b010513dc
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_talk.h
@@ -0,0 +1,143 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
+ */
+ virtual void talkInterface(const byte *&str);
+
+ /**
+ * Called when a character being spoken to has no talk options to display
+ */
+ virtual void nothingToSay();
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk();
+public:
+ TattooTalk(SherlockEngine *vm);
+ virtual ~TattooTalk() {}
+
+ /**
+ * 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.
+ */
+ virtual void talkTo(const Common::String filename);
+
+ /**
+ * Push the details of a passed object onto the saved sequences stack
+ */
+ virtual void pushSequenceEntry(Object *obj);
+
+ /**
+ * Pulls a background object sequence from the sequence stack and restore's the
+ * object's sequence
+ */
+ virtual void pullSequence(int slot = -1);
+
+ /**
+ * Returns true if the script stack is empty
+ */
+ virtual bool isSequencesEmpty() const;
+
+ /**
+ * Clears the stack of pending object sequences associated with speakers in the scene
+ */
+ virtual void clearSequences();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_user_interface.cpp b/engines/sherlock/tattoo/tattoo_user_interface.cpp
index e76322833f..160b1ca5a0 100644
--- a/engines/sherlock/tattoo/tattoo_user_interface.cpp
+++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp
@@ -21,6 +21,8 @@
*/
#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"
@@ -28,121 +30,936 @@ namespace Sherlock {
namespace Tattoo {
-TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm) {
- _menuBuffer = nullptr;
- _invMenuBuffer = nullptr;
- _tagBuffer = nullptr;
- _invGraphic = nullptr;
+bool WidgetList::contains(const WidgetBase *item) const {
+ for (const_iterator i = begin(); i != end(); ++i) {
+ if ((*i) == item)
+ return true;
+ }
+
+ return false;
}
-void TattooUserInterface::handleInput() {
- // TODO
- _vm->_events->pollEventsAndWait();
+/*-------------------------------------------------------------------------*/
+
+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[PALETTE_COUNT], 0);
+ Common::fill(&_lookupTable1[0], &_lookupTable1[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");
}
-void TattooUserInterface::drawInterface(int bufferNum) {
+TattooUserInterface::~TattooUserInterface() {
+ delete _interfaceImages;
+ delete _mask;
+ delete _mask1;
+}
+
+void TattooUserInterface::initScrollVars() {
Screen &screen = *_vm->_screen;
- TattooEngine &vm = *((TattooEngine *)_vm);
+ _scrollSize = screen._backBuffer1.w() - 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;
+ int cAnimSpeed = 0;
+
+ _lookPos = mousePos;
+ _menuMode = LOOK_MODE;
+
+ if (_personFound) {
+ desc = people[_bgFound - 1000]._examine;
+ } else {
+ // Check if there is a Look animation
+ if (_bgShape->_lookcAnim != 0) {
+ 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(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);
+ _key = -1;
+ _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[PALETTE_SIZE];
+
+ Common::copy(&_lookupTable[0], &_lookupTable[PALETTE_SIZE], &lookupTable[0]);
+ _menuMode = JOURNAL_MODE;
+ journal.show();
+
+ _menuMode = STD_MODE;
+ _windowOpen = false;
+ _key = -1;
+
+ // Restore the the old screen palette and greyscale lookup table
+ screen.clear();
+ screen.setPalette(screen._cMap);
+ Common::copy(&lookupTable[0], &lookupTable[PALETTE_SIZE], &_lookupTable[0]);
+
+ // Restore the scene
+ screen._backBuffer1.blitFrom(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;
+
+ // 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;
- if (_invMenuBuffer != nullptr) {
- Common::Rect r = _invMenuBounds;
- r.grow(-3);
- r.translate(-screen._currentScroll, 0);
- _grayAreas.clear();
- _grayAreas.push_back(r);
+ // Key handling
+ if (events.kbHit()) {
+ _keyState = events.getKey();
- drawGrayAreas();
- screen._backBuffer1.transBlitFrom(*_invMenuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top));
+ if (_keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog && !_lockoutTimer) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = STARTING_GAME_SCENE;
+ } else if (_menuMode == STD_MODE) {
+ if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode) {
+ vm._fastMode = !vm._fastMode;
+
+ } else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) {
+ // Beging used for testing that Look dialogs work
+ lookAtObject();
+ }
+ }
}
- if (_menuBuffer != nullptr) {
- Common::Rect r = _menuBounds;
- r.grow(-3);
- r.translate(-screen._currentScroll, 0);
- _grayAreas.clear();
- _grayAreas.push_back(r);
+ if (!events.isCursorVisible())
+ _keyState.keycode = Common::KEYCODE_INVALID;
+
+ // 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();
- drawGrayAreas();
- screen._backBuffer1.transBlitFrom(*_menuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top));
+ // Handle input depending on what mode we're in
+ switch (_menuMode) {
+ case STD_MODE:
+ doStandardControl();
+ break;
+ case LOOK_MODE:
+ doLookControl();
+ break;
+ default:
+ break;
}
+}
- // See if we need to draw a Text Tag floating with the cursor
- if (_tagBuffer != nullptr)
- screen._backBuffer1.transBlitFrom(*_tagBuffer, Common::Point(_tagBounds.left, _tagBounds.top));
+void TattooUserInterface::drawInterface(int bufferNum) {
+ Screen &screen = *_vm->_screen;
- // See if we need to draw an Inventory Item Graphic floating with the cursor
- if (_invGraphic != nullptr)
- screen._backBuffer1.transBlitFrom(*_invGraphic, Common::Point(_invGraphicBounds.left, _invGraphicBounds.top));
+ // 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();
- if (vm._creditsActive)
- vm.drawCredits();
+ // 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 _oldMenuBounds was set, then either a new menu has been opened or the current menu has been closed.
- // Either way, we need to restore the area where the menu was displayed
- if (_oldMenuBounds.width() > 0)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldMenuBounds.left, _oldMenuBounds.top),
- _oldMenuBounds);
-
- if (_oldInvMenuBounds.width() > 0)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvMenuBounds.left, _oldInvMenuBounds.top),
- _oldInvMenuBounds);
-
- if (_menuBuffer != nullptr)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_menuBounds.left, _menuBounds.top), _menuBounds);
- if (_invMenuBuffer != nullptr)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_invMenuBounds.left, _invMenuBounds.top), _invMenuBounds);
+ // 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
- if (_oldTagBounds.width() > 0)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldTagBounds.left, _oldTagBounds.top),
- _oldTagBounds);
-
- // If there is an Inventory being shown, restore the graphics underneath it
- if (_oldInvGraphicBounds.width() > 0)
- screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvGraphicBounds.left, _oldInvGraphicBounds.top),
- _oldInvGraphicBounds);
+ _tooltipWidget.erase();
// If a canimation is active, restore the graphics underneath it
- if (scene._activeCAnim._images != nullptr)
+ if (scene._activeCAnim.active())
screen.restoreBackground(scene._activeCAnim._oldBounds);
- // If a canimation just ended, remove it's graphics from the backbuffer
+ // 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;
- int oldScroll = screen._currentScroll;
// If we're already at the target scroll position, nothing needs to be done
- if (screen._targetScroll == screen._currentScroll)
+ if (_targetScroll.x == screen._currentScroll.x)
return;
screen._flushScreen = true;
- if (screen._targetScroll > screen._currentScroll) {
- screen._currentScroll += screen._scrollSpeed;
- if (screen._currentScroll > screen._targetScroll)
- screen._currentScroll = screen._targetScroll;
- } else if (screen._targetScroll < screen._currentScroll) {
- screen._currentScroll -= screen._scrollSpeed;
- if (screen._currentScroll < screen._targetScroll)
- screen._currentScroll = screen._targetScroll;
+ 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;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+ Common::Point mousePos = events.mousePos();
+ bool noDesc = false;
+
+ // Don't do any input processing whilst the prolog is running
+ if (vm._runningProlog)
+ return;
+
+ // When the end credits are active, any press will open the ScummVM global main menu
+ if (_creditsWidget.active()) {
+ if (_keyState.keycode || 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 (_keyState.keycode) {
+ case Common::KEYCODE_F5:
+ // Save game
+ events.warpMouse();
+ saveGame();
+ return;
+
+ case Common::KEYCODE_F7:
+ // Load game
+ events.warpMouse();
+ loadGame();
+ return;
+
+ case Common::KEYCODE_F1:
+ // 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 Common::KEYCODE_TAB:
+ case Common::KEYCODE_F3:
+ // Display inventory
+ freeMenu();
+ doInventory(3);
+ return;
+
+ case Common::KEYCODE_F4:
+ // Display options
+ events.warpMouse();
+ _optionsWidget.load();
+ return;
+
+ case Common::KEYCODE_F10:
+ // 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();
+
+ 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();
+ _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;
+ Sound &sound = *_vm->_sound;
+
+ // See if a mouse button was released or a key pressed, and we want to initiate an action
+ // TODO: Not sure about _soundOn.. should be check for speaking voice for text being complete
+ if (events._released || events._rightReleased || _keyState.keycode || (sound._voices && !sound._soundOn)) {
+ // 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();
+ _key = -1;
+ _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;
+ _key = -1;
+
+ 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;
+ }
}
- if (_menuBuffer != nullptr)
- _menuBounds.translate(screen._currentScroll - oldScroll, 0);
- if (_invMenuBuffer != nullptr)
- _invMenuBounds.translate(screen._currentScroll - oldScroll, 0);
+ _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;
+ }
+}
+
+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[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 < 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 < 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 < 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.blitFrom(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.w())
+ _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.blitFrom(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.blitFrom(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;
+ }
+ }
+}
+
+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 = 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 = 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::drawGrayAreas() {
- // TODO
+void TattooUserInterface::addFixedWidget(WidgetBase *widget) {
+ _fixedWidgets.push_back(widget);
+ widget->summonWindow();
}
} // End of namespace Tattoo
diff --git a/engines/sherlock/tattoo/tattoo_user_interface.h b/engines/sherlock/tattoo/tattoo_user_interface.h
index 2125f1ba07..c92ff21dd1 100644
--- a/engines/sherlock/tattoo/tattoo_user_interface.h
+++ b/engines/sherlock/tattoo/tattoo_user_interface.h
@@ -24,34 +24,99 @@
#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:
- Common::Rect _menuBounds;
- Common::Rect _oldMenuBounds;
- Common::Rect _invMenuBounds;
- Common::Rect _oldInvMenuBounds;
- Common::Rect _tagBounds;
- Common::Rect _oldTagBounds;
- Common::Rect _invGraphicBounds;
- Common::Rect _oldInvGraphicBounds;
- Surface *_menuBuffer;
- Surface *_invMenuBuffer;
- Surface *_tagBuffer;
- Surface *_invGraphic;
- Common::Array<Common::Rect> _grayAreas;
+ int _scriptZone;
+ int _cAnimFramePause;
+ WidgetInventory _inventoryWidget;
+ WidgetMessage _messageWidget;
+ WidgetQuit _quitWidget;
+ WidgetList _fixedWidgets;
+ WidgetList _widgets;
+ byte _lookupTable[PALETTE_COUNT];
+ byte _lookupTable1[PALETTE_COUNT];
private:
/**
- * Draws designated areas of the screen that are meant to be grayed out using grayscale colors
+ * Handle any input when we're in standard mode (with no windows open)
*/
- void drawGrayAreas();
+ 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::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);
+ virtual ~TattooUserInterface();
/**
* Handles restoring any areas of the back buffer that were/are covered by UI elements
@@ -62,8 +127,109 @@ public:
* 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 accomodate 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(const char *formatStr, ...) GCC_PRINTF(2, 3);
+
+ /**
+ * Makes a greyscale translation table for each palette entry in the table
+ */
+ void setupBGArea(const byte cMap[PALETTE_SIZE]);
+
+ /**
+ * Erase any background as needed before drawing frame
+ */
+ void doBgAnimEraseBackground();
+
+ 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:
- virtual ~TattooUserInterface() {}
+ /**
+ * Resets the user interface
+ */
+ virtual void reset();
/**
* Main input handler for the user interface
@@ -74,6 +240,18 @@ public:
* Draw the user interface onto the screen's back buffers
*/
virtual void drawInterface(int bufferNum = 3);
+
+ /**
+ * Clear any active text window
+ */
+ virtual void clearWindow();
+
+ /**
+ * 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
+ */
+ virtual void banishWindow(bool slideUp = true);
};
} // End of namespace Tattoo
diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp
new file mode 100644
index 0000000000..9e10cee0d1
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_base.cpp
@@ -0,0 +1,375 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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;
+}
+
+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.blitFrom(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.transBlitFrom(_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;
+
+ if (vm._transparentMenus) {
+ ui.makeBGArea(bounds);
+ } else {
+ bounds.grow(-3);
+ 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;
+ const char *strP = str.c_str();
+
+ // Loop counting up lines
+ lines.clear();
+ do {
+ int width = 0;
+ const char *spaceP = nullptr;
+ const char *lineStartP = strP;
+
+ // Find how many characters will fit on the next line
+ while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] ||
+ (byte)*strP == talk._opcodes[OP_NULL])) {
+ width += _surface.charWidth(*strP);
+
+ // Keep track of the last space
+ if (*strP == ' ')
+ spaceP = strP;
+ ++strP;
+ }
+
+ // If the line was too wide to fit on a single line, go back to the last space
+ // if there was one, or otherwise simply break the line at this point
+ if (width >= maxWidth && spaceP != nullptr)
+ strP = spaceP;
+
+ // Add the line to the output array
+ lines.push_back(Common::String(lineStartP, strP));
+
+ // Move the string ahead to the next line
+ if (*strP == ' ' || *strP == 13)
+ ++strP;
+ } while (*strP && (lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER]
+ || (byte)*strP == talk._opcodes[OP_NULL]));
+
+ // Return any remaining text left over
+ return *strP ? Common::String(strP) : Common::String();
+}
+
+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.h())
+ _bounds.moveTo(_bounds.left, screen._backBuffer1.h() - _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.transBlitFrom(images[0], Common::Point(0, 0));
+ s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0));
+ s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height));
+ s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h()));
+
+ // Draw the top of the Info Box
+ s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP);
+ s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE);
+ s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM);
+
+ // Draw the bottom of the Info Box
+ s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP);
+ s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE);
+ s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM);
+
+ // Draw the left Side of the Info Box
+ s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP);
+ s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE);
+ s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM);
+
+ // Draw the right Side of the Info Box
+ s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP);
+ s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE);
+ s.vLine(s.w() - 1, images[0]._height, s.h()- 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 - 3), 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::KeyCode keycode = ui._keyState.keycode;
+ Common::Point mousePos = events.mousePos();
+
+ Common::Rect r = getScrollBarBounds();
+ r.translate(_bounds.left, _bounds.top);
+
+ if (ui._scrollHighlight != SH_NONE || keycode == Common::KEYCODE_HOME || keycode == Common::KEYCODE_END
+ || keycode == Common::KEYCODE_PAGEUP || keycode == Common::KEYCODE_PAGEDOWN
+ || keycode == Common::KEYCODE_UP || keycode == Common::KEYCODE_DOWN) {
+ // 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 || keycode == Common::KEYCODE_UP) && scrollIndex)
+ --scrollIndex;
+
+ // Check for Page Up
+ else if ((ui._scrollHighlight == SH_PAGE_UP || keycode == Common::KEYCODE_PAGEUP) && scrollIndex)
+ scrollIndex -= pageSize;
+
+ // Check for Page Down
+ else if ((ui._scrollHighlight == SH_PAGE_DOWN || keycode == Common::KEYCODE_PAGEDOWN)
+ && (scrollIndex + pageSize < max)) {
+ scrollIndex += pageSize;
+ if (scrollIndex + pageSize >max)
+ scrollIndex = max - pageSize;
+ }
+
+ // Check for Scroll Down
+ else if ((ui._scrollHighlight == SH_SCROLL_DOWN || keycode == Common::KEYCODE_DOWN) && (scrollIndex + pageSize < max))
+ ++scrollIndex;
+ }
+
+ if (keycode == Common::KEYCODE_END)
+ scrollIndex = max - pageSize;
+
+ if (scrollIndex < 0 || keycode == Common::KEYCODE_HOME)
+ scrollIndex = 0;
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_base.h b/engines/sherlock/tattoo/widget_base.h
new file mode 100644
index 0000000000..dcafc8fb21
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_base.h
@@ -0,0 +1,147 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
diff --git a/engines/sherlock/tattoo/widget_credits.cpp b/engines/sherlock/tattoo/widget_credits.cpp
new file mode 100644
index 0000000000..b8e297709f
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_credits.cpp
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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.h();
+
+ _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.w() - 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.w(), screen.h());
+ Surface &bb1 = screen._backBuffer1;
+
+ for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < screen.h(); ++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.w(), screen.h() + _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);
+ }
+
+ _creditLines[idx]._position.y -= _creditSpeed;
+ }
+}
+
+void WidgetCredits::eraseCredits() {
+ Screen &screen = *_vm->_screen;
+ Common::Rect screenRect(0, -_creditSpeed, screen.w(), screen.h() + _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);
+ }
+ }
+
+ 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
diff --git a/engines/sherlock/tattoo/widget_credits.h b/engines/sherlock/tattoo/widget_credits.h
new file mode 100644
index 0000000000..d31c6ac216
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_credits.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_CREDITS_H
+#define SHERLOCK_TATTOO_CREDITS_H
+
+#include "common/array.h"
+#include "common/rect.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
diff --git a/engines/sherlock/tattoo/widget_files.cpp b/engines/sherlock/tattoo/widget_files.cpp
new file mode 100644
index 0000000000..c9a20b0f8a
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_files.cpp
@@ -0,0 +1,428 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/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"
+
+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;
+ } 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.fill(TRANSPARENCY);
+ makeInfoArea();
+
+ switch (_fileMode) {
+ case SAVEMODE_LOAD:
+ _surface.writeString(FIXED(LoadGame),
+ Common::Point((_surface.w() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP);
+ break;
+
+ case SAVEMODE_SAVE:
+ _surface.writeString(FIXED(SaveGame),
+ Common::Point((_surface.w() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP);
+ break;
+
+ default:
+ break;
+ }
+
+ _surface.hLine(3, _surface.fontHeight() + 7, _surface.w() - 4, INFO_TOP);
+ _surface.hLine(3, _surface.fontHeight() + 8, _surface.w() - 4, INFO_MIDDLE);
+ _surface.hLine(3, _surface.fontHeight() + 9, _surface.w() - 4, INFO_BOTTOM);
+ _surface.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6));
+ _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, _surface.fontHeight() + 6));
+
+ int xp = _surface.w() - 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.transBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8));
+ _surface.transBlitFrom(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 (OP_NAMES || idx == _selector || idx == _oldSelector) {
+ 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.w() - 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 neccessary
+ 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::KeyState keyState = ui._keyState;
+
+ // 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 Tab key
+ if (keyState.keycode == Common::KEYCODE_TAB) {
+ // 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 Tab or Shift Tab
+ if (keyState.flags & Common::KBD_SHIFT) {
+ // We're doing Shift Tab
+ if (_selector == _savegameIndex)
+ _selector = _savegameIndex + 4;
+ else
+ --_selector;
+ } else {
+ // We're doing Tab
+ ++_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 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 || keyState.keycode == Common::KEYCODE_ESCAPE) {
+ ui._scrollHighlight = SH_NONE;
+
+ if (_outsideMenu && !_bounds.contains(mousePos)) {
+ close();
+ } else {
+ _outsideMenu = false;
+
+ if (_selector != -1) {
+ if (_fileMode == SAVEMODE_LOAD) {
+ // We're in Load Mode
+ _vm->loadGameState(_selector);
+ } else if (_fileMode == SAVEMODE_SAVE) {
+ // We're in Save Mode
+ if (getFilename())
+ _vm->saveGameState(_selector, _savegames[_selector]);
+ 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 width;
+ 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.right - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), 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);
+ width = screen.charWidth(currentChar);
+
+ // Wait for keypress
+ while (!events.kbHit()) {
+ 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;
+ }
+
+ 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.w() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY);
+ _surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
+
+ } else if ((keyState.keycode == Common::KEYCODE_LEFT && index > 0)
+ || (keyState.keycode == Common::KEYCODE_RIGHT && index < 49 && pt.x < (_bounds.right - BUTTON_SIZE - 20))
+ || (keyState.keycode == Common::KEYCODE_HOME && index > 0)
+ || (keyState.keycode == Common::KEYCODE_END)) {
+ _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 (keyState.keycode) {
+ case Common::KEYCODE_LEFT:
+ pt.x -= _surface.charWidth(filename[index - 1]);
+ --index;
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ pt.x += _surface.charWidth(filename[index]);
+ ++index;
+ break;
+
+ case Common::KEYCODE_HOME:
+ pt.x = _surface.stringWidth("00.") + _surface.widestChar() + 5;
+ index = 0;
+ break;
+
+ case Common::KEYCODE_END:
+ 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 (keyState.keycode == Common::KEYCODE_INSERT) {
+ insert = !insert;
+ if (insert)
+ cursorColor = 192;
+ else
+ cursorColor = 200;
+
+ } else if (keyState.keycode == Common::KEYCODE_DELETE) {
+ filename.deleteChar(index);
+
+ _surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.right - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY);
+ _surface.writeString(filename + index, pt, COMMAND_HIGHLIGHTED);
+
+ } else if (keyState.keycode == Common::KEYCODE_RETURN) {
+ done = 1;
+
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _selector = -1;
+ render(RENDER_NAMES_AND_SCROLLBAR);
+ done = -1;
+ }
+
+ if ((keyState.ascii >= ' ') && ((keyState.ascii <= 168) || (keyState.ascii == 225)) && (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() - 1), TRANSPARENCY);
+ _surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
+ pt.x += _surface.charWidth(keyState.ascii);
+ ++index;
+ }
+ }
+ } 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() - 16);
+ scrollRect.moveTo(_bounds.width() - BUTTON_SIZE - 3, _surface.fontHeight() + 13);
+
+ return scrollRect;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_files.h b/engines/sherlock/tattoo/widget_files.h
new file mode 100644
index 0000000000..94a029d18d
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_files.h
@@ -0,0 +1,87 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
+ */
+ virtual Common::Rect getScrollBarBounds() const;
+public:
+ WidgetFiles(SherlockEngine *vm, const Common::String &target);
+ virtual ~WidgetFiles() {}
+
+ /**
+ * Prompt the user whether to quit
+ */
+ void show(SaveMode mode);
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_foolscap.cpp b/engines/sherlock/tattoo/widget_foolscap.cpp
new file mode 100644
index 0000000000..8246e9a371
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_foolscap.cpp
@@ -0,0 +1,299 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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"
+
+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.blitFrom(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;
+}
+
+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) {
+ 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 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;
+
+ if (((toupper(keyState.ascii) >= 'A') && (toupper(keyState.ascii) <= 'Z')) ||
+ ((keyState.ascii >= 128) && ((keyState.ascii <= 168) || (keyState.ascii == 225)))) {
+ // Visible key pressed, set it and set the keycode to move the caret to the right
+ _answers[_lineNum][_charNum] = keyState.ascii;
+ keyState.keycode = Common::KEYCODE_RIGHT;
+ }
+
+ // 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_ESCAPE:
+ close();
+ break;
+
+ case Common::KEYCODE_UP:
+ if (_lineNum) {
+ --_lineNum;
+ if (_charNum >= (int)strlen(_solutions[_lineNum]))
+ _charNum = (int)strlen(_solutions[_lineNum]) - 1;
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ if (_lineNum < 2) {
+ ++_lineNum;
+ if (_charNum >= (int)strlen(_solutions[_lineNum]))
+ _charNum = (int)strlen(_solutions[_lineNum]) - 1;
+ }
+ break;
+
+ case Common::KEYCODE_BACKSPACE:
+ case Common::KEYCODE_LEFT:
+ if (_charNum)
+ --_charNum;
+ else if (_lineNum) {
+ --_lineNum;
+
+ _charNum = strlen(_solutions[_lineNum]) - 1;
+ }
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE)
+ _answers[_lineNum][_charNum] = ' ';
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ if (_charNum < (int)strlen(_solutions[_lineNum]) - 1)
+ ++_charNum;
+ else if (_lineNum < 2) {
+ ++_lineNum;
+ _charNum = 0;
+ }
+ break;
+
+ case Common::KEYCODE_DELETE:
+ _answers[_lineNum][_charNum] = ' ';
+ 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.blitFrom(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;
+
+ // 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
diff --git a/engines/sherlock/tattoo/widget_foolscap.h b/engines/sherlock/tattoo/widget_foolscap.h
new file mode 100644
index 0000000000..3c85998cee
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_foolscap.h
@@ -0,0 +1,82 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetFoolscap();
+
+ /**
+ * Show the foolscap puzzle
+ */
+ void show();
+
+ /**
+ * Close the window
+ */
+ void close();
+
+ /**
+ * Handle events whilst the widget is on-screen
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp
new file mode 100644
index 0000000000..3555ecdffd
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_inventory.cpp
@@ -0,0 +1,789 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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"
+
+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.fill(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::GR_GRE) {
+
+ 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::GR_GRE) {
+ 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::GR_GRE ? "" : 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.fill(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.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1)));
+ _surface.transBlitFrom(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 started pressing outside of the menu
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ ui._scrollHighlight = SH_NONE;
+ banishWindow();
+
+ if ((_outsideMenu && !innerBounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _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);
+ }
+
+ // 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.fill(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.transBlitFrom(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.transBlitFrom(images[6], Common::Point(x - 1, 1));
+ _surface.transBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4));
+ _surface.transBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5));
+ _surface.transBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2));
+ }
+
+ _surface.hLine(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.transBlitFrom(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._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ 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._keyState.keycode != Common::KEYCODE_ESCAPE) {
+ // If user pointed at an item, use the selected inventory item with this item
+ bool found = false;
+ if (ui._bgFound != -1) {
+ if (ui._personFound) {
+ for (int idx = 0; idx < 2; ++idx) {
+ if (!people[ui._bgFound - 1000]._use[idx]._verb.compareToIgnoreCase(_verb) &&
+ !people[ui._bgFound - 1000]._use[idx]._target.compareToIgnoreCase(_invTarget)) {
+ ui.checkAction(people[ui._bgFound - 1000]._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._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ // 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();
+
+ _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
diff --git a/engines/sherlock/tattoo/widget_inventory.h b/engines/sherlock/tattoo/widget_inventory.h
new file mode 100644
index 0000000000..a051c328e9
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_inventory.h
@@ -0,0 +1,158 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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
+ */
+ virtual void drawBackground() {}
+public:
+ WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner);
+ virtual ~WidgetInventoryTooltip() {}
+
+ /**
+ * Set the text for the tooltip
+ */
+ void setText(const Common::String &str);
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetInventoryVerbs : public WidgetBase {
+private:
+ WidgetInventory *_owner;
+ Common::StringArray _inventCommands;
+
+ void highlightControls();
+public:
+ int _invVerbSelect, _oldInvVerbSelect;
+public:
+ WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner);
+ virtual ~WidgetInventoryVerbs() {}
+
+ void load();
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+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);
+ virtual ~WidgetInventory() {}
+
+ /**
+ * 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
+ */
+ virtual void handleEvents();
+
+ /**
+ * Close a currently active menu
+ */
+ virtual void banishWindow();
+
+ /**
+ * 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();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_lab.cpp b/engines/sherlock/tattoo/widget_lab.cpp
new file mode 100644
index 0000000000..2873b12f22
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_lab.cpp
@@ -0,0 +1,198 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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();
+ bool noDesc = false;
+
+ // 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("");
+
+ 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
diff --git a/engines/sherlock/tattoo/widget_lab.h b/engines/sherlock/tattoo/widget_lab.h
new file mode 100644
index 0000000000..2f19200b81
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_lab.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetLab() {}
+
+ /**
+ * Summon the window
+ */
+ virtual void summonWindow();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_options.cpp b/engines/sherlock/tattoo/widget_options.cpp
new file mode 100644
index 0000000000..5dc6fc55b9
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_options.cpp
@@ -0,0 +1,388 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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"
+
+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;
+}
+
+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();
+
+ if (talk._talkToAbort) {
+ sound.stopSound();
+ return;
+ }
+
+ // Flag if they started pressing outside the window
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ if (events.kbHit()) {
+ ui._keyState = events.getKey();
+
+ // Emulate a mouse release if Enter or Space Bar is pressed
+ if (ui._keyState.keycode == Common::KEYCODE_RETURN || ui._keyState.keycode == Common::KEYCODE_SPACE) {
+ events._pressed = events._oldButtons = false;
+ events._released = true;
+ } else if (ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ 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();
+ 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 neccessary) 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 temp = sound._soundVolume;
+ sound._soundVolume = (_digiSliderX - _surface.widestChar()) * 255 / (_bounds.width() - _surface.widestChar() * 2);
+ if (sound._soundVolume != temp) {
+ sound.setVolume(sound._soundVolume);
+ 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
+ close();
+ ui.loadGame();
+ break;
+
+ case 1:
+ // Save Game
+ 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._voices = !sound._voices;
+
+ 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
+ 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.fill(TRANSPARENCY);
+ makeInfoArea();
+
+ // Draw the lines separating options in the dialog
+ int yp = _surface.fontHeight() + 7;
+ for (int idx = 0; idx < 7; ++idx) {
+ _surface.transBlitFrom(images[4], Common::Point(0, yp - 1));
+ _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1));
+ _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP);
+ _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE);
+ _surface.hLine(3, yp + 2, _surface.w() - 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.w() - 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: {
+ int num = (_surface.fontHeight() + 4) & 0xfe;
+ int sliderY = yp + num / 2 - 8;
+
+ _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5,
+ sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY);
+ _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2,
+ _surface.w() - _surface.widestChar() - 1, sliderY + 3), INFO_MIDDLE);
+ drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6));
+
+ _surface.fillRect(Common::Rect(_midiSliderX - 1, sliderY - (num - 6) / 2 + 2,
+ _midiSliderX + 1, sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE);
+ drawDialogRect(Common::Rect(_midiSliderX - 3, sliderY - (num - 6) / 2,
+ _midiSliderX + 4, sliderY - (num - 6) / 2 + num));
+
+ if (_midiSliderX - 4 > _surface.widestChar())
+ _surface.fillRect(Common::Rect(_midiSliderX - 4, sliderY, _midiSliderX - 4, sliderY + 4), INFO_BOTTOM);
+ if (_midiSliderX + 4 < _surface.w() - _surface.widestChar())
+ _surface.fillRect(Common::Rect(_midiSliderX + 4, sliderY, _midiSliderX + 4, sliderY + 4), INFO_BOTTOM);
+ 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._voices]);
+ break;
+
+ case 6: {
+ int num = (_surface.fontHeight() + 4) & 0xfe;
+ int sliderY = yp + num / 2 - 8;
+
+ _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5,
+ sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY);
+ _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, _surface.w() - _surface.widestChar() - 1,
+ sliderY + 3), INFO_MIDDLE);
+ drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6));
+ _surface.fillRect(Common::Rect(_digiSliderX - 1, sliderY - (num - 6) / 2 + 2, _digiSliderX + 1,
+ sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE);
+ drawDialogRect(Common::Rect(_digiSliderX - 3, sliderY - (num - 6) / 2, _digiSliderX + 4,
+ sliderY - (num - 6) / 2 + num));
+ if (_digiSliderX - 4 > _surface.widestChar())
+ _surface.fillRect(Common::Rect(_digiSliderX - 4, sliderY, _digiSliderX - 4, sliderY + 4), INFO_BOTTOM);
+ if (_digiSliderX + 4 < _surface.w() - _surface.widestChar())
+ _surface.fillRect(Common::Rect(_digiSliderX + 4, sliderY, _digiSliderX + 4, sliderY + 4), INFO_BOTTOM);
+ 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.w() - _surface.stringWidth(str)) / 2;
+ _surface.writeString(str, Common::Point(xp, yp), color);
+ }
+ }
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_options.h b/engines/sherlock/tattoo/widget_options.h
new file mode 100644
index 0000000000..9be0105ab4
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_options.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+public:
+ WidgetOptions(SherlockEngine *vm);
+ virtual ~WidgetOptions() {}
+
+ /**
+ * Load and then display the options dialog
+ */
+ void load();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_password.cpp b/engines/sherlock/tattoo/widget_password.cpp
new file mode 100644
index 0000000000..3dd0e308ff
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_password.cpp
@@ -0,0 +1,210 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_password.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_user_interface.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.fill(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.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 7 - 1));
+ _surface.transBlitFrom(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();
+}
+
+void WidgetPassword::handleEvents() {
+ Events &events = *_vm->_events;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ const Common::KeyCode &keycode = ui._keyState.keycode;
+ char currentChar = (_index == (int)_password.size()) ? ' ' : _password[_index];
+ int width = _surface.charWidth(currentChar);
+
+ if (!keycode) {
+ // 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 ((keycode == Common::KEYCODE_LEFT && _index > 0)
+ || (keycode == Common::KEYCODE_RIGHT && _index < (int)_password.size() && _cursorPos.x < (_bounds.width() - _surface.widestChar() - 3))
+ || (keycode == Common::KEYCODE_HOME && _index > 0)
+ || (keycode == Common::KEYCODE_END)) {
+ // 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 (keycode) {
+ case Common::KEYCODE_LEFT:
+ _cursorPos.x -= _surface.charWidth(_password[_index - 1]);
+ --_index;
+ break;
+ case Common::KEYCODE_RIGHT:
+ _cursorPos.x += _surface.charWidth(_password[_index]);
+ ++_index;
+ break;
+ case Common::KEYCODE_HOME:
+ _cursorPos.x = _surface.widestChar();
+ _index = 0;
+ break;
+ case Common::KEYCODE_END:
+ _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 (keycode == Common::KEYCODE_INSERT) {
+ _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 < 169) || ui._keyState.ascii == 225)) {
+ 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;
+
+ 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
diff --git a/engines/sherlock/tattoo/widget_password.h b/engines/sherlock/tattoo/widget_password.h
new file mode 100644
index 0000000000..f7e82c798d
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_password.h
@@ -0,0 +1,68 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetPassword() {}
+
+ /**
+ * Show the password entry window
+ */
+ void show();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_quit.cpp b/engines/sherlock/tattoo/widget_quit.cpp
new file mode 100644
index 0000000000..f853e7f47f
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_quit.cpp
@@ -0,0 +1,155 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_quit.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_user_interface.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.fill(TRANSPARENCY);
+ makeInfoArea();
+
+ // Draw the message text
+ _surface.writeString(FIXED(AreYouSureYou), Common::Point((_surface.w() - _surface.stringWidth(FIXED(AreYouSureYou))) / 2, 5), INFO_TOP);
+ _surface.writeString(FIXED(WishToQuit), Common::Point((_surface.w() - _surface.stringWidth(FIXED(WishToQuit))) / 2,
+ _surface.fontHeight() + 9), INFO_TOP);
+
+ // Draw the horizontal bars seperating the commands and the message
+ int yp = (_surface.fontHeight() + 4) * 2 + 3;
+ for (int idx = 0; idx < 2; ++idx) {
+ _surface.transBlitFrom(images[4], Common::Point(0, yp - 1));
+ _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1));
+ _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP);
+ _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE);
+ _surface.hLine(3, yp + 2, _surface.w() - 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();
+}
+
+void WidgetQuit::handleEvents() {
+ Events &events = *_vm->_events;
+ Talk &talk = *_vm->_talk;
+ 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);
+
+ if (talk._talkToAbort)
+ return;
+
+ // Determine the highlighted item
+ _select = -1;
+ if (yesRect.contains(mousePos))
+ _select = 1;
+ else if (noRect.contains(mousePos))
+ _select = 0;
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ switch (keyState.keycode) {
+ case Common::KEYCODE_TAB:
+ // 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 Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_n:
+ close();
+ return;
+
+ case Common::KEYCODE_y:
+ 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.w() - _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.w() - _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();
+ close();
+ if (_select == 1)
+ // Yes selected
+ _vm->quitGame();
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_quit.h b/engines/sherlock/tattoo/widget_quit.h
new file mode 100644
index 0000000000..b8b640f2d2
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_quit.h
@@ -0,0 +1,57 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetQuit() {}
+
+ /**
+ * Prompt the user whether to quit
+ */
+ void show();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_talk.cpp b/engines/sherlock/tattoo/widget_talk.cpp
new file mode 100644
index 0000000000..00e8233a95
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_talk.cpp
@@ -0,0 +1,470 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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"
+
+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
+ int numStatements = 0;
+ for (uint idx = 0; idx < talk._statements.size(); ++idx) {
+ if (talk._statements[idx]._talkMap != -1)
+ ++numStatements;
+ }
+
+ 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.fill(TRANSPARENCY);
+
+ // Form the background for the new window
+ makeInfoArea();
+}
+
+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;
+ 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 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 (keycode == Common::KEYCODE_TAB && 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 (ui._keyState.flags & Common::KBD_SHIFT) {
+ _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, t = 0, y = 0;
+
+ do {
+ if (y == (keycode - Common::KEYCODE_1)) {
+ _selector = _statementLines[t]._num;
+ _outsideMenu = false;
+ hotkey = true;
+ break;
+ }
+
+ ++t;
+ if (_statementLines[x]._num != _statementLines[t]._num) {
+ x = t;
+ ++y;
+ }
+ } while (t < (int)_statementLines.size());
+ }
+
+ // Display the selected statement highlighted and reset the last statement.
+ if (_selector != _oldSelector) {
+ render(HL_CHANGED_HIGHLIGHTS);
+ _oldSelector = _selector;
+ }
+
+ if (events._released || events._rightReleased || keycode == Common::KEYCODE_ESCAPE || hotkey) {
+ events.clearEvents();
+ _dialogTimer = 0;
+ ui._scrollHighlight = SH_NONE;
+
+ // See if they want to close the menu (click outside the window or Escape pressed)
+ if ((_outsideMenu && !_bounds.contains(mousePos)) || keycode == Common::KEYCODE_ESCAPE) {
+ if (keycode == Common::KEYCODE_ESCAPE)
+ _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;
+
+ 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;
+
+ 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;
+ int yp = 5;
+ int statementNum = 1;
+ byte color;
+
+ if (highlightMode != HL_SCROLLBAR_ONLY) {
+ // 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.w() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY);
+
+ // Different coloring based on whether the option has been previously chosen or not
+ 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 statment 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
diff --git a/engines/sherlock/tattoo/widget_talk.h b/engines/sherlock/tattoo/widget_talk.h
new file mode 100644
index 0000000000..f5e939b393
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_talk.h
@@ -0,0 +1,95 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetTalk() {}
+
+ /**
+ * 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
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_text.cpp b/engines/sherlock/tattoo/widget_text.cpp
new file mode 100644
index 0000000000..86aa067301
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_text.cpp
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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;
+
+ 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.fill(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.fill(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) {
+ // Close the window
+ banishWindow();
+
+ // Reset cursor and switch back to standard mode
+ events.setCursor(ARROW);
+ events.clearEvents();
+ ui._key = -1;
+ ui._oldBgFound = -1;
+ ui._menuMode = STD_MODE;
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_text.h b/engines/sherlock/tattoo/widget_text.h
new file mode 100644
index 0000000000..f027bdae9f
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_text.h
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetText() {}
+
+ /**
+ * 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);
+ virtual ~WidgetMessage() {}
+
+ /**
+ * Load the data for the text window
+ */
+ void load(const Common::String &str, int time);
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_tooltip.cpp b/engines/sherlock/tattoo/widget_tooltip.cpp
new file mode 100644
index 0000000000..b29f45f531
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_tooltip.cpp
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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.transBlitFrom(_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 &str) {
+ Events &events = *_vm->_events;
+ Common::Point mousePos = events.mousePos();
+ bool reset = false;
+
+ // 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.fill(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
diff --git a/engines/sherlock/tattoo/widget_tooltip.h b/engines/sherlock/tattoo/widget_tooltip.h
new file mode 100644
index 0000000000..87f5d54f0a
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_tooltip.h
@@ -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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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) {}
+ virtual ~WidgetTooltipBase() {}
+
+ /**
+ * 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();
+};
+
+class WidgetTooltip: public WidgetTooltipBase {
+public:
+ int _offsetY;
+public:
+ WidgetTooltip(SherlockEngine *vm);
+ virtual ~WidgetTooltip() {}
+
+ /**
+ * Set the text for the tooltip
+ */
+ void setText(const Common::String &str);
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetSceneTooltip : public WidgetTooltip {
+public:
+ WidgetSceneTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetMapTooltip : public WidgetTooltip {
+public:
+ WidgetMapTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_verbs.cpp b/engines/sherlock/tattoo/widget_verbs.cpp
new file mode 100644
index 0000000000..0b523a93e9
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_verbs.cpp
@@ -0,0 +1,314 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "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"
+
+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;
+
+ // 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];
+ TattooPerson &npc = people[ui._activeObj - 1001];
+
+ if (!scumm_strnicmp(npc._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.fill(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.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1));
+ _surface.transBlitFrom(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();
+ bool noDesc = false;
+
+ 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];
+
+ 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();
+
+ // 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();
+
+ // 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._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ // User closing the menu with the ESC key
+ banishWindow();
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ }
+}
+
+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
diff --git a/engines/sherlock/tattoo/widget_verbs.h b/engines/sherlock/tattoo/widget_verbs.h
new file mode 100644
index 0000000000..ce67842409
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_verbs.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef 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);
+ virtual ~WidgetVerbs() {}
+
+ /**
+ * Turns on the menu with all the verbs that are available for the given object
+ */
+ void load(bool objectsOn);
+
+ /**
+ * Process input for the dialog
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp
index 9fff7cc999..bb0667d66a 100644
--- a/engines/sherlock/user_interface.cpp
+++ b/engines/sherlock/user_interface.cpp
@@ -21,8 +21,9 @@
*/
#include "sherlock/user_interface.h"
-#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
namespace Sherlock {
@@ -45,7 +46,9 @@ UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) {
_helpStyle = false;
_windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, SHERLOCK_SCREEN_HEIGHT - 1);
_lookScriptFlag = false;
+ _exitZone = -1;
+ _bgFound = _oldBgFound = -1;
_key = _oldKey = '\0';
_selector = _oldSelector = -1;
_temp = _oldTemp = 0;
@@ -53,4 +56,154 @@ UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) {
_lookHelp = 0;
}
+void UserInterface::checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Point32 pt(-1, -1);
+
+ if (action._useFlag)
+ // Automatically set the given flag
+ _vm->setFlags(action._useFlag);
+
+ if (IS_SERRATED_SCALPEL && objNum >= 1000)
+ // Ignore actions done on characters
+ return;
+
+ if (IS_SERRATED_SCALPEL && !action._cAnimSpeed) {
+ // Invalid action, to print error message
+ _infoFlag = true;
+ clearInfo();
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, action._cAnimNum);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
+ _infoFlag = true;
+
+ // Set how long to show the message
+ _menuCounter = 30;
+ } else {
+ BaseObject *obj;
+ if (objNum >= 1000)
+ obj = &people[objNum - 1000];
+ else
+ obj = &scene._bgShapes[objNum];
+
+ int cAnimNum;
+ if (action._cAnimNum == 0)
+ // Really a 10
+ cAnimNum = 9;
+ else
+ cAnimNum = action._cAnimNum - 1;
+
+ int dir = -1;
+ if (action._cAnimNum != 99) {
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (action._cAnimNum != 99) {
+ if (action._cAnimSpeed & REVERSE_DIRECTION) {
+ pt = anim._teleport[0];
+ dir = anim._teleport[0]._facing;
+ } else {
+ pt = anim._goto[0];
+ dir = anim._goto[0]._facing;
+ }
+ }
+ } else {
+ pt = Point32(-1, -1);
+ dir = -1;
+ }
+
+ // Has a value, so do action
+ // Show wait cursor whilst walking to object and doing action
+ events.setCursor(WAIT);
+ bool printed = false;
+
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
+ && toupper(action._names[nameIdx][1]) == 'W') {
+ if (obj->checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+ }
+
+ bool doCAnim = true;
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) {
+ char ch = toupper(action._names[nameIdx][1]);
+
+ if (ch == 'T' || ch == 'B') {
+ printed = true;
+ if (pt.x != -1)
+ // Holmes needs to walk to object before the action is done
+ people[HOLMES].walkToCoords(pt, dir);
+
+ if (!talk._talkToAbort) {
+ // Ensure Holmes is on the exact intended location
+ people[HOLMES]._position = pt;
+ people[HOLMES]._sequenceNumber = dir;
+ people[HOLMES].gotoStand();
+
+ talk.talkTo(action._names[nameIdx].c_str() + 2);
+ if (ch == 'T')
+ doCAnim = false;
+ }
+ }
+ }
+ }
+
+ if (doCAnim && !talk._talkToAbort) {
+ if (pt.x != -1)
+ // Holmes needs to walk to object before the action is done
+ people[HOLMES].walkToCoords(pt, dir);
+ }
+
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
+ && toupper(action._names[nameIdx][1]) == 'F') {
+ if (obj->checkNameForCodes(action._names[nameIdx].c_str() + 2, fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+ }
+
+ if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99)
+ scene.startCAnim(cAnimNum, action._cAnimSpeed);
+
+ if (!talk._talkToAbort) {
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) {
+ if (obj->checkNameForCodes(action._names[nameIdx], fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+
+ // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set
+ if (IS_SERRATED_SCALPEL && scene._goToScene != 1 && !printed && !talk._talkToAbort) {
+ _infoFlag = true;
+ clearInfo();
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Done...");
+
+ // Set how long to show the message
+ _menuCounter = 30;
+ }
+ }
+ }
+
+ // Reset cursor back to arrow
+ events.setCursor(ARROW);
+}
+
+void UserInterface::reset() {
+ _bgFound = _oldBgFound = -1;
+ _oldKey = -1;
+ _oldTemp = _temp = -1;
+ _exitZone = -1;
+}
+
+
} // End of namespace Sherlock
diff --git a/engines/sherlock/user_interface.h b/engines/sherlock/user_interface.h
index 042997a3e2..c16c9f5d11 100644
--- a/engines/sherlock/user_interface.h
+++ b/engines/sherlock/user_interface.h
@@ -28,6 +28,7 @@
#include "sherlock/surface.h"
#include "sherlock/objects.h"
#include "sherlock/resources.h"
+#include "sherlock/fixed_text.h"
namespace Sherlock {
@@ -47,7 +48,16 @@ enum MenuMode {
GIVE_MODE = 9,
JOURNAL_MODE = 10,
FILES_MODE = 11,
- SETUP_MODE = 12
+ SETUP_MODE = 12,
+
+ // Rose Tattoo specific
+ LAB_MODE = 20,
+ MESSAGE_MODE = 21,
+ VERB_MODE = 22,
+ OPTION_MODE = 23,
+ QUIT_MODE = 24,
+ FOOLSCAP_MODE = 25,
+ PASSWORD_MODE = 26
};
class UserInterface {
@@ -66,10 +76,12 @@ public:
bool _helpStyle;
Common::Rect _windowBounds;
bool _lookScriptFlag;
+ int _bgFound, _oldBgFound;
+ int _exitZone;
// TODO: Not so sure these should be in the base class. May want to refactor them to SherlockEngine, or refactor
// various Scalpel dialogs to keep their own private state of key/selections
- char _key, _oldKey;
+ signed char _key, _oldKey;
int _selector, _oldSelector;
int _temp, _oldTemp;
int _temp1;
@@ -79,9 +91,14 @@ public:
virtual ~UserInterface() {}
/**
+ * Called for OPEN, CLOSE, and MOVE actions are being done
+ */
+ void checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
+public:
+ /**
* Resets the user interface
*/
- virtual void reset() {}
+ virtual void reset();
/**
* Draw the user interface onto the screen's back buffers
@@ -118,11 +135,6 @@ public:
* Clear any active text window
*/
virtual void clearWindow() {}
-
- /**
- * Print the previously selected object's decription
- */
- virtual void printObjectDesc() {}
};
} // End of namespace Sherlock
diff --git a/engines/sky/music/adlibchannel.cpp b/engines/sky/music/adlibchannel.cpp
index 8400fef6eb..c7acb9b6c1 100644
--- a/engines/sky/music/adlibchannel.cpp
+++ b/engines/sky/music/adlibchannel.cpp
@@ -29,7 +29,7 @@
namespace Sky {
-AdLibChannel::AdLibChannel(FM_OPL *opl, uint8 *pMusicData, uint16 startOfData) {
+AdLibChannel::AdLibChannel(OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData) {
_opl = opl;
_musicData = pMusicData;
_channelData.loopPoint = startOfData;
@@ -45,6 +45,8 @@ AdLibChannel::AdLibChannel(FM_OPL *opl, uint8 *pMusicData, uint16 startOfData) {
_channelData.frequency = 0;
_channelData.instrumentData = NULL;
+ _musicVolume = 128;
+
uint16 instrumentDataLoc;
if (SkyEngine::_systemVars.gameVersion == 109) {
@@ -86,7 +88,7 @@ bool AdLibChannel::isActive() {
}
void AdLibChannel::updateVolume(uint16 pVolume) {
- // Do nothing. The mixer handles the music volume for us.
+ _musicVolume = pVolume;
}
/* This class uses the same area for the register mirror as the original
@@ -95,7 +97,7 @@ void AdLibChannel::updateVolume(uint16 pVolume) {
*/
void AdLibChannel::setRegister(uint8 regNum, uint8 value) {
if (_adlibRegMirror[regNum] != value) {
- OPLWriteReg (_opl, regNum, value);
+ _opl->writeReg(regNum, value);
_adlibRegMirror[regNum] = value;
}
}
@@ -208,6 +210,8 @@ void AdLibChannel::setupChannelVolume(uint8 volume) {
uint32 resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op2 + 1)) << 1;
resVol &= 0xFFFF;
resVol *= (_channelData.channelVolume + 1) << 1;
+ resVol >>= 8;
+ resVol *= _musicVolume << 1;
resVol >>= 16;
assert(resVol < 0x81);
resultOp = ((_channelData.instrumentData->scalingLevel << 6) & 0xC0) | _opOutputTable[resVol];
@@ -216,6 +220,8 @@ void AdLibChannel::setupChannelVolume(uint8 volume) {
resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op1 + 1)) << 1;
resVol &= 0xFFFF;
resVol *= (_channelData.channelVolume + 1) << 1;
+ resVol >>= 8;
+ resVol *= _musicVolume << 1;
resVol >>= 16;
} else
resVol = _channelData.instrumentData->totOutLev_Op1;
diff --git a/engines/sky/music/adlibchannel.h b/engines/sky/music/adlibchannel.h
index 80dae93b2c..4504e3b570 100644
--- a/engines/sky/music/adlibchannel.h
+++ b/engines/sky/music/adlibchannel.h
@@ -60,14 +60,15 @@ typedef struct {
class AdLibChannel : public ChannelBase {
public:
- AdLibChannel (FM_OPL *opl, uint8 *pMusicData, uint16 startOfData);
+ AdLibChannel (OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData);
virtual ~AdLibChannel();
virtual uint8 process(uint16 aktTime);
virtual void updateVolume(uint16 pVolume);
virtual bool isActive();
private:
- FM_OPL *_opl;
+ OPL::OPL *_opl;
uint8 *_musicData;
+ uint16 _musicVolume;
AdLibChannelType _channelData;
InstrumentStruct *_instruments;
diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp
index dd64c5bc81..be5e7b2353 100644
--- a/engines/sky/music/adlibmusic.cpp
+++ b/engines/sky/music/adlibmusic.cpp
@@ -22,6 +22,7 @@
#include "common/endian.h"
+#include "common/textconsole.h"
#include "sky/music/adlibmusic.h"
#include "sky/music/adlibchannel.h"
@@ -32,44 +33,21 @@ namespace Sky {
AdLibMusic::AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
_driverFileBase = 60202;
- _sampleRate = pMixer->getOutputRate();
- _opl = makeAdLibOPL(_sampleRate);
+ _opl = OPL::Config::create();
+ if (!_opl || !_opl->init())
+ error("Failed to create OPL");
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _opl->start(new Common::Functor0Mem<void, AdLibMusic>(this, &AdLibMusic::onTimer), 50);
}
AdLibMusic::~AdLibMusic() {
- OPLDestroy(_opl);
- _mixer->stopHandle(_soundHandle);
+ delete _opl;
}
-int AdLibMusic::readBuffer(int16 *data, const int numSamples) {
- if (_musicData == NULL) {
- // no music loaded
- memset(data, 0, numSamples * sizeof(int16));
- } else if ((_currentMusic == 0) || (_numberOfChannels == 0)) {
- // music loaded but not played as of yet
- memset(data, 0, numSamples * sizeof(int16));
- // poll anyways as pollMusic() can activate the music
+void AdLibMusic::onTimer() {
+ if (_musicData != NULL)
pollMusic();
- _nextMusicPoll = _sampleRate / 50;
- } else {
- uint32 render;
- uint remaining = numSamples;
- while (remaining) {
- render = (remaining > _nextMusicPoll) ? _nextMusicPoll : remaining;
- remaining -= render;
- _nextMusicPoll -= render;
- YM3812UpdateOne(_opl, data, render);
- data += render;
- if (_nextMusicPoll == 0) {
- pollMusic();
- _nextMusicPoll = _sampleRate / 50;
- }
- }
- }
- return numSamples;
}
void AdLibMusic::setupPointers() {
@@ -87,7 +65,6 @@ void AdLibMusic::setupPointers() {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x1201);
_initSequence = _musicData + 0xE91;
}
- _nextMusicPoll = 0;
}
void AdLibMusic::setupChannels(uint8 *channelData) {
@@ -102,26 +79,15 @@ void AdLibMusic::setupChannels(uint8 *channelData) {
void AdLibMusic::startDriver() {
uint16 cnt = 0;
while (_initSequence[cnt] || _initSequence[cnt + 1]) {
- OPLWriteReg (_opl, _initSequence[cnt], _initSequence[cnt + 1]);
+ _opl->writeReg(_initSequence[cnt], _initSequence[cnt + 1]);
cnt += 2;
}
}
void AdLibMusic::setVolume(uint16 param) {
_musicVolume = param;
- _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, 2 * param);
-}
-
-bool AdLibMusic::isStereo() const {
- return false;
-}
-
-bool AdLibMusic::endOfData() const {
- return false;
-}
-
-int AdLibMusic::getRate() const {
- return _sampleRate;
+ for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
+ _channels[cnt]->updateVolume(_musicVolume);
}
} // End of namespace Sky
diff --git a/engines/sky/music/adlibmusic.h b/engines/sky/music/adlibmusic.h
index 886eef026e..7b51f2d3a0 100644
--- a/engines/sky/music/adlibmusic.h
+++ b/engines/sky/music/adlibmusic.h
@@ -25,32 +25,32 @@
#include "sky/music/musicbase.h"
#include "audio/audiostream.h"
-#include "audio/fmopl.h"
+
+namespace OPL {
+class OPL;
+}
namespace Sky {
-class AdLibMusic : public Audio::AudioStream, public MusicBase {
+class AdLibMusic : public MusicBase {
public:
AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk);
~AdLibMusic();
// AudioStream API
- int readBuffer(int16 *buffer, const int numSamples);
- bool isStereo() const;
- bool endOfData() const;
- int getRate() const;
virtual void setVolume(uint16 param);
private:
- FM_OPL *_opl;
- Audio::SoundHandle _soundHandle;
+ OPL::OPL *_opl;
uint8 *_initSequence;
- uint32 _sampleRate, _nextMusicPoll;
+ uint32 _sampleRate;
virtual void setupPointers();
virtual void setupChannels(uint8 *channelData);
virtual void startDriver();
void premixerCall(int16 *buf, uint len);
+
+ void onTimer();
};
} // End of namespace Sky
diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp
index 5d7dcf2506..eb0f0390dc 100644
--- a/engines/sword25/fmv/movieplayer.cpp
+++ b/engines/sword25/fmv/movieplayer.cpp
@@ -123,7 +123,7 @@ void MoviePlayer::update() {
if (_decoder.endOfVideo()) {
// Movie complete, so unload the movie
unloadMovie();
- } else {
+ } else if (_decoder.needsUpdate()) {
const Graphics::Surface *s = _decoder.decodeNextFrame();
if (s) {
// Transfer the next frame
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
index 8a7305f63b..9b4e2494e0 100644
--- a/engines/tinsel/music.cpp
+++ b/engines/tinsel/music.cpp
@@ -27,6 +27,8 @@
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
+// Miles Audio for Discworld 1
+#include "audio/miles.h"
#include "audio/decoders/adpcm.h"
#include "backends/audiocd/audiocd.h"
@@ -373,8 +375,78 @@ void DeleteMidiBuffer() {
g_midiBuffer.pDat = NULL;
}
-MidiMusicPlayer::MidiMusicPlayer() {
- MidiPlayer::createDriver();
+MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) {
+ _driver = NULL;
+ _milesAudioMode = false;
+ bool milesAudioEnabled = false;
+
+ if (vm->getPlatform() == Common::kPlatformDOS) {
+ // Enable Miles Audio for DOS only
+ milesAudioEnabled = true;
+ }
+
+ if ((vm->getGameId() == GID_DW1) && (milesAudioEnabled)) {
+ // Discworld 1 (DOS) uses Miles Audio 3
+ // use our own Miles Audio drivers
+ //
+ // It seems that there are multiple versions of Discworld 1
+ //
+ // Version 1:
+ // Has SAMPLE.AD for AdLib and SAMPLE.OPL for OPL-3
+ // Timbre files are inside a subdirectory of the CD called "/drivers". Main game files are in
+ // another subdirectory, which means the user has to copy those files over.
+ // Installer script copies all drivers directly to harddrive without name changes
+ //
+ // Version 2:
+ // Has FAT.OPL only (gets copied by the installer into MIDPAK.AD or MIDPAK.OPL)
+ // Timbre file is inside subdirectory "drivers" right in the main game directory.
+ // Installer copies FAT.OPL to MIDPAK.AD all the time, even when user selected AWE32
+ //
+ // Neither have timbre data for MT32
+
+ ::MidiDriver::DeviceHandle dev = ::MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ ::MusicType musicType = ::MidiDriver::getMusicType(dev);
+ Common::File fileClass;
+
+ switch (musicType) {
+ case MT_ADLIB:
+ if (fileClass.exists("FAT.OPL")) {
+ // Version 2: fat.opl, may be in drivers-subdirectory
+ _driver = Audio::MidiDriver_Miles_AdLib_create("", "FAT.OPL");
+ } else {
+ if (fileClass.exists("MIDPAK.AD")) {
+ // Version 2: drivers got installed and fat.opl got copied over by the user
+ _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "");
+ } else {
+ // Version 1: sample.ad / sample.opl, have to be copied over by the user for this version
+ // That's why we check those last, because then the user gets a proper error message for them
+ _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+ }
+ }
+ break;
+ case MT_MT32:
+ // Discworld 1 doesn't have a MT32 timbre file
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ musicType = MT_MT32;
+ }
+ break;
+ default:
+ break;
+ }
+ if (!_driver) {
+ // nothing got created yet? -> create default driver
+ MidiPlayer::createDriver();
+ } else {
+ _milesAudioMode = true;
+ }
+
+ } else {
+ MidiPlayer::createDriver();
+ }
int ret = _driver->open();
if (ret == 0) {
@@ -394,6 +466,11 @@ void MidiMusicPlayer::setVolume(int volume) {
}
void MidiMusicPlayer::send(uint32 b) {
+ if (_milesAudioMode) {
+ _driver->send(b);
+ return;
+ }
+
Audio::MidiPlayer::send(b);
byte channel = (byte)(b & 0x0F);
diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h
index 0a78c39a76..422d80ae30 100644
--- a/engines/tinsel/music.h
+++ b/engines/tinsel/music.h
@@ -60,7 +60,7 @@ void dumpMusic();
class MidiMusicPlayer : public Audio::MidiPlayer {
public:
- MidiMusicPlayer();
+ MidiMusicPlayer(TinselEngine *vm);
virtual void setVolume(int volume);
@@ -77,6 +77,8 @@ public:
// means. The default is 120.
uint32 getBaseTempo() { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; }
+ bool _milesAudioMode;
+
private:
void playXMIDI(uint32 size, bool loop);
void playSEQ(uint32 size, bool loop);
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 57d8432f0e..6dc8e3bb35 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -885,12 +885,15 @@ void TinselEngine::initializePath(const Common::FSNode &gamePath) {
} else {
// Add DW2 subfolder to search path in case user is running directly from the CDs
SearchMan.addSubDirectoryMatching(gamePath, "dw2");
+
+ // Location of Miles audio files (sample.ad and sample.opl) in Discworld 1
+ SearchMan.addSubDirectoryMatching(gamePath, "drivers");
Engine::initializePath(gamePath);
}
}
Common::Error TinselEngine::run() {
- _midiMusic = new MidiMusicPlayer();
+ _midiMusic = new MidiMusicPlayer(this);
_pcmMusic = new PCMMusicPlayer();
_sound = new SoundManager(this);
_bmv = new BMVPlayer();
diff --git a/engines/toltecs/music.cpp b/engines/toltecs/music.cpp
index e4e067de39..97d8b1aea2 100644
--- a/engines/toltecs/music.cpp
+++ b/engines/toltecs/music.cpp
@@ -21,6 +21,7 @@
*/
#include "audio/midiparser.h"
+#include "audio/miles.h"
#include "common/textconsole.h"
#include "toltecs/toltecs.h"
@@ -30,20 +31,47 @@
namespace Toltecs {
MusicPlayer::MusicPlayer(bool isGM) : _isGM(isGM), _buffer(NULL) {
- MidiPlayer::createDriver();
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ switch (musicType) {
+ case MT_ADLIB:
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+ break;
+ case MT_MT32:
+ // Not recommended since it sounds awful, but apparently the
+ // original sounded just as bad. I guess MT-32 support was
+ // added by default, not because anyone actually put any work
+ // into it.
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ default:
+ _milesAudioMode = false;
+ MidiPlayer::createDriver();
+ break;
+ }
int ret = _driver->open();
if (ret == 0) {
- if (_nativeMT32)
- _driver->sendMT32Reset();
- else
- _driver->sendGMReset();
+ if (musicType != MT_ADLIB) {
+ if (musicType == MT_MT32 || _nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+ }
_driver->setTimerCallback(this, &timerCallback);
}
}
void MusicPlayer::send(uint32 b) {
+ if (_milesAudioMode) {
+ _driver->send(b);
+ return;
+ }
+
if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
}
diff --git a/engines/toltecs/music.h b/engines/toltecs/music.h
index e6dc3dd146..25d67e9334 100644
--- a/engines/toltecs/music.h
+++ b/engines/toltecs/music.h
@@ -47,6 +47,7 @@ protected:
private:
byte *_buffer;
+ bool _milesAudioMode;
};
class Music : public MusicPlayer {
diff --git a/engines/tony/window.cpp b/engines/tony/window.cpp
index 3b3687419b..d312d58091 100644
--- a/engines/tony/window.cpp
+++ b/engines/tony/window.cpp
@@ -28,7 +28,7 @@
#include "common/scummsys.h"
#include "graphics/surface.h"
-#include "util.h"
+#include "engines/util.h"
#include "tony/window.h"
#include "tony/game.h"
#include "tony/tony.h"
diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h
index 1dfc3e6fd2..109ac353e6 100644
--- a/engines/tsage/detection_tables.h
+++ b/engines/tsage/detection_tables.h
@@ -185,7 +185,7 @@ static const tSageGameDescription gameDescriptions[] = {
GType_Ringworld2,
GF_CD | GF_ALT_REGIONS | GF_DEMO
},
-
+#ifdef TSAGE_SHERLOCK_ENABLED
// The Lost Files of Sherlock Holmes - The Case of the Serrated Scalpel (Logo)
{
{
@@ -200,6 +200,7 @@ static const tSageGameDescription gameDescriptions[] = {
GType_Sherlock1,
GF_FLOPPY
},
+#endif
{ AD_TABLE_END_MARKER, 0, 0 }
};
diff --git a/engines/tsage/globals.cpp b/engines/tsage/globals.cpp
index 1be3e2b6da..b880f35007 100644
--- a/engines/tsage/globals.cpp
+++ b/engines/tsage/globals.cpp
@@ -157,12 +157,15 @@ Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface
_game = new Ringworld2::Ringworld2Game();
_sceneHandler = new Ringworld2::SceneHandlerExt();
break;
-
+#ifdef TSAGE_SHERLOCK_ENABLED
case GType_Sherlock1:
_inventory = nullptr;
_sceneHandler = new Sherlock::SherlockSceneHandler();
_game = new Sherlock::SherlockLogo();
break;
+#endif
+ default:
+ break;
}
}
diff --git a/engines/tsage/sherlock/sherlock_logo.cpp b/engines/tsage/sherlock/sherlock_logo.cpp
index 437fdc6d94..e27ce76576 100644
--- a/engines/tsage/sherlock/sherlock_logo.cpp
+++ b/engines/tsage/sherlock/sherlock_logo.cpp
@@ -20,6 +20,7 @@
*
*/
+#ifdef TSAGE_SHERLOCK_ENABLED
#include "tsage/sherlock/sherlock_logo.h"
#include "tsage/scenes.h"
#include "tsage/tsage.h"
@@ -148,7 +149,7 @@ void SherlockLogoScene::Action1::signal() {
scene._object1.changeZoom(100);
scene._object1.setPosition(Common::Point(170, 142));
scene._object1._numFrames = 7;
- scene._object1.animate(ANIM_MODE_5, nullptr);
+ scene._object1.animate(ANIM_MODE_5, (const void *)nullptr);
ADD_MOVER(scene._object1, 158, 71);
break;
@@ -164,7 +165,7 @@ void SherlockLogoScene::Action1::signal() {
scene._object2._frame = 1;
scene._object2.setPosition(Common::Point(152, 98));
scene._object2.changeZoom(100);
- scene._object2.animate(ANIM_MODE_NONE, nullptr);
+ scene._object2.animate(ANIM_MODE_NONE, (const void *)nullptr);
setDelay(120);
break;
@@ -176,7 +177,7 @@ void SherlockLogoScene::Action1::signal() {
scene._object3._frame = 1;
scene._object3.setPosition(Common::Point(33, 91));
scene._object3.changeZoom(100);
- scene._object3.animate(ANIM_MODE_NONE, nullptr);
+ scene._object3.animate(ANIM_MODE_NONE, (const void *)nullptr);
setDelay(5);
break;
@@ -341,7 +342,7 @@ void SherlockLogoScene::postInit(SceneObjectList *OwnerList) {
_object4._frame = 1;
_object4.setPosition(Common::Point(155, 94));
_object4.changeZoom(100);
- _object4.animate(ANIM_MODE_NONE, nullptr);
+ _object4.animate(ANIM_MODE_NONE, (const void *)nullptr);
_object4.hide();
setAction(&_action1);
@@ -354,3 +355,5 @@ void SherlockLogoScene::finish() {
} // End of namespace Sherlock
} // End of namespace TsAGE
+
+#endif
diff --git a/engines/tsage/sherlock/sherlock_logo.h b/engines/tsage/sherlock/sherlock_logo.h
index 95fc0e272f..01b5b7f75f 100644
--- a/engines/tsage/sherlock/sherlock_logo.h
+++ b/engines/tsage/sherlock/sherlock_logo.h
@@ -20,6 +20,7 @@
*
*/
+#ifdef TSAGE_SHERLOCK_ENABLED
#ifndef TSAGE_SHERLOCK_LOGO_H
#define TSAGE_SHERLOCK_LOGO_H
@@ -76,3 +77,4 @@ public:
} // End of namespace TsAGE
#endif
+#endif
diff --git a/engines/tsage/sound.cpp b/engines/tsage/sound.cpp
index b95b614f09..0d3fb55dd3 100644
--- a/engines/tsage/sound.cpp
+++ b/engines/tsage/sound.cpp
@@ -20,9 +20,9 @@
*
*/
+#include "audio/fmopl.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
-#include "audio/decoders/raw.h"
#include "audio/audiostream.h"
#include "tsage/core.h"
#include "tsage/globals.h"
@@ -2743,17 +2743,9 @@ AdlibSoundDriver::AdlibSoundDriver(): SoundDriver() {
_groupData._pData = &adlib_group_data[0];
_mixer = g_vm->_mixer;
- _sampleRate = _mixer->getOutputRate();
_opl = OPL::Config::create();
assert(_opl);
- _opl->init(_sampleRate);
-
- _samplesTillCallback = 0;
- _samplesTillCallbackRemainder = 0;
- _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND;
- _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND;
-
- _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _opl->init();
Common::fill(_channelVoiced, _channelVoiced + ADLIB_CHANNEL_COUNT, false);
memset(_channelVolume, 0, ADLIB_CHANNEL_COUNT * sizeof(int));
@@ -2772,11 +2764,12 @@ AdlibSoundDriver::AdlibSoundDriver(): SoundDriver() {
_channelVoiced[i] = false;
_pitchBlend[i] = 0;
}
+
+ _opl->start(new Common::Functor0Mem<void, AdlibSoundDriver>(this, &AdlibSoundDriver::onTimer), CALLBACKS_PER_SECOND);
}
AdlibSoundDriver::~AdlibSoundDriver() {
DEALLOCATE(_patchData);
- _mixer->stopHandle(_soundHandle);
delete _opl;
}
@@ -3019,33 +3012,12 @@ void AdlibSoundDriver::setFrequency(int channel) {
((dataWord >> 8) & 3) | (var2 << 2));
}
-int AdlibSoundDriver::readBuffer(int16 *buffer, const int numSamples) {
+void AdlibSoundDriver::onTimer() {
Common::StackLock slock1(SoundManager::sfManager()._serverDisabledMutex);
Common::StackLock slock2(SoundManager::sfManager()._serverSuspendedMutex);
- int32 samplesLeft = numSamples;
- memset(buffer, 0, sizeof(int16) * numSamples);
- while (samplesLeft) {
- if (!_samplesTillCallback) {
- SoundManager::sfUpdateCallback(NULL);
- flush();
-
- _samplesTillCallback = _samplesPerCallback;
- _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
- if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) {
- _samplesTillCallback++;
- _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND;
- }
- }
-
- int32 render = MIN<int>(samplesLeft, _samplesTillCallback);
- samplesLeft -= render;
- _samplesTillCallback -= render;
-
- _opl->readBuffer(buffer, render);
- buffer += render;
- }
- return numSamples;
+ SoundManager::sfUpdateCallback(NULL);
+ flush();
}
/*--------------------------------------------------------------------------*/
diff --git a/engines/tsage/sound.h b/engines/tsage/sound.h
index 49558b4bca..68755a48c8 100644
--- a/engines/tsage/sound.h
+++ b/engines/tsage/sound.h
@@ -27,12 +27,15 @@
#include "common/mutex.h"
#include "common/queue.h"
#include "audio/audiostream.h"
-#include "audio/fmopl.h"
#include "audio/mixer.h"
#include "common/list.h"
#include "tsage/saveload.h"
#include "tsage/core.h"
+namespace OPL {
+class OPL;
+}
+
namespace TsAGE {
class Sound;
@@ -446,21 +449,15 @@ public:
#define ADLIB_CHANNEL_COUNT 9
-class AdlibSoundDriver: public SoundDriver, Audio::AudioStream {
+class AdlibSoundDriver: public SoundDriver {
private:
GroupData _groupData;
Audio::Mixer *_mixer;
- FM_OPL *_opl;
- Audio::SoundHandle _soundHandle;
- int _sampleRate;
+ OPL::OPL *_opl;
byte _portContents[256];
const byte *_patchData;
int _masterVolume;
Common::Queue<RegisterValue> _queue;
- int _samplesTillCallback;
- int _samplesTillCallbackRemainder;
- int _samplesPerCallback;
- int _samplesPerCallbackRemainder;
bool _channelVoiced[ADLIB_CHANNEL_COUNT];
int _channelVolume[ADLIB_CHANNEL_COUNT];
@@ -495,13 +492,8 @@ public:
virtual void proc38(int channel, int cmd, int value);
virtual void setPitch(int channel, int pitchBlend);
- // AudioStream interface
- virtual int readBuffer(int16 *buffer, const int numSamples);
- virtual bool isStereo() const { return false; }
- virtual bool endOfData() const { return false; }
- virtual int getRate() const { return _sampleRate; }
-
- void update(int16 *buf, int len);
+private:
+ void onTimer();
};
class SoundBlasterDriver: public SoundDriver {
diff --git a/engines/tsage/tsage.cpp b/engines/tsage/tsage.cpp
index 4412d0670f..b94b82f423 100644
--- a/engines/tsage/tsage.cpp
+++ b/engines/tsage/tsage.cpp
@@ -112,10 +112,12 @@ void TSageEngine::initialize() {
// Reset all global variables
R2_GLOBALS.reset();
} else if (g_vm->getGameID() == GType_Sherlock1) {
+#ifdef TSAGE_SHERLOCK_ENABLED
g_resourceManager->addLib("SF3.RLB");
g_globals = new Globals();
return;
+#endif
}
g_globals->gfxManager().setDefaults();
diff --git a/engines/wintermute/base/base_script_holder.cpp b/engines/wintermute/base/base_script_holder.cpp
index 8383657239..5b1c961479 100644
--- a/engines/wintermute/base/base_script_holder.cpp
+++ b/engines/wintermute/base/base_script_holder.cpp
@@ -302,7 +302,7 @@ bool BaseScriptHolder::addScript(const char *filename) {
for (uint32 i = 0; i < _scripts.size(); i++) {
if (scumm_stricmp(_scripts[i]->_filename, filename) == 0) {
if (_scripts[i]->_state != SCRIPT_FINISHED) {
- BaseEngine::LOG(0, "BaseScriptHolder::AddScript - trying to add script '%s' mutiple times (obj: '%s')", filename, getName());
+ BaseEngine::LOG(0, "BaseScriptHolder::AddScript - trying to add script '%s' multiple times (obj: '%s')", filename, getName());
return STATUS_OK;
}
}
diff --git a/engines/zvision/configure.engine b/engines/zvision/configure.engine
index 38a5959995..226870c3fd 100644
--- a/engines/zvision/configure.engine
+++ b/engines/zvision/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine zvision "ZVision" yes "" "" "freetype2 16bit"
+add_engine zvision "Z-Vision" yes "" "" "freetype2 16bit"
diff --git a/engines/zvision/core/clock.h b/engines/zvision/core/clock.h
index cbf52be560..ae8c968111 100644
--- a/engines/zvision/core/clock.h
+++ b/engines/zvision/core/clock.h
@@ -67,14 +67,14 @@ public:
}
/**
- * Pause the clock. Any future delta times will take this pause into account.
- * Has no effect if the clock is already paused.
- */
+ * Un-pause the clock.
+ * Has no effect if the clock is already un-paused.
+ */
void start();
/**
- * Un-pause the clock.
- * Has no effect if the clock is already un-paused.
+ * Pause the clock. Any future delta times will take this pause into account.
+ * Has no effect if the clock is already paused.
*/
void stop();
};
diff --git a/engines/zvision/core/console.cpp b/engines/zvision/core/console.cpp
index f5cacb582c..336541d82a 100644
--- a/engines/zvision/core/console.cpp
+++ b/engines/zvision/core/console.cpp
@@ -275,7 +275,7 @@ bool Console::cmdDumpFiles(int argc, const char **argv) {
bool Console::cmdDumpImage(int argc, const char **argv) {
if (argc != 2) {
- debugPrintf("Use %s <TGA/TGZ name> to dump a ZVision TGA/TGZ image into a regular BMP image\n", argv[0]);
+ debugPrintf("Use %s <TGA/TGZ name> to dump a Z-Vision TGA/TGZ image into a regular BMP image\n", argv[0]);
return true;
}
diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp
index c817cbf3e9..f44e653c2a 100644
--- a/engines/zvision/detection.cpp
+++ b/engines/zvision/detection.cpp
@@ -24,8 +24,9 @@
#include "base/plugins.h"
+#include "engines/advancedDetector.h"
+
#include "zvision/zvision.h"
-#include "zvision/detection.h"
#include "zvision/file/save_manager.h"
#include "zvision/scripting/script_manager.h"
@@ -36,277 +37,39 @@
namespace ZVision {
-uint32 ZVision::getFeatures() const {
- return _gameDescription->desc.flags;
-}
+struct ZVisionGameDescription {
+ ADGameDescription desc;
+ ZVisionGameId gameId;
+};
+ZVisionGameId ZVision::getGameId() const {
+ return _gameDescription->gameId;
+}
Common::Language ZVision::getLanguage() const {
return _gameDescription->desc.language;
}
+uint32 ZVision::getFeatures() const {
+ return _gameDescription->desc.flags;
+}
} // End of namespace ZVision
-static const PlainGameDescriptor zVisionGames[] = {
- {"zvision", "ZVision Game"},
- {"znemesis", "Zork Nemesis: The Forbidden Lands"},
- {"zgi", "Zork: Grand Inquisitor"},
- {0, 0}
-};
-
-namespace ZVision {
-
-#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
-#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2
-#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3
-#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4
-#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5
-
-static const ZVisionGameDescription gameDescriptions[] = {
-
- {
- // Zork Nemesis English version
- {
- "znemesis",
- 0,
- AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741),
- Common::EN_ANY,
- Common::kPlatformDOS,
- ADGF_NO_FLAGS,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_NEMESIS
- },
-
- {
- // Zork Nemesis French version
- {
- "znemesis",
- 0,
- {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873},
- {"NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219},
- AD_LISTEND
- },
- Common::FR_FRA,
- Common::kPlatformDOS,
- ADGF_NO_FLAGS,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_NEMESIS
- },
-
- {
- // Zork Nemesis German version
- {
- "znemesis",
- 0,
- {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873},
- {"NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505},
- AD_LISTEND
- },
- Common::DE_DEU,
- Common::kPlatformDOS,
- ADGF_NO_FLAGS,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_NEMESIS
- },
-
- {
- // Zork Nemesis Italian version
- {
- "znemesis",
- 0,
- {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873},
- {"NEMESIS.STR", 0, "7c568feca8d9f9ae855c47183612c305", 9061},
- AD_LISTEND
- },
- Common::IT_ITA,
- Common::kPlatformDOS,
- ADGF_NO_FLAGS,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_NEMESIS
- },
-
- {
- // Zork Nemesis English demo version
- {
- "znemesis",
- "Demo",
- AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539),
- Common::EN_ANY,
- Common::kPlatformWindows,
- ADGF_DEMO,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_NEMESIS
- },
-
- {
- // Zork Grand Inquisitor English CD version
- {
- "zgi",
- "CD",
- AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944),
- Common::EN_ANY,
- Common::kPlatformWindows,
- ADGF_NO_FLAGS,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- // Zork Grand Inquisitor French CD version, reported by ulrichh on IRC
- {
- "zgi",
- "CD",
- AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133),
- Common::FR_FRA,
- Common::kPlatformWindows,
- ADGF_NO_FLAGS,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- // Zork Grand Inquisitor German CD version, reported by breit in bug #6760
- {
- "zgi",
- "CD",
- AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133),
- Common::DE_DEU,
- Common::kPlatformWindows,
- ADGF_NO_FLAGS,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- // Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764
- {
- "zgi",
- "CD",
- AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141),
- Common::ES_ESP,
- Common::kPlatformWindows,
- ADGF_NO_FLAGS,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- // Zork Grand Inquisitor English DVD version
- {
- "zgi",
- "DVD",
- AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326),
- Common::EN_ANY,
- Common::kPlatformWindows,
- ADGF_NO_FLAGS,
- GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- // Zork Grand Inquisitor English demo version
- {
- "zgi",
- "Demo",
- AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239),
- Common::EN_ANY,
- Common::kPlatformWindows,
- ADGF_DEMO,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
- },
- GID_GRANDINQUISITOR
- },
-
- {
- AD_TABLE_END_MARKER,
- GID_NONE
- }
-};
-
-} // End of namespace ZVision
-
-static const char *directoryGlobs[] = {
- "znemscr",
- 0
-};
-
-static const ADExtraGuiOptionsMap optionsList[] = {
- {
- GAMEOPTION_ORIGINAL_SAVELOAD,
- {
- _s("Use original save/load screens"),
- _s("Use the original save/load screens, instead of the ScummVM ones"),
- "originalsaveload",
- false
- }
- },
-
- {
- GAMEOPTION_DOUBLE_FPS,
- {
- _s("Double FPS"),
- _s("Increase game FPS from 30 to 60"),
- "doublefps",
- false
- }
- },
-
- {
- GAMEOPTION_ENABLE_VENUS,
- {
- _s("Enable Venus"),
- _s("Enable the Venus help system"),
- "venusenabled",
- true
- }
- },
-
- {
- GAMEOPTION_DISABLE_ANIM_WHILE_TURNING,
- {
- _s("Disable animation while turning"),
- _s("Disable animation while turning in panoramic mode"),
- "noanimwhileturning",
- false
- }
- },
-
- {
- GAMEOPTION_USE_HIRES_MPEG_MOVIES,
- {
- _s("Use the hires MPEG movies"),
- _s("Use the hires MPEG movies of the DVD version, instead of the lowres AVI ones"),
- "mpegmovies",
- true
- }
- },
-
- AD_EXTRA_GUI_OPTIONS_TERMINATOR
-};
+#include "zvision/detection_tables.h"
class ZVisionMetaEngine : public AdvancedMetaEngine {
public:
- ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames, optionsList) {
+ ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), ZVision::zVisionGames, ZVision::optionsList) {
_maxScanDepth = 2;
- _directoryGlobs = directoryGlobs;
+ _directoryGlobs = ZVision::directoryGlobs;
_singleid = "zvision";
}
virtual const char *getName() const {
- return "ZVision";
+ return "Z-Vision";
}
virtual const char *getOriginalCopyright() const {
- return "ZVision Activision (C) 1996";
+ return "Z-Vision (C) 1996 Activision";
}
virtual bool hasFeature(MetaEngineFeature f) const;
diff --git a/engines/zvision/detection_tables.h b/engines/zvision/detection_tables.h
new file mode 100644
index 0000000000..06bc58ee7f
--- /dev/null
+++ b/engines/zvision/detection_tables.h
@@ -0,0 +1,277 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_DETECTION_TABLES_H
+#define ZVISION_DETECTION_TABLES_H
+
+namespace ZVision {
+
+static const PlainGameDescriptor zVisionGames[] = {
+ { "zvision", "Z-Vision Game" },
+ { "znemesis", "Zork Nemesis: The Forbidden Lands" },
+ { "zgi", "Zork: Grand Inquisitor" },
+ { 0, 0 }
+};
+
+static const char *directoryGlobs[] = {
+ "znemscr",
+ 0
+};
+
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2
+#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3
+#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4
+#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+
+ {
+ GAMEOPTION_ORIGINAL_SAVELOAD,
+ {
+ _s("Use original save/load screens"),
+ _s("Use the original save/load screens instead of the ScummVM interface"),
+ "originalsaveload",
+ false
+ }
+ },
+
+ {
+ GAMEOPTION_DOUBLE_FPS,
+ {
+ _s("Double FPS"),
+ _s("Increase framerate from 30 to 60 FPS"),
+ "doublefps",
+ false
+ }
+ },
+
+ {
+ GAMEOPTION_ENABLE_VENUS,
+ {
+ _s("Enable Venus"),
+ _s("Enable the Venus help system"),
+ "venusenabled",
+ true
+ }
+ },
+
+ {
+ GAMEOPTION_DISABLE_ANIM_WHILE_TURNING,
+ {
+ _s("Disable animation while turning"),
+ _s("Disable animation while turning in panorama mode"),
+ "noanimwhileturning",
+ false
+ }
+ },
+
+ {
+ GAMEOPTION_USE_HIRES_MPEG_MOVIES,
+ {
+ _s("Use high resolution MPEG video"),
+ _s("Use MPEG video from the DVD version, instead of lower resolution AVI"),
+ "mpegmovies",
+ true
+ }
+ },
+
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+static const ZVisionGameDescription gameDescriptions[] = {
+
+ {
+ // Zork Nemesis English version
+ {
+ "znemesis",
+ 0,
+ AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Nemesis French version
+ {
+ "znemesis",
+ 0,
+ {
+ { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 },
+ { "NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219 },
+ AD_LISTEND
+ },
+ Common::FR_FRA,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Nemesis German version
+ {
+ "znemesis",
+ 0,
+ {
+ { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 },
+ { "NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505 },
+ AD_LISTEND
+ },
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Nemesis Italian version
+ {
+ "znemesis",
+ 0,
+ {
+ { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 },
+ { "NEMESIS.STR", 0, "7c568feca8d9f9ae855c47183612c305", 9061 },
+ AD_LISTEND
+ },
+ Common::IT_ITA,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Nemesis English demo version
+ {
+ "znemesis",
+ "Demo",
+ AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Grand Inquisitor English CD version
+ {
+ "zgi",
+ "CD",
+ AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ // Zork Grand Inquisitor French CD version, reported by ulrichh on IRC
+ {
+ "zgi",
+ "CD",
+ AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133),
+ Common::FR_FRA,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ // Zork Grand Inquisitor German CD version, reported by breit in bug #6760
+ {
+ "zgi",
+ "CD",
+ AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ // Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764
+ {
+ "zgi",
+ "CD",
+ AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ // Zork Grand Inquisitor English DVD version
+ {
+ "zgi",
+ "DVD",
+ AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ // Zork Grand Inquisitor English demo version
+ {
+ "zgi",
+ "Demo",
+ AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ AD_TABLE_END_MARKER,
+ GID_NONE
+ }
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/file/save_manager.cpp b/engines/zvision/file/save_manager.cpp
index 63b54269de..d169679e28 100644
--- a/engines/zvision/file/save_manager.cpp
+++ b/engines/zvision/file/save_manager.cpp
@@ -205,7 +205,7 @@ bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &hea
return true;
}
if (tag != SAVEGAME_ID) {
- warning("File is not a ZVision save file. Aborting load");
+ warning("File is not a Z-Vision save file. Aborting load");
return false;
}
diff --git a/engines/zvision/graphics/render_manager.cpp b/engines/zvision/graphics/render_manager.cpp
index ce0a02a1ad..f978ef7844 100644
--- a/engines/zvision/graphics/render_manager.cpp
+++ b/engines/zvision/graphics/render_manager.cpp
@@ -196,7 +196,7 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics:
uint32 imageHeight;
Image::TGADecoder tga;
uint16 *buffer;
- // All ZVision images are in RGB 555
+ // All Z-Vision images are in RGB 555
destination.format = _engine->_resourcePixelFormat;
bool isTGZ;
diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp
index 9a8b734e0c..e1380b0eb2 100644
--- a/engines/zvision/scripting/actions.cpp
+++ b/engines/zvision/scripting/actions.cpp
@@ -47,15 +47,18 @@
namespace ZVision {
-ResultAction::ResultAction(ZVision *engine, int32 slotkey) : _engine(engine), _slotKey(slotkey), _scriptManager(engine->getScriptManager()) {
+ResultAction::ResultAction(ZVision *engine, int32 slotKey) :
+ _engine(engine),
+ _slotKey(slotKey),
+ _scriptManager(engine->getScriptManager()) {
}
//////////////////////////////////////////////////////////////////////////////
// ActionAdd
//////////////////////////////////////////////////////////////////////////////
-ActionAdd::ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionAdd::ActionAdd(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
_value = 0;
@@ -71,8 +74,8 @@ bool ActionAdd::execute() {
// ActionAssign
//////////////////////////////////////////////////////////////////////////////
-ActionAssign::ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionAssign::ActionAssign(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
char buf[64];
@@ -94,8 +97,8 @@ bool ActionAssign::execute() {
// ActionAttenuate
//////////////////////////////////////////////////////////////////////////////
-ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
_attenuation = 0;
@@ -115,8 +118,8 @@ bool ActionAttenuate::execute() {
// ActionChangeLocation
//////////////////////////////////////////////////////////////////////////////
-ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_world = 'g';
_room = 'a';
_node = 'r';
@@ -137,8 +140,8 @@ bool ActionChangeLocation::execute() {
// ActionCrossfade
//////////////////////////////////////////////////////////////////////////////
-ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_keyOne = 0;
_keyTwo = 0;
_oneStartVolume = 0;
@@ -181,8 +184,8 @@ bool ActionCrossfade::execute() {
// ActionCursor
//////////////////////////////////////////////////////////////////////////////
-ActionCursor::ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionCursor::ActionCursor(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
Common::String up = line;
up.toUppercase();
_action = 0;
@@ -213,8 +216,8 @@ bool ActionCursor::execute() {
// ActionDelayRender
//////////////////////////////////////////////////////////////////////////////
-ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_framesToDelay = 0;
sscanf(line.c_str(), "%u", &_framesToDelay);
// Limit to 10 frames maximum. This fixes the script bug in ZGI scene px10
@@ -231,8 +234,8 @@ bool ActionDelayRender::execute() {
// ActionDisableControl
//////////////////////////////////////////////////////////////////////////////
-ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
sscanf(line.c_str(), "%u", &_key);
@@ -247,8 +250,8 @@ bool ActionDisableControl::execute() {
// ActionDisplayMessage
//////////////////////////////////////////////////////////////////////////////
-ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_control = 0;
_msgid = 0;
@@ -282,8 +285,8 @@ bool ActionDissolve::execute() {
// ActionDistort - only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30)
//////////////////////////////////////////////////////////////////////////////
-ActionDistort::ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionDistort::ActionDistort(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_distSlot = 0;
_speed = 0;
_startAngle = 60.0;
@@ -311,8 +314,8 @@ bool ActionDistort::execute() {
// ActionEnableControl
//////////////////////////////////////////////////////////////////////////////
-ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
sscanf(line.c_str(), "%u", &_key);
@@ -327,8 +330,8 @@ bool ActionEnableControl::execute() {
// ActionFlushMouseEvents
//////////////////////////////////////////////////////////////////////////////
-ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotkey) :
- ResultAction(engine, slotkey) {
+ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotKey) :
+ ResultAction(engine, slotKey) {
}
bool ActionFlushMouseEvents::execute() {
@@ -341,8 +344,8 @@ bool ActionFlushMouseEvents::execute() {
// ActionInventory
//////////////////////////////////////////////////////////////////////////////
-ActionInventory::ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionInventory::ActionInventory(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_type = -1;
_key = 0;
@@ -393,8 +396,8 @@ bool ActionInventory::execute() {
// ActionKill - only used by ZGI
//////////////////////////////////////////////////////////////////////////////
-ActionKill::ActionKill(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionKill::ActionKill(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
_type = 0;
char keytype[25];
@@ -432,8 +435,8 @@ bool ActionKill::execute() {
// ActionMenuBarEnable
//////////////////////////////////////////////////////////////////////////////
-ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_menus = 0xFFFF;
sscanf(line.c_str(), "%hu", &_menus);
@@ -448,8 +451,8 @@ bool ActionMenuBarEnable::execute() {
// ActionMusic
//////////////////////////////////////////////////////////////////////////////
-ActionMusic::ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global) :
- ResultAction(engine, slotkey),
+ActionMusic::ActionMusic(ZVision *engine, int32 slotKey, const Common::String &line, bool global) :
+ ResultAction(engine, slotKey),
_note(0),
_prog(0),
_universe(global) {
@@ -527,8 +530,8 @@ bool ActionMusic::execute() {
// ActionPanTrack
//////////////////////////////////////////////////////////////////////////////
-ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey),
+ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey),
_pos(0),
_musicSlot(0) {
@@ -552,8 +555,8 @@ bool ActionPanTrack::execute() {
// ActionPreferences
//////////////////////////////////////////////////////////////////////////////
-ActionPreferences::ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionPreferences::ActionPreferences(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
if (line.compareToIgnoreCase("save") == 0)
_save = true;
else
@@ -573,8 +576,8 @@ bool ActionPreferences::execute() {
// ActionPreloadAnimation
//////////////////////////////////////////////////////////////////////////////
-ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_mask = 0;
_framerate = 0;
@@ -612,8 +615,8 @@ bool ActionPreloadAnimation::execute() {
// ActionUnloadAnimation
//////////////////////////////////////////////////////////////////////////////
-ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
sscanf(line.c_str(), "%u", &_key);
@@ -632,8 +635,8 @@ bool ActionUnloadAnimation::execute() {
// ActionPlayAnimation
//////////////////////////////////////////////////////////////////////////////
-ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_x = 0;
_y = 0;
_x2 = 0;
@@ -690,8 +693,8 @@ bool ActionPlayAnimation::execute() {
// ActionPlayPreloadAnimation
//////////////////////////////////////////////////////////////////////////////
-ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_controlKey = 0;
_x1 = 0;
_y1 = 0;
@@ -729,8 +732,8 @@ bool ActionQuit::execute() {
// ActionRegion - only used by Zork: Nemesis
//////////////////////////////////////////////////////////////////////////////
-ActionRegion::ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionRegion::ActionRegion(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_delay = 0;
_type = 0;
_unk1 = 0;
@@ -808,8 +811,8 @@ bool ActionRegion::execute() {
// ActionRandom
//////////////////////////////////////////////////////////////////////////////
-ActionRandom::ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionRandom::ActionRandom(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
char maxBuffer[64];
memset(maxBuffer, 0, 64);
sscanf(line.c_str(), "%s", maxBuffer);
@@ -830,8 +833,8 @@ bool ActionRandom::execute() {
// ActionRestoreGame
//////////////////////////////////////////////////////////////////////////////
-ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
char buf[128];
sscanf(line.c_str(), "%s", buf);
_fileName = Common::String(buf);
@@ -846,8 +849,8 @@ bool ActionRestoreGame::execute() {
// ActionRotateTo
//////////////////////////////////////////////////////////////////////////////
-ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_time = 0;
_toPos = 0;
@@ -864,8 +867,8 @@ bool ActionRotateTo::execute() {
// ActionSetPartialScreen
//////////////////////////////////////////////////////////////////////////////
-ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_x = 0;
_y = 0;
@@ -904,8 +907,8 @@ bool ActionSetPartialScreen::execute() {
// ActionSetScreen
//////////////////////////////////////////////////////////////////////////////
-ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
char fileName[25];
sscanf(line.c_str(), "%24s", fileName);
@@ -922,8 +925,8 @@ bool ActionSetScreen::execute() {
// ActionStop
//////////////////////////////////////////////////////////////////////////////
-ActionStop::ActionStop(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionStop::ActionStop(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_key = 0;
sscanf(line.c_str(), "%u", &_key);
}
@@ -937,8 +940,8 @@ bool ActionStop::execute() {
// ActionStreamVideo
//////////////////////////////////////////////////////////////////////////////
-ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_x1 = 0;
_x2 = 0;
_y1 = 0;
@@ -1019,8 +1022,8 @@ bool ActionStreamVideo::execute() {
// ActionSyncSound
//////////////////////////////////////////////////////////////////////////////
-ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_syncto = 0;
char fileName[25];
@@ -1047,8 +1050,8 @@ bool ActionSyncSound::execute() {
// ActionTimer
//////////////////////////////////////////////////////////////////////////////
-ActionTimer::ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionTimer::ActionTimer(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
char timeBuffer[64];
memset(timeBuffer, 0, 64);
sscanf(line.c_str(), "%s", timeBuffer);
@@ -1071,8 +1074,8 @@ bool ActionTimer::execute() {
// ActionTtyText
//////////////////////////////////////////////////////////////////////////////
-ActionTtyText::ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line) :
- ResultAction(engine, slotkey) {
+ActionTtyText::ActionTtyText(ZVision *engine, int32 slotKey, const Common::String &line) :
+ ResultAction(engine, slotKey) {
_delay = 0;
char filename[64];
diff --git a/engines/zvision/scripting/actions.h b/engines/zvision/scripting/actions.h
index ff19fc54fc..bde1baa291 100644
--- a/engines/zvision/scripting/actions.h
+++ b/engines/zvision/scripting/actions.h
@@ -269,7 +269,6 @@ public:
bool execute();
private:
- uint32 _animationKey;
uint32 _controlKey;
uint32 _x1;
uint32 _y1;
diff --git a/engines/zvision/scripting/controls/input_control.cpp b/engines/zvision/scripting/controls/input_control.cpp
index df0c77ba96..9525333ef0 100644
--- a/engines/zvision/scripting/controls/input_control.cpp
+++ b/engines/zvision/scripting/controls/input_control.cpp
@@ -43,7 +43,6 @@ InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStre
_nextTabstop(0),
_focused(false),
_textChanged(false),
- _cursorOffset(0),
_enterPressed(false),
_readOnly(false),
_txtWidth(0),
diff --git a/engines/zvision/scripting/controls/input_control.h b/engines/zvision/scripting/controls/input_control.h
index 9b48514e16..6abdb3c692 100644
--- a/engines/zvision/scripting/controls/input_control.h
+++ b/engines/zvision/scripting/controls/input_control.h
@@ -51,15 +51,12 @@ private:
Common::String _currentInputText;
bool _textChanged;
- uint _cursorOffset;
bool _enterPressed;
bool _readOnly;
int16 _txtWidth;
int16 _maxTxtWidth;
Video::VideoDecoder *_animation;
- int32 _frameDelay;
- int16 _frame;
public:
void focus() {
diff --git a/engines/zvision/scripting/menu.cpp b/engines/zvision/scripting/menu.cpp
index e7775cbe3f..064bd1b57d 100644
--- a/engines/zvision/scripting/menu.cpp
+++ b/engines/zvision/scripting/menu.cpp
@@ -46,7 +46,7 @@ MenuHandler::MenuHandler(ZVision *engine) {
MenuZGI::MenuZGI(ZVision *engine) :
MenuHandler(engine) {
menuMouseFocus = -1;
- inmenu = false;
+ inMenu = false;
scrolled[0] = false;
scrolled[1] = false;
scrolled[2] = false;
@@ -60,15 +60,15 @@ MenuZGI::MenuZGI(ZVision *engine) :
char buf[24];
for (int i = 1; i < 4; i++) {
sprintf(buf, "gmzau%2.2x1.tga", i);
- _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][0], false);
+ _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][0], false);
sprintf(buf, "gmzau%2.2x1.tga", i + 0x10);
- _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][1], false);
+ _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][1], false);
}
for (int i = 0; i < 4; i++) {
sprintf(buf, "gmzmu%2.2x1.tga", i);
- _engine->getRenderManager()->readImageToSurface(buf, menubar[i][0], false);
+ _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][0], false);
sprintf(buf, "gmznu%2.2x1.tga", i);
- _engine->getRenderManager()->readImageToSurface(buf, menubar[i][1], false);
+ _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][1], false);
}
for (int i = 0; i < 50; i++) {
@@ -86,12 +86,12 @@ MenuZGI::MenuZGI(ZVision *engine) :
MenuZGI::~MenuZGI() {
for (int i = 0; i < 3; i++) {
- menuback[i][0].free();
- menuback[i][1].free();
+ menuBack[i][0].free();
+ menuBack[i][1].free();
}
for (int i = 0; i < 4; i++) {
- menubar[i][0].free();
- menubar[i][1].free();
+ menuBar[i][0].free();
+ menuBar[i][1].free();
}
for (int i = 0; i < 50; i++) {
if (items[i][0]) {
@@ -208,9 +208,9 @@ void MenuZGI::onMouseUp(const Common::Point &Pos) {
void MenuZGI::onMouseMove(const Common::Point &Pos) {
if (Pos.y < 40) {
- if (!inmenu)
+ if (!inMenu)
redraw = true;
- inmenu = true;
+ inMenu = true;
switch (menuMouseFocus) {
case kMenuItem:
if (menuBarFlag & kMenubarItems) {
@@ -311,7 +311,7 @@ void MenuZGI::onMouseMove(const Common::Point &Pos) {
if (Common::Rect(64, 0, 64 + 512, 8).contains(Pos)) { // Main
menuMouseFocus = kMenuMain;
scrolled[kMenuMain] = false;
- scrollPos[kMenuMain] = menuback[kMenuMain][1].h - menuback[kMenuMain][0].h;
+ scrollPos[kMenuMain] = menuBack[kMenuMain][1].h - menuBack[kMenuMain][0].h;
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 2);
}
@@ -337,9 +337,9 @@ void MenuZGI::onMouseMove(const Common::Point &Pos) {
break;
}
} else {
- if (inmenu)
+ if (inMenu)
clean = true;
- inmenu = false;
+ inMenu = false;
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 0);
menuMouseFocus = -1;
@@ -369,7 +369,7 @@ void MenuZGI::process(uint32 deltatime) {
}
}
if (redraw) {
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][0], scrollPos[kMenuItem], 0);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][0], scrollPos[kMenuItem], 0);
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
@@ -438,7 +438,7 @@ void MenuZGI::process(uint32 deltatime) {
}
}
if (redraw) {
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0);
for (int i = 0; i < 12; i++) {
bool inrect = false;
@@ -503,45 +503,45 @@ void MenuZGI::process(uint32 deltatime) {
}
}
if (redraw) {
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][0], 30, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][0], 30, scrollPos[kMenuMain]);
if (menuBarFlag & kMenubarExit) {
if (mouseOnItem == kMainMenuExit)
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]);
else
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]);
}
if (menuBarFlag & kMenubarSettings) {
if (mouseOnItem == kMainMenuPrefs)
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]);
else
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]);
}
if (menuBarFlag & kMenubarRestore) {
if (mouseOnItem == kMainMenuLoad)
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]);
else
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]);
}
if (menuBarFlag & kMenubarSave) {
if (mouseOnItem == kMainMenuSave)
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]);
else
- _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]);
}
redraw = false;
}
break;
default:
if (redraw) {
- if (inmenu) {
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][1], 30, 0);
+ if (inMenu) {
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][1], 30, 0);
if (menuBarFlag & kMenubarItems)
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][1], 0, 0);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][1], 0, 0);
if (menuBarFlag & kMenubarMagic)
- _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][1], 640 - 28, 0);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][1], 640 - 28, 0);
}
redraw = false;
}
@@ -551,7 +551,7 @@ void MenuZGI::process(uint32 deltatime) {
MenuNemesis::MenuNemesis(ZVision *engine) :
MenuHandler(engine) {
- inmenu = false;
+ inMenu = false;
scrolled = false;
scrollPos = 0;
mouseOnItem = -1;
@@ -565,7 +565,7 @@ MenuNemesis::MenuNemesis(ZVision *engine) :
_engine->getRenderManager()->readImageToSurface(buf, but[i][j], false);
}
- _engine->getRenderManager()->readImageToSurface("bar.tga", menubar, false);
+ _engine->getRenderManager()->readImageToSurface("bar.tga", menuBar, false);
frm = 0;
}
@@ -575,7 +575,7 @@ MenuNemesis::~MenuNemesis() {
for (int j = 0; j < 6; j++)
but[i][j].free();
- menubar.free();
+ menuBar.free();
}
static const int16 buts[4][2] = { {120 , 64}, {144, 184}, {128, 328}, {120, 456} };
@@ -631,7 +631,7 @@ void MenuNemesis::onMouseUp(const Common::Point &Pos) {
void MenuNemesis::onMouseMove(const Common::Point &Pos) {
if (Pos.y < 40) {
- inmenu = true;
+ inMenu = true;
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 2);
@@ -681,7 +681,7 @@ void MenuNemesis::onMouseMove(const Common::Point &Pos) {
delay = 200;
}
} else {
- inmenu = false;
+ inMenu = false;
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 0);
mouseOnItem = -1;
@@ -689,7 +689,7 @@ void MenuNemesis::onMouseMove(const Common::Point &Pos) {
}
void MenuNemesis::process(uint32 deltatime) {
- if (inmenu) {
+ if (inMenu) {
if (!scrolled) {
float scrl = 32.0 * 2.0 * (deltatime / 1000.0);
@@ -715,7 +715,7 @@ void MenuNemesis::process(uint32 deltatime) {
}
if (redraw) {
- _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos);
if (menuBarFlag & kMenubarExit)
if (mouseOnItem == kMainMenuExit)
@@ -752,7 +752,7 @@ void MenuNemesis::process(uint32 deltatime) {
scrollPos = -32;
if (redraw) {
- _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos);
+ _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos);
redraw = false;
}
}
diff --git a/engines/zvision/scripting/menu.h b/engines/zvision/scripting/menu.h
index a88587966f..f6b21b9c97 100644
--- a/engines/zvision/scripting/menu.h
+++ b/engines/zvision/scripting/menu.h
@@ -68,8 +68,8 @@ public:
void onMouseUp(const Common::Point &Pos);
void process(uint32 deltaTimeInMillis);
private:
- Graphics::Surface menuback[3][2];
- Graphics::Surface menubar[4][2];
+ Graphics::Surface menuBack[3][2];
+ Graphics::Surface menuBar[4][2];
Graphics::Surface *items[50][2];
uint itemId[50];
@@ -77,11 +77,11 @@ private:
uint magicId[12];
int menuMouseFocus;
- bool inmenu;
+ bool inMenu;
int mouseOnItem;
- bool scrolled[3];
+ bool scrolled[3];
int16 scrollPos[3];
bool clean;
@@ -98,13 +98,13 @@ public:
void process(uint32 deltaTimeInMillis);
private:
Graphics::Surface but[4][6];
- Graphics::Surface menubar;
+ Graphics::Surface menuBar;
- bool inmenu;
+ bool inMenu;
int mouseOnItem;
- bool scrolled;
+ bool scrolled;
int16 scrollPos;
bool redraw;
@@ -114,6 +114,6 @@ private:
};
-}
+} // End of namespace ZVision
#endif
diff --git a/engines/zvision/sound/zork_raw.cpp b/engines/zvision/sound/zork_raw.cpp
index 7bdd4875fc..124235e0e0 100644
--- a/engines/zvision/sound/zork_raw.cpp
+++ b/engines/zvision/sound/zork_raw.cpp
@@ -33,7 +33,6 @@
#include "zvision/sound/zork_raw.h"
#include "zvision/zvision.h"
-#include "zvision/detection.h"
namespace ZVision {
@@ -136,7 +135,8 @@ int RawChunkStream::readBuffer(int16 *buffer, Common::SeekableReadStream *stream
return bytesRead;
}
-const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {{'0', 0x1F40, false, false, false},
+const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {
+ {'0', 0x1F40, false, false, false},
{'1', 0x1F40, true, false, false},
{'2', 0x1F40, false, false, true},
{'3', 0x1F40, true, false, true},
@@ -170,7 +170,8 @@ const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {{'0', 0x1F40,
{'x', 0xAC44, true, true, true}
};
-const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {{'4', 0x2B11, false, false, false},
+const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {
+ {'4', 0x2B11, false, false, false},
{'5', 0x2B11, true, false, false},
{'6', 0x2B11, false, false, true},
{'7', 0x2B11, true, false, true},
diff --git a/engines/zvision/text/string_manager.h b/engines/zvision/text/string_manager.h
index f4564ee1ec..2c31cf7afe 100644
--- a/engines/zvision/text/string_manager.h
+++ b/engines/zvision/text/string_manager.h
@@ -23,7 +23,6 @@
#ifndef ZVISION_STRING_MANAGER_H
#define ZVISION_STRING_MANAGER_H
-#include "zvision/detection.h"
#include "zvision/text/truetype_font.h"
namespace Graphics {
diff --git a/engines/zvision/text/text.h b/engines/zvision/text/text.h
index d35b90499d..5dd872a440 100644
--- a/engines/zvision/text/text.h
+++ b/engines/zvision/text/text.h
@@ -24,7 +24,6 @@
#ifndef ZVISION_TEXT_H
#define ZVISION_TEXT_H
-#include "zvision/detection.h"
#include "zvision/text/truetype_font.h"
#include "zvision/zvision.h"
@@ -59,7 +58,7 @@ public:
public:
Common::String _fontname;
- TextJustification _justification; // 0 - center, 1-left, 2-right
+ TextJustification _justification;
int16 _size;
uint8 _red; // 0-255
uint8 _green; // 0-255
diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp
index da80ff9d02..779fdc4464 100644
--- a/engines/zvision/zvision.cpp
+++ b/engines/zvision/zvision.cpp
@@ -29,7 +29,6 @@
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/file/save_manager.h"
#include "zvision/text/string_manager.h"
-#include "zvision/detection.h"
#include "zvision/scripting/menu.h"
#include "zvision/file/search_manager.h"
#include "zvision/text/text.h"
@@ -184,10 +183,10 @@ void ZVision::initialize() {
_searchManager->addDir("FONTS");
_searchManager->addDir("addon");
- if (_gameDescription->gameId == GID_GRANDINQUISITOR) {
+ if (getGameId() == GID_GRANDINQUISITOR) {
if (!_searchManager->loadZix("INQUIS.ZIX"))
error("Unable to load file INQUIS.ZIX");
- } else if (_gameDescription->gameId == GID_NEMESIS) {
+ } else if (getGameId() == GID_NEMESIS) {
if (!_searchManager->loadZix("NEMESIS.ZIX")) {
// The game might not be installed, try MEDIUM.ZIX instead
if (!_searchManager->loadZix("ZNEMSCR/MEDIUM.ZIX"))
@@ -209,7 +208,7 @@ void ZVision::initialize() {
_textRenderer = new TextRenderer(this);
_midiManager = new MidiManager();
- if (_gameDescription->gameId == GID_GRANDINQUISITOR)
+ if (getGameId() == GID_GRANDINQUISITOR)
_menu = new MenuZGI(this);
else
_menu = new MenuNemesis(this);
@@ -217,7 +216,7 @@ void ZVision::initialize() {
// Initialize the managers
_cursorManager->initialize();
_scriptManager->initialize();
- _stringManager->initialize(_gameDescription->gameId);
+ _stringManager->initialize(getGameId());
registerDefaultSettings();
@@ -396,8 +395,8 @@ void ZVision::fpsTimer() {
}
void ZVision::initScreen() {
- uint16 workingWindowWidth = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH;
- uint16 workingWindowHeight = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT;
+ uint16 workingWindowWidth = (getGameId() == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH;
+ uint16 workingWindowHeight = (getGameId() == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT;
_workingWindow = Common::Rect(
(WINDOW_WIDTH - workingWindowWidth) / 2,
(WINDOW_HEIGHT - workingWindowHeight) / 2,
diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h
index 854cd77bb8..4c948faaa4 100644
--- a/engines/zvision/zvision.h
+++ b/engines/zvision/zvision.h
@@ -24,7 +24,6 @@
#ifndef ZVISION_ZVISION_H
#define ZVISION_ZVISION_H
-#include "zvision/detection.h"
#include "zvision/core/clock.h"
#include "zvision/file/search_manager.h"
@@ -51,7 +50,6 @@ class VideoDecoder;
* - Zork: Grand Inquisitor
*
*/
-
namespace ZVision {
struct ZVisionGameDescription;
@@ -74,12 +72,12 @@ enum {
HIRES_WINDOW_WIDTH = 800,
HIRES_WINDOW_HEIGHT = 600,
- // Zork nemesis working window sizes
- ZNM_WORKING_WINDOW_WIDTH = 512,
+ // Zork Nemesis working window sizes
+ ZNM_WORKING_WINDOW_WIDTH = 512,
ZNM_WORKING_WINDOW_HEIGHT = 320,
// ZGI working window sizes
- ZGI_WORKING_WINDOW_WIDTH = 640,
+ ZGI_WORKING_WINDOW_WIDTH = 640,
ZGI_WORKING_WINDOW_HEIGHT = 344,
ROTATION_SCREEN_EDGE_OFFSET = 60,
@@ -88,6 +86,12 @@ enum {
KEYBUF_SIZE = 20
};
+enum ZVisionGameId {
+ GID_NONE = 0,
+ GID_NEMESIS = 1,
+ GID_GRANDINQUISITOR = 2
+};
+
class ZVision : public Engine {
public:
ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc);
@@ -116,12 +120,12 @@ private:
ScriptManager *_scriptManager;
RenderManager *_renderManager;
CursorManager *_cursorManager;
- SaveManager *_saveManager;
StringManager *_stringManager;
- MenuHandler *_menu;
SearchManager *_searchManager;
TextRenderer *_textRenderer;
MidiManager *_midiManager;
+ SaveManager *_saveManager;
+ MenuHandler *_menu;
// Clock
Clock _clock;
@@ -141,12 +145,15 @@ private:
bool _videoIsPlaying;
uint8 _cheatBuffer[KEYBUF_SIZE];
+
public:
- uint32 getFeatures() const;
- Common::Language getLanguage() const;
Common::Error run();
void pauseEngineIntern(bool pause);
+ ZVisionGameId getGameId() const;
+ Common::Language getLanguage() const;
+ uint32 getFeatures() const;
+
ScriptManager *getScriptManager() const {
return _scriptManager;
}
@@ -174,12 +181,10 @@ public:
MenuHandler *getMenuHandler() const {
return _menu;
}
+
Common::RandomSource *getRandomSource() const {
return _rnd;
}
- ZVisionGameId getGameId() const {
- return _gameDescription->gameId;
- }
int16 getKeyboardVelocity() const {
return _keyboardVelocity;
}
@@ -236,6 +241,7 @@ public:
bool canSaveGameStateCurrently();
Common::Error loadGameState(int slot);
Common::Error saveGameState(int slot, const Common::String &desc);
+
private:
void initialize();
void initFonts();